集合num = {a1,a2,...an},将其按非递减排序。
1、插入排序
//increasing
void insert_sort(int num[],int n)
{
for(int i = 1;i < n;i++)
{
int j = i,tmp = num[i];
for(;j && num[j - 1] > tmp;j--)
num[j] = num[j - 1];
num[j] = tmp;
}
}
最好情况:num[]排序前就是非递减,那么时间复杂度为O(n)
最坏情况:num[]排序前为非递增,那么时间复杂度为O(n^2)
所以插入排序时间复杂度为O(n^2),空间复杂度为O(1)
2、选择排序
void select_sort(int num[],int n)
{
for(int i = 0;i < n;i++)
for(int j = i + 1;j < n;j++)
if(num[i] > num[j])
swap(num[i],num[j]);
}
最好情况:num[]排序前就是非递减,那么时间复杂度为O(n)
最坏情况:num[]排序前为非递增,那么时间复杂度为O(n^2)
所以选择排序时间复杂度为O(n^2),空间复杂度为O(1)
3、冒泡排序
void bubble_sort(int num[],int n)
{
for(int i = 0;i < n;i++)
for(int j = n - 1;j >= i + 1;j--)
if(num[j - 1] > num[j])
swap(num[j],num[j - 1]);
}
最好情况:num[]排序前就是非递减,那么时间复杂度为O(n)
最坏情况:num[]排序前为非递增,那么时间复杂度为O(n^2)
所以冒泡排序时间复杂度为O(n^2),空间复杂度为O(1)
4、归并排序
/*
* p <= q < r
* num[p...q]及num[q + 1...r]已经有序
*/
void merge(int num[],int p,int q,int r)
{
int tmp[N],n,i,j;
n = 0;
i = p,j = q + 1;
while(i <= q && j <= r)
if(num[i] < num[j])
tmp[n++] = num[i++];
else
tmp[n++] = num[j++];
while(i <= q)
tmp[n++] = num[i++];
while(j <= r)
tmp[n++] = num[j++];
for(i = 0;i < n;i++)
num[i + p] = tmp[i];
}
/*
* 将num[p...q]排序
*/
void merge_sort(int num[],int p,int r)
{
if(p < r)
{
int q = (p + r) / 2;
merge_sort(num,p,q);
merge_sort(num,q + 1,r);
merge(num,p,q,r);
}
}
因为均为num[]已经有序与否,merge()函数一定是要执行的,它的时间复杂度为O(n),所以合并排序最好与最坏的时间复杂度均为为O(nlgn),合并排序的时间复杂度就为O(nlgn),它的空间复杂度为O(n)。
5、堆排序
int left(int x)
{
return x << 1;
}
int right(int x)
{
return (x << 1) + 1;
}
int parent(int x)
{
return x >> 1;
}
/*
*递归式:O(lgn)
*/
/*
void max_heapify(int num[],int i,int heapsize)
{
int r,l,lmax;
l = left(i),r = right(i);
if(l <= heapsize && num[i] < num[l])
lmax = l;
else
lmax = i;
if(r <= heapsize && num[lmax] < num[r])
lmax = r;
if(lmax != i)
{
swap(num[i],num[lmax]);
max_heapify(num,lmax,heapsize);
}
}
*/
void max_heapify(int num[],int i,int heapsize)
{
int r,l,lmin,res;
res = num[i];
while(1)
{
l = left(i),r = right(i);
if(l > heapsize)//无左结点,即此时i为叶子结点
break;
lmin = l;
if(r <= heapsize && num[lmin] < num[r])
lmin = r;
if(res >= num[lmin])//res比两棵子树的根都要小,就不需要调整了
break;
num[i] = num[lmin];
i = lmin;
}
num[i] = res;
}
/*
* num[n / 2]为倒数第一个非叶子结点
*/
void build_max_heap(int num[],int n)
{
for(int i = n / 2;i >= 1;i--)
max_heapify(num,i,n);
}
void heap_sort(int num[],int n)
{
int heapsize = n;
build_max_heap(num,n);
for(int i = n;i >= 2;i--)
{
swap(num[1],num[heapsize]);
max_heapify(num,1,--heapsize);
}
}
max_heapify()函数的时间复杂度为O(lgn),堆排序最好与最好坏情况的时间复杂度均为O(nlgn),因为是原地排序,所以空间复杂度为O(1)。
6、快速排序
int partition(int num[],int low,int high)
{
int key = num[low];
while(low < high)
{
while(low < high && num[high] > key)
high--;
num[low] = num[high];
while(low < high && num[low] <= key)
low++;
num[high] = num[low];
}
num[low] = key;
return low;
}
void quick_sort(int num[],int p,int r)
{
if(p < r)
{
int q = partition(num,p,r);
quick_sort(num,p,q - 1);
quick_sort(num,q + 1,r);
}
}
快速排序也应用了分治的思想,在partition()函数中每次都将第一个数作为关键字比较。快速排序的最坏情况是当num[]已经是非递减有序的时候,此时 partition()函数每次都要执行high - low次,所以此时时间复杂度退化为O(n^2),而快排一般的复杂度为O(nlgn),空间复杂度为O(lgn),即递归时栈所用。
7、快速排序的随机化版本
基本的快速排序当数据基本有序时,时间复杂度退化为O(n^2),其实这里我们可以进行一个随机优化。即partition()函数在取关键字key时,并不是取第一个,而是从num[low...high]中随机取一个作为关键字,这样就可以避免最坏情况。
int random_partition(int num[],int low,int high)
{
if(low < high)
{
srand((unsigned)time(NULL));
int tmp = rand() % (high - low)+ low;
swap(num[low],num[tmp]);
}
return partition(num,low,high);
}
void random_quick_sort(int num[],int p,int r)
{
if(p < r)
{
int q = random_partition(num,p,r);
random_quick_sort(num,p,q - 1);
random_quick_sort(num,q + 1,r);
}
}
8、计数排序
计数排序假设n个输入元素中的每一个元素都是介于0到k之间的整数,k为某个整数。运用hash法,计算出每个元素在所有元素中的位置,最后直接放入到数组相应的位置上。
/*
* 要排序的数在num[]中,都是在0~1000之间,排完序后存入num1
*/
void count_sort(int num[],int num1[],int n)
{
int tmp[1001];
memset(tmp,0,sizeof(tmp));
for(int i = 0;i < n;i++)
tmp[num[i]]++;
for(int i = 1;i < 1001;i++)
tmp[i] += tmp[i - 1];
for(int i = 0;i < n;i++)
num1[--tmp[num[i]]] = num[i];
}
计数排序不是比较排序算法,它的 时间复杂度为O(k + n),当k = O(n)时,它的复杂度即为O(n)。空间复杂度为O(k + n)。
9、桶排序
跟计数排序一样,桶排序也对输入亻了某种假设。桶排序假设输入由一个随机过程产生,该过程将元素均匀而独立地分页在区间[0,1)上。并把[0,1)区间分成n个大小相同的子区间,或称桶。然后将n个输入数据分页到各个桶当中去。然后对各个桶中的数据进行排序,最后按次序把各桶中的数据列出来即可。
void bucket_sort(double num[],int n)
{
double tmp[100][100];
memset(tmp,0,sizeof(tmp));
for(int i = 0;i < n;i++)
{
int t = int(floor(num[i] * n));
tmp[t][0] += 1;
tmp[t][(int)tmp[t][0]] = num[i];
}
for(int i = 0;i < n;i++)
if(tmp[i][0])
{
for(int j = 1;j <= (int)tmp[i][0];j++)
for(int k = j + 1;k <= (int)tmp[i][0];k++)
if(tmp[i][j] > tmp[i][k])
swap(tmp[i][j],tmp[i][k]);
}
int t = 0;
for(int i = 0;i < n;i++)
for(int j = 1;j <= (int)tmp[i][0];j++)
num[t++] = tmp[i][j];
for(int i = 0;i < n;i++)
printf("%.3lf ",num[i]);
puts("");
}
桶排序关键时间关键就在第2个for循环,《算法导论》上讲,桶排序的期望时间是线性的。
各排序方法时间效率比较(时间单位ms)
1、20次每次随机生成10000组数据
数据 | 插入排序 | 选择排序 | 冒泡排序 | 归并排序 | 堆排序 | 快速排序 | 计数排序 |
1 | 196 | 606 | 937 | 4 | 3 | 3 | 0 |
2 | 196 | 607 | 922 | 3 | 3 | 3 | 0 |
3 | 170 | 609 | 949 | 3 | 3 | 3 | 0 |
4 | 178 | 618 | 944 | 3 | 3 | 3 | 0 |
5 | 172 | 622 | 964 | 3 | 3 | 3 | 0 |
6 | 176 | 653 | 977 | 4 | 3 | 3 | 0 |
7 | 177 | 615 | 956 | 3 | 3 | 3 | 0 |
8 | 177 | 625 | 954 | 4 | 3 | 2 | 1 |
9 | 173 | 621 | 955 | 4 | 3 | 2 | 0 |
10 | 176 | 630 | 953 | 4 | 3 | 2 | 0 |
11 | 175 | 620 | 955 | 3 | 3 | 2 | 1 |
12 | 174 | 632 | 952 | 4 | 3 | 2 | 1 |
13 | 173 | 606 | 948 | 3 | 4 | 2 | 0 |
14 | 168 | 613 | 958 | 3 | 3 | 3 | 0 |
15 | 180 | 629 | 955 | 3 | 3 | 4 | 0 |
16 | 178 | 644 | 954 | 3 | 3 | 3 | 0 |
17 | 173 | 606 | 951 | 4 | 3 | 2 | 0 |
18 | 171 | 614 | 941 | 4 | 3 | 2 | 0 |
19 | 173 | 612 | 953 | 3 | 3 | 2 | 0 |
20 | 176 | 622 | 946 | 3 | 4 | 2 | 0 |
平均 | 176.60 | 620.20 | 951.20 | 3.40 | 3.10 | 2.55 | 0.00 |
2、20次每次随机生成100000组数据
数据 | 插入排序 | 选择排序 | 冒泡排序 | 归并排序 | 堆排序 | 快速排序 | 计数排序 |
1 | 17534 | 50206 | 95927 | 39 | 41 | 32 | 3 |
2 | 17389 | 50172 | 95039 | 40 | 48 | 29 | 3 |
3 | 17754 | 49930 | 94944 | 40 | 42 | 31 | 2 |
4 | 17380 | 49762 | 94753 | 39 | 40 | 31 | 2 |
5 | 17240 | 49389 | 95831 | 40 | 41 | 30 | 3 |
6 | 17927 | 50816 | 95111 | 39 | 40 | 29 | 2 |
7 | 17132 | 49336 | 95079 | 39 | 40 | 29 | 3 |
8 | 17166 | 49378 | 95925 | 39 | 40 | 33 | 3 |
9 | 17224 | 49202 | 97115 | 39 | 40 | 29 | 2 |
10 | 17881 | 50673 | 97334 | 39 | 40 | 29 | 3 |
11 | 17715 | 52176 | 96918 | 39 | 41 | 20 | 2 |
12 | 17392 | 50451 | 96250 | 38 | 40 | 29 | 2 |
13 | 17448 | 49394 | 95954 | 40 | 42 | 31 | 2 |
14 | 17151 | 49347 | 96585 | 39 | 40 | 29 | 2 |
15 | 17638 | 50890 | 95298 | 39 | 40 | 29 | 2 |
16 | 17575 | 49348 | 94430 | 39 | 40 | 29 | 2 |
17 | 17188 | 49233 | 94149 | 40 | 44 | 30 | 3 |
18 | 17188 | 50872 | 96410 | 42 | 41 | 29 | 3 |
19 | 17462 | 50807 | 96933 | 40 | 41 | 32 | 3 |
20 | 17619 | 50601 | 96671 | 39 | 40 | 29 | 2 |
平均 | 17450.15 | 50099.15 | 95832.80 | 39.4 | 41.05 | 29.45 | 2.45 |
从表中可以看出,计数排序的平均时间是最好的,当然,计数排序需要有数据形式的限制。除了计数排序,快速排序的平均时间最好,而冒泡排序最差。