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)