排序算法

算法比较

稳定性

插入排序,冒泡排序,二路归并排序和基数排序是稳定的排序方法;

选择排序,希尔排序,快速排序和堆排序是不稳定的排序方法;

复杂度

排序方法 平均时间 最坏情况 辅助空间
插入排序 O(n^2) O(n^2) O(1)
希尔排序 O(nlogn) O(nlogn) O(1)
冒泡排序 O(n^2) O(n^2) O(1)
快速排序 O(nlogn) O(n^2) O(logn)
选择排序 O(n^2) O(n^2) O(1)
堆排序 O(nlogn) O(nlogn) O(1)
归并排序 O(nlogn) O(nlogn) O(n)
基数排序 O(d(n+r)) O(d(n+r)) O(r+n)

方法选择

(1) 排序数据的规模n较大,关键字元素分布比较随机,并且不要求排序稳定性时,宜选用快速排序;

(2) 排序数据规模n较大,内存空间又允许,并且有排序稳定性要求,宜采用归并排序;

(3) 排序数据规模n较大,元素分布可能出现升序或者逆序的情况,并且对排序的稳定性不要求,宜采用堆排序或者归并排序;

(4) 排序数据规模n较小,元素基本有序,或者分布也比较随机,并且有排序稳定性要求时,宜采用插入排序;

(5) 排序数据规模n较小,对排序稳定性又不做要求时,宜采用选择排序;

算法实现

插入排序

插入是个比较容易理解的排序算法,每次取下一个未排序的数,在前面已排序的部分从后往向前查找合适的位置,并将该数插入,而不满足排序条件的数字则需要不断的复制到下一位置,为待排序数字腾出位置;

 1 void insert_sort(int a[], int n)
 2 {
 3     int i = 0;
 4     int p = 0;
 5     int temp = 0;
 6 
 7     /* 认为第一个数字是有序的,从第二个数字开始 */
 8     for (i = 1; i < n; ++i)
 9     {
10         temp = a[i]; /* 记录待插入数字 */
11         p = i - 1; /* 从前面已经排好序的位置中选择 */
12         
13         /* 查找可插入位置,注意0位置也会进入循环,p可能为-1 */
14         while (p >= 0 && temp < a[p])
15         {
16             a[p+1] = a[p]; /* 把不符合插入位置的数据后移 */
17             p--; /* 继续向前查找 */
18         }
19         
20         a[p+1] = temp; /* 待排序元素插入对应位置 */
21     }
22 }

(1) 插入排序接住了一个temp作为辅助空间;

(2) 插入排序是一个稳定的排序方法;

(3) 最优情况:当序列是以有序状态输入时,达到最小比较次数,即n-1次比较,无需移动元素;

最坏情况:当序列是以逆序状态输入时,达到最大比较次数n(n-1)/2,需要移动n(n-1)/2次元素;

平均情况:最优与最坏情况的平均比较次数约为n^2/4次;

综上:该算法的平均复杂度为O(n^2);

选择排序

选择排序是按照位置进行的,即从第1个到第n个位置,依次选择未排序部分最小的值放入,如果最小值不是当前位置的初始值,则需要进行数值对调;

 1 void select_sort(int a[], int n)
 2 {
 3     int i = 0;
 4     int j = 0;
 5     int min = 0;
 6     int temp = 0;
 7     
 8     /* 遍历序列,最后一个元素就不需要遍历了 */
 9     for (i = 0; i < n - 1; ++i)
10     {
11         /* 设置最小值为当前位置 */
12         min = i;
13         
14         /* 从后面查找比当前最小值还小的值的位置 */
15         for (j = i + 1; j < n; ++j)
16         {
17             /* 不断与前一个最小值进行比较,如果更小则更新位置 */
18             /* 注意这里要使用每次的最小值a[min]与后面元素进行比较 */
19             if (a[j] < a[min])
20             {
21                 min = j;
22             }
23         }
24         
25         /* 起始记录位置与最小值位置不一致,说明存在更小值,交换位置 */
26         if (i != min)
27         {
28             temp = a[i];
29             a[i] = a[min];
30             a[min] = temp;
31         }
32     }
33 }

(1) 选择排序是不稳定排序;

(2) 选择排序的交换次数,当为有序序列时最优,交换次数为0,当为逆序序列时最坏,交换次数为3(n-1),其中3为交换操作;

(3) 选择排序的比较次数是不随序列是否有序的原始状态变化的,均为n(n-1)/2次比较;即算法的平均复杂度为O(n^2);

冒泡排序

冒泡排序的思想是未排序的部分从头到尾两两比较,若逆序则交换,每次排序后,最大的元素都会在序列尾端,然后再对前面未排序的部分进行起泡;

 1 /* 进一步优化,可以加flag判断是否交换,若无交换则排序完成 */
 2 /* 如下面注释掉的部分代码 */
 3 void bubble_sort(int a[], int n)
 4 {
 5     int i = 0;
 6     int j = 0;
 7     int temp = 0;
 8     //int flag = 0;
 9     
10     /* 不断将最大数起泡到序列结尾,n-1次完成 */
11     for (i = 0; i < n - 1; ++i)
12     {
13         /* 如果没有进行交换,则说明排序完成,直接退出 */
14         //if (flag == 0)
15         //{
16         //    break;
17         //}
18         
19         /* 每次排序都需要重置标记 */
20         //flag = 0;
21     
22         /* 对尚未排好序的序列从头到尾进行起泡 */
23         for (j = 0; j < n-1-i; ++j)
24         {
25             /* 交换数据 */
26             if (a[j] > a[j+1])
27             {
28                 temp = a[j];
29                 a[j] = a[j+1];
30                 a[j+1] = temp;
31                 
32                 /* 有交换,则打标记 */
33                 //flag = 1;
34             }
35         }
36     
37     }
38 }

(1) 冒泡排序是稳定的排序;

(2) 分析使用flag的情况,如果序列有序时最优,只需要进行一次气泡过程即可,元素位置不变;如果序列逆序时最坏,需要进行n(n-1)/2比较;所以冒泡排序的评价时间复杂度为O(n^2);

 

希尔排序

对于插入排序和冒泡排序等,在序列基本有序的情况下,会得到更好的排序时间;希尔排序的思想是在进行上述排序之前,对元素进行移动使序列达到基本有序,从而减少比较和移动次数;希尔排序首先对所有元素按照一个gap为一组,组中元素小的往前移动,这样在达到最后一次排序之前,小的元素基本上都已经移动到前侧了,而最后一次gap=1的排序,可以认为是对基本有序的序列进行插入或冒泡排序,所需比较和移动次数大大减少;

 1 void shell_sort(int a[], int n)
 2 {
 3     int i = 0;
 4     int p = 0;
 5     int gap = 0;
 6     
 7     /* gap的规则,按照折半缩小,直到gap=1时,进行直接插入排序 */
 8     for (gap = n/2; gap > 0; gap /= 2)
 9     {
10         /* 从gap开始,对属于每个gap范围的元素中小元素进行前移 */
11         for (i = gap; i < n; ++i)
12         {
13             temp = a[i];
14             p = i - gap;
15             
16             /* 前移位置查找 */
17             while (p >= 0 && a[p] > temp)
18             {
19                 a[p+gap] = a[p];
20                 p -= gap;
21             }
22             
23             /* 插入合适位置 */
24             a[p+gap] = temp;
25         }
26     }
27 }

(1) 希尔排序是不稳定的排序;

(2) 希尔排序的平均时间复杂度为O(nlogn);

(3) 希尔排序的gap取法,以及内部的移动方式也不是固定的;

 

快速排序

快速排序的思想是找一个基准元素,然后将序列中比基准元素小的放到左侧,大的放到右侧,然后在分别对基准元素左右的序列再次重复上述步骤;

 1 int partion(int a[], int low, int high)
 2 {
 3     /* 最左侧元素为基准值 */
 4     int t = a[low];
 5     
 6     /* 左右不相等则进行比对,相等则对调完毕 */
 7     while (low < high)
 8     {
 9         /* 从右侧向左侧查找小于基准值的元素 */
10         while (low < high && a[high] >= t)
11         {
12             high--;
13         }
14         
15         /* 较小值移动到左侧*/
16         a[low] = a[high];
17         
18         /* 从左侧向右侧查找大于基准值的元素 */
19         while (low < high && a[low] <= t)
20         {
21             low++;
22         }
23         
24         /* 较大值移动到右侧 */
25         a[high] = a[low];
26     }
27     
28     /* 基准元素放到中间位置 */
29     a[low] = t;
30     
31     return low;
32 }
33 
34 
35 void quick_sort(int a[], int low, int high)
36 {
37     int pivot = 0;
38     
39     if (low < high)
40     {
41         /* 分区 */
42         pivot = partion(a, low, high);
43         
44         /* 分别对左右区域做排序 */
45         quick_sort(a, low, pivot - 1);
46         quick_sort(a, pivot + 1, high);
47     }
48 }

(1) 快速排序是一种不稳定的排序;

(2) 在序列有序的情况下,快排退化为冒泡排序,此时的时间复杂度最高,约为O(n^2);在划分中如果均为中位数时,时间复杂度为O(nlogn);但对于平均情况来说,快排仍然是最好的内排序方法;

(3) 分界的基准元素取值方法有多种,通常是首元素,尾元素和中间元素;

 

归并排序

归并排序的思想是将待排序序列进行区域划分与合并,先将整个序列分成两个序列,然后其中的每个在分成两个,依次细分,然后对小区域进行归并,归并之后再对上层区域进行归并,最终得到有序序列;

 1 void merge(int a[], int low, int mid, int high, int temp[])
 2 {
 3     int i = low;
 4     int j = mid + 1;
 5     int k = 0;
 6     
 7     /* 两个区域都从头开始比较大小,合并到temp */
 8     while (i <= mid && j <= high)
 9     {
10         if (a[i] <= a[j])
11         {
12             temp[k++] = a[i++];
13         }
14         else
15         {
16             temp[k++] = a[j++];
17         }
18     }
19     
20     /* 若其中一个区域有剩余元素,则直接并入 */
21     while (i <= mid)
22     {
23         temp[k++] = a[i++];
24     }
25     
26     while (j <= high)
27     {
28         temp[k++] = a[j++];
29     }
30     
31     
32     /* 将temp填入待排序列中 */
33     for (i = 0; i < k; ++i)
34     {
35         a[low+i] = temp[i];
36     }
37 }
38 
39 
40 void merge_sort(int a[], int low, int high, int temp[])
41 {
42     int mid = 0;
43     
44     if (low < high)
45     {
46         /* 取中位数 */
47         mid = (low + high)/2;
48         
49         /* 分别对左右进行归并排序 */
50         merge_sort(a, low, mid, temp);
51         merge_sort(a, mid + 1, high, temp);
52         
53         /* 排序的合并过程 */
54         merge(a, low, mid, high, temp);
55     }
56 }

(1) 归并排序是一种稳定的排序算法;

(2) 归并排序需要一个与待排序序列同样大小的空间;

(3) 归并排序的时间复杂度为O(nlogn);

 

堆排序

堆排序的思想是把一个待排序序列看成一个近似完全二叉树,第一步是从最后一个非叶子节点开始一直到第一个节点,对序列进行调整,调整之后进行排序;排序首先将0位置的最大节点与最后一个元素相交换,然后对前面的0元素按照堆的规则进行调整;如上,直至全部元素排序结束;

 1 void heap_adjust(int a[], int i, int n)
 2 {
 3     int child = 0;
 4     int temp = 0;
 5     
 6     /* 对i节点进行调整 */
 7     while (i * 2 + 1 < n)
 8     {
 9         /* 找到做孩子节点 */
10         child = 2 * i + 1;
11         
12         /* 如果存在右孩子,并且右孩子比较大,那么记录改成右孩子 */
13         if (child < n - 1 && a[child + 1] > a[child])
14         {
15             child++;
16         }
17         
18         /* 如果父节点大于等于最大的孩子节点,不需要调整 */
19         if (a[i] >= a[child])
20         {
21             break;
22         }
23         /* 父节点小于孩子节点,则需要与孩子节点对调 */
24         else
25         {
26             temp = a[i];
27             a[i] = a[child];
28             a[child] = temp;
29         }
30     
31         /* 继续调整孩子节点 */
32         i = child;
33     }
34 }
35 
36 void heap_sort(int a[], int n)
37 {
38     int i = 0;
39     int temp = 0;
40     
41     
42     /* 起始a[]认为是个数组形式表示的近似完全二叉树 */
43     
44     /* 从最后一个非叶子节点开始,向前逐步调整堆 */
45     for (i = n / 2 - 1; i >= 0; --i)
46     {
47         heap_adjust(a, i, n);
48     }
49     
50     /* 排序过程为第一个元素与最后一个未排序元素交换 */
51     /* 然后调整前面未排序的部分,保证最大的首节点放到最后 */
52     /* 调整之后,新的最大节点在根0位置 */
53     for (i = n - 1; i > 0; --i)
54     {
55         /* 交换首元素和最后未排序元素 */
56         temp = a[0];
57         a[0] = a[i];
58         a[i] = temp;
59         
60         /* 调整堆 */
61         heap_adjust(a, 0, i);
62     }
63 }

(1) 堆排序是一种不稳定的排序;

(2) 堆排序的时间复杂度是O(nlogn);无论是最坏或者平均情况皆如此;

 

posted @ 2019-10-30 22:29  AlexAlex  阅读(201)  评论(0编辑  收藏  举报