关于排序,已经有大量的文献可以参阅了,如果你看着大摞大摞的书和论文觉得头大,那么网上有一篇极好的文章,在这里,这里,和这里,作者是Matrix67大牛。不过大牛代码之间游刃有余,基本上没有任何代码实现,对于初学者们来说,代码还是很重要的,不管是读代码还是写代码。学生时代写代码的机会本来就很萧条,有写代码的时候千万不要得过且过。下面是一个简单的模板实现,但高手们不要见笑——高手都不用来看这篇水文——俺是个懒人呀,没有实现算法真正的泛化,STL那么精密巧妙的实现,俺这种菜鸟学不来。不过,如果有下一回,我会对STL中的算法的实现做一简单的分析,看看那些巨牛B的代码到底是怎么实现的,并对性能做一测试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 | //争取把排序算法都总结出来,因为排序既是基础,又内容较多, //当然最重要的,也还算有趣。 //其次就是总结这一思维过程的乐趣了。
namespace _SORT_HELP{ template<typename T> inline void swap(T& _x,T& _y) { T temp=x; x=y; y=temp; } }
//以下均默认为从小到大排序
#include<iostream> #include<limits>
using _SORT_HELP::swap;
/****************************************************************************************************************** 选择排序,每次从数列中选出最小(大)的数字放到最前面,说实话这算法实在是笨拙,没啥用,不过生活中倒是有对应当人们渴望先知道排在前面的是谁时,就是选择排序的思路,复杂度是稳稳当当的 n^2 ******************************************************************************************************************/ template<typename T>void selection_sort(T a[],int n) { for(int i=0;i<n;i++) { int min_index=i; for(int j=i+1;j<n;j++){ if(a[j]<a[min_index]) min_index=j; } if(i!=min_index) swap(a[min_index],a[i]); } }
/****************************************************************************************************************** 冒泡排序,分为若干趟进行,每一趟排序从前往后比较每两个相邻的元素的大小(因此一趟排序要比较n-1对位置相邻的数)并在每次发现前面的那个数比紧接它后的数大时交换位置 唯一的优化(其实应该说是“必须”!)就是引入一个标记,若某趟遍历中完全没有进行交换动作,则已然有序 ******************************************************************************************************************/ template<typename T>void bubble_sort(T a[],int n) { bool flag=false; for(int i=0;i<n;i++){ for(int j=i;j<n;j++){ if(a[j]>a[j+1]) { swap(a[j],a[j+1]); flag=true; } } if(!flag) return; } }
/****************************************************************************************************************** 插入排序,每次从数列中取一个还没有取出过的数,并按照大小关系插入到已经取出的数中使得已经取出的数仍然有序 ******************************************************************************************************************/ template<typename T>void insertion_sort(T a[],int n) { for(int i=1;i!=n;i++){ if(a[i]<a[i-1]) { temp = a[i]; left = 0; right = i-1;
while (left <= right) { mid = (left + right)/2; if (temp<a[mid]) right = mid-1; else left = mid+1; }
for (j = i-1; j >= left; j--) a[j+1] = a[j]; if (left!= i) a[left] = temp; } }
/****************************************************************************************************************** 堆排序,这是我很喜欢的排序算法之一,《算法导论》还专门列出了一章,时间上保证了严格的n*logn的上界,空间上只需要常数的额外空间。几乎是“鱼与熊掌兼得”了 ******************************************************************************************************************/ template<typename T>void adjust(T a[],int i,int n) { int j=2*i; T item=a[i]; while(j<=n){ if((j<n)&&(a[j]<a[j+1])) j++; if(item>=a[j]) break; a[j/2]=a[j]; j*=2; } a[j/2]=item; }
template<typename T>void heapify(T a[],int n) { for(int i=n/2;i>0;i--) adjust(a,i,n); }
template<typename T>void heap_sort(T a[],int n)//堆排序主函数 { heapify(a,n); for(int i=n;i>=2;i--){ swap(a[1],a[i]); adjust(a,1,i-1); } }
/****************************************************************************************************************** 希尔排序,这个排序要说清楚还是挺复杂的,它估计是最不稳定的。迄今为止,除了在一些特殊的情况下,还没有人能够从理论上分析希尔排序的效率,即便是在Don Knuth的书里我也没找到足够的分析证据。不过有各种基于试验的评估,估计它的时间级从O(N^3/2)到O(N^7/6)之间。 Shell排序基本思路是:先取一个小于n的整数d1作为第一个增量,把文件的全部记录分成d1个组。所有距离为dl的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量dt=1(dt<dt-l<…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。该方法实质上是一种分组插入方法。 选择间隔序列算是和选择Hash函数一样神奇的魔法,常用的有h=(h-1)/3有一个绝对的条件,就是逐渐缩小的间隔最后一定要等于1,因此最后一趟排序是一次普通的插入排序。 间隔序列中的数字互质通常被认为很重要:也就是说,除了1之外它们没有公约数。这个约束条件使每一趟排序更有可能保持前一趟排序以排好的效果,希尔最初以N/2为间隔的低效率性就是归咎于它没有遵守这个准则。 ******************************************************************************************************************/ template<typename T>void shell_insert(T a[],int dis,int n)//dis为当前递增量 { for(int i=dis;i<n;i++){ if(a[i]<a[i-dis]){ T temp=a[i]; int j=i-dis; do{ a[j+dis]=a[j]; j-=dis; }while((j>=0)&&(a[j]>temp)); a[j]=temp; } } }
template<typename T>void shell_sort(T a[],int n) { int dis=n; do{ dis=dis/3+1; shell_insert(a,dis,n); }while(dis>0); }
/****************************************************************************************************************** 归并排序,这也是我很喜欢的排序算法之一,原因有点八卦,《算法导论》MIT的视频第一堂课就是Charles E. Leiserson讲解的归并排序,很喜欢他的课,然后一鼓作气把主定理(master theory)都看完了,爽YY哇~~ 归并排序时间表现还是很好的,n*logn,主要是空间上花销大了点 ******************************************************************************************************************/ template<typename T> void merge(T a[],int left,int partion,int right) { int n1=partion-left+1,n2=right-partion; T _L[n1+1],_R[n2+1]; _L[0]=_R[0]=INT_MAX;
for(int i=1;i<n1;i++) _L[i]=a[left+i-1]; for(int j=1;j<n2;j++) _R[j]=a[q+j];
_L[n1+1]=_R[n2+1]=INT_MAX;
int ii=jj=1; for(int k=left;k<right;k++) { do{ if(_L[ii]<=_R[jj]) a[k]=_L[ii++]; else a[k]=_R[jj++]; }while((_L[ii]!=INT_MAX)&&(_R[jj]!=INT_MAX)); } }
template<typename T> void merge_sort(T a[],int left,int right) { if(left<right) { int mid=(left+right)/2; merge_sort(a,left,mid); merge_sort(a,mid+1,right); merge(a,left,mid,right); } }
/****************************************************************************************************************** 快速排序,可能本人有时候喜欢特地和和主流作对吧,快速排序大概是最常用的了。不过我觉得不好,比对排序差劲多了。最坏清醒为O(n^2),有了这样的最坏清醒,平均性能也就O(n*logn),哪里能和堆排序比?不过说老实话,真正实际中快速排序还是最快的。 ******************************************************************************************************************/
template<typename T> int partition(T a[],int first,int last) { T ref=a[last]; int i=first,j=last; do{ do{i++;}while(a[i]<ref); do{j++;}while(a[j]>ref); if(i<j) swap(a[i],a[j]); }while(i<j); a[first]=a[j]; a[j]=ref; return j; }
template<typename T> void quick_sort(T a[],int first,int last) { if(first<last){ int tmp=partion(a,first,last+1); quick_sort(a,first,tmp-1); quick_sort(a,tmp+1,last); } }
/****************************************************************************************************************** 计数排序,计数排序算法的基本思想是对于给定的输入序列中的每一个元素x,确定该序列中值小于x的元素的个数。一旦有了这个信息,就可以将x直接存放到最终的输出序列的正确位置上。很简单,就不多说了。 ******************************************************************************************************************/ template<typename T> void count_sort(T a[],T b[],int n) { T _max=a[0]; for(int i=1;i<n;i++){ if(a[i]>_max) _max=a[i]; }
T* _HELP=new T[_max]; memset(_HELP, 0, _max* sizeof(int));
for(int j=0;j<n;j++) ++_HELP[a[j]];//使_HELP[i]包含等于i的元素的个数
for(int k=1; k<_max; k++) _HELP[k]=_HELP[k]+_HELP[k-1];//使_HELP[i]包含小于或等于i的元素的个数
for(int t=n-1; t; t--) { b[_HELP[a[t]]]=a[t]; --_HELP[a[t]]; }
delete[] _HELP;
}
/******************************************************************************************************************基数排序(radix sort),基数排序不是那么好写,首先它只适合于整数和字符串,但是在应用中它应该是大有价值的,比如我就觉得(不一定正确啊),磁盘上文件的按名称排序就可以用基数排序的思想。
桶排序(bucket sort),把[0,1)划分为n个大小相同的子区间,每一子区间是一个桶。然后将n个记录分配到各个桶中。因为关键字序列是均匀分布在[0,1)上的,所以一般不会有很多个记录落入同一个桶中。由于同一桶中的记录其关键字不尽相同,所以必须采用关键字比较的排序方法(通常用插入排序)对各个桶进行排序,然后依次将各非空桶中的记录连接(收集)起来即可。
由于这两种排序都有特别的处理方案,在此不写代码了。 ******************************************************************************************************************/ |