您的位置:首页 > 理论基础 > 数据结构算法

浅关堆排序

2016-03-12 11:23 197 查看
堆排序:

1,将n个数的数列当作完全二叉树,构建大顶堆(或者小顶堆)

2,将最顶部的最大值(大顶端)或者最小值(小顶堆)跟最后的值交换

3,n = n - 1;(当n > 0),继续执行步骤(1,2),如果(n == 0 ),退出程序

这里我们知道里面涉及到了二叉树的一些概念,以及大顶堆,小顶堆

完全二叉树,满二叉树:

满二叉树(如左下图):一颗深度为k且有2^k-1个结点的二叉树称为满二叉树(除叶子节点以外,所有节点的都有两个节点)

完全二叉树(如右下图):若设二叉树的深度为h,除第h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第h层出现的节点,也都是从左往右,不会说是出现右边的没有左边的


        


完全二叉树只比满二叉树少了最后几个结点,所以满二叉树是一种特殊的完全二叉树

这里我们要构建的就是这个完全二叉树,它是一种效率很高的数据结构

大顶堆:

1,根节点(堆顶)的值,是堆中所有节点的最大值

2,所以有有孩子节点的值都比孩子节点还要大

小顶堆:

1,根节点(堆顶)的值,是堆中所有节点的最小值

2,所以有有孩子节点的值都比孩子节点还要小

例如:我们要将数组(16,7,3,20,17,8)构建成大顶堆

一:将其当作一个完全二叉树                                    



二:将其构建成大顶堆(只是构建依次大顶堆)

       非叶子节点(对于数组n,当起始是从0开始的话,那么最后一个非叶子节点就是(n-1)/2),我们用的就是这种方法,如上,数组的下标就是:0,1,2,3,4,5,故最后一个非叶子节点就是下标为2,也就是里面存储的是3

       左孩子节点下标:(父亲节点下标×2+1),如上就是2×2+1,也就是下标为5,存储为8

       右孩子节点下标:同理可的,父亲节点下标×2+2(如果合理的话)

       1,找寻最后一个非叶子节点(t)(一定有左孩子节点,可能有右孩子节点)

       2,判断这个非叶子节点(t)

       3,如果其孩子节点是不合理的,直接进行步骤5;倘使孩子节点是合理的,找到孩子节点中的最大值,然后跟父亲节点进行交换

       4,继续对交换后的孩子节点进行步骤二(确保时刻交换后的结果都是大顶堆雏形)

       5,将步骤2的非叶子节点下标进行减一操作(t-1),如果(t-1 小于0),那么整体退出,不然继续执行步骤二

对第一个非叶子节点(此时t为2,存储为3,且左孩子节点为5,存储为8,右孩子节点为6,明显不合理)进行判断,并交换 ,如下图,为交换后的结果


                 

  将非叶子节点下移(t-1),并且进行判断孩子节点,此时非叶子节点为1,存储为7,左孩子节点为3,存储为20,右孩子节点为4,存储为17,均合理,然后比较,找寻到最大的孩子节点跟父亲节点进行比较,如果比父亲节点大,那么进行交换,如下图,为交换后的结果



继续判断并且下移非叶子节点,此时t为0,存储为16,左孩子节点为1,存储为20,右孩子节点为2,存储为8,依次比较交换



这样我们的堆排,排完啦吗??哈哈,还没有,因为堆排排完啦之后,所有的父亲节点的值一定比孩子节点的值大,但是,上面好像并不满足啊!!!

我们可以看到对于上面刚刚交换后的非叶子节点1,存储为16,左孩子节点为3,存储为4,右孩子节点为4,存储为17

所以我们还应该在加一步

对于所有交换后的孩子节点(tt),继续进行判断,判断当前的tt的孩子节点合理不,有没有孩子节点,如果有,继续找寻孩子节点中的最大值,继续比较,交换,如下图



如上,我们对交换后的节点继续进行判断并交换的目的,就是为了保证满足大顶堆的雏形,

当我们构建完之后,那么就应该将第一个值和最后一个值进行交换,同时,继续对n-1个数,进行大顶堆的构建



依次执行建堆的过程就ok了

依次内推,直到n-1个数为1是,那么只要对数组进行逆序输出时,就可以达到我们的目的了,

好啦,下面我们看代码:

#include <stdio.h>

#define N 8

void sure_heap(int *a, int child, int n)                 //确保大顶堆的雏形
{
if(2*child+1 <= n)
{
if(2*child+2 <= n  && a[2*child+1] <= a[2*child+2] && a[2*child+2] >= a[child])
{
int t;
t = a[child]; a[child] = a[2*child+2]; a[2*child+2] = t;

printf("zi:%d  zivlue:%d   youzi:%d  youzizhi:%d\n", child, a[child], 2*child+2, a[2*child+2]);
getchar();
sure_heap(a, 2*child+2, n);   //继续对交换后的结果继续进行判断,是不是合理
}
if(a[2*child+1] >= a[child])
{
int tt;
tt = a[child]; a[child] = a[2*child+1]; a[2*child+1] = tt;
printf("zi:%d  zivlue:%d   zuozi:%d  zuozizhi:%d\n", child, a[child], 2*child+1, a[2*child+1]);
getchar();
sure_heap(a, 2*child+1, n);   //继续对交换后的结果继续进行判断,是不是合理
}
}
}

void build_heap(int *a, int n)
{
        int i, t;
        for( i= n/2-1; i>= 0; i--)          //从最后一个非叶子节点开始,当数组是从0开始的,那么就是n/2-1;
        {                                   //当数组是从1开始的,那么最后一个非子叶节点就是n/2
                sure_heap(a, i,n);
        }
        t = a[0]; a[0] = a
; a
= t;

}

int main()
{
        int a
= {2, 4 , 1 , 7 , 5 , 9 , 3 , 8};        //构建大顶堆

        int i,ii;
        for(i=N-1; i>0 ; i--)
        {
                build_heap(a, i);       
                for(ii = N-1; ii>=0; ii--)
                        printf("%d  ", a[ii]);
                printf("\n");
                getchar();      
        }
        return 0;
}      

 


当然,中间还夹杂着一些,排错的语句,如:printf

运行结果:



如上,只是一次建堆的实现,然后逆序输出的结果!!!



这是我们最终的结果!!!

上面这样的实现,还是在不断改变中调节的,本来我是打算把比较交换的函数写在build_heap(int *a, int n),但是后来发现,这样写并不能保证当一次构建大顶堆完成之后,完全满足大顶堆的情况,尽管这样是可以的,但是直接导致了其时间复杂度大大的提高,因为当完全满足大顶堆之后,后面的构建堆的过程就比较简单方便,不需要在做一些无用的交换判断来浪费时间,后来想着重新写一个函数sure_heap(int *a, int child, int n)(哈哈,博主英语不好,随便起得),来用递归来确保交换后的结果时刻是大顶堆的雏形!!!

堆排序是一种树形选择排序,在排序过程中,将A
看成是完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子结点之间的内在关系来选择最小的元素。

堆排序:不稳定(不能确保相同值的具体顺序),时间复杂度 O(nlog n)

由于博主理解简单,只是说一说自己的心得体会(希望对大家有所帮助),并不涉及堆的删除和插入,忘大家见谅!!!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息