[Algorithm] Sort for Fun!
Updated, July, 2019
2011年的原文
看到园子里有人发排序算法的随笔,想起之前发现的一个好网页,日本人写的,用脚本编写的一个算法柱形图演示,很是形象。趁着一时的兴趣,也就自己写了个程序,希望能通过终端实现类似的演示效果。写了一半,最后的图形显示遇到点小麻烦,之后也就忙的没再解决。
程序也简单,将不同的算法模块加进去,比较不同的算法的排序效率。当一百万的随机数用快排在数秒钟搞定,而其他则是花费数倍,甚至十几倍的时间才能出结果时,“效率”两个字便突然间有了很重要的地位。花时间在代码质量方面做做文章,虽不会有立竿见影的效果,但对一个人的编程态度会有很大的改观。米卢不是说了么:“态度决定一切”。
这个是日本人的那个网页链接:http://jsdo.it/norahiko/oxIy/fullscreen
OUTLINE
Ref: 十大经典排序算法最强总结
一、比较排序法
选择排序:关键是 再剩余项中选出最大值。
冒泡排序:关键是 两个循环,与“选择排序”相比,会有频繁的“相邻的两两比较”。
插入排序:list preferred,讲剩余项中选出一个插入到“已有序的序列”中。
希尔排序:
希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序,同时该算法是冲破O(n2)的第一批算法之一。
- 通过排序子列表, 我们已将项目移动到更接近他们实际所属的位置。
- 最后一次就是增量为1的标准插入排序,但此时大部分是有序的。
- 通过执行之前的子列表排序, 我们减少了将列表置于其最终顺序所需的移位操作的总数。
[与插入排序的不同之处]
它会优先比较距离较远的元素。希尔排序又叫缩小增量排序 (“希尔增量”是减半变化的,但不是最优的选择)。
快速排序:(Quick Sort)
快排之所以块,就是因为它高度优化的内部循环(分割)。
它既不像 "归并排序" 那样需要辅助数组来回复制元素,也不像 "堆排序" 无法利用缓存并且有许多无用的比较,并且最坏情况可以采用一些方法避免。
- 最左边的6作为“基准”,右边先走,“不对劲”时轮到左边走。(同时走也是可以的)
- 交换6和3,3作为下一次的“基准”。此时,6的左边都小于6,6的右边都大于6。
#include<stdio.h>
void Swap(int arr[], int low, int high) { int temp; temp = arr[low]; arr[low] = arr[high]; arr[high] = temp; }
//////////////////////////////////////////////////////
int Partition(int arr[], int low, int high) { int base = arr[low]; while(low < high) { while(low < high && arr[high] >= base) { high --; } Swap(arr, low, high); while(low < high && arr[low] <= base) { low ++; } Swap(arr, low, high); } return low; }
//////////////////////////////////////////////////////
void QuickSort(int arr[], int low, int high) { if(low < high) { int base = Partition(arr, low, high); // 可随机选择主元 QuickSort(arr, low, base - 1); QuickSort(arr, base + 1, high); } }
//////////////////////////////////////////////////////
int main() { int n; scanf("%d\n",&n); int arr[n]; int i , j; for(i = 0; i < n; i ++) { scanf("%d",&arr[i]); } printf("\n"); QuickSort(arr, 0, n-1); for(j = 0; j < n; j ++) { printf("%4d",arr[j]); } return 0; }
归并排序:(Merge Sort)也可适用于“外存”;但频繁复制降低了效率。
def mergeSort(alist): print("Splitting ",alist) if len(alist)>1: mid = len(alist)//2 lefthalf = alist[:mid] righthalf = alist[mid:]
mergeSort(lefthalf) mergeSort(righthalf)
i=0 j=0 k=0
# 俩指针同步移动 while i < len(lefthalf) and j < len(righthalf): if lefthalf[i] < righthalf[j]: alist[k]=lefthalf[i] i=i+1 else: alist[k]=righthalf[j] j=j+1 k=k+1
# 剩余部分的处理 while i < len(lefthalf): alist[k]=lefthalf[i] i=i+1 k=k+1
while j < len(righthalf): alist[k]=righthalf[j] j=j+1 k=k+1
print("Merging ",alist)
alist = [54,26,93,17,77,31,44,55,20] mergeSort(alist) print(alist)
堆排序:(Heap Sort)每次更新都需维护,无法充分利用缓冲技术。
Tim排序:Python、 Java、 Android平台 和 GNU Octave 的默认排序算法。
针对现实中需要排序的数据分析看,大多数据通常是有部分已经排好序的数据块,Timsort 就利用了这一特点。
本质上 Timsort 是一个经过大量优化的归并排序,而归并排序已经到达了最坏情况下,比较排序算法时间复杂度的下界,所以在最坏的情况下,Timsort 时间复杂度为 O(nlogn)O(nlogn)O(nlogn)。在最佳情况下,即输入已经排好序,它则以线性时间运行O(n)O(n)O(n)。可以看出Timsort是目前最好的排序方式。
Ref: Tim Sort Explained
(1) Binary Insertion Sort.
窗口逐渐增大,所以新的元素要插入“sorted"的序列时,可以通过二分查找法找到插入位置。
(2) Chunk
Chunk,也可能叫做"run":从左到右,如下,当发现有一个元素不是”递增“ or ”递减“时,插入排序,然后当chunk结束;
遍历整个序列后,便得到一堆的”有序小序列”。(有点希尔排序的意思)
(3) Merging runs efficiently
不等长序列的合并,如何高效实现。
[优化一]
涉及到贪心算法,序列长度排序,从小的开始两两合并。
如此,有点Fibonacci的意思:Run(n) > Run(n-1) + Run(n-2)
(4) Merging runs optimally with Galloping
[优化二]
合并过程中,需要找到合适的点,这是一个搜索问题,可以考虑"Galloping research"。
From: TimSort | Sorting Algorithm【代码】
import random def InsertionSort(array): for x in range (1, len(array)): for i in range(x, 0, -1): if array[i] < array[i - 1]: t = array[i] array[i] = array[i - 1] array[i - 1] = t else: break i = i - 1 return array def Merge(aArr, bArr): a = 0 b = 0 cArr = [] while a < len(aArr) and b < len(bArr): if aArr[a] < bArr[b]: cArr.append(aArr[a]) a = a + 1 elif aArr[a] > bArr[b]: cArr.append(bArr[b]) b = b + 1 else: cArr.append(aArr[a]) cArr.append(bArr[b]) a = a + 1 b = b + 1 while a < len(aArr): cArr.append(aArr[a]) a = a + 1 while b < len(bArr): cArr.append(bArr[b]) b = b + 1 return cArr def TimSort(): for x in range(0, len(arr), RUN): arr[x : x + RUN] = InsertionSort(arr[x : x + RUN]) RUNinc = RUN while RUNinc < len(arr): for x in range(0, len(arr), 2 * RUNinc): arr[x : x + 2 * RUNinc] = Merge(arr[x : x + RUNinc], arr[x + RUNinc: x + 2 * RUNinc]) RUNinc = RUNinc * 2 arr = [] RUN = 32 for x in range(0, 50): arr.append(random.randint(0, 100)) TimSort() print(arr)
二、非比较排序法
桶排序是基础
Ref: 基数排序、桶排序和计数排序的区别
其中, d 表示位数, k 在基数排序中表示 k 进制,在桶排序中表示桶的个数, maxV 和 minV 表示元素最大值和最小值。
首先,基数排序和计数排序都可以看作是桶排序。
-
- 计数排序本质上是一种特殊的桶排序,当桶的个数取最大( maxV-minV+1 )的时候,就变成了计数排序。
- 基数排序也是一种桶排序。桶排序是按值区间划分桶,基数排序是按数位来划分;基数排序可以看做是多轮桶排序,每个数位上都进行一轮桶排序。
基数排序"退化"
当用最大值作为基数时,基数排序 ----> 退化成 ----> 计数排序。
当使用2进制时, k=2 最小,位数 d 最大,时间复杂度 O(nd) 会变大,空间复杂度 O(n+k) 会变小。
当用最大值作为基数时, k=maxV 最大, d=1 最小,此时时间复杂度 O(nd) 变小,但是空间复杂度 O(n+k) 会急剧增大,此时基数排序退化成了计数排序。
桶排序:(Bucket Sort)
Ref: 算法:排序算法之桶排序
利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。
基数排序:(Radix Sort)
Ref: 基数排序 顺序实现与链式实现
计数排序:(Counting Sort)
Ref: 漫画:什么是计数排序?
以“数列最大值和最小值的差+1”作为统计数组的长度。
三、大数据排序
Ref: 硅谷之路 054 深入浅出Spark(七)如何排序100TB Preview
/* 略,不在此学习,但仍然是一个不可忽视的话题 */
2011年的旧代码
下面是我的这个小程序,随便一看。
1 #ifndef _HEAD_H_ 2 #define _HEAD_H_ 3 4 #include<stdio.h> 5 #include<stdlib.h> 6 #include<string.h> 7 8 #include <unistd.h> 9 #include <sys/times.h> 10 11 #define FOR(i, count) \ 12 for(i = 0; i < count; ++i) 13 14 #endif 15 #include"head.h" 16 17 #define COUNT 1000000 //比较的数字个数,randArr[0]有其他用处。 18 #define LEN (COUNT+1) 19 #define ROW 20 #define COL (COUNT+1) 21 #define TIME_VALUE 200000 //改速度 22 23 24 25 #define BLACK 40 26 #define WHITE 47 27 #define RED 41 28 #define PURPLE 45 29 #define GREEN 42 30 #define BLUE 44 31 32 33 #define BG BLACK 34 #define FG WHITE 35 36 37 38 int randArr[COUNT+1]; //randArr[0]有其他用处。 39 int dlta[] = {5, 3, 1}; 40 41 typedef enum { 42 StraightInsertion = 0, 43 Shell = 1, 44 Quick = 2, 45 Merge = 3, 46 Bubble = 4, 47 Heap = 5, 48 /*添加算法标志*/ 49 }SORTING_STYPE; 50 51 //------------------------------------------------------------------------ 52 53 typedef int* TYPE; 54 55 int swap(TYPE a, TYPE b) 56 { 57 *a ^= *b; 58 *b ^= *a; 59 *a ^= *b; 60 61 return 0; 62 } 63 64 65 66 67 68 //------------------------------------------------------------------------ 69 70 void _draw_clr(int which) //which: 把第几个柱子清除 71 { 72 int i, j; 73 74 /*draw column*/ 75 FOR(j, ROW) { 76 77 printf("\33[%d;%dH", j, which); 78 printf("\33[%dm \33[0m", BG); 79 } 80 printf("\33[%d;%dH", ROW, which); 81 } 82 83 84 void _draw_col(int which, int color ) //以何种颜色显示第几根柱子 85 { 86 int i, j; 87 88 /*clear column*/ 89 _draw_clr(which); 90 91 /*draw column*/ 92 FOR(j, randArr[which]) { 93 94 printf("\33[%d;%dH", j, which); 95 printf("\33[%dm \33[0m", color); 96 } 97 printf("\33[%d;%dH", ROW, which); 98 usleep(TIME_VALUE); 99 } 100 101 //------------------------------------------------------------------------ 102 103 104 void drawArr(void) 105 { 106 int i, j; 107 108 #if 0 109 /*clear and init tty*/ 110 printf("\33[1;1H"); 111 112 FOR(i, ROW) { 113 FOR(j, COL) { 114 115 printf("\33[%dm \33[0m", BG); 116 } 117 printf("\n"); 118 } 119 120 FOR(i, LEN) { 121 FOR(j, randArr[i]) { 122 printf("\33[%d;%dH", j, i+1); 123 printf("\33[%dm \33[0m", FG); 124 } 125 } 126 127 printf("\33[%d;%dH", ROW-2, 0); 128 129 #endif 130 131 #if 1 132 FOR(j, COUNT) { 133 printf("randArr[%d] = %d\n", j+1, randArr[j+1]); 134 } 135 #endif 136 } 137 138 139 void init(void) 140 { 141 int i = 0, j = 0; 142 srand(getpid()); 143 144 /*80 number*/ 145 FOR(i, COUNT) { 146 randArr[i+1] = rand() % ROW + 1; 147 //randArr[i+1] = i; 148 } 149 150 //drawArr(); 151 } 152 153 //------------------------------------------------------------------------ 154 155 /*排序参数简化,查找参数要全*/ 156 157 /*查找*/ 158 159 int Search_Bin(int low, int high, int key) 160 { 161 int mid = 0; 162 163 while (low < high) 164 { 165 mid = (low + high) >> 1; 166 if (randArr[mid] == key) 167 { 168 return mid; 169 } 170 else if (randArr[mid] > key) 171 { 172 high = mid - 1; 173 } 174 else 175 { 176 low = mid + 1; 177 } 178 } 179 180 return 0; 181 } 182 183 184 //------------------------------------------------ 185 186 /*排序算法实现*/ 187 /*排序的图形化演示,未实现,有兴趣可以一试*/ 188 /* 189 void InsertSort(void) 190 { 191 int i = 0, j = 0; 192 193 for (i = 2; i <= COUNT; ++i) 194 { 195 _draw_col(i, RED); 196 _draw_col(i-1, GREEN); 197 if(randArr[i] < randArr[i-1]) //not ascend... 198 { 199 _draw_col(i-1, FG); 200 //_draw_col(i-1, BLUE); 201 202 _draw_clr(i); 203 randArr[0] = randArr[i]; 204 _draw_col(0, RED); 205 206 _draw_clr(i-1); 207 randArr[i] = randArr[i-1]; 208 _draw_col(i, GREEN); 209 _draw_col(i, FG); 210 211 j = i - 1; 212 _draw_col(0, RED); 213 _draw_col(j, GREEN); 214 while(randArr[0] < randArr[j]) 215 { 216 //_draw_col(0, BLUE); 217 218 _draw_clr(j); 219 randArr[j+1] = randArr[j]; 220 _draw_col(j+1, GREEN); 221 _draw_col(i, FG); 222 --j; 223 _draw_col(0, RED); 224 _draw_col(j, GREEN); 225 } 226 _draw_col(j, FG); 227 _draw_clr(0); 228 randArr[j+1] = randArr[0]; 229 _draw_col(j+1, RED); 230 231 } 232 _draw_col(i, FG); 233 _draw_col(i-1, FG); 234 } 235 } 236 */ 237 238 239 void InsertSort(void) 240 { 241 int i = 0, j = 0; 242 int tmp = 0; 243 244 for (i = 2; i <= COUNT; ++i) 245 { 246 if(randArr[i] < randArr[i-1]) 247 { 248 /*放哨兵*/ 249 randArr[0] = randArr[i]; 250 251 /*找位置*/ 252 j = i - 2; 253 while(randArr[0] < randArr[j]) 254 { 255 --j; 256 } 257 258 /*整体移动,腾位置*/ 259 tmp = i-1; 260 while(j+1 <= tmp) 261 { 262 randArr[tmp+1] = randArr[tmp]; 263 --tmp; 264 } 265 266 /*坐位置*/ 267 randArr[j+1] = randArr[0]; 268 } 269 } 270 } 271 272 //-------------------------------------------- 273 274 void ShellInsert(int dk) 275 { 276 int i = 0, j = 0; 277 int tmp = 0; 278 279 for (i = dk+1; i <= COUNT; ++i) 280 { 281 if(randArr[i] < randArr[i-1*dk]) 282 { 283 /*放哨兵*/ 284 randArr[0] = randArr[i]; 285 286 /*找位置*/ 287 j = i - 2*dk; 288 while(randArr[0] < randArr[j]) 289 { 290 j -= dk; 291 } 292 293 /*整体移动,腾位置*/ 294 tmp = i - 1*dk; 295 while(j+1*dk <= tmp) 296 { 297 randArr[tmp+1*dk] = randArr[tmp]; 298 tmp -= dk; 299 } 300 301 /*坐位置*/ 302 randArr[j+1*dk] = randArr[0]; 303 } 304 } 305 } 306 307 308 void ShellSort(void) 309 { 310 int k = 0; 311 int t = 3; 312 int shellArr[] = {5, 3, 1}; 313 314 //按增量序列dlta[0..t-1]对顺序表作排序 315 for (k = 0; k < t; ++k) 316 { 317 ShellInsert(shellArr[k]); 318 } 319 } 320 321 322 323 //------------------------------------------------------------------------ 324 325 int Partition2(int low, int high) //升级版, 取中值 326 { 327 int tmp = 0; //= randArr[low]; 328 int pivotkey = 0; //= randArr[low]; 329 int mid = 0; 330 331 mid = (low + high) / 2; 332 swap(&randArr[mid], &randArr[low]); 333 334 tmp = randArr[low]; 335 pivotkey = randArr[low]; 336 337 while(low < high) 338 { 339 while(low < high && randArr[high] >= pivotkey) 340 { 341 --high; 342 } 343 randArr[low] = randArr[high]; 344 345 while(low < high && randArr[low] <= pivotkey) 346 { 347 ++low; 348 } 349 randArr[high] = randArr[low]; 350 } 351 352 randArr[low] = tmp; 353 354 return low; 355 } 356 357 int Partition(int low, int high) 358 { 359 int tmp = randArr[low]; 360 int pivotkey = randArr[low]; 361 362 while(low < high) 363 { 364 while(low < high && randArr[high] >= pivotkey) 365 { 366 --high; 367 } 368 randArr[low] = randArr[high]; 369 370 while(low < high && randArr[low] <= pivotkey) 371 { 372 ++low; 373 } 374 randArr[high] = randArr[low]; 375 } 376 377 randArr[low] = tmp; 378 379 return low; 380 } 381 382 383 384 void QSort(int low, int high) 385 { 386 int pivotloc; 387 388 if (low < high) 389 { 390 pivotloc = Partition2(low, high); 391 QSort(low, pivotloc-1); 392 QSort(pivotloc+1, high); 393 } 394 } 395 396 397 void QuickSort2(void) 398 { 399 QSort(1, COUNT); 400 } 401 402 403 void QuickSort(void) 404 { 405 QSort(1, COUNT); 406 } 407 408 409 //------------------------------------------------------------------------ 410 411 412 void merge(int tmpArr[], int i, int m, int n) 413 { 414 int j = m + 1; 415 int k = i; 416 417 while(i <= m && j <= n) 418 { 419 if (randArr[i] < randArr[j]) 420 { 421 tmpArr[k++] = randArr[i++]; 422 } 423 else 424 { 425 tmpArr[k++] = randArr[j++]; 426 } 427 } 428 429 while (i <= m) 430 { 431 tmpArr[k++] = randArr[i++]; 432 } 433 while (j <= n) 434 { 435 tmpArr[k++] = randArr[j++]; 436 } 437 } 438 439 440 441 void copy(int mergeArr[], int l, int r) 442 { 443 int i; 444 for( i = l ; i <= r; i ++ ){ 445 randArr[i] = mergeArr[i]; 446 } 447 } 448 449 450 451 void Msort(int mergeArr[], int left, int right) 452 { 453 int mid; 454 455 if( left < right ) 456 { 457 mid = ( left + right ) / 2; 458 Msort(mergeArr, left, mid ); 459 Msort(mergeArr, mid + 1, right ); 460 461 merge (mergeArr, left, mid, right ); 462 copy(mergeArr, left, right ); 463 } 464 } 465 466 467 468 void MergeSort() 469 { 470 int randArrMerge[COUNT+1]; 471 472 Msort(randArrMerge, 1, COUNT); 473 } 474 475 476 477 478 479 //------------------------------------------------------------------------ 480 481 void BubbleSort(void) 482 { 483 int i, j; 484 FOR(i, COUNT-1) { 485 FOR(j, COUNT - 1 - i) { 486 487 swap(&randArr[j], &randArr[j+1]); 488 } 489 } 490 } 491 492 //------------------------------------------------------------------------ 493 494 495 /* i为根节点,n为节点总数...关键 */ 496 void sift(int a[], int i, int n) 497 { 498 int child, tmpParent; 499 500 for (tmpParent = a[i]; n >= 2 * i; i = child) 501 { 502 /* i的左孩子为2*i,右孩子为2*i+1 */ 503 child = 2 * i; 504 505 /* 让child指向孩子中较大的一个 */ 506 if (child != n && a[child + 1] > a[child]) 507 { 508 /*i*2之后且=n,表明为左孩子即当前末结点*/ 509 510 child++; 511 } 512 513 /* 如果孩子节点大 */ 514 if (tmpParent < a[child]) 515 { 516 /* 交换孩子节点和根节点 */ 517 a[i] = a[child]; 518 } 519 else 520 { 521 /*若比孩子结点大,肯定比孙子大,则不用再比较*/ 522 break; 523 } 524 } 525 526 /* 将根放在合适位置, tmpParent无需每次移动 */ 527 a[i] = tmpParent; 528 } 529 530 531 532 void heapsort(int a[], int n) 533 { 534 int i; 535 536 /* 将a[1...n]建成大根堆 */ 537 for (i = n / 2; i >= 1; i--) 538 { 539 sift(a, i, n); 540 } 541 542 543 /* 进行n-1趟排序 */ 544 for (i = n; i >= 2; i--) 545 { 546 /* 交换堆顶元素和最后一个元素 */ 547 swap(&a[1], &a[i]); 548 549 /* 将a[1...i-1]重建为大顶堆 */ 550 sift(a, 1, i-1); 551 } 552 } 553 554 555 556 void HeapSort(void) 557 { 558 heapsort(randArr, COUNT); 559 } 560 561 562 //------------------------------------------------------------------------ 563 564 565 void do_job(int style) 566 { 567 568 switch(style) { 569 case StraightInsertion: 570 { 571 fprintf(stderr, "\nStraight Insertion Sort:\n"); 572 InsertSort(); 573 break; 574 } 575 case Shell: 576 { 577 fprintf(stderr, "\nShell's Sort:\n"); 578 ShellSort(); 579 break; 580 } 581 case Quick: 582 { 583 fprintf(stderr, "\nQuick Sort:\n"); 584 QuickSort(); 585 break; 586 } 587 case Merge: //超过一百万 堆栈溢出 588 { 589 fprintf(stderr, "\nMerge Sort:\n"); 590 MergeSort(); 591 break; 592 } 593 case Bubble: 594 { 595 fprintf(stderr, "\nBubble Sort:\n"); 596 BubbleSort(); 597 break; 598 } 599 case Heap: 600 { 601 fprintf(stderr, "\nHeap Sort:\n"); 602 HeapSort(); 603 break; 604 } 605 /*这里添加排序算法*/ 606 607 default: 608 { 609 break; 610 } 611 } 612 } 613 614 //------------------------------------------------------------------------ 615 616 int main(void) 617 { 618 619 clock_t start, end;
End.