序言
通常所说的排序算法往往指的是内部排序算法,即数据记录在内存中进行排序。
排序算法大体可分为两种:
比较排序:冒泡排序,选择排序,插入排序,归并排序,堆排序,快速排序等
非比较排序:基数排序,计数排序,桶排序等
排序算法的稳定性:如果Ai = Aj,排序前Ai在Aj之前,排序后Ai还在Aj之前,则称这种排序算法是稳定的。通俗地讲就是保证排序前后两个相等的数的相对顺序不变。
排序算法是否为稳定的是由具体算法决定的,不稳定的算法在某种条件下可以变为稳定的算法,而稳定的算法在某种条件下也可以变为不稳定的算法。
稳定排序算法的好处:排序算法如果是稳定的,那么从一个键上排序,然后再从另一个键上排序,第一个键排序的结果可以为第二个键排序所用。
1. 冒泡排序
原理:将序列划分为无序和有序区,不断通过交换较大元素至无序区尾完成排序。
要点:设计交换判断条件,提前结束已排好序的序列循环。
步骤:
(1) 比较相邻的元素,如果前一个比后一个大,就把它们两个调换位置。
(2) 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。一轮比较结束后,最后的元素会是最大的数。
(3) 针对所有的元素重复以上的步骤,除了最后一个。
(4) 持续每次对往后没有比较的元素重复上面的步骤,直到没有任何一对数字需要比较。
/* 冒泡排序核心代码 */
// 分类 -------------- 内部比较排序
// 数据结构 ---------- 数组
// 最差时间复杂度 ---- O(n^2)
// 最优时间复杂度 ---- 如果能在内部循环第一次运行时,使用一个旗标来表示有无需要交换的可能,可以把最优时间复杂度降低到O(n)
// 平均时间复杂度 ---- O(n^2)
// 所需辅助空间 ------ O(1)
// 稳定性 ------------ 稳定
int BubbleSort_1 (int A[],int length)
{
int i,j,temp;
for (i = 0; i < length - 1; i++)
{
for (j = i + 1; j < length; j++)
{
if (A[j] < A[i]) //较轻元素往上浮,每次保证最轻的元素移到当前循环首部
{
temp = A[i];
A[i] = A[j];
A[j] = temp;
}
}
}
return 0;
}
int BubbleSort_2(int A[], int length)
{
int i,j,temp;
for (i = length - 1; i > 0; i--)
{
for (j = i - 1; j >= 0; j--)
{
if (A[j] > A[i]) //较重元素往下沉,每次保证最重元素移到尾部
{
temp = A[i];
A[i] = A[j];
A[j] = temp;
}
}
}
return 0;
}
int BubbleSort_3(int A[], int length)
{
int i,j,temp;
int ischanged; //设置跳出条件
for (i = 0; i < length; i++) //单轮循环不能保证有序
{
ischanged = 0;
for (j = length - 1; j >= i; j--) //较轻元素往上浮,每次保证最轻的元素移到当前循环首部
{
if (A[j - 1] > A[j])
{
temp = A[j];
A[j] = A[j - 1];
A[j - 1] = temp;
ischanged = 1;
}
}
}
if (ischanged == 0) break; //若没有移动则说明序列已经有序,直接跳出
return 0;
}
尽管冒泡排序是最容易了解和实现的排序算法之一,但它对于元素较多的数列排序是很没有效率的。
鸡尾酒排序
也叫定向冒泡排序,是冒泡排序的一种改进。此算法与冒泡排序的不同处在于从低到高然后从高到低,而冒泡排序则仅从低到高去比较序列里的每个元素。他可以得到比冒泡排序稍微好一点的效能。
void exchange(int A[], int i, int j) // 交换A[i]和A[j]
{
int temp = A[i];
A[i] = A[j];
A[j] = temp;
}
int main()
{
int A[] = { 6, 5, 3, 1, 8, 7, 2, 4 }; // 从小到大定向冒泡排序
int n = sizeof(A) / sizeof(int); // 计算数组长度
int left = 0; // 初始化边界
int right = n - 1;
while (left < right)
{
for (int i = left; i < right; i++) // 前半轮,将最大元素放到后面
if (A[i] > A[i + 1])
{
exchange(A, i, i + 1);
}
right--; //每比一轮减减
for (int i = right; i > left; i--) // 后半轮,将最小元素放到前面
if (A[i] < A[i - 1])
{
exchange(A, i - 1, i);
}
left++; //每比一轮加加
}
return 0;
}
以序列(2,3,4,5,1)为例,鸡尾酒排序只需要访问一次序列就可以完成排序,但如果使用冒泡排序则需要四次。但是在乱数序列的状态下,鸡尾酒排序与冒泡排序的效率都很差劲。
2. 快速排序
原理:快速排序采用了一种分治策略(Divide and Conquer),通常称其为分治法,其基本思想是:将原问题分解为若干个规模更小但结构与原问题相似的子问题。递归地解这些子问题,然后将这些子问题的解组合为原问题的解。
步骤:
(1) 从序列中挑出一个元素,作为"基准"(pivot)。
(2) 把所有比基准值小的元素放在基准前面,所有比基准值大的元素放在基准的后面(相同的数可以到任一边),这个称为分区(partition)操作。
(3) 对每个分区“递归”地进行步骤1~3,递归的结束条件是序列的大小是0或1,这时整体已经被排好序了。
/* 快速排序核心代码 */
// 分类 ------------ 内部比较排序
// 数据结构 --------- 数组
// 最差时间复杂度 ---- 每次选取的基准都是最大的元素(或者每次都是最小),导致每次只划分出了一个子序列,需要进行n-1次划分才能结束递归,时间复杂度为O(n^2)
// 最优时间复杂度 ---- 每次选取的基准都能使划分均匀,只需要logn次划分就能结束递归,时间复杂度为O(nlogn)
// 平均时间复杂度 ---- O(nlogn)
// 所需辅助空间 ------ O(logn)~O(n),主要是递归造成的栈空间的使用(用来保存left和right等局部变量),取决于递归树的深度
// 一般为O(logn),最差为O(n)(基本有序的情况)
// 稳定性 ---------- 不稳定
void exchange(int A[],int i, int j) //数组元素互换
{
int temp = A[i];
A[i] = A[j];
A[j] = A[i];
}
int ArrayDivide(int A[], int left, int right)
{
int pivot = A[right]; //最后元素为基准与元素
int index = left - 1; //小于基准元素的子数组索引
int i;
for (i = left; i < right; i++)
{
if (A[i] < pivot)
{
index++;
exchange(A[], index, i);
}
}
exchange(A[], index + 1; right); //将基准元素放到子数组的最后
//该操作可能打乱后面元素的稳定性,比如跟基准元素相同的其他元素
return index + 1; //返回基准的索引
}
void QuickSort(int A[], int left, int right)
{
int pivot_index; // 基准的索引
if (left < right)
{
pivot_index = ArrayDivide(A, left, right);
QuickSort(A, left, pivot_index - 1); //较小元素子数组递归
QuickSort(A, pivot_index + 1, right); //较大元素子数组递归
}
}
快速排序的思想来自冒泡排序,冒泡排序是通过相邻元素的比较和交换把最小的冒泡到最顶端,而快速排序是比较和交换小数和大数,这样一来不仅把小数冒泡到上面同时也把大数沉到下面。
快速排序是不稳定的排序算法,不稳定发生在基准元素与A[tail+1]交换的时刻。
比如序列:{ 1, 3, 4, 2, 6, 9, 5, 7, 5 },基准元素是5,一次划分操作后最后一个5挪到了前边,从而改变了两个元素5的相对次序。
3. 选择排序
原理:选择排序(Selection sort)是一种简单直观的排序算法。将序列划分为无序和有序区,寻找无序区中的最小值和无序区的首元素交换,使得有序区扩大一个,循环最终完成全部排序。
步骤:首先在未排序序列中找到最小元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小元素,然后放到排序序列末尾。
/* 选择排序核心代码 */
// 分类 -------------- 内部比较排序
// 数据结构 ---------- 数组
// 最差时间复杂度 ---- O(n^2)
// 最优时间复杂度 ---- O(n^2)
// 平均时间复杂度 ---- O(n^2)
// 所需辅助空间 ------ O(1)
// 稳定性 ------------ 不稳定
/* 升序排列 */
int SelectSort(int A[],int length)
{
int i,j,min,temp;
for (i = 0; i < length - 1; i++) //有序区设置
{
min = i; //无序区首位
for (j = i + 1; j < length; j++) //无序区
{
if (A[j] < A[i])
{
min = j;
}
}
if (min != i) //发现最小元素,移动到有序区
{
temp = A[i];
A[i] = A[min];
A[min] = A[i];
}
}
return 0;
}
其实选择排序可以看成冒泡排序的优化,因为其目的相同,只是选择排序只有在确定了最小数的前提下才进行交换,大大减少了交换的次数。
快速排序的思想:冒泡 + 二分 + 递归分治
选择排序是不稳定的排序算法,不稳定发生在最小元素与A[i]交换的时刻。
比如序列:{ 5, 8, 5, 2, 9 },一次选择的最小元素是2,然后把2和第一个5进行交换,从而改变了两个元素5的相对次序。
4. 堆排序
原理:利用大根堆(又称大顶堆,指父节点的值大于或等于子节点的值)或小根堆思想,首先建立堆,然后将堆首与堆尾交换,堆尾之后为有序区。
要点:
建堆(大顶堆/小顶堆)
交换(队首队尾交换)
调整堆(递归继续进行)
步骤:
(1) 创建一个堆
(2) 把堆顶元素(最大值)和堆尾元素互换
(3) 把堆的尺寸缩小1,并调用heapify(A, 0)从新的堆顶元素开始进行堆调整
(4) 重复步骤2,直到堆的尺寸为1
/* 堆排序核心代码-升序 */
// 分类 -------------- 内部比较排序
// 数据结构 ---------- 数组
// 最差时间复杂度 ---- O(nlogn)
// 最优时间复杂度 ---- O(nlogn)
// 平均时间复杂度 ---- O(nlogn)
// 所需辅助空间 ------ O(1)
// 稳定性 ------------ 不稳定
int heap_length; //堆大小
void MaxHeapify(int A[], int root) //堆调整函数; 大顶堆:建堆过程将当前堆最大元素移到根节点
{
int left,right,temp;
left = 2*root + 1; //左孩子索引
right = 2*root + 2; //右孩子索引
int largest = root; //根节点子节点最大值指示
if (left < heap_length && A[left] > A[root])
largest = left;
if (right < heap_length && A[right] > A[largest]) //与largest比较保证大顶堆
largest = right;
if (largest != root)
{
temp = A[root];
A[root] = A[largest];
A[largest] = temp;
MaxHeapify(A, largest); //递归过程将最大元素移到根节点
}
}
void BuildHeap(int A[],int length) //建堆
{
heap_length = length; //堆大小
int i;
for (i = (heap_length - 1)/2; i >= 0; i--) //-1/2,保证数组总元素奇偶数情况下每个元素都能被遍历到
MaxHeapify(A, i); //不断的堆调整;建什么样的堆
}
int HeapSort(int A[], int length)
{
BuildHeap(A,length); //建堆
int i,temp;
for (i = length - 1; i >= 0; i--)
{
temp = A[0]; //交换,堆顶元素与最后一个元素互换
A[0] = A[i];
A[i] = temp;
length--; //堆调整,去掉最后一个元素
MaxHeapify(A, 0); //从新的堆顶元素进行堆调整
}
}
堆排序是借助堆来实现的选择排序,思想同简单的选择排序。
堆排序是不稳定的排序算法,不稳定发生在堆顶元素与A[i]交换的时刻。
比如序列:{ 8, 6_1, 7, 6_2 },堆顶元素是8,堆排序下一步将8和6_2进行交换,得到序列 { 6_2, 6_1, 7, 8 },再进行堆调整得到{ 7, 6_1, 6_2, 8 },重复之前的操作最后得到{ 6_2, 6_1, 7, 8 }从而改变了两个6的相对次序。
5. 插入排序 - 简单/直接插入排序
原理:[数组内]将数组分为无序区和有序区两个区,然后不断将无序区的第一个元素按大小顺序插入到有序区中去,最终将所有无序区元素都移动到有序区完成排序。
要点:设立哨兵(标识/索引),作为临时存储和判断数组边界之用。
步骤:
(1) 从第一个元素开始,该元素可以认为已经被排序
(2) 取出下一个元素,在已经排序的元素序列中从后向前扫描
(3) 如果该元素(已排序)大于新元素,将该元素移到下一位置
(4) 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
(5) 将新元素插入到该位置后
(6) 重复步骤2~5
/* 插入排序核心代码 - 升序 */
// 分类 ------------- 内部比较排序
// 数据结构 ---------- 数组
// 最差时间复杂度 ---- 最坏情况为输入序列是降序排列的,此时时间复杂度O(n^2)
// 最优时间复杂度 ---- 最好情况为输入序列是升序排列的,此时时间复杂度O(n)
// 平均时间复杂度 ---- O(n^2)
// 所需辅助空间 ------ O(1)
// 稳定性 ------------ 稳定
#include <stdio.h>
/* 直接插入排序 */
void InsertionSort(int A[], int length)
{
for (int i = 0; i < length; i++)
{
int temp = A[i];
int j = i;
while (j - 1 >= 0 && temp < A[j - 1])
{
A[j] = A[j - 1];
j--;
}
A[j] = temp;
}
}
int main()
{
int A[] = { 5, 2, 9, 4, 7, 6, 1, 3, 8 }; //从小到大
int length = sizeof(A) / sizeof(int);
//函数调用
InsertionSort(A, length);
//输出
for (int i = 0; i < length; i++)
{
printf("%d ", A[i]);
}
printf("\n");
return 0;
}
如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。
插入排序不适合对于数据量比较大的排序应用。但是,如果需要排序的数据量很小,比如量级小于千,那么插入排序还是一个不错的选择。
6. 二分插入排序 - 插入排序的改进
原理:应用二分查找法,减小了比较的次数。
要点:二分法定位新元素的位置。
// 分类 -------------- 内部比较排序
// 数据结构 ---------- 数组
// 最差时间复杂度 ---- O(n^2)
// 最优时间复杂度 ---- O(nlogn)
// 平均时间复杂度 ---- O(n^2)
// 所需辅助空间 ------ O(1)
// 稳定性 ------------ 稳定
/* 二分插入排序 */
#include <stdio.h>
int main()
{
int A[] = { 5, 2, 9, 4, 7, 6, 1, 3, 8 }; //从小到大
int length = sizeof(A) / sizeof(int);
int left,right,middle;
int i,j,temp;
for (i = 0; i < length; i++)
{
left = 0;
right = i;
temp = A[i];
while (left < right) //二分查找插入位置
{
middle = (left + right) / 2;
if (temp < A[middle])
right = middle;
else
left = middle + 1;
}
for (j = i; j >= left; j--)
{
A[j] = A[j - 1];
}
A[j] = temp;
}
//输出
for (i = 0; i < length; i++)
{
printf("%d ", A[i]);
}
printf("\n");
return 0;
}
当n较大时,二分插入排序的比较次数比直接插入排序的最差情况好得多,但比直接插入排序的最好情况要差。所当以元素初始序列已经接近升序时,直接插入排序比二分插入排序比较次数少。二分插入排序元素移动次数与直接插入排序相同,依赖于元素初始序列。
7. 希尔排序 - 插入排序的更高效改进
原理:希尔排序也叫缩小增量排序,是插入排序的一种高效率实现。基本思想是:先将整个待排记录序列分割成若干子序列分别进行直接插入排序,待整个序列中的记录基本有序时再对全体记录进行一次直接插入排序。希尔排序是基于插入排序的以下两点性质而提出改进方法的:
(1) 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率
(2) 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位
要点:增量的选择,子序列,直接插入排序
从上述排序过程可见,希尔排序的特点是,子序列的构成不是简单的逐段分割,而是将某个相隔某个增量的记录组成一个子序列。
如上面的例子,第一趟排序时的增量为5,第二趟排序的增量为3。由于前两趟的插入排序中记录的关键字是和同一子序列中的前一个记录的关键字进行比较,因此关键字较小的记录就不是一步一步地向前挪动,而是 跳跃式地往前移,从而使得进行最后一趟排序时,整个序列已经做到基本有序,只要作记录的少量比较和移动即可。
步骤:
(1) 先将序列按增量划分为元素个数相同的若干组
(2) 使用直接插入排序法进行排序
(3) 然后不断缩小增量重复(1)(2)直至增量缩小至1
(4) 最后使用直接插入排序完成排序。
/* 希尔排序 - 升序 */
/* 增量确定方式1 */
int ShellSort(int A[],int length)
{
int h = length >> 1; //设置增量
int i,j,temp;
while (h >= 1)
{
for (i = h; i < length; i++)
{
temp = A[i];
j = i - h;
while (j >= 0 && temp < A[j])
{
A[j + h] = A[j];
j = j - h; //跳跃式往前移
}
A[j + h] = temp;
}
h = h >> 1; //增量变为原来的一半
}
return 0;
}
/* 增量确定方式2 */
#include <stdio.h>
// 分类 -------------- 内部比较排序
// 数据结构 ---------- 数组
// 最差时间复杂度 ---- 根据步长序列的不同而不同。已知最好的为O(n(logn)^2)
// 最优时间复杂度 ---- O(n)
// 平均时间复杂度 ---- 根据步长序列的不同而不同。
// 所需辅助空间 ------ O(1)
// 稳定性 ------------ 不稳定
int main()
{
int A[] = {5, 2, 9, 4, 7, 6, 1, 3, 8};
int length = sizeof(A) / sizeof(int);
int i,j,temp;
int h = 0;
while (h <= length) //初始增量的选择
{
h = 3*h + 1;
}
while (h >= 1) //增量不小于1
{
for (i = h; i < length; i++)
{
j = i - h;
temp = A[i];
while (j >= 0 && A[j] > temp)
{
A[j + h] = A[j];
j = j - h;
}
A[j + h] = temp;
}
h = (h - 1) / 3; //增量缩小
}
printf("Shell Sort:");
for (i = 0; i < length; i++)
{
printf("%d ", A[i]);
}
printf("\n");
return 0;
}
希尔排序是按照不同步长对元素进行插入排序,当刚开始元素很无序的时候,步长最大,所以插入排序的元素个数很少,速度很快;当元素基本有序了,步长很小,插入排序对于有序的序列效率很高。所以,希尔排序的时间复杂度会比o(n^2)好一些。希尔排序的分析是复杂的,时间复杂度是所取增量的函数,这涉及一些数学上的难题。但是在大量实验的基础上推出当n在某个范围内时,时间复杂度可以达到O(n^1.3)。
由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以希尔排序是不稳定的。
比如序列:{ 3, 5, 10, 8_1, 7, 2, 8_2, 1, 20, 6 },h=2时分成两个子序列 { 3, 10, 7, 8_2, 20 } 和 { 5, 8_1, 2, 1, 6 } ,未排序之前第二个子序列中的8在前面,现在对两个子序列进行插入排序,得到 { 3, 7, 8_2, 10, 20 } 和 { 1, 2, 5, 6, 8_1 } ,即 { 3, 1, 7, 2, 8_2, 5, 10, 6, 20, 8_1 } ,两个8的相对次序发生了改变。
8. 归并排序
原理:归并排序的实现分为递归实现与非递归(迭代)实现。
递归实现 的归并排序是算法设计中分治策略的典型应用,我们将一个大问题分割成小问题分别解决,然后用所有小问题的答案来解决整个大问题。
非递归(迭代)实现 的归并排序首先进行是两两归并,然后四四归并,然后是八八归并,一直下去直到归并了整个数组。
要点:分治,归并
步骤:
(1) 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
(2) 设定两个指针,最初位置分别为两个已经排序序列的起始位置
(3) 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
(4) 重复步骤3直到某一指针到达序列尾
(5) 将另一序列剩下的所有元素直接复制到合并序列尾
#include <stdio.h>
// 分类 -------------- 内部比较排序
// 数据结构 ---------- 数组
// 稳定性 ------------ 稳定
void merge(int A[], int left, int middle, int right); //合并函数
void mergesort_recursion(int A[], int left, int right); //递归归并排序函数
void mergesort_iteration(int A[], int left, int right); //非递归-迭代归并排序函数
int main()
{
int A1[] = { 7, 6, 4, 2, 9, 8, 3, 5 };
int A2[] = { 5, 4, 2, 0, 7, 6, 1, 3 };
int len1 = sizeof(A1) / sizeof(int);
int len2 = sizeof(A2) / sizeof(int);
mergesort_recursion(A1, 0, len1 - 1); // 递归实现
mergesort_iteration(A2, 0, len2 - 1); // 非递归实现
printf("递归实现的归并排序:");
for (int i = 0; i < n1; i++)
{
printf("%d ",A1[i]);
}
printf("\n");
printf("非递归实现的归并排序:");
for (int i = 0; i < n2; i++)
{
printf("%d ", A2[i]);
}
printf("\n");
return 0;
}
void merge(int A[], int left, int middle, int right)// 合并两个已排好序的数组A[left...middle]和A[middle+1...right]
{
int n1 = middle - left + 1; //两个数组的大小
int n2 = right - middle;
int left_array[n1]; //辅助存储空间,存储子数组元素
int right_array[n2];
for (int i = 0; i < n1; i++) //把两部分复制到暂存数组中
left_array[i] = A[left + i];
for (int j = 0; j < n2; j++)
right_array[j] = A[middle + j + 1];
int i = 0; //左数组索引
int j = 0; //右数组索引
for (int k = left; k <= right; k++) // 依次比较两个子数组中的值,每次取出更小的那一个放入原数组
{
while (i < n1 && j < n2) //两数组均有元素
{
if (left_array[i] <= right_array[j])
{
A[k] = left_array[i];
i++;
}
else
{
A[k] = right_array[j];
j++;
}
}
if (i >= n1) //左数组处理完
{
A[k] = right_array[j]; //右数组
j++;
}
else //右数组处理完
{
A[k] = left_array[i]; //左数组
i++;
}
}
}
/* 递归实现的归并排序(自顶向下) */
//递归深度为log2n
void mergesort_recursion(int A[], int left, int right)
{
int middle = (left + right) / 2;
while (left < right) //left = right,最后细分的一个元素,递归“回升”
{
mergesort_recursion(A, left, middle);
mergesort_recursion(A, middle + 1; right);
merge(A, left, middle, right); //从最后一个元素回升,能保证合并时每个数组有序
}
}
/* 非递归实现的归并排序 - 迭代 - 自底向上 */
void mergesort_iteration(int A[], int left, int right)
{
int low,mid,high; //子数组索引
int array_size; //指示每一轮合并的子数组的大小
/* 从最少元素个数的数组开始处理 */
for (array_size = 1; array_size <= right - left; array_size*2) //每轮翻倍
{
low = left;
while (low + array_size - 1 <= right - 1) //保证需要归并的另一个数组存在;非对分时左右只相差一个元素
{
mid = low + array_size - 1; //归并时的中间元素
high = mid + array_size;
if (high > right) //右数组长度不足array_size
high = right;
merge(A, low, mid, high); //调用合并函数,下一轮调用时将合并之前合并的子数组
low = high + 1; //每一轮合并需要对所有元素进行
}
}
}
在归并排序中,相等的元素的顺序不会改变,所以它是稳定的算法。
归并排序和堆排序、快速排序的比较:
若从空间复杂度来考虑,首选堆排序,其次是快速排序,最后是归并排序。
若从稳定性来考虑,应选取归并排序,因为堆排序和快速排序都是不稳定的。
若从平均情况下的排序速度考虑,应该选择快速排序。
小结
从算法的简单性来看,我们将上述8种算法分为两类:
简单算法:冒泡、简单选择、直接插入。
改进算法:二分插入、希尔、堆、并、快速。
从平均情况来看:显然最后3种改进算法要胜过希尔排序,并远远胜过前3种简单算法。
从最好情况看:反而冒泡和直接插入排序要更胜一筹,也就是说,如果你的待排序序列总是基本有序,反而不应该考虑后5种复杂的改进算法。
从最坏情况看:堆排序与归并排序又强过快速排序以及其他简单排序。
(1) 时间复杂度:对比堆排序、归并排序和快速排序,我们可以得出这样一个认识。堆排序和归并排序就像两个参加奥数考试的优等生,心理素质强,发挥稳定。而快速排序像是很情绪化的天才,心情好时表现极佳,碰到较糟糕环境会变得差强人意。但是他们如果都来比赛计算个位数的加减法,它们反而算不过成绩极普通的冒泡和直接插入。
(2) 空间复杂度:归并排序强调要马跑得快,就得给马吃个饱。快速排序也有相应的空间要求,反而堆排序等却都是少量索取,大量付出,对空间要求是O(1)。如果执行算法的软件所处的环境非常在乎内存使用量的多少时,选择归并排序和快速排序就不是一个较好的决策了。
(3) 稳定性:归并排序独占鳌头,我们前面也说过,对于非常在乎排序稳定性的应用中,归并排序是个好算法。
(4) 从待排序记录的个数上来说,待排序的个数n越小,采用简单排序方法越合适。反之,n越大,采用改进排序方法越合适。
参考文章:
http://blog.csdn.net/wangwei890702/article/details/8197597#
http://www.cnblogs.com/eniac12/p/5332117.html
http://blog.csdn.net/cao478208248/article/details/38871915
http://www.cnblogs.com/wxisme/p/5243631.html
2017.04.04