void BubbleSort(Sqlist &L) { bool flag=0;//这里设置一个标志位,用来判断是否进行了交换,如果没有交换,说明不需要再排,这个就是冒泡排序的好处 for(int i=1;i<L.length;i++)//只需要排L.length-1次 { for(int j=L.length-1;j>=1;j--)//每一次都要从后往前找 { if(L.r[j-1]>L.r[j]) { swap(j-1,j,L); flag=1; } } if(!flag) break; } }
思想:从后面两两比较,若是小的,就进行交换,这样每循环一次就冒泡出一个最小。同时其它的数进行了移位。
如果排序较好,那么需要移动的次数还是比较少的。
#include "stdafx.h" #include<iostream> using namespace std; const int maxsize=5; struct Sqlist { int r[maxsize]; int length; Sqlist():length(0){} }; void swap(int i,int j,Sqlist &L) { int temp=L.r[i]; L.r[i]=L.r[j]; L.r[j]=temp; } void SlectSort(Sqlist &L) { for(int i=0;i<L.length;i++)// { int min=i;//这种游标法非常好,我很喜欢 for(int j=i+1;j<=L.length;j++) { if(L.r[j]<L.r[min]) min=j; } if(i!=min) swap(i,min,L); } } int _tmain(int argc, _TCHAR* argv[]) { Sqlist L; for(int i=0;i<maxsize;i++) { cin>>L.r[i];//以后就用这种方法读入数据了 L.length++; } SlectSort(L); for(int i=0;i<L.length;i++) cout<<L.r[i];//输出输入不加endl换行很方便</p><p> return 0; }
思想:相当于是不断从后面找到最小的,放到前面相应的位置
void InsertSort(Sqlist &L) { for(int i=1;i<L.length;i++)//从第二个位置开始 { int temp=L.r[i]; int j; if(L.r[i]<L.r[i-1]) { for(j=i-1;L.r[j]>L.r[i];j--)//注意是停止条件,为> L.r[j+1]=L.r[j]; L.r[j+1]=temp; //最后再插过去 这样节省时间,前面只要做覆盖移动即可 } } }
思想:向前插到合适的位置。
void shellsort(Sqlist &L) { int i,j; int increment=L.length; do { increment=increment/3+1; for(i=increment+1;i<L.length;i++) { if(L.r[i]<L.r[i-increment]) { int temp=L.r[i];//接下来和插入排序有点像了 for(j=i-increment;L.r[j]>L.r[i];j-=increment) L.r[j+increment]=L.r[j]; L.r[j+increment]=temp; } } } while(increment>1); //这个do while记住是有;的 }
思想:希尔排序是插入排序的改进版,只是更改了插入间隔为increment
(1)采用do while,从increment +1 遍历
(2)和前一个间隔值i-incremtnt比较,若小,则进入循环 (条件为,间隔值比i值大,)若是,则进行用前值 j 覆盖后值j+increment,最终用i值来刷新前值j+increment
(3)while 后面记得加上;
void HeapAdjust(Sqlist &L,int s,int m) { int temp,j; temp=L.r[s]; for(j=2*s;j<=m;j*=2)//定位要调整的小堆的位置, { if(j<m && L.r[j]<L.r[j+1])//和被调整的数对比,看是哪一个分支,然后用分支覆盖掉,并滑向分支,最后插入调整数 j++; if(temp>=L.r[j]) break; L.r[s]=L.r[j]; s=j; } L.r[s]=temp; } void HeapSort(Sqlist &L) { L.length--;//从1开始排,若length为5则变成4,刚好是末端数 int i; for(i=L.length/2;i>0;i--) //从第一个有子节点的位置开始调整为大顶堆,直到i=1 HeapAdjust(L,i,L.length); for(i=L.length;i>1;i--)//不断交换,共交换L.length-1次 { swap(i,1,L);//交换 HeapAdjust(L,1,i-1);//调整大顶堆,由于其它的已经调整好,只有第一个没有调整,因此代入下去即可 } L.length++;//恢复 这样size还是没有变,只是r[0]没有用到,这里主要是方便理解 }
(1)全部调整堆,使其满足大顶堆的要求,这样堆顶的元素是最大的。(调整顺序:从length/2开始调整,刚好是最底下一个子堆)
(2) 交换和调整大顶堆n-1次,就完成了整个排序。
调整大顶堆的思想:
(1)存储要调整点的值
(2)找到子堆中,最大点的位置,
(3)如果最大点比调整点大,用最大点覆盖掉调整点,
(4)处理最大点所在的子堆,在里面找到调整点该放到哪个位置
(5)重复步骤(3)
(5)完毕后,将调整点插入到合适的位置。
这个是自己根据大话数据结构改进的,跟以前排序方法,风格统一
void merge(Sqlist &L,int i,int m,int n) { int j,k,l; int ti=i; int tsize=n-i+1; int *S=new int[n-i+1];//初始化数组,用来存储排序,做缓存用 for(int t=0;t<tsize;t++) S[t]=0; for(j=m+1,k=0;i<=m && j<=n;k++) { if(L.r[i]<L.r[j])//看大哪边大就哪哪边的,这种i++的方式非常好 S[k]=L.r[i++]; else S[k]=L.r[j++]; } if(i<=m)//若j那边取完了i这边还有剩的,那么就把剩下的直接加在后面 { for(l=0;l<=m-i;l++) S[k+l]=L.r[i+l]; } if(j<=n) { for(l=0;l<=n-j;l++) S[k+l]=L.r[j+l]; } for(int a=0;a<tsize;a++) L.r[ti+a]=S[a]; delete S;//不要忘了释放动态数组 } void msort(Sqlist &L,int s,int t)//s为待排序数组的第一个元素下标,t为待排序数组最后一个元素的下标 { int m; if(s==t) return;//直接返回,无需归并 else if(s+1==t) { if(L.r[s]>L.r[t])//只要交换两个元素的位置,就已经做好了排序,不需要在做归并 swap(s,t,L); return; } else { m=(s+t)/2; msort(L,s,m); msort(L,m+1,t); merge(L,s,m,t); } } void MergeSort(Sqlist &L) { msort(L,0,L.length-1); }
排序思想:
(1)要想将一个数组排序,那么就是从中间分开,将两边排好序之后,再归并,就完成排序。
(2)而两边要想排好序,也是要做同样的工作。
(3)因此用到递归
(4)递归的工作是,分成两边,两边排好序(递归),归并(return)
(5)递归的出口是,当数组中元素个数为1时,说明已经排好序,且无需做分开,归并的工作,就能返回(return)。当元素个数为2的时候,也无需分开,只需要简单排序就能返回(return)。
归并思想:
(1)对于一个数组,其中i...m已经排好序,m+1...n已经排好序,将其归并i...n要做的工作是。
(2)定义一个i到n长度的动态数组,不断比较两边的大小,哪边大,就将哪边的值放到动态数组中,同时那边的游标往前走一个。
(3)当一边已经完成,而另外一边还没有放完时,就将剩下的接在数组后面。
(4)将原来数组中i...n部分的值更新为动态数组中的值。
int partition(Sqlist &L,int low,int high)//简单来讲 { int pivotkey; pivotkey=L.r[low]; while(low<high) { while(low<high && L.r[high]>=pivotkey) high--; swap(low,high,L); while(low<high && L.r[low]<=pivotkey) low++; swap(low,high,L); } return low; } void qsort(Sqlist &L,int low,int high) { int pivot; if(low<high) { pivot=partition(L,low,high); qsort(L,low,pivot-1); qsort(L,pivot+1,high); } } void QuickSort(Sqlist &L) { qsort(L,0,L.length-1); }
(1)要想将一个数组排序,可以将其分成两部分,较大值部分可较小值部分,再将两部分做同样的工作后,就完成了排序。
(2)因此还是用到递归
(3)递归的工作是,分成两边,左边为小值部分,右边为大值部分。
(3)递归出口,当小数组只有一个元素的时候直接返回(return),当有两个元素的时候,交换排序后返回(return)
分部实现:
(1)选取一个关键字,为第一个值,排序结果是,让一部分比它小,一部分比它大。
(2)不另外建立缓存
(3)采用左右移动的方式完成,先比较高端部分,高端有值比关键字小,那么将高端值交换到低端来,
(4)再比较低端,若低端有值比关键字大,那么将其交换到高端。
(5)如此往复,就完成了分部工作。
这种交换方法,省空间,而且特别巧妙。
(1)优化选取关键字
(2)优化不必要的交换,相当于是备份了一个,temp然后用覆盖的方式 ,就和堆排序里面有点像。
(3)优化递归操作
可以将算法非为两类:
简单算法:冒泡、简单选择、直接插入
改进算法:希尔、堆、归并、快排
总的来说,改进算法中堆和归并排序各种情况算法时间复杂度都是nlogn。
而希尔和快排最坏情况是n2,快排最好是nlogn,希尔最坏是n1.5这里看取的增量而定。
递归需要占用logn的空间,而归并排序同样需要一个数组来存储。耗费n的空间。
冒泡和直接插入最好情况时间复杂度为n,是排序很好的情况。
从平均情况来看,后面三种算法要比希尔排序好,但是比前面三种简单算法好
从最好情况来看,冒泡排序和直接插入排序又更胜一筹,如果排序基本有序,那么就不要考虑复杂算法。
从最坏情况来看,堆排序与归并排序又强过快排以及其它排序
可以看出:堆排序与归并排序,表现的比较稳定
但是快排:有时候表现的特别好,有时候比较差
空间复杂度来讲:归并排序和快速排序都是用的递归,所以对空间的要求比较大,反而堆排序堆空间要求比较低。
稳定性来看:归并排序最好
从排序个数来看:当个数较少时采用简单排序法
总之,经过优化的快速排序是性能最好的排序方法,但是要根据不同的场合来考虑不同的算法。