【算法导论】第8章线性时间排序_计数排序、基数排序、桶排序

时间:2022-10-11 17:08:36


 

1、问题引入

  之前的堆排序、快速排序都是基于输入元素间的比较,这类排序叫做比较排序,对于n个元素的比较排序可以证明其在最坏情况下都要用O(nlgn)次比较来进行排序,本节中将讨论三种以线性时间运行的算法:计数排序、基数排序、桶排序,这些算法都用非比较的一些操作来确定排序顺序。

 



 

2、算法讨论

2.1 计数排序

  计数排序假设n个输入元素中的每一个都是介于0到k之间的整数(k为某个整数),其基本思想是:对于每一个输入元素x,确定出小于x的元素个数。有了这一信息,就可以吧x直接放到它在最终输出数组中的位置上,例如,如果有17个元素小于x,则x就属于第18个输出位置。

  在计数排序中,假定输入数组是A[1...n],length[A]=n.另外还需要两个数组:存放排序结果的B[1...n],以及提供临时存储区的C[0...k]。

  具体实现代码如下:

【算法导论】第8章线性时间排序_计数排序、基数排序、桶排序【算法导论】第8章线性时间排序_计数排序、基数排序、桶排序View Code
 1 #include<stdio.h>
2 #include<malloc.h>
3 void countingsort(int a[],int b[],int n,int k);
4 void countingsort(int a[],int b[],int n,int k)
5 {
6 int i;
7 int *c=(int*)malloc(sizeof(int)*(k+1));
8 //利用三个for循环实现c[i]的初始化
9 for(i=0;i<=k;i++)
10 c[i]=0;
11 for(i=0;i<n;i++)
12 c[a[i]]+=1;
13 for(i=1;i<=k;i++)
14 c[i]=c[i]+c[i-1];
15 //for(i=0;i<=k;i++)
16 // printf("%d ",c[i]);
17
18 for(int j=n-1;j>=0;j--)//注意a[],b[]数组下标开始顺序
19 {
20 b[c[a[j]]-1]=a[j];
21 c[a[j]]-=1;
22 }
23 }
24 void main()
25 {
26 int k=5,n=8;
27 int a[8]={2,5,3,0,2,3,0,3};
28 int b[8];
29
30 printf("before sort:\n");
31 for(int j=0;j<8;j++)
32 printf("%d ",a[j]);
33 countingsort(a,b,n,k);
34 printf("after sort:\n");
35 for(int i=0;i<8;i++)
36 printf("%d ",b[i]);
37 }

 

 



 

2.2 基数排序

  基数排序是借助“分配”和“收集”两种操作对单逻辑关键字进行排序的一种内部排序方法,例如关键字是数值,且其值都在0《k《999之间,则可以把每一个十进制数字看成一个关键字,即认为k由3个关键字(k1,k2,k3)组成,其中k1为个位数,k2为十位数,k3为百位数,例如三位的数值排序,可以设radix(=10)个队列,d=3;按照LSD进行排序,只要从最低位关键字起,按照关键字的不同值将序列中记录“分配”到RADIX队列后再“收集”,如此重复d次,这种方法就是基数排序。其中“基”是指radix的取值。

  本次实现中采用链队列:

  基本算法过程如下:

【算法导论】第8章线性时间排序_计数排序、基数排序、桶排序

  具体算法如上图所示,下面给出具体实现代码:

【算法导论】第8章线性时间排序_计数排序、基数排序、桶排序【算法导论】第8章线性时间排序_计数排序、基数排序、桶排序View Code
 1 #include"queue.h"
2 #include<stdio.h>
3 #define keysize 3//关键字位数为3
4 #define radix 10//基数为10
5 void radixsort(int r[],int n);
6 void distribute(int r[],linkqueue b[],int j,int n);
7 void collect(int r[],linkqueue b[]);
8 void radixsort(int r[],int n)
9 {
10 int i;
11 linkqueue b[radix];
12 for(i=0;i<radix;i++)
13 initqueue(b[i]);
14 for(i=keysize-1;i>=0;i--)
15 {
16 distribute(r,b,i,n);
17 collect(r,b);
18 }
19 }
20 void distribute(int r[],linkqueue b[],int j,int n)
21 {
22 int i,k,t;
23 j=keysize-j;
24 for(i=0;i<n;i++)
25 {
26 k=r[i];
27 for(t=1;t<j;t++)k=k/10;
28 k=k%10;
29 enqueue(b[k],r[i]);
30 }
31 }
32 void collect(int r[],linkqueue b[])
33 {
34 int e,i=0,j;
35 for(j=0;j<radix;j++)
36 while(!queueempty(b[j]))
37 {
38 dequeue(b[j],e);
39 r[i++]=e;
40 }
41 }
42 void main()
43 {
44 int i,n=7;
45 int r[7]={329,457,657,839,436,720,355};
46 radixsort(r,n);
47 for(i=0;i<7;i++)
48 printf("%d ",r[i]);
49 }

 



 

2.3 桶排序

  当桶排序的输入符合均匀分布时,即可以以线性期望时间运行。与计数排序类似,桶排序也对输入作了某种假设,因而运行得很快。具体来说,计数排序假设输入是由一个小范围内的整数构成,而桶排序则假设输入由一个随机过程产生,该过程将元素均匀而独立的分布在区间[0,1)上。

  桶排序的思想就死把区间[0,1)划分成n个相同大小的子区间,或称桶。然后,将n个输入数分布到各个桶中去。因为输入数均匀且独立分布在[0,1)上,所以,一般不会有很多数落在一个桶中的情况。为了得到结果,先对各个桶中的数进行排序,然后按次序把各桶中的元素列出来即可。

  在桶排序算法的代码中,假设输入的是一个含n个元素的数组A,且每个元素满足0《A[i]<1.另外,还需要一个辅助数组B[0..n-1]来存放链表(桶),并且可以用某种机制来维护这些表。

  具体的实现代如下:

【算法导论】第8章线性时间排序_计数排序、基数排序、桶排序【算法导论】第8章线性时间排序_计数排序、基数排序、桶排序View Code
 1 #include<stdio.h>
2 #include<stdlib.h>
3 struct keyNum
4 {
5 double key;//关键字
6 struct keyNum *next;
7 };
8 struct keyNum*hash[100];
9 struct keyNum* insertHash(struct keyNum*,double);//关键字插入链表
10 void print(struct keyNum*);//打印链表
11 void main()
12 {
13 int i,k,n=10,num=10;//n表示划分的桶的个数,num表示输入元素个数。
14 double a[10]={0.78, 0.17, 0.39, 0.26, 0.72, 0.94, 0.21, 0.12, 0.23, 0.68 };
15 struct keyNum *head=NULL;
16 for(i=0;i<num;i++)
17 hash[i]=NULL;
18
19 for(i=0;i<num;i++)
20 {
21 k=int(a[i]*10);
22 hash[k]=insertHash(hash[k],a[i]);//将关键字k插入到哈希值为m的链表中
23 }
24 printf("排序前为:\n");
25 for(i=0;i<num;i++)
26 printf(" %f",a[i]);
27 printf("\n");
28
29 printf("排序结果为:\n");
30 for(i=0;i<num;i++)
31 print(hash[i]);
32 }
33 struct keyNum * insertHash(struct keyNum*head,double m)
34 {
35 struct keyNum *p0,*p1,*p2,*temp;
36 temp=(struct keyNum*)malloc(sizeof(struct keyNum));
37 temp->key=m;
38 p1=head;
39 p0=temp;//要插入的节点(值为m);
40 if(head==NULL)//1,原来的链表为空,插入到head后
41 {
42 head=p0;
43 p0->next=NULL;
44 }
45 else//原来的链表不为空
46 {
47 while((p0->key>p1->key)&&(p1->next!=NULL))//移动到适当位置
48 {
49 p2=p1;
50 p1=p1->next;
51 }
52 if(p0->key<=p1->key)
53 {
54 if(head==p1)head=p0;//2,插入到第一个节点之前
55 else p2->next=p0;//3,插入到p2指向的节点之后
56 p0->next=p1;
57 }
58 else//4,插入到结尾处
59 {
60 p1->next=p0;
61 p0->next=NULL;
62 }
63 }
64 return(head);
65 }
66
67 void print(struct keyNum*head)//打印链表head
68 {
69 struct keyNum*p;
70 p=head;
71 if(head!=NULL)
72 {
73 do
74 {
75 printf(" %f",p->key);
76 p=p->next;
77 }while(p!=NULL);
78 }
79 else{}
80 }

 

3、小结
  具体的代码实现参见算法就可以很容易完成,不过没有给出具体的时间复杂度分析。。

4、参考文献:

(1)算法导论

(2)数据结构

(3)http://baike.baidu.com/view/1170573.htm