数据结构-堆和堆排序java

堆是一颗特殊的二叉树,什么样的二叉树才是堆?只需要满足一下两个条件,那么它就是堆。

  • 堆是一颗完全二叉树
  • 堆中每一个节点的值都必须大于等于(或者小于或等于)其子树中每一个节点的值。

对于每个节点的值大于等于子树中每个节点中的值的堆,称之为大顶堆。对于每个节点的值小于或等于子树中每个节点的值的堆,称之为小顶堆

如下图:

编号1和编号2是大顶堆,编号3是小顶堆,编号4不是堆。

如何实现一个堆?

要实现一个堆,需要先知道,如何存储一个堆以及堆支持哪些操作

堆中数据的存储

堆是一颗完全二叉树,所以堆一般都是使用数组来存储数据的。把堆中的元素从上到下,从左至右,从数组的第一个位置开始依次存储在数组中。如下图。如果一个节点在数组中的下标为i。可以计算该节点的父节点的下标为i/2。其左子节点的下标为2*i,右子节点的下标为2*i+1

堆化

如果往堆中插入一个数据元素,需要判断其是否满足堆的特性,如果不合符,我们需要对其进行调整,让其重新满足堆的特性。而这个过程称之为堆化

堆化的过程有两种,一种是从下往上堆化,一种是从上往下堆化

后面会对这两个过程进行介绍。

 

堆所支持的操作

这里会以大顶堆为例,介绍插入一个数据元素和删除堆顶元素的操作。

插入一个数据元素

往堆中插入一个数据元素如下图。

ru

会把数据元素添加到数组的末尾,需要判断这个插入的数据元素是不是满足堆的特性,如果不满足,则需要与父节点交换位置。直至满足堆的特性为止。这样的一个过程是一个从下往上堆化的过程。如下图

代码实现:

public class Heap {
    private int[] heap; //数组,从下标1开始存储数据
    private int n;  //堆中可存储的最大数据个数
    private int count; //堆中已经存在的数据个数

    public Heap(int capacity) {
        heap =new int[capacity+1];
        this.n = capacity;
        count=0;
    }

    //插入
    public boolean insert(int num){
        if(count>=n){
            System.out.println("堆满");
            return false;
        }
        ++count;
        heap[count]=num; //把插入的数据元素放入堆的末尾
        int i=count;
        //从下往上堆化的过程
        while (i/2>0 && heap[i]>heap[i/2]){
            swap(heap,i,i/2);
            i=i/2;
        }
        return true;
    }

    private void swap(int[] array,int x,int y){
        int temp=array[x];
        array[x]=array[y];
        array[y]=temp;
    }
}

删除堆顶元素

删除堆顶元素,然后把最后一个元素放到堆顶,判断该元素是否满足堆的特性,也就是与子节点进行比对,如果不满足,则与子节点交换位置,直至满足堆的特性为止。这样堆化数据元素的过程是一个从上往下堆化的过程。

如下图:

代码实现。

    public boolean deleteMax(){
        if(count==0) return false;
        heap[1]=heap[count];
        --count;
        downHeapify(heap,count,1);
        return true;
    }
    //对数组中下标为i的数据元素进行从上往下堆化
    //n为数组中数据元素的个数,i为要堆化的数据元素的索引
    private void downHeapify(int[] array,int n,int i){
        while (true){
            int pos=i;
            if(2*i<=n && array[2*i]>array[i]) pos=2*i; //与左子节点进行比较
            if(2*i+1<=n && array[2*i+1] >array[pos]) pos=2*i+1;
            if(pos == i)break;
            swap(array,i,pos);
            i=pos;
        }
    }

堆排序

堆排序大致可以分为两个步骤:建堆排序

建堆

首先将数组原地建成一个堆,就是在原数组上进行操作,建堆过程有两种思路。

第一种实现思路,就是上面讲的插入一个元素的思路,这种思路跟插入排序的思路有点类似。把数组分为已堆化部分和未堆化部分,把数组中未堆化部分逐一插入已堆化部分的尾部,在从下到上堆化

第二种思路,把数组中的数据元素构建成完全二叉树的数据结构,把所有非叶子节点从上往下堆化。因为堆中的数据元素是从数组的第一个数据元素开始存储的。所以最有一个非叶子节点的下标为n/2(n为堆中元素个数)。也就是把下标为为n/2到下标为1的数据元素依次从上往下堆化。如下图:

需要依次对8、19、5、7这四个数据元素进行从上往下堆化。堆化后如下图所示

代码实现:

public class HeapSort {
    public void buildHeap(int[] array,int n){ //n为堆中数据元素的个数
        for (int i=n/2;i>=1;i--){
            downHeapify(array,n,i);
        }
    }
    //对数组中下标为i的数据元素进行从上往下堆化
    //n为数组中数据元素的个数,i为要堆化的数据元素的索引
    private void downHeapify(int[] array,int n,int i){
        while (true){
            int pos=i;
            if(2*i<=n && array[2*i]>array[i]) pos=2*i; //与左子节点进行比较
            if(2*i+1<=n && array[2*i+1] >array[pos]) pos=2*i+1;
            if(pos == i)break;
            swap(array,i,pos);
            i=pos;
        }
    }

    private void swap(int[] heap, int a, int b) {
        int t = heap[a];
        heap[a] = heap[b];
        heap[b] = t;
    }
}

测试代码:

    @Test
    public void test(){
        int[] array={0,7,5,19,8,4,1,20,13,16};
        buildHeap(array,array.length-1);
        System.out.println(Arrays.toString(array));
    }

测试结果:

建堆的时间复杂度分析:

叶子节点不需要堆化,所以需要从倒数第二层开始,每个节点堆化的过程中,需要比较和交换的节点个数跟高度成正比。

每一层的节点个数和对应的高度,如下图所示

可推导出公式如下

进一步推导:把公式左右都乘以2,就得到另一个公式s_{2}。我们将s_{2}错位对齐,然后使用s_{2}减去s_{1}。如下图所示:

推导如公式如下图:

其中高度h=log_{2}^{n}。可以得到时间复杂度为O(n);

 

排序

排序的算法跟移除堆顶元素的算法差不多。排序算法是依次把堆顶元素和数组的最后一个数据元素交换位置,然后排除最后一个数据元素,对堆顶元素向下堆化。如下图:

代码实现:

    public void sort(int[] array,int n){ //n为堆中数据元素的个数
        buildHeap(array,n);
        while (n>1){
            swap(array,n,1); //交换堆顶元素与与未排序部分最有一个数据元素的位置
            --n;
            downHeapify(array,n,1); //堆顶元素向下堆化
        }
    }

排序算法的时间复杂度为O(nlog^{n}),而建堆的时间复杂度为O(n)。根据时间复杂度的加法法则,可得到堆排序的时间复杂度为

O(nlog^{n})

 

总结:知识点

堆的基本概念

如何创建一个堆?

    使用数组存储堆。

     根据节点的下标,获取该节点父节点下标和该节点左右子节点下标

    堆化:从上往下堆化和从下往上堆化

     堆的新增和删除堆顶元素

堆排序:建堆与排序。以及复杂度分析

 

参考:数据结构与算法之美--王争

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值