基础知识复习与整理,内容摘抄于维基百科,推荐一个在线学习的网站:visualgo.net
排序算法常规分类
- 计算的时间复杂度(最差、平均、和最好性能),依据列表(list)的大小(n)。一般而言,好的性能是O(n log n),且坏的性能是O(n2)。对于一个排序理想的性能是O(n)。仅使用一个抽象关键比较运算的排序算法总平均上总是至少需要O(n log n)。
- 存储器使用量(以及其他电脑资源的使用)
- 稳定性:稳定排序算法会让原本有相等键值的纪录维持相对次序。也就是如果一个排序算法是稳定的,当有两个相等键值的纪录R和S,且在原本的列表中R出现在S之前,在排序过的列表中R也将会是在S之前。
- 依据排序的方法:插入、交换、选择、合并等等。
常用排序算法
稳定的排序
- 冒泡排序(bubble sort)— O(n2)
- 鸡尾酒排序(cocktail sort)—O(n2)
- 插入排序(insertion sort)—O(n2)
- 桶排序(bucket sort)—O(n);需要O(k)额外空间
- 计数排序(counting sort)—O(n+k);需要O(n+k)额外空间
- 归并排序(merge sort)—O(n log n);需要O(n)额外空间
- 原地归并排序— O(n log2 n)如果使用最佳的现在版本
- 二叉排序树排序(binary tree sort)— O(n log n)期望时间;O(n2)最坏时间;需要O(n)额外空间
- 鸽巢排序(pigeonhole sort)—O(n+k);需要O(k)额外空间
- 基数排序(radix sort)—O(n·k);需要O(n)额外空间
- 侏儒排序(gnome sort)— O(n2)
- 图书馆排序(library sort)— O(n log n)期望时间;O(n2)最坏时间;需要(1+ε)n额外空间
- 块排序(block sort)— O(n log n)
**不稳定的排序 **
- 选择排序(selection sort)—O(n2)
- 希尔排序(shell sort)—O(n log2 n)如果使用最佳的现在版本
- Clover排序算法(Clover sort)—O(n)期望时间,O(n2)最坏情况
- 梳排序— O(n log n)
- 堆排序(heap sort)—O(n log n)
- 平滑排序(smooth sort)— O(n log n)
- 快速排序(quick sort)—O(n log n)期望时间,O(n2)最坏情况;对于大的、随机数列表一般相信是最快的已知排序
- 内省排序(introsort)—O(n log n)
- 耐心排序(patience sort)—O(n log n + k)最坏情况时间,需要额外的O(n + k)空间,也需要找到最长的递增子序列(longest increasing subsequence)
不实用的排序
- Bogo排序— O(n × n!),最坏的情况下期望时间为无穷。
- Stupid排序—O(n3);递归版本需要O(n2)额外存储器
- 珠排序(bead sort)— O(n) or O(√n),但需要特别的硬件
- 煎饼排序—O(n),但需要特别的硬件
- 臭皮匠排序(stooge sort)算法简单,但需要约n^2.7的时间
冒泡排序
入门算法,对有序序列排序时有劣势,一般建议用插入排序替代。也可以在内部循环时,可以做个标记变量,减少对有序序列排序时的循环,若在每次走访数列时,把走访顺序反过来,也可以稍微地改进效率。有时候称为鸡尾酒排序,因为算法会从数列的一端到另一端之间穿梭往返。
维基上的助记码
i∈[0,N-1) //循环N-1遍
j∈[0,N-1-i) //每遍循环要处理的无序部分
swap(j,j+1) //两两排序(升序/降序)
C#版本
static void BubbleSort(int[] intArray) {
int temp = 0;//存储临时变量
for (int i = 0; i < intArray.Length; i++)
for (int j = i - 1; j >= 0; j--)
if (intArray[j + 1] < intArray[j]) {
temp = intArray[j + 1];
intArray[j + 1] = intArray[j];
intArray[j] = temp;
}
}
Python版本
def bubble(List):
for j in range(len(List)-1,0,-1):
for i in range(0,j):
if List[i]>List[i+1]:List[i],List[i+1]=List[i+1],List[i]
return List
鸡尾酒排序
鸡尾酒排序,也就是定向冒泡排序,鸡尾酒搅拌排序,搅拌排序(也可以视作选择排序的一种变形),涟漪排序,来回排序or 快乐小时排序,是冒泡排序的一种变形。此算法与冒泡排序的不同处在于排序时是以双向在序列中进行排序。
伪代码
function cocktail_sort(list, list_length){ // the first element of list has index 0
bottom = 0;
top = list_length - 1;
swapped = true;
while(swapped == true) //如果没有交换发生,序列就已经是有序的了
{
swapped = false;
for(i = bottom; i < top; i = i + 1)
{
if(list[i] > list[i + 1])
{
swap(list[i], list[i + 1]);
swapped = true;
}
}
// decreases top the because the element with the largest value in the unsorted
// part of the list is now on the position top
top = top - 1;
for(i = top; i > bottom; i = i - 1)
{
if(list[i] < list[i - 1])
{
swap(list[i], list[i - 1]);
swapped = true;
}
}
// increases bottom because the element with the smallest value in the unsorted
// part of the list is now on the position bottom
bottom = bottom + 1;
}
}
与冒泡排序不同的地方
鸡尾酒排序等于是冒泡排序的轻微变形。不同的地方在于从低到高然后从高到低,而冒泡排序则仅从低到高去比较序列里的每个元素。他可以得到比冒泡排序稍微好一点的效能,原因是冒泡排序只从一个方向进行比对(由低到高),每次循环只移动一个项目。
以序列(2,3,4,5,1)为例,鸡尾酒排序只需要访问一次序列就可以完成排序,但如果使用冒泡排序则需要四次。但是在乱数序列的状态下,鸡尾酒排序与冒泡排序的效率都很差劲。
插入排序
插入排序不适合对于数据量比较大的排序应用。但是,如果需要排序的数据量很小,例如,量级小于千,那么插入排序还是一个不错的选择。 插入排序在工业级库中也有着广泛的应用,在STL的sort算法和stdlib的qsort算法中,都将插入排序作为快速排序的补充,用于少量元素的排序(通常为8个或以下)
具体算法描述如下:
- 从第一个元素开始,该元素可以认为已经被排序
- 取出下一个元素,在已经排序的元素序列中从后向前扫描
- 如果该元素(已排序)大于新元素,将该元素移到下一位置
- 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
- 将新元素插入到该位置后
- 重复步骤2~5
如果比较操作的代价比交换操作大的话,可以采用二分查找法来减少比较操作的数目。该算法可以认为是插入排序的一个变种,称为二分查找插入排序。
C#实现
public static void InsertSort(double[] data) {
int i, j;
var count = data.Length;
for (i = 1 ; i < count ; i++) {
var t = data[i];
for(j = i - 1; j >= 0 && data[j] > t; j--)
data[j + 1] = data[j];
data[j + 1] = t;
}
}
Python
def insertion_sort(n):
if len(n) == 1:
return n
b = insertion_sort(n[1:])
m = len(b)
for i in range(m):
if n[0] <= b[i]:
return b[:i]+[n[0]]+b[i:]
return b + [n[0]]
#版本2
def insertion_sort(lst):
if len(lst) == 1:
return
for i in xrange(1, len(lst)):
temp = lst[i]
j = i - 1
while j >= 0 and temp < lst[j]:
lst[j + 1] = lst[j]
j -= 1
lst[j + 1] = temp