排序算法是众多算法的基础,是实际工程中真正需要用到的算法之一。
在数据的组织中,排序因为能将数据变得有序,往往能带来访问效率的提高。另一方面,在有些时候,排序算法中前k大值等的求取本身就是直接需求。
数据hash算法是与排序相匹敌的另一类高效访问算法,效率高到O(1),但hash算法只能用于普通的插入查找,不能用于需要比较数值的情况,另一方面,对于小规模数据,hash算法的实际代码长度抵消了复杂度优势。
纸牌游戏中的插入排序通常是身边最能接触到的最简单排序算法,而堆排序则是一种稳定高效的排序算法。本文将由简入繁介绍相关的排序算法。
int sort_insertion(int* data, int n){
for (int i = 1; i<n; i++){
//insert data[i] into sorted array data[0:i-1];
int j = 0;
int iv = data[i];
for (j = i-1; j >= 0 && data[j]>iv; j--){
data[j + 1] = data[j];
}
data[j + 1] = iv;
}
return 0;
}
这个算法原理是,依次将第 i 个数插入在 之前排好序的 i 大小的队列中,复杂度为 O(n^2)。
2、冒泡排序
int sort_up(int *data, int n){
for (int i = 0; i<n; i++){
for (int j = i + 1; j<n; j++){
if (data[i] > data[j]){
int temp = data[i];
data[i] = data[j];
data[j] = temp;
}
}
}
return 0;
}
这个算法原理是将余下的数依次上翻两两比较,从而将最小的数浮到顶部。1和2的算法简单,适合于小规模数据,由于其代码简短的优点,即使复杂度为O(n^2),效率依然不错。
3、快速排序
int sort_quick(int *data, int p, int q){
if (p >= q){
return 0;
}
int sep = data[q];
int d = p - 1;
for (int i = p; i<q; i++){
if (data[i] < sep){
d++;
int temp = data[i];
data[i] = data[d];
data[d] = temp;
}
}
d++;
data[q] = data[d];
data[d] = sep;
sort_quick(data, p, d - 1);
sort_quick(data, d + 1, q);
return 0;
}
这个算法原理是用尾部数将数据分成大小两段,再分别递归。复杂度为 O(n logn),但是在极端情况下,比如数据已经排好序的情况下,复杂度退化为 O(n^2)。
4、计数排序
//k = max(data(n))
int sort_count(int* data, int k, int n){
int *a = new int[k];
int *b = new int[n];
memset(a, 0, k*sizeof(int));
memset(b, 0, n*sizeof(int));
for (int i = 0; i < n; i++){
a[data[i]] ++;
}
for (int i = 1; i < k; i++){
a[i] = a[i - 1] + a[i];
}
for (int i = n-1; i >= 0; i--){
b[a[data[i]]-1] = data[i];
a[data[i]]--;
}
memcpy(data, b, n*sizeof(int));
delete[]a;
delete[]b;
return 0;
}
计数排序时间复杂度为O(k+n),因为他并不是比较排序算法,所以下界优于 O( nlogn )。一个优点,它是稳定的。一个缺点,它不是原地的。
5、基数排序
技术排序利用了计数排序稳定性的特点。
SORT_RADIX(A, d)
for i<-1, d
do use a stable sort to sort array A on digit i
基数排序时间复杂度为O(d*(n+k))。
6、桶排序
当数据的输入符合均匀分布时,可以以线性期望时间运行。
BUCKET_SORT(A, n)
for i in 1 to n:
do insert A[i] into list B[ |n*A[i]|下界 ]
for i in 0 to n-1:
do sort B[i] with insertion sort;
concatenate the list B[0], B[1], ..., B[n-1] together in order
时间复杂度为O(n* (2-1/n)),即O(n).
7、归并排序
归并排序是一种分治合并策略。
int sort_merge(int *a, int p, int r){
if (p >= r){
return 0;
}
int q = (p + r + 1) / 2;
int *b1 = new int[q - p + 1];
int *b2 = new int[r - q + 1 + 1];
memcpy(b1, a, (q - p)*sizeof(int));
memcpy(b2, a + q, (r - q + 1)*sizeof(int));
sort_merge(b1, 0, q - p - 1);
sort_merge(b2, 0, r - q);
b1[q - p] = b2[r - q + 1] = 0x7fffffff;
for (int k = p, i = 0, j = 0; k <= r; k++){
if (b1[i] < b2[j]){
a[k] = b1[i];
i++;
}
else{
a[k] = b2[j];
j++;
}
}
delete[]b1;
delete[]b2;
return 0;
}
归并排序时间复杂度为O(nlogn)
8、堆排序
//a[1:n]
int max_heap(int *a, int i, int n){
while (i < n){
if (a[i] < a[i * 2] && i * 2 == n){
swap(a[i], a[i * 2]);
break;
}
else if (a[i] < a[i * 2] && i * 2 + 1 <= n && a[i * 2] >= a[i * 2 + 1]) {
swap(a[i], a[i * 2]);
i = i * 2;
}
else if (a[i]<a[i * 2 + 1] && i * 2 + 1 <= n && a[i * 2 + 1]>a[i * 2]){
swap (a[i], a[i * 2 + 1]);
i = i * 2 + 1;
}
else{
break;
}
}
return 0;
}
int bulid_max_heap(int *a, int n){
for (int i = n / 2; i >= 1; i--){
max_heap(a, i, n);
}
return 0;
}
int heap_sort(int *a, int n){
bulid_max_heap(a, n);
for (int i = n; i >= 1; i--){
swap (a[i], a[1]);
max_heap(a, 1, i - 1);
}
return 0;
}
堆排序时间复杂度为O(nlogn)。