各个排序具体的思路在网上比比皆是,在此之作简要概括。
快速排序:
1、在待排序的对象中选择一个标志位(通常选择第一个元素),根据其他元素与其相比较的结果,将其他元素分为两集合,大于标准位的一个集合,小于标志位的一个集合,
2、再分别对两个集合递归进行步骤一,直到每个集合只有一个元素为止。
冒泡排序:
1、依次比较每个元素与其后面的元素,如果后面的元素小,则交换。这样将所以的元素比较完以便后,最后的元素一定是最大的。
2、在余下的元素,重复进行步骤1,直到余下的元素个数为1,至此所有元素已有序,此方法需要两层嵌套循环,效率较低。
选择排序:
1、依次比较每个元素与其后面的元素,记录最大元素出现的位置,当比较完一遍后,将最大位置的元素,与最后一个元素交换,这样能保证最后一个元素最大。
2、重复对余下元素进行相同操作,直到余下元素个数为1,至此所以元素已有序。此方法同样需要两层嵌套,效率较低。
归并排序:
1、先让队列局部有序,再逐渐扩大局部的范围。例如先让每两个元素之间有序列,再让扩大有序的范围,让每四个元素间有序,依次类推。最后实现整个队列有序。
基数排序:
1、先统计所有元素一共占有几位,比如元素最大位数到百位。
2、依次访问元素,将元素按个位分类,0-9不同类,此处可用链表实现
3、在步骤2的基础上,从0到9依次将元素按十位分类,此处若用带指针的节点,比较方便。
4、依次执行N次,比较到元素的最高位,此时有10个链表0-9,从链表头到链表尾,依次有序,顺序赋值给相应元素即可。
堆排序:
1、初始化,将所以元素构造成一个大值堆,此时数组的第0个元素即为最大值。
2、将第0个元素与最后一个元素交换,此时最后一个元素为最大,再将最后一个元素的位置剔除出堆。此事由于修改了第0个元素,破坏了堆的特性,再对堆进行调整。
3、重复步骤2,直到堆内剩余1个元素,排序结束,元素有序。
各排序算法时间复杂度及排序排序N个数所有时间:
| 种类 |
平均时间复杂度 |
N=100 |
1000 | 10000 | 10000 | 1000000 | 时间复杂注释 |
| 冒泡 |
O(n2) 稳定 |
77(未特殊标注,单位:usec) |
7696 |
404809 |
39764791 |
4084s |
比较次数: n+(n-1)+..+1=O(n2) |
| 选择 |
O(n2) 稳定 | 43 |
3340 |
171065 |
16244589 |
1709s |
比较次数: n+(n-1)+..+1=O(n2) |
| 快速 |
O(n*log2n) 不稳定 |
17 |
223 |
1621 |
25543 |
257401 |
将元素分为log2n层,每层比较n次 |
| 归并 |
O(n*log2n) 稳定 | 29 |
364 |
2956 |
40223 |
385268 |
与快速类似 |
| 基数 |
O(d*(n+r)) d是位数,如12345,则d = 5 r是链表数0-9,r=10 稳定 |
131 |
387 |
2913 |
40868 |
760370 |
进行d次排序,每次都需要移动n个数,已经对r个头指针的移动,所以时间复杂度O(d*(n+r)) |
| 堆 |
O(n*log2n)不稳定 | 26 |
346 |
3835 |
37392 |
510834 |
由于是采用二叉树,与快速类似 |
注:以上测试结果均未进行多次统计求平均值的过程,只是为了比较当排序数量按指数增长时,各个排序算法的性能的横向和纵向比较关系。
由结果分析可得,随着待排序元素个数随着呈指数增长,冒泡排序和选择排序的时间呈平方倍数增长。而其他排序增长相对平缓。所以测试结果基本与时间复杂度相符。
空间复杂度:
| 种类 |
空间复杂度 |
空间注释 |
| 冒泡 |
O(1) | 只需要一个临时变量,用于元素交换 |
| 选择 |
O(1) | 只需要一个临时变量,用于元素交换 |
| 快速 |
O(log2n) |
O(log2n)为递归的深度,此处空间复杂度由栈深度决定 |
| 归并 |
O(n) | 归并排序,需要一个等长的数组,用于存放临时变量 |
| 基数 |
O(n + r) | 若用链表来实现,需要指针作为额外空间,个数也是n,此外需要r个链表头(自己理解,不知道是否正确) |
| 堆 |
O(1) | 只需要一个临时变量,用于元素交换 |
下面贴出具体测试代码:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
#define NUM 1000000
int number[NUM];
// 初始化NUM个数
void initNumber()
{
int i = 0;
for (i = 0; i < NUM;++i)
number[i] = i;
}
// 打乱NUM个数
void upsetNumber()
{
int i = 0, r1, r2, temp;
srand((int)time(0));
for (i = 0;i < NUM;++i)
{
r1 = rand() % NUM;
r2 = rand() % NUM;
temp = number[r1];
number[r1] = number[r2];
number[r2] = temp;
}
}
// 输入当前数组中元素
void outputNumber()
{
int i = 0;
for (i = 0; i< NUM;++i)
printf(((i + 1)%20 == 0)?"%5d\n":"%5d", number[i]);
printf("=======================================>\n");
}
// 快速排序:
// 参数 front,需要比较第一个元素的位置,behind需要比较最后一个元素位置
void quickSort(int front, int behind)
{
int f = front + 1, b = behind, temp;
if (front >= behind)
return;
while (f != b)
{
if (number[f] > number[front])
{
temp = number[f];
number[f] = number[b];
number[b] = temp;
--b;
}
else
{
++f;
}
}
// 将标志位放到数组中间
if (number[f] > number[front])
--f;
temp = number[front];
number[front] = number[f];
number[f] = temp;
// 分别对不同分组递归进行快速排序
quickSort(front, f - 1);
quickSort(f + 1, behind);
}
// 冒泡排序
void bubbleSort()
{
int i = 0,j = 0, temp;
for (i = 0;i < NUM;++i)
for (j = 0;j < NUM - 1 - i;++j)
{
if (number[j] > number[j + 1])
{
temp = number[j];
number[j] = number[j + 1];
number[j + 1] = temp;
}
}
}
// 选择排序
void selectSort()
{
int min, i, temp, j;
for (i = 0;i < NUM;++i)
{
for (min = i, j = i + 1;j < NUM;++j)
{
if (number[j] < number[min])
{
min = j;
}
}
temp = number[min];
number[min] = number[i];
number[i] =temp;
}
}
// 归并排序子函数
// front 已有序的第一个序列的起点,mid 已有序第二个序列的起点,behind 已有序第二个序列的终点
void mergechild(int front, int mid, int behind)
{
int n = behind - front;
int temp[n], a, b ,c;
for (a = front, b = mid, c = 0; c < n;++c)
{
if (a == mid )
{
temp[c] = number[b++];
continue;
}
if (b == behind)
{
temp[c] = number[a++];
continue;
}
temp[c] = (number[a] < number[b])?number[a++]:number[b++];
}
for (c = 0; c < n;++c)
number[front + c] = temp[c];
}
// 归并排序
void merge()
{
int i = 0, j = 0;
// 呈2倍扩大,有序数组的大小
for (i = 1; i < NUM; i *= 2)
{
for (j = 0;j < NUM;j += 2*i)
{
mergechild(j, (j + i < NUM)?(j + i):NUM, (j + 2*i < NUM)?(j+2*i):NUM);
}
}
}
// 基数排序
void LSD()
{
struct node
{
int value;
struct node* next;
};
// 由于需要用到计数参数较多,具体参数在每个程序块内作用可能不同,未一一注释
int num = NUM, i = 1, j, k, m = 10;
while (num /= 10)
i *= 10;
struct node* dig[10], *tail[10], *temp[10], *p;
for (j = 0;j < 10;++j)
{
dig[j] = NULL;
temp[j] = NULL;
tail[j] = NULL;
}
// 将元素放在以个位分类的10个链表中
for (j = 0;j < NUM;++j)
{
p = (struct node*)malloc(sizeof(struct node));
p->value = number[j];
p->next = NULL;
k = number[j] - number[j]/10*10;
if (dig[k] == NULL)
{
dig[k] = tail[k] = p;
}
else
{
tail[k]->next = p;
tail[k] = p;
}
}
// 依次再以十位, 百位分类
while (i >= m)
{
for (k = 0;k < 10;++k)
{
int l;
while ((p = dig[k]))
{
dig[k] = dig[k]->next;
l = p->value%m/(m/10);
p->next = NULL;
if (temp[l] == NULL)
{
temp[l] = tail[l] = p;
}
else
{
tail[l]->next = p;
tail[l] = p;
}
}
}
for (k = 0;k < 10;++k)
{
dig[k] = temp[k];
temp[k] = NULL;
}
m *= 10;
}
// 将已排序好的元素, 复制到数组中
for (k = 0, i = 0;k < 10;++k)
{
while ((p = dig[k]))
{
dig[k] = dig[k]->next;
number[i++] = p->value;
free(p);
}
}
}
// 调整从位置p,到位置i的堆的结构
void heap_sort(int p, int i)
{
int k, j;
while(1)
{
if ((2*p+1) > i)
break;
else
{
if ((2*p+1) == i)
k = 2*p+1;
else
k = (number[2*p + 1]>number[2*p + 2])?(2*p + 1):(2*p + 2);
if (number[k] > number[p])
{
j = number[k];
number[k] = number[p];
number[p] = j;
p = k;
}
else
break;
}
}
}
// 堆排序
void heap()
{
int i, j;
// 分别替换第一个元素到结尾,再重新调整堆的结构
for(i = NUM - 1;i >= 0;i--)
{
j = number[0];
number[0] = number[i];
number[i] = j;
heap_sort(0, i - 1);
}
}
// 堆的初始化,建立一个大值堆
void heap_init()
{
int i;
for (i = NUM/2;i >= 0;i--)
// 从堆的最后一个含有子节点的元素开始调整堆,依次向上调整
heap_sort(i, NUM - 1);
}
int main(int argc, char** argv)
{
struct timeval beg, end;
initNumber();
outputNumber();
upsetNumber();
outputNumber();
gettimeofday(&beg, NULL);
//heap_init();
//heap();
//quickSort(0, NUM - 1);
bubbleSort();
//selectSort();
//merge();
//LSD();
gettimeofday(&end, NULL);
outputNumber();
printf("Time: %ld\n", (end.tv_sec - beg.tv_sec)*1000000 + (end.tv_usec - beg.tv_usec));
return 0;
}
原文:http://my.oschina.net/u/2313065/blog/522554