写在前面:

排序是计算机程序设计中的一种重要操作,它的功能是将一个数据元素的随意序列,又一次排列成一个按keyword有序的序列。因此排序掌握各种排序算法很重要。

对以下介绍的各个排序,我们假定全部排序的keyword都是整数、对传入函数的參数默认是已经检查好了的。仅仅是简单的描写叙述各个算法并给出了详细实现代码。并未做其它深究探讨。

基础知识:

因为待排序的记录数量不同,使得排序过程中设计的存储器不同,可将排序方法分为两大类:一类是内部排序,指的是待排序记录存放在计算机随机存储器中进行的排序过程。还有一类是外部排序,指的是待排序记录的数量非常大。以致内存一次不能容纳所有记录。在排序过程中尚需对外存进行訪问的排序过程。

在排序的过程中需进行下列两种基本操作:1、比較两个keyword的大小;2、将记录从一个位置移动至还有一个位置。

操作1对大多数排序方法来说都是必要的。而操作2能够通过改变记录的存储方式予以避免。

待排序记录序列可有下列3种存储方式:1、待排序的一组记录存放在地址连续的一组存储单元上。2、一组待排序记录存放在静态链表中,记录之间的次序关系由指针指示,则实现不须要移动记录,仅须要改动指针就可以。3、待排序记录本身存储在一组地址连续的存储单元内,同一时候另设一个指示各个记录存储位置的地址向量。在排序过程中不移动记录本身。而移动地址向量中这些记录的”地址“,在排序结束之后再依照地址向量中的值调整记录的存储位置。

算法分析:

1、插入排序:

基本思想:将一个记录插入到已排好序的有序表中。从而得到一个新的、记录数增1的有序表。

时间复杂度为O(n^2),若待排记录序列为正序,时间复杂度可提高至O(n);空间上仅仅须要一个记录的辅助空间。

a、直接插入排序

演示样例代码1:

  1. void InsertionSort(ElementType A[], int N)
  2. {
  3. int j, P;
  4.  
  5. ElementType Tmp;//记录辅助空间
  6. for(P = 1; P < N; P++){
  7. Tmp = A[P];
  8. for(j = P; j > 0 && A[j - 1] > Tmp; j--)//将一个记录插入已排好序的有序表中
  9. A[j] = A[j - 1];
  10. A[j] = Tmp;
  11. }
  12. }

演示样例代码2:

  1. void insertionsort(ElementType A[], int N)
  2. {
  3. for(int i = 1; i < N; i++){
  4. int tmp = A[i]; //记录辅助空间
  5. int j = i - 1;
  6. while(j > -1 && A[j] > A[i]){
  7. A[j+1] = A[j]; //将一个记录插入已排好序的有序表中
  8. --j;
  9. }
  10. A[j+1] = tmp;
  11. }
  12. return;
  13. }

插入排序算法简单,且easy实现。当待排序记录的数量n非常小时。这是一种非常好的排序方法。但n非常大时,则不宜採用直接排序。由于直接排序。基本的时间消耗在“比較”和“移动”上,因此。在直接排序的基础上,从降低“比較”和“移动”这两种操作的次数着眼,可得“折半插入排序”、“2-路插入排序”、“表插入排序”等。

b、折半插入排序

因为插入排序的基本操作是一个有序表中进行查找和插入,这个"查找"操作可利用"折半查找"来实现。由此进行的插入排序称之为折半插入排序。

2、希尔排序

基本思想:先将整个待排记录序列切割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行一次直接插入排序。能够看出希尔排序事实上仅仅是改进了的插入排序,因此上面的插入排序也被称为直接插入排序。

特点:子序列的构成不是简单地“逐段切割”。而是将相隔某个“增量”的记录组成一个子序列。它通过比較相距一定间隔的元素来工作;各趟比較所用的距离随着算法的进行而减小,直到仅仅比較相邻元素的最后一趟排序为止。

演示样例代码

  1. void Shellsort(ElementType A[], int N)
  2. {
  3. int i, j, Increment;
  4. ElementType Tmp;
  5.  
  6. for(Increment = N / 2; Increment > 0; Increment /= 2){
  7. for(i = Increment; i < N; i++){
  8. Tmp = A[i];
  9. for(j = i; j >= Increment; j -= Increment){
  10. if(Tmp < A[j - Increment])
  11. A[j] = A[j - Increment];
  12. else
  13. break;
  14. }
  15. A[j] = Tmp;
  16. }
  17. }
  18. }

上面给出的演示样例中选择的排序增量是使用shell建议的序列:N/2和Increment/2。

使用希尔增量时希尔排序的最坏情形执行时间为O(n^2)。

3、冒泡排序

基本思想:首先将第一个记录的keyword和第二个记录的keyword进行比較。若为逆序,则将两个记录交换之,然后比較第二个记录和第三个记录的keyword。依次类推,直至第n-1个记录和第n个记录的keyword进行过比較为止。上述过程称做第一趟冒泡排序,其结果使得keyword最大的记录被安置到最后一个记录的位置上。

然后进行第二趟冒泡排序。对前n-1个记录进行相同操作,其结果是使keyword次大的记录被安置到第n-1个记录的位置上。一般地。第i趟冒泡排序是从1到n-i+1依次比較相邻两个keyword,并在“逆序”时交换相邻记录,其结果是这n-i+1个记录中keyword最大的记录被交换到第n-i+1的位置上。判别冒泡排序结束的条件应该是“在一趟排序过程中没有进行过交换记录的操作”。

演示样例代码1:

  1. void bubblesort(ElementType A[], int N)
  2. {
  3. int i, j;
  4. ElementType tmp;
  5. for(i = 0; i < N; i++)
  6. {
  7. for(j = 0; j < N-i; j++){
  8. if(A[j] > A[j+1]){
  9. tmp = A[j];
  10. A[j] = A[j+1];
  11. A[j+1] = tmp;
  12. }
  13. }
  14. }
  15. }

演示样例代码2:

  1. void bubblesort(ElementType a[], int n)
  2. {
  3. int j;
  4. bool flag;
  5. ElementType tmp;
  6.  
  7. flag = true;
  8. while(flag){
  9. flag = false;
  10. for(j = 1; j < n; j++){
  11. if(a[j-1] > a[j]){
  12. tmp = a[j-1];
  13. a[j-1] = a[j];
  14. a[j] = tmp;
  15. flag = true;
  16. }
  17. }
  18. n--;
  19. }
  20. }

冒泡排序的时间复杂度为O(n^2)。

效率比較底下。当数据量比較小的时候,能够採用冒泡排序。

4、简单选择排序

基本思想:每一趟在n-i+1(i=1,2,…,n-1)个记录中选取keyword最小的记录作为有序序列中第i个记录。

直接选择排序和直接插入排序类似,都将数据分为有序区和无序区,所不同的是直接插入排序是将无序区的第一个元素直接插入到有序区以形成一个更大的有序区,而直接选择排序是从无序区选一个最小的元素直接放到有序区的最后。

演示样例代码:

  1. void Selectsort(int a[], int n)
  2. {
  3. int i, j, nMinIndex, tmp;
  4. for(i = 0; i < n; i++){
  5. nMinIndex = i;
  6. for(j = i + 1; j < n; j++)
  7. if(a[j] < a[nMinIndex])
  8. nMinIndex = j;
  9.  
  10. tmp = a[i];
  11. a[i] = a[nMinIndex];
  12. a[nMinIndex] = tmp;
  13. }
  14. }

简单选择排序的时间复杂度是O(n^2)。

5、高速排序

基本思想:高速排序是对冒泡排序的一种改进。

它的基本思想是。通过一趟排序将待排记录切割成独立的两部分,当中一部分记录的keyword均比还有一部分记录的keyword小。则可分别对这两部分记录继续进行排序,以达到整个序列有效。

一趟高速排序的详细做法是:附设两个指针low和high,它们的初值分别为low和high。设枢轴记录的keyword为pivotkey,则首先从high所指位置起向前搜索找到第一个keyword小于prvotkey的记录和枢轴记录互相交换,然后从low所指位置起向后搜索,找到第一个keyword大于privotkey的记录和枢轴记录互相交换,反复这两步直至low=high为止。

演示样例代码1:

  1. void Swap(ElementType *left, ElementType *right)
  2. {
  3. ElementType temp = *left;
  4. *left = *right;
  5. *right = temp;
  6. }
  7.  
  8. int Partition(ElementType A[], int low, int high)
  9. {
  10. ElementType pivotkey = A[low];
  11.  
  12. while(low < high){
  13. while(low < high && A[high] >= pivotkey)
  14. high--;
  15. Swap(&A[low], &A[high]);
  16.  
  17. while(low < high && A[low] <= pivotkey)
  18. low++;
  19. Swap(&A[low], &A[high]);
  20. }
  21.  
  22. return low;
  23. }
  24.  
  25. void QSort(ElementType A[], int low, int high)
  26. {
  27. int pivotloc;
  28.  
  29. if(low < high){
  30. pivotloc = Partition(A, low, high);
  31. QSort(A, low, pivotloc - 1);
  32. QSort(A, pivotloc + 1, high);
  33. }
  34. }
  35.  
  36. void QuickSort(ElementType A[], int low, int high)
  37. {
  38. QSort(A, low, high);
  39. }

高速排序的平均时间为O(n) = nlogn;它是眼下被觉得的最好的一种内部排序方法。

6、归并排序

基本思想:将两个或两个以上的有序表组合成一个新的有序表。2-路归并排序为例:如果初始序列含有n个记录,则可看成是n个有序的子序列,每一个子序列的长度为1,然后两两归并,得到n/2(或n/2+1)个长度为2或1的有序子序列;再两两归并,……如此反复。直至得到一个长度为n的有序序列为止。

演示样例代码:

  1. void Merge(ElementType A[], ElementType TmpArray[], int Lpos, int Rpos, int RightEnd)
  2. {
  3. int i, LeftEnd, NumElements, TmpPos;
  4.  
  5. LeftEnd = Rpos - 1;
  6. TmpPos = Lpos;
  7. NumElements = RightEnd - Lpos + 1;
  8.  
  9. /*main loop*/
  10. while(Lpos <= LeftEnd && Rpos <= RightEnd)
  11. if(A[Lpos] <= A[Rpos])
  12. TmpArray[TmpPos++] = A[Lpos++];
  13. else
  14. TmpArray[TmpPos++] = A[Rpos++];
  15.  
  16. while(Lpos <= LeftEnd) /*Copy rest of first half*/
  17. TmpArray[TmpPos++] = A[Lpos++];
  18. while(Rpos <= RightEnd) /*Copy rest of second half*/
  19. TmpArray[TmpPos++] = A[Rpos++];
  20.  
  21. /*Copy TmpArray back*/
  22. for(i = 0; i < NumElements; i++, RightEnd--)
  23. A[RightEnd] = TmpArray[RightEnd];
  24. }
  25.  
  26. void MSort(ElementType A[], ElementType TmpArray[], int Left, int Right)
  27. {
  28. int Center;
  29.  
  30. if(Left < Right){
  31. Center = (Left + Right) / 2;
  32. MSort(A, TmpArray, Left, Center);
  33. MSort(A, TmpArray, Center + 1, Right);
  34. Merge(A, TmpArray, Left, Center + 1, Right);
  35. }
  36. }
  37.  
  38. void Mergesort(ElementType A[], int N)
  39. {
  40. ElementType *TmpArray;
  41.  
  42. TmpArray = (ElementType *)malloc(N*sizeof(ElementType));
  43. if(TmpArray == NULL){
  44. fprintf(stderr, "no space for tmp array!\n");
  45. return;
  46. }
  47.  
  48. MSort(A, TmpArray, 0, N-1);
  49. free(TmpArray);
  50.  
  51. return;
  52.  
  53. }

归并排序的效率比較高,设数列长为N。将数列分开成小数列一共要logN步,每步都是一个合并有序的数列的过程,时间复杂度记为O(N),因此时间复杂度是O(N*LogN)。

它非常难用于主存排序,主要问题在于合并两个排序的表须要线性附加内存。在整个算法中还要花费将数据复制到暂时数组再拷贝回来这样一些附加的工作,其结果严重放慢了排序的速度。

7、堆排序

堆是具有下列性质的全然二叉树:每一个节点的值都大于或等于其左右孩子节点的值,称为大顶堆;或者每一个节点的值都小于或等于其左右孩子节点的值,称为小顶堆。

堆排序就是利用堆进行排序的方法.基本思想是:将待排序的序列构造成一个大顶堆.此时,整个序列的最大值就是堆顶 的根结点.将它移走(事实上就是将其与堆数组的末尾元素交换, 此时末尾元素就是最大值),然后将剩余的n-1个序列又一次构造成一个堆,这样就会得到n个元素的次大值.如此重复运行,便能得到一个有序序列了。 时间复杂度为 O(nlogn),好于冒泡,简单选择,直接插入的O(n^2)

该算法的主要问题在于它使用了一个附加的数组。因此,存储需求添加一倍。注意:将第二个数组拷贝回第一个数组的额外时间消耗仅仅是O(N),这不可能显著影响执行时间。这个问题是空间的问题。

演示样例代码:

  1. #define LeftChild(i) (2*(i) + 1)
  2.  
  3. void Swap(ElementType *pa, ElementType *pb)
  4. {
  5. ElementType *pc = pa;
  6. pa = pb;
  7. pb = pc;
  8. }
  9.  
  10. void PercDown(ElementType A[], int i, int N)
  11. {
  12. int Child;
  13. ElementType Tmp;
  14.  
  15. for(Tmp = A[i]; LeftChild(i) < N; i = Child){
  16. Child = LeftChild(i);
  17. if(Child != N-1 && A[Child + 1] > A[Child])
  18. Child++;
  19. if(Tmp < A[Child])
  20. A[i] = A[Child];
  21. else
  22. break;
  23. }
  24. A[i] = Tmp;
  25. }
  26.  
  27. void Heapsort(ElementType A[], int N)
  28. {
  29. int i;
  30.  
  31. for(i = N/2; i >= 0; i--) /*BuildHeap*/
  32. PercDown(A, i, N);
  33. for(i = N - 1; i > 0; i--){
  34. Swap(&A[0], &A[i]); /*DeleteMax*/
  35. PercDown(A, 0, i);
  36. }
  37. }

总结:

上面尽管给了7种内部排序的方法。可简单的对它们大致分为下面几类:插入排序(直接插入排序、希尔排序)、高速排序(冒泡排序、高速排序)、选择排序(简单选择排序、堆排序)、归并排序和基数排序。

各内部排序方法的比較:

1、平均时间性能而言,高速排序最佳,其所需时间最省。但高速排序的最坏情况下的时间性能不如堆排序和归并排序。而后两者相比較的结果是,在n较大时,归并排序所需时间较堆排序省,但它所需的辅助存储量最多。

2、简单排序包含除希尔排序之外的全部插入排序,冒泡排序和简单选择排序。当中以直接插入排序为最简单。当序列中的记录"基本有序"或n值较小时,它是最佳的排序方法。因此常将它和其它的排序方法,诸如高速排序、归并排序等结合在一起使用。

3、基数排序的实际复杂度可写成O(d*n)。它最适用于n值非常大而keyword较小的序列。

若keyword也非常大,而序列中大多数记录的"最高位keyword"均不同。则亦可先按"最高位keyword"不同将序列分成若干"小"的子序列,而后进行直接插入排序。

4、从方法的稳定性来比較,基数排序是稳定的内排方法,全部时间复杂度为O(n^2)的简单排序法也是稳定的。然而。高速排序、堆排序和希尔排序时性能更好的排序方法是不稳定。

版权声明:本文博客原创文章,博客,未经同意,不得转载。

七内部排序算法汇总(插入排序、Shell排序、冒泡排序、请选择类别、、高速分拣合并排序、堆排序)的更多相关文章

  1. python实现排序算法 时间复杂度、稳定性分析 冒泡排序、选择排序、插入排序、希尔排序

    说到排序算法,就不得不提时间复杂度和稳定性! 其实一直对稳定性不是很理解,今天研究python实现排序算法的时候突然有了新的体会,一定要记录下来 稳定性: 稳定性指的是 当排序碰到两个相等数的时候,他 ...

  2. C++/C实现各种排序算法(持续更新)--冒泡排序,选择排序,归并排序

    2018 3 17 今日总结一下C++中的排序算法: 1冒泡排序 它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来.走访数列的工作是重复地进行直到没有再需要交换,也就是 ...

  3. 排序算法三:Shell插入排序

    排序算法三:Shell插入排序 声明:引用请注明出处http://blog.csdn.net/lg1259156776/ 引言 在我的博文<"主宰世界"的10种算法短评> ...

  4. JavaScript 数据结构与算法之美 - 十大经典排序算法汇总(图文并茂)

    1. 前言 算法为王. 想学好前端,先练好内功,内功不行,就算招式练的再花哨,终究成不了高手:只有内功深厚者,前端之路才会走得更远. 笔者写的 JavaScript 数据结构与算法之美 系列用的语言是 ...

  5. 排序算法汇总(C/C++实现)

    前言:     本人自接触算法近2年以来,在不断学习中越多地发觉各种算法中的美妙.之所以在这方面过多的投入,主要还是基于自身对高级程序设计的热爱,对数学的沉迷.回想一下,先后也曾参加过ACM大大小小的 ...

  6. 排序算法汇总(java实现,附源代码)

    整理系统的时候发现了原来写的各种算法的总结,看了一下,大吃一惊,那时候的我还如此用心,具体的算法,有的已经模糊甚至忘记了,看的时候就把内容整理出来,顺便在熟悉一下,以后需要的时候就可以直接过来摘抄了. ...

  7. java排序算法(四):冒泡排序

    java排序算法(四):冒泡排序 冒泡排序是计算机的一种排序方法,它的时间复杂度是o(n^2),虽然不及堆排序.快速排序o(nlogn,底数为2).但是有两个优点 1.编程复杂度很低.很容易写出代码 ...

  8. 面试必备:排序算法汇总(c++实现)

    排序算法主要考点: 7种排序 冒泡排序.选择排序.插入排序.shell排序.堆排序.快速排序.归并排序 以上排序算法是面试官经常会问到的算法,至于其他排序比如基数排序等等,这里不列举. 以下算法通过c ...

  9. Java排序算法(四):Shell排序

    [基本的想法] 将原本有大量记录数的记录进行分组.切割成若干个子序列,此时每一个子序列待排序的记录个数就比較少了,然后在这些子序列内分别进行直接插入排序,当整个序列都基本有序时.再对全体记录进行一次直 ...

随机推荐

  1. Handler不同线程间的通信

    转http://www.iteye.com/problems/69457 Activity启动后点击一个界面按钮后会开启一个服务(暂定为padService),在padService中会启动一个线程( ...

  2. zookeeer 集群和伪集群模式

    环境变量设置: # .bash_profile # Get the aliases and functions if [ -f ~/.bashrc ]; then . ~/.bashrc fi # U ...

  3. 基于visual Studio2013解决算法导论之009快速排序随机版本

     题目 快速排序随机版本 解决代码及点评 #include <stdio.h> #include <stdlib.h> #include <malloc.h> ...

  4. PHP脚本监控程序

    #!/bin/sh # Find ip IP=`/sbin/ifconfig eth1 | grep 'inet addr' | awk '{ print substr($2, index($2, & ...

  5. 【手打】LZW编码的C/C++实现

    LZW编码通过建立一个字符串表,用较短的代码来表示较长的字符串来实现压缩. LZW压缩算法是Unisys的专利,有效期到2003年,所以相关算法大多也已过期. 本代码只完毕了LZW的编码与解码算法功能 ...

  6. NDK Android* 应用移植方法

    概述 本指南用于帮助开发者将现有的基于 ARM* 的 NDK 应用移植到 x86.假设您已经拥有一个正常执行的应用,须要知道怎样可以高速让 x86 设备在 Android* Market 中找到您的应 ...

  7. anroid里面的post请求

    一.需要用到的场景 在jQuery中使用$.post()就可以方便的发起一个post请求,在android程序中有时也要从服务器获取一些数据,就也必须得使用post请求了. 二.需要用到的主要类 在a ...

  8. poj1830

    高斯消元求秩,难在构造方程. ; ; i < equ; i++)     {         ; j < var + ; j++)         {             cout & ...

  9. php连接oracle及简单操作

    使你的php支持oracle,按照以下步骤即可: 1.安装php环境,找一下appserv或者xampp,一键安装,很方便 2.把php的ext目录下的php_oci8.dll拷到system32目录 ...

  10. Android ListView条目全选功能,不用checkbox实现!

    大家好,翻了翻曾经的笔记,发现了一个我特别标记的功能,那就是ListView全选功能,顿时想起了我那个时候苦逼的生涯,因为我大学机械出身,大学毕业了都不知道什么叫代码,在58干了一段销售.实在是干不下 ...