排序算法总结与C代码

  最近参加笔试,感觉排序算法需要好好的整理一下,感觉部分排序算法理解得不是很清楚;通过这段时间的整理与总结来对排序算法的一个复习吧。

        主要参考了《大话数据结构》:
1. 冒泡排序的基本思想:两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录为止。

[cpp] view plaincopy
  1. #define length 10  
  2.   
  3. void swap(int *a,int *b)  
  4. {  
  5.     int temp;  
  6.     temp=*a;  
  7.     *a=*b;  
  8.     *b=temp;  
  9. }  
  10. /*采用冒泡排序,是数据从小到大排列*/  
  11. void BubbleSort0(int a[],int len)    
  12. {  
  13.     int i,j;  
  14.     for (i=0;i<len;i++)  
  15.     {  
  16.         for (j=i+1;j<len;j++)  
  17.         {  
  18.             if (a[i]>a[j])  
  19.                 swap(&a[i],&a[j]);  
  20.         }  
  21.     }  
  22. }  
冒泡算法的改进1,主要讲相邻两个数比较

[cpp] view plaincopy
  1. void BubbleSort1(int a[],int len)  
  2. {  
  3.     int i,j;  
  4.     for (i=0;i<len;i++)  
  5.     {  
  6.         for(j=len-2;j>=i;j--)  
  7.         {  
  8.             if (a[j]>a[j+1])    //前者与后者比较,这里与前面算法的区别  
  9.                 swap(&a[j],&a[j+1]);  
  10.         }  
  11.     }  
  12. }  
冒泡算法的进一步优化,使用一个标志位来记录是否有数据的交换,如果数据有交换,则将标志flag赋值为true

[cpp] view plaincopy
  1. void BubbleSort2(int a[],int len)  
  2. {  
  3.     int i,j;  
  4.     int flag=1;  
  5.     for (i=0;i<len &&flag;i++)  
  6.     {  
  7.         flag=0;  
  8.         for (j=len-2;j>=i;j--)  
  9.         {  
  10.             if (a[j]>a[j+1])  
  11.             {  
  12.                 swap(&a[j],&a[j+1]);  
  13.                 flag=1;  
  14.             }  
  15.         }  
  16.     }  
  17. }  
冒泡排序复杂度分析:当最好的情况(序列已经有序),根据最后的改进的代码,只有n-1次的比较,没有数据之间的交换,时间复杂度为O(n);当最坏的情况下(序列为逆序),此时需要比较n(n-1)/2,并且还需要进行等数量级的记录移动,因此总的时间复杂度为O(n^2)
2.简单选择排序

简单选择排序:就是通过n-i次关键字间的比较,从n-i+1个记录中选出关键字最小的记录,并和第i个记录交换:

[cpp] view plaincopy
  1. void SelectSort(int a[],int len)  
  2. {  
  3.     int i,j,min;  
  4.     for (i=0;i<len;i++)  
  5.     {  
  6.         min=i;  
  7.         for (j=i+1;j<len;j++)  
  8.         {  
  9.             if (a[min]>a[j])  
  10.                 min=j;  
  11.         }  
  12.         if (i!=min)  
  13.             swap(&a[min],&a[i]);  
  14.     }  
  15. }  
简单选择排序复杂度分析:它的比较次数一定:n(n-1)/2。也因此无论在序列何种情况下,它都不会有优秀的表现,可见对数据的有序性不敏感。它虽然比较次数多,而对于交换次数而言,当最好的时候,交换为0次,最坏的时候交换次数为n-1次,所以数据交换量却很少,基于时间复杂度是比较与交换次数的总和,因此总的时间复杂度依然为O(n^2)。尽管与冒泡排序算法的时间复杂度相同,但是简单选择排序的性能上还是略优于冒泡排序。
3.直接插入排序

直接插入排序的基本操作是将一个记录插入到已经排好序的有序表中,从而得到一个新的记录数增1的有序表

[cpp] view plaincopy
  1. void InsertSort(int a[],int len)  
  2. {  
  3.     int i,j,temp;  
  4.     for (i=1;i<len;i++)  
  5.     {  
  6.         if (a[i]<a[i-1])  
  7.         {  
  8.             temp=a[i];  
  9.             for (j=i-1;a[j]>temp;j--)  
  10.                 a[j+1]=a[j];  
  11.             a[j+1]=temp;  
  12.         }  
  13.     }  
  14. }  
直接插入排序复杂度分析:每次比较后最多移掉一个逆序,因此与冒泡排序的效率相同。但它在速度上还是要高点,这是因为在冒泡排序下是进行值交换,而在插入排序下是值移动,所以直接插入排序将要优于冒泡排序。直接插入法也是一种对数据的有序性非常敏感的一种算法。在有序情况下只需要经过n-1次比较,由于每次都是a[j]>a[j-1]因此没有移动记录,时间复杂度为O(n);在最坏情况下,将需要(n+2)(n-1)/2次比较,并且将移动次数也达到最大值(n+4)(n-1)/2。如果排序记录是随机的,根据概率相同原则,平均比较和移动次数约为你(n^2)/4,所以直接插入排序的时间复杂度为O(n^2).

4.希尔排序

希尔排序的基本思想:先将整个待排记录序列分割成为若干子序列分别进行直接插入排序,并使整个序列中的记录基本有序,再对全体记录进行一次排序。

[cpp] view plaincopy
  1. /*主要利用插入排序的思想*/  
  2. void ShellSort(int a[],int len)  
  3. {  
  4.     int i,j,temp,increment;  
  5.     increment=len;  
  6.     do  
  7.     {  
  8.         increment=increment/3+1;  
  9.         for (i=increment;i<len;i++)  
  10.         {  
  11.             if(a[i]<a[i-increment])  
  12.             {  
  13.                 temp=a[i];  
  14.                 for (j=i-increment;j>=0&&a[j]>temp;j-=increment)   
  15.                 {  
  16.                     a[j+increment]=a[j];  
  17.                 }  
  18.                 a[j+increment]=temp;  
  19.             }  
  20.         }  
  21.     }while(increment>1);  
  22. }  


希尔排序复杂度分析:希尔排序的“增量”的选取非常关键,大量的研究表示,当增量序列为2^(t-k+1)-1(0=<k=<t=<log2(n+1))时,可以获得不错的效果,其时间复杂度为O(n^1.5)。增量的选择将影响希尔排序的效率。但是无论怎样选择增量,最后一定要使增量为1,进行一次直接插入排序。但它相对于直接插入排序,由于在子表中每进行一次比较,就可能移去整个经性表中的多个逆序,从而改善了整个排序性能。希尔排序算是一种基于插入排序的算法,所以对数据有序敏感。

5.堆排序

堆排序的思想:首先将待排序列构建为大顶堆(大顶堆:每个结点的值大于或等于其左右孩子结点的值)。此时整个序列的最大值就是堆顶的根结点。将它移走(其实就是讲其与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余的n-1个子序列重新构造成一个堆,这样就会得到n个元素中的次大值。如此反复执行,便得到一个有序序列。

[cpp] view plaincopy
  1. void HeapAdjust(int a[],int s,int m)  
  2. {  
  3.     int temp,j=0;  
  4.     temp=a[s];  
  5.     for (j=2*s+1;j<m;j=j*2+1)     
  6.     {  
  7.         if (j<m && a[j]<a[j+1])  
  8.             ++j;  
  9.         if (j<m && temp>=a[j])  
  10.             break;  
  11.         a[s]=a[j];  
  12.         s=j;  
  13.     }  
  14.     a[s]=temp;  
  15. }  

[cpp] view plaincopy
  1. void HeapSort(int a[],int len)  
  2. {  
  3.     int i;  
  4.     for (i=(len)/2-1;i>=0;i--)  
  5.         HeapAdjust(a,i,len);  
  6.     for (i=len-1;i>=1;i--)  
  7.     {  
  8.         swap(&a[0],&a[i]);  
  9.         HeapAdjust(a,0,i-1);  
  10.     }  
  11. }  


堆排序的复杂度分析:在构建堆的过程中,因为是完全二叉树从最下层最后边的非终端结点开始构建,将它与其孩子进行比较和若有必要的交换,对于每个非终端结点来说,其中最多进行两次比较和互换操作,因此整个构建堆的时间复杂度为O(n)。

    在正式排序时,第i次取堆顶记录重建堆需要用O(logi)的时间(完全二叉树的某个结点到根节点的距离为[logi]+1),并且需要取n-1次堆顶记录,因此重建堆的时间复杂度为O(nlogn)。

    所以总体来说,堆排序的时间复杂度为O(nlogn)。由于堆排序对原始记录的排序状态并不敏感,因此无论最好、最坏平均时间复杂度均为O(nlogn)。所以一般在小规模的序列中不合适,但对于较大的序列,将表现出优越的性能。

6.归并排序
归并排序思想:假设初始序列含有n个记录,则可以看成是n个有序的子序列,每个子序列的长度的长度为1,然而两两归并,得到[n/2]([x]表示不小于x的最小整数)个长度为2或1的有序子序列;再两两归并,.....,如此重复,直至得到一个长度为n的有序序列为止。

[cpp] view plaincopy
  1. /*将有序的SR归并到TR中*/  
  2. void Merge(int SR[],int TR[],int i,int m,int n)  
  3. {  
  4.     int j,k,l;  
  5.     for (j=m+1,k=i;i<=m && j<=n;k++)  /*对1...m与m+1...n中等长的数组比较大小,并且保存较小元素*/  
  6.     {  
  7.         if (SR[i]<SR[j])  
  8.             TR[k]=SR[i++];  
  9.         else   
  10.             TR[k]=SR[j++];  
  11.     }  
  12.     if (i<=m)  /*将SR[1...m]中多余中的部分添加到TR中*/  
  13.     {  
  14.         for (l=0;l<=m-i;l++)  
  15.             TR[k+l]=SR[i+l];  
  16.     }  
  17.     if (j<=n)    /*将SR[m+1...n]中多余中的部分添加到TR中*/  
  18.     {  
  19.         for (l=0;l<=n-j;l++)  
  20.             TR[k+l]=SR[j+l];  
  21.     }  
  22. }  
  23. /*将SR归并排序为TR1*/  
  24. void MSort(int SR[],int TR1[],int s,int t)  
  25. {  
  26.     int m=0;  
  27.     int TR2[length+1]={0};  
  28.     if (s==t)  
  29.         TR1[s]=SR[s];  
  30.     else  
  31.     {  
  32.         m=(s+t)/2;  
  33.         MSort(SR,TR2,s,m);  
  34.         MSort(SR,TR2,m+1,t);  
  35.         Merge(TR2,TR1,s,m,t);  
  36.     }  
  37. }  

归并排序复杂度分析:一趟归并需要将SR[0]~SR[n-1]中相邻的长度为h的有序序列进行两两归并。并将结果放到TR[0]~TR[n-1]中,这需要将待排序列中的记录扫描一遍,因此耗费O(n)时间,而由完全二叉树的深度可知,整个归并排序需要进行[log(n)]次,因此,总的时间复杂度为O(nlogn),而且这是归并排序算法中最好、最坏、平均的时间性能。并且归并排序是一种稳定的排序算法。对数据的有序性不敏感。若数据节点数据量大,那将不适合。但可改造成索引操作,效果将非常出色。

对于归并排序的非递归实现在此就不在说明。

7.快速排序

快速排序的思想:通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序的目的。

[cpp] view plaincopy
  1. int Partiton(int a[],int low,int high)  
  2. {  
  3.     int pivotkey;  
  4.     pivotkey=a[low];//枢轴的选取可以选取中间值  
  5.     while (low<high)  
  6.     {  
  7.         while (low<high &&a[high]>=pivotkey)  
  8.             high--;  
  9.         swap(&a[high],&a[low]);  
  10.         while (low<high && a[low]<=pivotkey)  
  11.             low++;  
  12.         swap(&a[high],&a[low]);  
  13.     }  
  14.     return low;  
  15. }  
  16.   
  17. int Partiton1(int a[],int low,int high)/*y优化不需要的交换*/  
  18. {  
  19.     int temp,pivotkey;  
  20.     pivotkey=a[low];  
  21.     temp=pivotkey;  
  22.     while (low<high)  
  23.     {  
  24.         while (low<high &&a[high]>=pivotkey)  
  25.             high--;  
  26.         a[low]=a[high];  
  27.         while (low<high && a[low]<=pivotkey)  
  28.             low++;  
  29.         a[high]=a[low];  
  30.     }  
  31.     a[low]=temp;  
  32.     return low;  
  33. }  
  34.   
  35. void Qsort(int a[],int low,int high)  
  36. {  
  37.     int pivot;  
  38.     if (low<high)  
  39.     {  
  40.         pivot=Partiton1(a,low,high);  
  41.         Qsort(a,low,pivot-1);  
  42.         Qsort(a,pivot+1,high);  
  43.     }  
  44.   
  45. }  
快速排序复杂度分析:它同样是冒泡排序的改进,它通过一次交换能消除多个逆序,这样可以减少逆序时所消耗的扫描和数据交换次数。在最优情况下,它的排序时间复杂度为O(nlog2n)。即每次划分序列时,能均匀分成两个子串。但最差情况下它的时间复杂度将是O(n^2)。即每次划分子串时,一串为空,另一串为m-1(如果程序中采用每次取序列中部数据作为划分点,那将在正序和逆时达到最优)。有的书上这解释“快速排序”,在理论上讲,如果每次能均匀划分序列,它将是最快的排序算法,因此称它作快速排序。虽然很难均匀划分序列,但就平均性能而言,它仍是基于关键字比较的内部排序算法中速度最快者。

按平均时间将排序分为类 
(1) 平方阶(O(n2))排序
  各类简单排序,例如直接插入、简单选择和冒泡排序; 
(2) 线性对数阶(O(nlog2n))排序
  如快速排序堆排序归并排序 
(3) O(n1+§))排序
  §是介于0和1之间的常数。希尔排序便是一种;

排序方法的选择

因为不同的排序方法适应不同的应用环境和要求,所以选择合适的排序方法很重要
(1)若n较小,可采用直接插入直接选择排序。
当记录规模较小时,直接插入排序较好,它会比选择更少的比较次数;

但当记录规模较大时,因为直接选择移动的记录数少于直接插人,所以宜用选直接选择排序。
这两种都是稳定排序算法。
(2)若文件初始状态基本有序(指正序),则应选用直接插人、冒泡或随机的快速排序为宜(这里的随机是指基准取值的随机,原因见上的快速排序分析);这里快速排序算法将不稳定。

(3)若n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序、堆排序或归并排序序。
快速排序是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;
堆排序虽不会出现快速排序可能出现的最坏情况。但它需要建堆的过程。这两种排序都是不稳定的。
  
归并排序是稳定的排序算法,但它有一定数量的数据移动,所以我们可能过与插入排序组合,先获得一定长度的序列,然后再合并,在效率上将有所提高。




本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/510480.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

最小生成树(普利姆算法、克鲁斯卡尔算法)

给定一个带权的无向连通图,如何选取一棵生成树,使树上所有边上权的总和为最小,这叫最小生成树. 求最小生成树的算法 (1) 克鲁斯卡尔算法 图的存贮结构采用边集数组,且权值相等的边在数组中排列次序可以是任意的.该方法对于边相对比较多的不是很实用,浪费时间. (2) 普里姆算法 图…

《数字化与碳中和(园区篇)》报告正式发布,助力加快推进国家“双碳”战略实施

2021年10月&#xff0c;国务院印发《2030年前碳达峰行动方案》&#xff0c;明确提出要建设绿色低碳园区&#xff0c;并选择100个具有典型代表性的城市和园区开展碳达峰试点建设&#xff0c;在政策、资金、技术等方面对试点城市和园区给予支持。此后&#xff0c;碳达峰、碳中和正…

基于开放共享的自主研发—MaxCompute 持续增强生态与开放性建设

MaxCompute产品与生态架构 MaxCompute是一个具有先进架构的Serverless云数据仓库&#xff0c;自从商业化后&#xff0c;使用的用户涉及各个行业的头部客户。在生态上需要支持主流的开源产品以及阿里云云产品。其主要包括以下几个方面&#xff1a; 数据接入生态。目前官方提供…

还是畅通工程(思想+代码)

Description 某省调查乡村交通状况&#xff0c;得到的统计表中列出了任意两村庄间的距离。省政府“畅通工程”的目标是使全省任何两个村庄间都可以实现公路交通&#xff08;但不一定有直接的公路相连&#xff0c;只要能间接通过公路可达即可&#xff09;&#xff0c;并要求铺设…

构建数据中台的组织架构

一、中台是一种企业架构 1.TOGAF企业架构标准 TOGAF是一套企业架构标准。企业架构是指整个公司或企业的软件和其他技术的整体观点和方法。企业架构又细分为业务架构、应用架构、数据架构、技术架构几个方向。 其中业务架构的定义是“定义业务战略和组织&#xff0c;关键业务…

源于加速,不止加速——10年沉淀,破局改变

20余年技术&#xff0c;面临破局。CDN(Content Delivery Network&#xff0c;内容分发网络) 是一个超大规模的分布式系统&#xff0c;为互联网各类App和Web站点提供动 / 静态内容、实时流媒体加速以及网络安全防护等能力。在线购物、直播、音乐、游戏、社交等等一切&#xff0c…

C++注释符问题

#include<iostream>using namespace std;int main(){std::cout<<"/*";std::cout<<"*/";std::cout<</*"*/"*/;std::cout<</*"*/"/*"/*"*/;return 0;} 运行出错&#xff0c;第二行无法识别[Wa…

5分钟让你在大火的多模态领域权威榜单VQA上超越人类

ModelScope上开源了达摩院众多业界最强多模态模型&#xff0c;其中就有首超人类的多模态预训练视觉问答模型mPLUG&#xff0c;小编激动的搓搓小手&#xff0c;迫不及待的体验了一下。 一探&#xff1a;浅草才能没马蹄 市面上有好多号称“用户上手简单”&#xff0c;“一步到位…

表达式类型出错

#include<iostream>using namespace std;int main(){int a-1;unsigned b1;cout<<a*b<<endl;return 0;} 编译结果为一个随机数&#xff0c;因为a&#xff0c;b分别为带符号类型和无符号型整数。

私有化输出的服务网格我们是这样做的

介绍 微服务开发的问题 微服务架构下我们在开发中遇到的常见的问题有以下 4 个&#xff1a; 多语言问题&#xff1a;有多种编程语言&#xff0c;node.js, JAVA, GoLang…微服务需要为每种语言都维护一种中间件 SDK升级推动难&#xff1a;SDK 升级需要推动业务应用进行代码修…

技术解读:Dragonfly 基于 P2P 的智能镜像加速系统

背景 网络下载 提起网络下载领域&#xff0c;你应该首先会想到基于 TCP/IP 协议簇的 C/S 模式。这种模式希望每一个客户机都与服务器建立 TCP 连接&#xff0c;服务器轮询监听 TCP 连接并依次响应&#xff0c;如下图&#xff1a; 上世纪末期&#xff0c;基于 C/S 模式的思想&…

多行书写的字符串字面值

#include<iostream>using namespace std;int main(){cout<<"Hello Word!""Best Coder!"<<endl;return 0;}

c++中值得初始化

代码中四种初始化&#xff0c;a中都为0 #include<iostream> using namespace std;int main(){int a0;cout<<a<<endl;int a1{0};cout<<a1<<endl;int a2{0};cout<<a2<<endl;int a3(0);cout<<a3<<endl;return 0;}

Kruise Rollout:灵活可插拔的渐进式发布框架

前言 Kruise Rollout 是 OpenKruise 社区开源的渐进式交付框架。Kruise Rollout 支持配合流量和实例灰度的金丝雀发布、蓝绿发布、A/B Testing 发布&#xff0c;以及发布过程能够基于 Prometheus Metrics 指标自动化分批与暂停&#xff0c;并提供旁路的无感对接、兼容已有的多…

最小生成树的Prime算法的思想

Prime算法的核心步骤是&#xff1a;在带权连通图中V是包含所有顶点的集合&#xff0c; U已经在最小生成树中的节点&#xff0c;从图中任意某一顶点v开始&#xff0c;此时集合U{v}&#xff0c;重复执行下述操作&#xff1a;在所有u∈U,w∈V-U的边(u,w)∈E中找到一条权值最小的边…

c++引用“”

常见的是“左值引用”在c11中新增了“右值引用” 引用&#xff1a;为对象起另外一个名字&#xff0c;引用类型引用另外一种类型。通过声明符写成&d的形式来定义引用类型&#xff0c;其中d是引用类型名。 例如&#xff1a; int a1024; int &ba;//那么b就等于a的值了…

一线技术人应该关注的四种思维能力

引言 作为长期奋战在一线的技术人&#xff0c;我深刻体会到如下几个思维能力对技术人成长的重要性&#xff0c;熟练运用这几种思维可以帮助我们快速的进入到新的领域&#xff0c;在分析、定位和解决问题上有很大帮助。 抽象思维&#xff1a;帮助我们快速抽取面对问题的关键要素…

Nacos 企业版如何提升读写性能和可观测性

概述 微服务引擎 MSE 发布 2.0.4.0 版本&#xff0c;新版本主要在性能和可观测能力升大幅提升&#xff0c;也加固了安全性。性能方面&#xff0c;基于 Dragonwell 进行构建&#xff0c;服务发现和配置性能提升达 40%以上&#xff1b;可观测方面&#xff0c;提供了服务注册的轨…

「技术人生」第9篇:如何设定业务目标

写在前面 上一篇文章讲了如何构建业务大图&#xff0c;看到有评论说这和设定 OKR 差不多啊。希望其他读者不要被类似的看法带偏。业务大图是业务顶层设计&#xff0c;是战略目标、业务长期价值、业务维度拆分、业务组织设计、业务长期发展方向、关键业务战役、短期重点事项的综…

SPFA总结

动态逼近法&#xff1a;设立一个先进先出的队列用来保存待优化的结点&#xff0c;优化时每次取出队首结点u&#xff0c;并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作&#xff0c;如果v点的最短路径估计值有所调整&#xff0c;且v点不在当前的队列中&#…