五大排序算法总结

1.冒泡排序

图解冒泡

  我们以[8,2,5,9,7]这组数字来做示例,上图来战:

  我们从左往右依次冒泡,将小的往右移动

  首先比较第一个数和第二个数的大小,我们发现2比8要小,那么保持原位,不做改动。位置还是8,2,5,9,7。

 

  指针往右移动一格,接着比较:

  比较第二个数和第三个数的大小,发现2比5要小,所以位置交换,交换后数组更新为:[8,5,2,9,7]。

 

  指针再往右移动一格,继续比较:

  比较第三个数和第四个数的大小,发现2比9要小,所以位置交换,交换后数组更新为:[8,5,9,2,7]

 

  同样,指针再往右移动,继续比较:

  比较第4个数和第5个数的大小,发现2比7要小,所以位置交换,交换后数组更新为:[8,5,9,7,2]

 

  下一步,指针再往右移动,发现已经到底了,则本轮冒泡结束,处于最右边的2就是已经排好序的数字。

  通过这一轮不断的对比交换,数组中最小的数字移动到了最右边。

 

  接下来继续第二轮冒泡:

五大排序算法总结_第1张图片

五大排序算法总结_第2张图片

  由于右边的2已经是排好序的数字,就不再参与比较,所以本轮冒泡结束,本轮冒泡最终冒到顶部的数字5也归于有序序列中,现在数组已经变化成了[8,9,7,5,2]。

 

 

  让我们开始第三轮冒泡吧!

  由于8比7大,所以位置不变,此时第三轮冒泡也已经结束,第三轮冒泡的最后结果是[9,8,7,5,2]

 

  紧接着第四轮冒泡:

  9和8比,位置不变,即确定了8进入有序序列,那么最后只剩下一个数字9,放在末尾,自此排序结束。

 

参考代码:

class Solution{
public:
    //冒泡排序
    void mp_sort(int* array, int length)
    {
        for (int i = 0; i < length; ++i) {
            for (int j = 0; j < length-1; ++j) {
                if (array[j] > array[j+1])
                    swap(array[j],array[j+1]);
            }

        }
    }
};

 

2.选择排序

图解选排

  我们还是以[8,2,5,9,7]这组数字做例子。

  第一次选择,先找到数组中最小的数字2,然后和第一个数字交换位置。(如果第一个数字就是最小值,那么自己和自己交换位置,也可以不做处理,就是一个if的事情)

 

  第二次选择,由于数组第一个位置已经是有序的,所以只需要查找剩余位置,找到其中最小的数字5,然后和数组第二个位置的元素交换。

 

  第三次选择,找到最小值7,和第三个位置的元素交换位置。

 

  第四次选择,找到最小值8,和第四个位置的元素交换位置。

  最后一个到达了数组末尾,没有可对比的元素,结束选择。

  如此整个数组就排序完成了。

 

参考代码:

class Solution{
public:  
 void xz_sort(int* array, int length)
    {
        int min;
        int index;
        for (int i = 0; i < length; ++i) {
            min = array[i];
            index = i;
            for (int j = i; j < length; ++j) {
                if (min > array[j])
                {
                    min = array[j];
                    index = j;
                }
            }
            swap(array[i],array[index]);
        }
    }
};

 

3.插入排序

图解插入

  数组初始化:[8,2,5,9,7],我们把数组中的数据分成两个区域,已排序区域和未排序区域,初始化的时候所有的数据都处在未排序区域中,已排序区域是空。

 

  第一轮,从未排序区域中随机拿出一个数字,既然是随机,那么我们就获取第一个,然后插入到已排序区域中,已排序区域是空,那么就不做比较,默认自身已经是有序的了。(当然了,第一轮在代码中是可以省略的,从下标为1的元素开始即可)

 

  第二轮,继续从未排序区域中拿出一个数,插入到已排序区域中,这个时候要遍历已排序区域中的数字挨个做比较,比大比小取决于你是想升序排还是想倒序排,这里排升序:

 

  第三轮,排5:

 

  第四轮,排9:

 

  第五轮,排7

  排序结束。

 

参考代码:

class Solution{
public:
void insert_sort(int* array, int length)
    {
        if (length == 1)
            return;

        int current;
        for (int i = 1; i < length; ++i) {
            current = array[i];
            for (int j = i-1; j >= 0 ; --j) {
                if(current < array[j])
                {
                    swap(array[j],array[j+1]);
                } else{
                    break;
                }
            }

        }
    }
};

 

 

4.快速排序(分治思想)

假设我们现在对“6  1  2 7  9  3  4  5 10  8”这个10个数进行排序。首先在这个序列中随便找一个数作为基准数(不要被这个名词吓到了,就是一个用来参照的数,待会你就知道它用来做啥的了)。为了方便,就让第一个数6作为基准数吧。接下来,需要将这个序列中所有比基准数大的数放在6的右边,比基准数小的数放在6的左边,类似下面这种排列。

       3  1  2 5  4  6  9 7  10  8

        在初始状态下,数字6在序列的第1位。我们的目标是将6挪到序列中间的某个位置,假设这个位置是k。现在就需要寻找这个k,并且以第k位为分界点,左边的数都小于等于6,右边的数都大于等于6。想一想,你有办法可以做到这点吗?

        给你一个提示吧。请回忆一下冒泡排序,是如何通过“交换”,一步步让每个数归位的。此时你也可以通过“交换”的方法来达到目的。具体是如何一步步交换呢?怎样交换才既方便又节省时间呢?

        方法其实很简单:分别从初始序列“6  1  2 7  9  3  4  5 10  8”两端开始“探测”。先从右往左找一个小于6的数,再从左往右找一个大于6的数,然后交换他们。这里可以用两个变量i和j,分别指向序列最左边和最右边。我们为这两个变量起个好听的名字“哨兵i”和“哨兵j”。刚开始的时候让哨兵i指向序列的最左边(即i=1),指向数字6。让哨兵j指向序列的最右边(即j=10),指向数字8。

                     

五大排序算法总结_第3张图片

                                    

      首先哨兵j开始出动。因为此处设置的基准数是最左边的数,所以需要让哨兵j先出动,这一点非常重要(请自己想一想为什么)。哨兵j一步一步地向左挪动(即j--),直到找到一个小于6的数停下来。接下来哨兵i再一步一步向右挪动(即i++),直到找到一个数大于6的数停下来。最后哨兵j停在了数字5面前,哨兵i停在了数字7面前。

                          

五大排序算法总结_第4张图片

       现在交换哨兵i和哨兵j所指向的元素的值。交换之后的序列如下。

        6  1  2  5  9 3  4  7  10  8

        到此,第一次交换结束。接下来开始哨兵j继续向左挪动(再友情提醒,每次必须是哨兵j先出发)。他发现了4(比基准数6要小,满足要求)之后停了下来。哨兵i也继续向右挪动的,他发现了9(比基准数6要大,满足要求)之后停了下来。

                                        

五大排序算法总结_第5张图片

此时再次进行交换,交换之后的序列如下。

        6  1  2 5  4  3  9  7 10  8

        第二次交换结束,“探测”继续。哨兵j继续向左挪动,他发现了3(比基准数6要小,满足要求)之后又停了下来。哨兵i继续向右移动,糟啦!此时哨兵i和哨兵j相遇了,哨兵i和哨兵j都走到3面前。说明此时“探测”结束。我们将基准数6和3进行交换。交换之后的序列如下。

        3  1  2  5  4  6  9 7  10  8                  

五大排序算法总结_第6张图片

   

        到此第一轮“探测”真正结束。此时以基准数6为分界点,6左边的数都小于等于6,6右边的数都大于等于6。回顾一下刚才的过程,其实哨兵j的使命就是要找小于基准数的数,而哨兵i的使命就是要找大于基准数的数,直到i和j碰头为止。

        OK,解释完毕。现在基准数6已经归位,它正好处在序列的第6位。此时我们已经将原来的序列,以6为分界点拆分成了两个序列,左边的序列是“3  1 2  5  4”,右边的序列是“9  7  10  8”。接下来还需要分别处理这两个序列。因为6左边和右边的序列目前都还是很混乱的。不过不要紧,我们已经掌握了方法,接下来只要模拟刚才的方法分别处理6左边和右边的序列即可。现在先来处理6左边的序列现吧。

        左边的序列是“3  1  2 5  4”。请将这个序列以3为基准数进行调整,使得3左边的数都小于等于3,3右边的数都大于等于3。好了开始动笔吧。

        如果你模拟的没有错,调整完毕之后的序列的顺序应该是。

                2  1  3  5  4

        OK,现在3已经归位。接下来需要处理3左边的序列“2 1”和右边的序列“5 4”。对序列“2 1”以2为基准数进行调整,处理完毕之后的序列为“1 2”,到此2已经归位。序列“1”只有一个数,也不需要进行任何处理。至此我们对序列“2 1”已全部处理完毕,得到序列是“1 2”。序列“5 4”的处理也仿照此方法,最后得到的序列如下。

           1  2  3 4  5  6 9  7  10  8

        对于序列“9  7  10  8”也模拟刚才的过程,直到不可拆分出新的子序列为止。最终将会得到这样的序列,如下。

           1  2  3 4  5  6  7  8 9  10

        到此,排序完全结束。细心的同学可能已经发现,快速排序的每一轮处理其实就是将这一轮的基准数归位,直到所有的数都归位为止,排序就结束了。
 

参考代码:

class Solution{
public:
    //快速排序(分治思想)
    void quick_sort(int* array, int start, int end)
    {
        if (start >= end)
            return;

        int base = array[start];
        int i = start;
        int j = end;

        while (i < j)
        {

            while (i base)
                {
                    swap(array[i],array[j]);
                    break;
                } else{
                    i++;
                }
            }

            if (i >= j)
            {
                swap(array[start],array[i]);
                break;
            }

        }

        quick_sort(array,start,i-1);
        quick_sort(array,i+1,end);
    }
};

快排优化:

       使用三数中值分割法:一组序列的中值(中位数)是枢纽元最好的选择(因为可以将序列均分为两个子序列,归并排序告诉我们,这时候是O(NlogN);但要计算一组数组的中位数就比较耗时,会减慢快排的效率。但可以通过计算数组的第一个,中间位置,最后一个元素的中值来代替。比如序列:[8,1,4,9,6,3,5,2,7,0]。第一个元素是8,中间(left+right)/2(向下取整)元素为6,最后一个元素为0。所以中位数是6,即枢纽元是6。显然使用三数分割法消除了预排序输入的坏情形,并且实际减少了14%的比较。

void quick_sort(vector& arr, int start, int end)
{
	if (start >= end)
		return;

	//采用三值法选base基准值
    //
	int pos = -1;
	int mid = start + ((end - start) >> 1);
	if (arr[start] >= arr[mid] && arr[mid] >= arr[end])
		pos = start;
	else if (arr[end] >= arr[mid] && arr[mid] >= arr[start])
		pos = end;
	else
		pos = mid;
	swap(arr[start], arr[pos]);
    //
	int base = arr[start];
	int i = start;
	int j = end;

	while (i < j)
	{
		while (i < j)
		{
			if (arr[j] < base)
			{
				break;
			}
			--j;
		}

		while (i < j)
		{
			if (arr[i] > base)
			{
				swap(arr[i],arr[j]);
				break;
			}
			++i;
		}
	}

	if (i >= j)
	{
		swap(arr[i],arr[start]);
	}

	quick_sort(arr,start,i-1);
	quick_sort(arr,i+1,end);
}

5.归并排序(分治思想)

图解归并

  我们以[8,2,5,9,7]这组数字来举例

五大排序算法总结_第7张图片

 

  首先,一刀切两半:

五大排序算法总结_第8张图片

 

  再切:

五大排序算法总结_第9张图片

 

  再切

五大排序算法总结_第10张图片

 

  粒度切到最小的时候,就开始归并

五大排序算法总结_第11张图片

五大排序算法总结_第12张图片

五大排序算法总结_第13张图片

 

  数据量设定的比较少,是为了方便图解,数据量为单数,是为了让你看到细节,下面我画了一张更直观的图可能你会更喜欢:

五大排序算法总结_第14张图片

 

参考代码:

class Solution{
public:
    int temp[100];//辅助数组
    void merge_sort(int* array, int start, int end)
    {
        if (start >= end)
            return;

        int mid = start + (end - start)/2;

        merge_sort(array,start,mid);
        merge_sort(array,mid+1,end);

        merge(array,start,end,mid);
    }

    void merge(int* array, int start, int end , int mid)
    {
        int left = start;
        int right = mid + 1;
        int k = start;

        if (left >= right)
            return;

        while (left < mid + 1 && right < end + 1)
        {
            if (array[left] > array[right])
            {
                temp[k++] = array[right++];
            } else{
                temp[k++] = array[left++];
            }
        }

        while (left < mid + 1)
        {
            temp[k++] = array[left++];
        }

        while (right < end + 1)
        {
            temp[k++] = array[right++];
        }

        for (int i = start; i < end + 1; ++i) {
            array[i] = temp[i];
        }

    }

};

 

各大排序算法性能比较:

五大排序算法总结_第15张图片

你可能感兴趣的:(五大排序算法总结)