图文并茂,再次温顾七大排序算法

来源:互联网 发布:java api百度云盘 编辑:程序博客网 时间:2024/05/16 04:43

1、插入排序

1.1、 算法原理

插入排序的基本方法是:每步将一个待排序序列按数据大小插到前面已经排序的序列中的适当位置,直到全部数据插入完毕为止。
假设有一组无序序列 R0, R1, … , Rn−1:
1、 将这个序列的第一个元素R0视为一个有序序列;
2、 依次把 R1, R2, … , Rn−1 插入到这个有序序列中;
3、 将Ri插入到有序序列中时,前 i-1 个数是有序的,将Ri和R0 ~ Ri−1从后往前进行比较,确定要插入的位置。

1.2、 算法代码

void InsertSort(vector<int> &vec)  {      if (vec.size() < 2) return;    for (int i = 1; i < vec.size(); i++)    {        if (vec[i] < vec[i - 1])        {            int temp = vec[i];            int j;            for (j = i - 1; j >= 0 && vec[j] > temp; j--)            {                vec[j + 1] = vec[j];            }            vec[j + 1] = temp;        }    }}

1.3、 算法实例

用插入排序对{8,3,2,5,9,3,6}排序,排序过程如下:




通过逐个插入,即可完成元素的排序。

2、冒泡排序

2.1、 算法原理

1、从待排序序列的起始位置开始,从前往后依次比较各个位置和其后一位置的大小并执行S2。
2、如果当前位置的值大于其后一位置的值,就把他俩的值交换(完成一次全序列比较后,序列最后位置的值即此序列最大值,所以其不需要再参与冒泡)。
3、将序列的最后位置从待排序序列中移除。若移除后的待排序序列不为空则继续执行S1,否则冒泡结束。

2.2、 算法实现

public static void bubbleSort(int[] array) {    int len = array.length;    for (int i = 0; i < len; i++)     {        for (int j = 0; j < len - i - 1; j++)         {            if (array[j] > array[j + 1])            {                int temp = array[j + 1];                array[j + 1] = array[j];                array[j] = temp;            }        }    }}

2.3、 算法实例


冒泡排序过程

3、Shell排序

3.1、 算法原理

希尔排序是一种插入排序算法,又称作缩小增量排序。是对直接插入排序算法的改进。
其基本思想是:
1、先取一个小于n的整数h1作为第一个增量,把全部数据分成h1个组。所有距离为h1的倍数的记录放在同一个组中。
2、先在各组内进行直接插入排序;
3、然后,取第二个增量h2

3.2、 算法实现(C++):

void ShellSort(vector<int> &vec){    int len = vec.size();    for (int group = len / 2; group > 0; group /= 2)//定义步长    {        for (int i = group; i < len; i++)//定义小组标志元素        {            for (int j = i - group; j >= 0; j -= group) //有了步长,有了小组标志元素,那么就可以得出整个小组的值,接下来小组内部排序            {                if (vec[j] > vec[j + group])                {                    int temp = vec[j];                    vec[j] = vec[j + group];                    vec[j + group] = temp;                }               }        }    }}

3.3、 算法实例

利用Shell排序对数组{9,1,5,8,3,7,4,6,2}进行排序,选取步长为3,2,1,排序过程如下:
1、步长为3:
数组分为三组,分别为:{9、8、4},{1、3、6},{5、7、2}
各组内排序后为:{4、8、9},{1、3、6},{2、5、7}
本次排序后结果为:4、1、2、8、3、5、9、6、7

2、步长为2:
数组分为两组,分别为:{4、2、3、9、7},{1、8、5、6}
各组内排序后:{2、3、4、7、9},{1,5,6,8}
本次排序后结果为:2,1,3,5,4,6,7,8,9

3、步长为1:
数组就一组:{2,1,3,5,4,6,7,8,9}
组内排序后:{1,2,3,4,5,6,7,8,9}
最终结果我{1,2,3,4,5,6,7,8,9}

整个过程如下图所示:


Shell排序过程

4、堆排序

4.1、堆

堆是一个数组,他可以被看成一个近似的完全二叉树,树上的每一个节点对应数组中的一个元素。除了最底层外,该树是完全充满的,而且是从左向右填充。二叉堆可以分为最大堆和最小堆。在这两种堆中,节点要满足堆得性质,在最大堆中,要满足节点的父节点一定不能比自己小;在最小堆中,要满足节点的父节点不能比自己大;

4.2、维护堆得性质

以大根堆为例,MAX_HEAPIFY是用于维护最大堆性质的函数。它的输入为一个数组和一个下标,以及数组大小。在调用MAX_HEAPIFY时,我们假定A[I]左右子树都是大根堆,但是有可能A[i]小于它的孩子(注意建堆时for size/2 down to 1),这样就违背了大根堆性质。就需要调整,让A[i]逐级下降,从而让下标i的根节点的子树从新遵循大根堆性质。代码如下(分别有大根堆,小根堆递归与非递归维护):

1、大根堆

递归版:
void MAX_HEAPIFY_1(int *A, int index, int size){    int max = index;    int left = 2 * index + 1;    int right = 2 * index + 2;    if (left<size&&A[left]>A[index])        max = left;    if (right<size&&A[right]>A[max])        max = right;    if (max != index)    {        Swap(A, max, index);        MAX_HEAPIFY_1(A, max, size);    }}
非递归版:
void MAX_HEAPIFY_2(int *A, int index, int size){    while ((2 * index + 1)<size)    {        int max = index;        int left = 2 * index + 1;        int right = 2 * index + 2;        if (A[left]>A[index])            max = left;        if (right<size&&A[right]>A[max])            max = right;        if (max != index)        {            Swap(A, max, index);            index = max;        }        else        {            break;        }    }}

2、小根堆

递归版:
void MIN_HEAPIFY_1(int *A, int index, int size){    int min = index;    int left = 2 * index + 1;    int right = 2 * index + 2;    if (left<size&&A[left]<A[index])        min = left;    if (right<size&&A[right]<A[min])        min = right;    if (min != index)    {        Swap(A, min, index);        MIN_HEAPIFY_1(A, min, size);    }}
非递归版:
void MIN_HEAPIFY_2(int *A, int index, int size){    while ((2 * index + 1)<size)    {        int min = index;        int left = 2 * index + 1;        int right = 2 * index + 2;        if (left<size&&A[left]<A[index])            min = left;        if (right<size&&A[right]<A[min])            min = right;        if (min != index)        {            Swap(A, min, index);            MIN_HEAPIFY_2(A, min, size);        }        else            break;    }}

交换函数:

void Swap(int *A, int a, int b){    int temp = A[a];    A[a] = A[b];    A[b] = temp;}

举个例子,说明MAX_HEAPIFY(A,3)在数组A={27,17,3,16,13,10,1,5,7,12,4,8,9,0}的操作过程:



4.3、建堆

们用自底向上的方法逐步调用MAX_HEAPIFY就可以把一个数组转化为大根堆。代码如下:

void BUILD_MAX_HEAP(int *A, int size){    for (int i = size / 2 - 1; i >= 0; i--)    {        MAX_HEAPIFY_2(A, i, size);    }}

这里要说明下为什么不从0 to siez/2-1,而要从 siez/2-1 to 0,这是因为从1开始我们不能保证A[2],A[3]为大根堆,因此要从下往上。
举个例子,说明BUILD_MAX_HEAP在数组A={5,3,17,10,84,19,6,22,9}的造作过程。



4.4、堆排序算法

初始时候,堆排序算法利用BUILD_MAX_HEAP将输入数组A建成大根堆,因为数组中最大元素总在根节点A[0]中,通过把它与A[n-1]交换,其中n是数组大小,我们可以让最大元素放到正确位置上。这时候我们从堆中去掉节点n(size–可实现),剩余节点中,原来孩子节点任然是最大堆,只是新的根节点违反了最大堆性质,为了维护最大堆性质,我们要做的时调用MAX_HEAPIFY,从而在A[0,1…..n-2]上构造一个新的大根堆。堆排序不断重复这个过程,知道堆得大小从n-2降到1;代码如下:

void HEAPSORT(int *A, int size){    BUILD_MAX_HEAP(A, size);    //PrintHeap(A, size);    for (int i = size - 1; i >= 1; i--)    {        Swap(A, 0, i);        size--;        MAX_HEAPIFY_1(A, 0, size);    }}

举个例子,看看HEAPSORT在数组A={5,13,2,25,7,17,20,8,4}上的操作过程:



5、快速排序

5.1、原理

快速排序和归并排序都是利用的分治策略,主要有三步:

1、分解:对于一个数组A[p…r],找到一个合适的q,将数组A[p…r]分成两部分。其中一部分A[p…q-1]都小于或等于A[q],另一部分A[q+1,r]都大于等于A[q]。
2、解决:对上面两个子数组分别进行快速排序;
3、合并:因为子数组都是原址排序的,所以不需要合并操作:数组A[p…r]已经有序。

5.2、 算法实现(C++):

一趟快速排序:

int Partition(int array[], int low, int high){    int temp = array[low];    //保存下子表的第一个记录      int pivotkey = array[low];   //枢轴记录关键字      while (low<high)    //从表的两端向中间开始扫面      {        while (low<high&&array[high] >= pivotkey)            --high;        array[low] = array[high];    //将比枢轴小的移动到低端          while (low<high&&array[low] <= pivotkey)            ++low;        array[high] = array[low];   //将比枢轴大的移动到高端      }    array[low] = temp;     //枢轴记录到位      return low;    //返回枢轴位置  }

整个快速排序:

void QSort(int array[], int low, int high){    int piv;    if (low<high)    {        piv = Partition(array, low, high);        QSort(array, low, piv - 1);        QSort(array, piv + 1, high);    }}

5.3、 算法实例

对于快速排序的实例,数据结构(严蔚敏 著)书上的那个例子还是比较经典也比较容易理解的,贴上来。它是对数组 {49,38,65,97,76,13,27,49}进行排序。一趟快速排序过程如下:



一趟快速排序过程

按照上述过程即可完成最终排序。

6、归并排序

6.1、 算法原理

利用分治策略,分为三步:
1、分解:分解待排序的n个元素的序列成各具n/2个元素的两个子序列。
2、解决:使用归并排序递归地排序两个子序列,直到子序列规模为1为止。
3、合并:合并两个已排序的子序列以产生已排序的答案。

归并排序,也就是要将n个元素的序列划分为两个序列,再将两个序列划分为4个序列,直到每个序列只有一个元素,最后,再将两个有序序列归并成一个有序的序列。

归并过程为:先以两个数组为例,解释归并过程。比较A[i]和B[j]的大小,若A[i]≤B[j],则将第一个有序表中的元素A[i]复制到C[k]中,并令i和k分别加上1;否则将第二个有序表中的元素B[j]复制到C[k]中,并令j和k分别加上1,如此循环下去,直到其中一个有序表取完,然后再将另一个有序表中剩余的元素复制到C中从下标k到下标t的单元。我们利用归并对数组排序时,可以将数组前半部分和后半部分看成两个数组进行归并。

6.2、 算法实现

首先看看两个数组的归并的代码,便于理解后续的归并排序:

void mergeArray(int A[], int a, int B[], int b, int C[]){    int i, j, k;    i = j = k = 0;    while (i<a&&j<b)    {        if (A[i]<B[j])            C[k++] = A[i++];        else            C[k++] = B[i++];    }    while (i<a)    {        C[k++] = A[i++];    }    while (j<b)    {        C[k++] = B[i++];    }}

我们可以根据上述代码写出单个数组归并排序:

void mergeArray(int A[], int first, int mid, int last, int temp[]){    int i = first, j = mid, m = mid + 1, n = last, k = 0;    while (i <= j&&m <= n)    {        if (A[i]<A[m])            temp[k++] = A[i++];        else            temp[k++] = A[m++];    }    while (i <= j)        temp[k++] = A[i++];    while (m <= n)        temp[k++] = A[m++];    for (i = 0; i<k; i++)    {        A[first + i] = temp[i];    }}

根据前面代码写出归并排序:

void mergeSort(int A[], int first, int last, int C[]){    if (first<last)    {        int mid = (first + last) / 2;        mergeSort(A, first, mid, C);        mergeSort(A, mid + 1, last, C);        mergeArray(A, first, mid, last, C);    }}

来个详细例子(本例子来自IdealSpace)


这里写图片描述

对于原始的数组2,1,3,8,5,7,6,4,10,在整个过程执行的是顺序是途中红色编号1-20。虽然我们描述中说的是程序先分解,再归并,但实际过程是一边分解一边归并,前半部分分先排好序,后半部分再拍好,最后整个归并为一个完整的序列,途中的merge过程它所在层的两个序列的merge过程:下图展示了每个merge过程对作用于数组的哪部分(红色)。

这里写图片描述

7、基数排序

7.1、 算法原理

基数排序的原理如下:将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零,最后形成关键字k1k2k3…的序列。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。
基数排序的方式有以下两种:
1、最高位优先(Most Significant Digit first)法,简称MSD法:先按k1排序分组,同一组中记录,关键码k1相等,再对各组按k2排序分成子组,之后,对后面的关键码继续这样的排序分组,直到按最次位关键码kd对各子组排序后。再将各组连接起来,便得到一个有序序列。
2、最低位优先(Least Significant Digit first)法,简称LSD法:先从kd开始排序,再对kd−1进行排序,依次重复,直到对k1排序后便得到一个有序序列。

7.2、 算法实现(C++):

void radixSort(int x[], int length){    int temp;    int m = 0;    vector < vector <int> > buckets;    buckets.resize(10);    //Begin Radix Sort    for (int i = 0; i<7; i++){        //Determine which bucket each element should enter        for (int j = 0; j<length; j++){            temp = (int)((x[j]) / pow(10, i)) % 10;            buckets[temp].push_back((x[j]));        }        //Transfer results of buckets back into main array        for (int k = 0; k<10; k++){            for (int l = 0; l<buckets[k].size(); l++){                x[m] = buckets[k][l];                m++;            }            //Clear previous bucket            buckets[k].clear();        }        m = 0;    }    buckets.clear();    printSorted(x, length);}

7.3、 算法实例

比如我们对一组数据{12,13,104,7,9}排序,补零后数组变为{012,013,104,007,009},这个是三位,我们分别从个位,十位,百位,对其进行排序。
1、个位排序:




个位排序后结果为:012,013,104,007,009

2、十位排序:




十位排序后的结果为:104,007,009,012,013

3、百位排序:




百位排序后结果:007,009,012,013,104

4、得出结果
最后结果就为:7,9,12,13,104

8、排序算法总结

8.1、排序算法分类

需要用到外部存储的是外部排序法,上面都是内部排序,内部排序分类如下:



内部排序算法分类

8.2、排序算法总结



排序算法汇总

0 0