堆排序

对《大话数据结构》P396~P406—堆排序,进行了自己的理解并完善了代码。

一、堆排序之前需要掌握以下知识

1、完全二叉树

例如下图这样的,编号出现空档,就不是完全二叉树。

2、完全二叉树的性质

这三个性质中最重要的是1,看图就很好理解,即如果i>1,i的双亲节点是i/2。

3、堆

4、堆的性质

这个公式的意思是:

1、对于大顶堆,双亲大于左右孩子;对于小顶堆,双亲小于左右孩子。

2、对于有左右孩子的双亲,双亲编号肯定≤n/2。

 

二、掌握以上四点后,可以讲堆排序的原理。

先看书上的讲解,第一次看的时候有点蒙,看懂堆排序原理之后再看这个定义,发现写得还挺好的。

解读一下思想:

1、先把序列构成一个大顶堆(双亲大于等于左右孩子)。对于上面的图,序列有9个数,那么双亲节点就是1,2,3,4。

A、4,8,9三个节点中,保证节点4最大。

B、3,6,7三个节点中,保证节点3最大。

C、2,4,5三个节点中,保证节点2最大。

D、1,2,3三个节点中,保证节点1最大。

这时候,序列是大顶堆。而且,根节点1肯定是最大的数。

2、把最大的节点1和节点9交换,这样节点9就归位了。然后让节点1~节点8,再构成一个大顶堆。

3、把最大的节点1(这时候只有8个节点,节点9已经归位,可以不考虑了)和节点8(相当于最后一个节点)交换,这样节点8就归位了。然后让节点1~7,再构成一个大顶堆。

............

4、最后把最大的节点1和节点2交换,这样节点2就归位了。结束排序。

举个例子:

代码和解释如下(VS2012测试通过):

 1 #include <iostream>
 2 using namespace std;
 3 #define MAXSIZE 9//用于要排序数组个数最大值,可根据需要修改
 4 
 5 //排序用的顺序表结构
 6 typedef struct
 7 {
 8     int r[MAXSIZE+1];//定义一个数组,用于存储要排序的数,r[0]作为临时变量
 9     int length;//用于记录顺序表的长度
10 }SqList;
11 
12 //排序表的初始化
13 SqList *InitSqList(SqList *L)
14 {
15     L=new SqList;
16     L->length=MAXSIZE;//本例中长度是9
17     cout<<"input list"<<endl;
18     for(int i=1;i<=L->length;i++)
19         cin>>L->r[i];
20     return L;
21 }
22 
23 //数组遍历输出
24 void PrintSqList(SqList *L)
25 {
26     for(int i=1;i<=L->length;i++)
27         cout<<L->r[i]<<" ";
28     cout<<endl;
29 }
30 
31 //交换r[i]和r[j]
32 void swap(SqList *L,int i,int j)
33 {
34     int temp=L->r[i];
35     L->r[i]=L->r[j];
36     L->r[j]=temp;
37 }
38 
39 //使L->r[s,..,m]成为一个大顶堆
40 void HeapAdjust(SqList *L,int s,int m)
41 { 
42     int temp,j;
43     temp=L->r[s];//把要替换的r[s]值放入临时变量
44     for(j=2*s;j<=m;j*=2)//从左孩子开始寻找放入r[s]的最大值
45     {
46         if(j<m && L->r[j]<L->r[j+1])
47             ++j;//如果左孩子小于右孩子,把j置为右孩子的下标
48         if(temp>=L->r[j])
49             break;//如果r[s]本身比左右孩子都大,不用替换,直接退出该双亲结点的循环
50         L->r[s]=L->r[j];//如果没有退出循环,说明左右孩子中有一个大于双亲,把孩子的值给双亲
51         s=j;//把孩子的下标给s,方便下面的进行r[s]=temp
52     }
53     L->r[s]=temp;//把双亲的值交换给孩子,如果没有交换双亲和孩子,这一步左右是相等的值,也没有关系
54     cout<<"after one sort"<<endl;
55     PrintSqList(L);
56 }
57 
58 //对顺序表L进行堆排序
59 void HeapSort(SqList *L)
60 {
61     int i;
62     for(i=L->length/2;i>0;i--)//把L构建成一个大根堆,i=4,3,2,1
63          HeapAdjust(L,i,L->length);//四次HeapAdjust之后,L是一个大根堆,双亲(4,3,2,1)都大于左右孩子,因为从i=4开始,最后根节点最大
64     for(i=L->length;i>1;i--)//i=9,8,...2
65     { 
66          swap(L,1,i);//把大根堆的根和最后一个数交换(最后一个数是相对的,已经归位的数就不算了),依次即把最大数归位
67          HeapAdjust(L,1,i-1);//将L->r[1,..,i-1]重新调整为大根堆,即根节点最大,下一步再把根节点和最后一个数交换,把该根节点的数归位
68     }
69 }
70 
71 int main()
72 {
73     SqList *p=NULL;
74     p=InitSqList(p);//初始化
75     HeapSort(p);//排序
76     cout<<"after sort"<<endl;
77     PrintSqList(p);//输出
78  }

运行结果:

时间复杂度:运行时间主要消耗在初始构建堆和重复构建堆。

1、初始构建堆

双亲最多有n/2个,每个双亲和左右孩子以及孩子之间,进行2次比较和互换。因此整个构建堆的时间复杂度是O(n)。

2、重复构建堆

首先看完全二叉树的性质:对一棵有n个结点的完全二叉树(其深度为log2n(向下取整)+1)。

完全二叉树第i个节点到根节点的距离为log2i(向下取整)+1,所以第i次重新构建堆需要O(log2i(向下取整))的时间。(相当于根节点只需要进行log2i(向下取整)次循环,因为i到根节点有log2i(向下取整)+1层,但是不需要和i比较,所以没有+1)

并且需要取n-1次堆顶记录和最后一个数交换。

因此重建构建堆的时间复杂度是(n-1)log2i,即O(nlogn)。

posted @ 2016-04-24 14:56  Pearl_zju  阅读(463)  评论(8编辑  收藏  举报