在快速排序, 堆排序,归并排序中 哪个是最稳定的排序方法
1 快速排序(QuickSort)
快速排序是一个就地排序,分而治之,大规模递归的算法。从本质上来说,它是归并排序的就地版本。快速排序可以由下面四步组成。
(1) 如果不多于1个数据,直接返回。
(2) 一般选择序列最左边的值作为支点数据。
(3) 将序列分成2部分,一部分都大于支点数据,另外一部分都小于支点数据。
(4) 对两边利用递归排序数列。
快速排序比大部分排序算法都要快。尽管我们可以在某些特殊的情况下写出比快速排序快的算法,但是就通常情况而言,没有比它更快的了。快速排序是递归的,对于内存非常有限的机器来说,它不是一个好的选择。
2 归并排序(MergeSort)
归并排序先分解要排序的序列,从1分成2,2分成4,依次分解,当分解到只有1个一组的时候,就可以排序这些分组,然后依次合并回原来的序列中,这样就可以排序所有数据。合并排序比堆排序稍微快一点,但是需要比堆排序多一倍的内存空间,因为它需要一个额外的数组。
3 堆排序(HeapSort)
堆排序适合于数据量非常大的场合(百万数据)。
堆排序不需要大量的递归或者多维的暂存数组。这对于数据量非常巨大的序列是合适的。比如超过数百万条记录,因为快速排序,归并排序都使用递归来设计算法,在数据量非常大的时候,可能会发生堆栈溢出错误。
堆排序会将所有的数据建成一个堆,最大的数据在堆顶,然后将堆顶数据和序列的最后一个数据交换。接下来再次重建堆,交换数据,依次下去,就可以排序所有的数据。
4 Shell排序(ShellSort)
Shell排序通过将数据分成不同的组,先对每一组进行排序,然后再对所有的元素进行一次插入排序,以减少数据交换和移动的次数。平均效率是O(nlogn)。其中分组的合理性会对算法产生重要的影响。现在多用D.E.Knuth的分组方法。
Shell排序比冒泡排序快5倍,比插入排序大致快2倍。Shell排序比起QuickSort,MergeSort,HeapSort慢很多。但是它相对比较简单,它适合于数据量在5000以下并且速度并不是特别重要的场合。它对于数据量较小的数列重复排序是非常好的。
5 插入排序(InsertSort)
插入排序通过把序列中的值插入一个已经排序好的序列中,直到该序列的结束。插入排序是对冒泡排序的改进。它比冒泡排序快2倍。一般不用在数据大于1000的场合下使用插入排序,或者重复排序超过200数据项的序列。
6 冒泡排序(BubbleSort)
冒泡排序是最慢的排序算法。在实际运用中它是效率最低的算法。它通过一趟又一趟地比较数组中的每一个元素,使较大的数据下沉,较小的数据上升。它是O(n^2)的算法。
7 交换排序(ExchangeSort)和选择排序(SelectSort)
这两种排序方法都是交换方法的排序算法,效率都是 O(n2)。在实际应用中处于和冒泡排序基本相同的地位。它们只是排序算法发展的初级阶段,在实际中使用较少。
8 基数排序(RadixSort)
基数排序和通常的排序算法并不走同样的路线。它是一种比较新颖的算法,但是它只能用于整数的排序,如果我们要把同样的办法运用到浮点数上,我们必须了解浮点数的存储格式,并通过特殊的方式将浮点数映射到整数上,然后再映射回去,这是非常麻烦的事情,因此,它的使用同样也不多。而且,最重要的是,这样算法也需要较多的存储空间。
9 总结
下面是一个总的表格,大致总结了我们常见的所有的排序算法的特点。
排序法 平均时间 最差情形 稳定度 额外空间 备注
冒泡 O(n2) O(n2) 稳定 O(1) n小时较好
交换 O(n2) O(n2) 不稳定 O(1) n小时较好
选择 O(n2) O(n2) 不稳定 O(1) n小时较好
插入 O(n2) O(n2) 稳定 O(1) 大部分已排序时较好
基数 O(logRB) O(logRB) 稳定 O(n)
B是真数(0-9),
R是基数(个十百)
Shell O(nlogn) O(ns) 1《s《2 不稳定 O(1) s是所选分组
快速 O(nlogn) O(n2) 不稳定 O(nlogn) n大时较好
归并 O(nlogn) O(nlogn) 稳定 O(1) n大时较好
堆 O(nlogn) O(nlogn) 不稳定 O(1) n大时较好
C++实现归并排序,过程如图
#include 《stdlib.h》
#include 《stdio.h》
#include 《time.h》
#define N 10
int randGenerator(double vArray,int n);
int Merge(double vArray,double Lr,int i,int m,int n);
int Msort(double vArray,double Lr,int s,int t);
int main()
{
double vArray[N];
randGenerator(vArray,N);
printf(“待排序序列:\n\n“);
for(int i = 0;i 《 N; i++)
{
printf(“%lf\n“,vArray[i]);
}
double Lr[N];
Msort(vArray,Lr,0,N-1);
printf(“\n\n已排序序列:\n\n“);
for(int j = 0;j 《 N; j++)
{
printf(“%lf\n“,vArray[j]);
}
getchar();
return 0;
}
/*---用(0,1)内的随机数初始化待排序数组---*/
int randGenerator(double vArray,int n)
{
srand((unsigned int)time(NULL));
for(int i = 0;i 《 n; i++)
{
vArray[i] = (double)(rand()/(RAND_MAX - 0.0));
}
return 0;
}
/*----将有序的vArray[i..m]和有序的vArray[m+1..n]归并为有序的Lr[i..n]---*/
int Merge(double vArray,double Lr,int i,int m,int n)
{
int j = i;
int k = m + 1;
int l = i;
//将有序的vArray[i..m]和有序的vArray[m+1..n]归并为有序的Lr[i..n]
while(j 《= m && k 《= n)
{
if(vArray[j] 《 vArray[k])
{
Lr[l++] = vArray[j++];
}
else
{
Lr[l++] = vArray[k++];
}
}
while(j 《= m)
{
Lr[l++] = vArray[j++];
}
while(k 《= n)
{
Lr[l++] = vArray[k++];
}
//将有序序列Lr[i..n]拷回vArray[i..n]中,使vArray[i..n]为有序
for(j = i;j 《= n; j++)
{
vArray[j] = Lr[j];
}
return 0;
}
/*---将vArray[s..t]归并排序为有序---*/
int Msort(double vArray,double Lr,int s,int t)
{
if(s 《 t)
{
int m = (s + t) / 2;
Msort(vArray,Lr,s,m);
Msort(vArray,Lr,m + 1,t);
Merge(vArray,Lr,s,m,t);
}
return 0;
}
用一组{14,15,30,28,5,10}关键字序列,写出初始建堆过程图示,再根据初始堆写出堆排序过程图示
起始序列为14,15,30,28,5,10,
(1)因此起始堆的情况如下:
14
15 30
28 5 10
(2)假设是打算得到一个从小到大的c,所以需要建大顶堆,起始状态从下向上建堆:
第一步: 第二步:
14 30
28 30 28 14
25 5 10 25 5 10
(3)此时已经建立完了初始的堆。此时堆顶元素30即为最大元素,将堆顶元素与堆最后
一个元素进行交换,此时30是最大元素位于队尾,因此无需继续排序。所以堆如下图
所示:10 28 14 25 5
(4)此时由于除被交换到堆顶的10以外其他的都基本有序,所以自上而下建堆得到的堆
如下:
28
25 14
10 5
(5)重复(3)和(4)步骤确定了28的位置并得到堆如下:
25
10 14
5
(6)重复(3)和(4)步骤确定了25的位置并得到堆如下:
14
10 5
(7)重复(3)和(4)步骤确定了14的位置并得到堆如下:
10
5
(8)重复(3)和(4)步骤确定了10的位置,此时只有一个数5也位于了堆的第一个位置,
因此排序完成。
扩展资料:
建堆效率
n个结点的堆,高度d =log2n。根为第0层,则第i层结点个数为2^i,考虑一个元素在堆中向下移动的距离。大约一半的结点深度为d-1,不移动(叶)。四分之一的结点深度为d-2,而它们至多能向下移动一层。树中每向上一层,结点的数目为前一层的一半,而子树高度加一。-归并排序
这种算法时间代价为Ο(n)
由于堆有log n层深,插入结点、删除普通元素和删除最小元素的平均时间代价和时间复杂度都是
Ο(log n)。
操作实现
在程序中,堆用于动态分配和释放程序所使用的对象。在以下情况中调用堆操作:
1.事先不知道程序所需对象的数量和大小。
2.对象太大,不适合使用堆栈分配器。
堆使用运行期间分配给代码和堆栈以外的部分内存。
传统上,操作系统和运行时库随附了堆实现。当进程开始时,操作系统创建称为进程堆的默认堆。如果没有使用其他堆,则使用进程堆分配块。
语言运行时库也可在一个进程内创建单独的堆。(例如,C 运行时库创建自己的堆。)除这些专用堆外,应用程序或许多加载的动态链接库 (DLL) 之一也可以创建并使用单独的堆。Win32 提供了一组丰富的 API用于创建和使用专用堆。有关堆函数的优秀教程,请参阅 MSDN 平台 SDK 节点。-堆排序
参考资料来源:百度百科-堆