常见排序算法总结及C语言实现

来源:互联网 发布:php 去掉斜线 编辑:程序博客网 时间:2024/05/16 12:55

一直没有好好的扎扎实实的算法的基础,要找工作了,临时抱下佛脚,顺便把学的东西整理下,以应对比较健忘的大脑。。。

废话不说,直接主题,其实整理这个,借鉴了不少这个blog,http://www.cppblog.com/shongbee2/archive/2009/04/25/81058.html 在此再次感谢这个博主,但愿有一天,自己也能请博主喝杯咖啡 哈哈~ 

先从最熟悉的冒泡排序开始吧:

冒泡排序(BubbleSort)

冒泡排序也是我接触到的最早的排序,其基本思想是把数组中每个元素都看成是有质量的气泡,每个气泡的质量就是数组对应位置的元素的大小。排序的过程就是不停的把质量较轻(元素较小)的气泡上浮的过程。第一次循环最小的元素起泡到第一个位置,第二次循环,次小的元素被放到正确的位置,这样n-1次循环之后,数组就是有序的了。值得说明的是,冒泡排序是原地排序的过程,即不需要额外的内存空间,时间复杂度为O(N^2),且是稳定的排序。

源代码:

int BubbleSort(int *pData,int len){//冒泡排序,pData 是待排序的数组,len是数组的长度bool isOk=false;//这是一个优化,标志数组是否有序for(int i=0;i<len-1&&!isOk;i++)//外层循环控制起泡循环的次数{isOk=true;//初始化为有序的for(int j=len-1;j>i;j--)//从最后一个元素开始,一次循环的结果是使第i小的元素出现在第i个位置上{if(pData[j]<pData[j-1])//如果第j-1个元素比第j个元素小,则交换.{int temp=pData[j];pData[j]=pData[j-1];pData[j-1]=temp;isOk =false;//有交换发生,说明数组是无序的,即排序没有完成.}}}return 1;}

快速排序(QuickSort)

快速排序和冒泡排序一样,都是基于交换的排序。但是值得注意的是快速排序用到了分治的思想。

利用分治的思想可以这样描述快排:对于待排序的无序的数组pData[1,2,...n],分解:任选其中一个元素做为键值进行划分,找到划分位置i,使得i左边的元素都小于等于找到的键值,i右边的元素都大于等于键值。 求解:递归的对i左边的元素和i右边的元素进行快速排序;组合:由于求解过程中递归步骤结束后,左右两部分都已经有序,这样对于排序来说,组合的过程就不需要操作了。可以看成是空操作。同样说明的是快速排序也是原地排序,且时间复杂度比冒泡排序低最好的情况是O(nlog(n)),最坏情况是O(N^2),是不稳定的排序。

源代码

int Partition(int *pData,int begining,int end){//划分的过程 pData是待排序的无序数组,begining是开始位置,end是结束位置,函数返回以起始位置元素做为键值的划分的位置int i = end,j;//i初始化为最后一个位置+1int nd=pData[begining-1];//选定起始位置为键值int tmp;for (j=end-1;j>begining-1;j--)//j从最后一个元素开始,到第二个元素结束{if(pData[j]>=nd)//如果pData[j]比选的键值大,那么i-1,同时交换pData[j]和pData[i],这样保证i右边的元素永远比键值大,同时i-1位置元素比键值小{--i;tmp=pData[i];pData[i]=pData[j];pData[j]=tmp;}}--i;//循环结束后i左边的元素都比键值小,而且i右边的元素(包括i位置元素)都比键值大,所以i值减一,同时和键值交换pData[begining-1]=pData[i];pData[i]=nd;return i+1;//考虑到数组下标是从零开始的}int QuickSortRecursion(int *pData,int begining,int end){{if (begining>=end)//终止条件return 1;}int i=Partition(pData,begining,end);//划分QuickSortRecursion(pData,begining,i);//递归的排序左半部分QuickSortRecursion(pData,i+1,end);//递归的排序右半部分return 1;} int QuickSort(int *pData,int len) { QuickSortRecursion(pData,1,len); return 1; }

选择排序(SelectSort)

选择排序的基本思想和冒泡排序有一点像,都是依次选择最小元素,次小元素…… ,但是和冒泡排序不同的是,冒泡排序发现小的就进行互换,但是选择排序是直接把小的放到应该出现的位置。选择排序的过程如下:

n个元素的数组可以经过n-1趟选择排序后得到有序的结果:初始状态:无序区pData[1,2,...n],有序区间为空。第一趟排序:在无序区pData[1,2,...n]中选择最小的元素pData[k],将他与第一个元素pData[1]交换,使得pData[1...1]和pData[2,3,...n],分别表示有序区和无序区。每一次循环,有序区的长度加一,同时无序区长度减一。这样n个元素的序列可以通过n-1趟排序得到有序的序列。选择排序的时间复杂度为O(N^2),是原地排序,且不稳定排序。

int SelectSort(int *pData,int num){//选择排序,pData是待排序数组,num是数组的长度for (int i=0;i<num-1;i++)//控制循环次数 num-1次循环即可得到有序序列{int index=i;//初始化最小元素的下标for(int j=i+1;j<num;j++)//从i+1个位置开始{if(pData[j]<pData[index])//如果有比当前最小值更小的元素,更新指向最小元素的索引index=j;}//循环结束后,index指向的是无序区最小元素的索引if(index!=i)//如果第i个元素不是当前循环最小元素,则交换{int temp=pData[i];pData[i]=pData[index];pData[index]=temp;}}return 1;}

插入排序(InsertionSort)

1、基本思想
     假设待排序的记录存放在数组R[1..n]中。初始时,R[1]自成1个有序区,无序区为R[2..n]。从i=2起直至i=n为止,依次将R[i]插入当前的有序区R[1..i-1]中,生成含n个记录的有序区。
2、第i-1趟直接插入排序:
     通常将一个记录R[i](i=2,3,…,n-1)插入到当前的有序区,使得插入后仍保证该区间里的记录是按关键字有序的操作称第i-1趟直接插入排序。
     排序过程的某一中间时刻,R被划分成两个子区间R[1..i-1](已排好序的有序区)和R[i..n](当前未排序的部分,可称无序区)。
     直接插入排序的基本操作是将当前无序区的第1个记录R[i]插人到有序区R[1..i-1]中适当的位置上,使R[1..i]变为新的有序区。因为这种方法每次使有序区增加1个记录,通常称增量法。
     插入排序与打扑克时整理手上的牌非常类似。摸来的第1张牌无须整理,此后每次从桌上的牌(无序区)中摸最上面的1张并插入左手的牌(有序区)中正确的位置上。为了找到这个正确的位置,须自左向右(或自右向左)将摸来的牌与左手中已有的牌逐一比较。

插入排序的时间复杂度是O(N^2),是原地排序,且是稳定的排序。

int InsertionSort(int *pData,int num){//插入排序int i,j,key;for(j=1;j<num;j++)//循环次数{key=pData[j];//把第j个元素插入到前j-1个有序的序列中i=j-1;//循环的初始while(i>=0&&pData[i]>key){pData[i+1]=pData[i];//从后往前遍历,若比key值大,则元素后移(为后面的插入做准备)i--;}//找到第一个比key值小的位置pData[i+1]=key;//key插入到第i一个比key小的元素的位置之后}return 1;}
插入排序的一个变种是希尔排序,用到了分治的思想,其实这个我也不是很懂,附上源代码,供大家参考

int SortGroup(int *pnData,int nlen,int nBegin,int nStep){for(int i=nBegin+nStep;i<nlen;i+=nStep){for (int j=nBegin;j<i;j+=nStep){if(pnData[i]<pnData[j]){int nTemp=pnData[i];for(int k=i;k>j;k-=nStep){pnData[k]=pnData[k-nStep];}pnData[j]=nTemp;}}}return 1;}int ShellSort(int *pnData,int nLen){for(int nStep=nLen/2;nStep>0;nStep/=2){for(int i=0;i<nStep;++i){SortGroup(pnData,nLen,i,nStep);}}return 1;}

归并排序(MergeSort)
归并排序的主要思想是分治

1、算法基本思路
     设两个有序的子文件(相当于输入堆)放在同一向量中相邻的位置上:R[low..m],R[m+1..high],先将它们合并到一个局部的暂存向量R1(相当于输出堆)中,待合并完成后将R1复制回R[low..high]中。
(1)合并过程
     合并过程中,设置i,j和p三个指针,其初值分别指向这三个记录区的起始位置。合并时依次比较R[i]和R[j]的关键字,取关键字较小的记录复制到R1[p]中,然后将被复制记录的指针i或j加1,以及指向复制位置的指针p加1。
     重复这一过程直至两个输入的子文件有一个已全部复制完毕(不妨称其为空),此时将另一非空的子文件中剩余记录依次复制到R1中即可。
(2)动态申请R1

     实现时,R1是动态申请的,因为申请的空间可能很大,故须加入申请空间是否成功的处理。

归并排序的时间复杂度是O(nlog(n))的,但是是需要额外的空间的O(N),同时是稳定的排序

int Merge(int *pData,int nP,int nM,int nR){//归并pData中nP到nM和nM到nR的元素int nLen1=nM-nP;int nLen2=nR-nM;int *pnD1=new int[nLen1];int *pnD2=new int[nLen2];int i,j;for(i=0;i<nLen1;++i){pnD1[i]=pData[nP+i];//复制pData中nP到nM的元素}for (j=0;j<nLen2;j++){pnD2[j]=pData[nM+j];//复制pData中nM到nR的元素}i=j=0;while(i<nLen1&&j<nLen2)//归并过程{if (pnD1[i]<pnD2[j]){pData[nP]=pnD1[i];++i;}else{pData[nP]=pnD2[j];j++;}nP++;}if (i<nLen1)//如果j先结束,复制i剩下的元素{while(nP<nR){pData[nP++]=pnD1[i++];}}else//复制j剩下的元素{while(nP<nR){pData[nP++]=pnD2[j++];}}delete pnD1;delete pnD2;return 1;}int MergeRecursion(int *pData,int nBegin,int nEnd){//递归调用if (nBegin>=nEnd-1){return 0;}int nMid=(nBegin+nEnd)/2;//中间位置MergeRecursion(pData,nBegin,nMid);//递归求解前半部分MergeRecursion(pData,nMid,nEnd);//递归求解后banbufMerge(pData,nBegin,nMid,nEnd);//组合,归并求解return 1;}int MergeSort(int *pData,int Len){return MergeRecursion(pData,0,Len);}
堆排序(HeapSort)

 堆的定义

     n个关键字序列Kl,K2,…,Kn称为堆,当且仅当该序列满足如下性质(简称为堆性质):(1) ki≤K2i且ki≤K2i+1 或(2)Ki≥K2i且ki≥K2i+1(1≤i≤(n/2) )满足(1)的叫做最小堆,满足(2)的称作最大堆。

 堆排序利用了大根堆(或小根堆)堆顶记录的关键字最大(或最小)这一特征,使得在当前无序区中选取最大(或最小)关键字的记录变得简单。
(1)用大根堆排序的基本思想
先将初始文件R[1..n]建成一个大根堆,此堆为初始的无序区
再将关键字最大的记录R[1](即堆顶)和无序区的最后一个记录R[n]交换,由此得到新的无序区R[1..n-1]和有序区R[n],且满足R[1..n-1].keys≤R[n].key
由于交换后新的根R[1]可能违反堆性质,故应将当前无序区R[1..n-1]调整为堆。然后再次将R[1..n-1]中关键字最大的记录R[1]和该区间的最后一个记录R[n-1]交换,由此得到新的无序区R[1..n-2]和有序区R[n-1..n],且仍满足关系R[1..n-2].keys≤R[n-1..n].keys,同样要将R[1..n-2]调整为堆。

    ……
直到无序区只有一个元素为止。

(2)大根堆排序算法的基本操作:

① 初始化操作:将R[1..n]构造为初始堆;

② 每一趟排序的基本操作:将当前无序区的堆顶记录R[1]和该区间的最后一个记录交换,然后将新的无序区调整为堆(亦称重建堆)。
  注意:
①只需做n-1趟排序,选出较大的n-1个关键字即可以使得文件递增有序。
②用小根堆排序与利用大根堆类似,只不过其排序结果是递减有序的。堆排序和直接选择排序相反:在任何时刻,堆排序中无序区总是在有序区之前,且有序区是在原向量的尾部由后往前逐步扩大至整个向量为止。

堆排序是原地进行的,时间复杂度为O(nlog(n)),是不稳定的排序。

void swap(int &i,int &j){//交换两个元素的值,纯属装X,从别地看到的不用第三个变量就能互换的方法if (i!=j){i^=j;j^=i;i^=j;}}int MaxHeapIfy(int *pData,int nPose,int nLen){//保证以nPose 为根的子树为最大堆,算法的前提是以Left[nPose]和Right[nPose]为根的子树都是最大堆int nMax=-1;//最大位置的索引int lChild=2*nPose+1;//考虑到数组下标从零开始,左子树的根的位置int rChild=2*nPose+2;//右子树根的位置if(lChild<nLen&&pData[lChild]>pData[nPose])nMax=lChild;//如果左孩子比根大,违反最大堆的定义,设定nMax为左孩子的索引elsenMax=nPose;if (rChild<nLen&&pData[rChild]>pData[nMax])nMax=rChild;//同理 若右孩子比根大,设定nMax为右孩子的索引if(nMax!=nPose)//如果nPose不是最大{swap(pData[nPose],pData[nMax]);//交换nPose和nMax的值MaxHeapIfy(pData,nMax,nLen);//交换后,可能以你年Max位置为根的子树不满足最大堆条件,递归的调用}else{return 1;}return 1;}int BuildMaxHeap(int *pData,int nLen){//建立最大堆for(int i=nLen/2;i>=0;i--)//由完全二叉树的性质知,后n/2的元素都是叶子节点,自成一个最大堆MaxHeapIfy(pData,i,nLen);//自底向上的建堆return 1;}int HeapSort(int *pData,int nLen){BuildMaxHeap(pData,nLen);//建堆while(nLen>=1){swap(pData[0],pData[nLen-1]);//当堆建好后,第一个元素肯定是最大的元素,交换第一个元素与最后一个元素,这样最大的元素就放在了最后--nLen;//nLen-1.对前nLen个元素重复上述过程MaxHeapIfy(pData,0,nLen);//交换后,可能第一个元素不满足最大堆条件,调整}return 1;}


原创粉丝点击