大根堆代码说明(堆的构建、插入、删除和堆排序)小根堆同理

我发现网上很多人将的都不清楚,如果只看文章的话,根本就理解不了。所以在我研究完代码之后记录下来,方便次观看,如果有益于跟多人的话那更好了。
写下的都是自己的理解,如果有误欢迎订正。
这里以大根堆举例,适时的会说一下小根堆
讲解代码

说在前面

首先,了解一下大根堆or小根堆 (下面简称’堆’)的性质。

  1. 堆是一个完全二叉树(最后一层可以不满,上面的每一层都是满的。一个结点若只有一个孩子结点,那一定是它的左孩子。如下图)
    在这里插入图片描述

  2. 因为它是一个完全二叉树,所以它也就可以用数组存储。具有以下特殊关系:
    下标为 i 的结点的父节点的下标是 (i - 1)/ 2;
    一个下标为 j的 结点,若它有孩子结点,则它的左孩子结点为 (2 * i) + 1; 右孩子结点为 (2 * i )+ 2;
    在这里插入图片描述

  3. 大根堆的任何一非叶节点的值大于其左右孩子节点的值。小根堆的任何一非叶节点的值小于其左右孩子节点的值。所以这要求构建堆的类型可以比较大小,不然没有意义。但是没有要求左右两个孩子的值的大小。

两个重要函数siftDown() 和 siftUp()

先说堆中用到的两个函数siftDown()和siftUp()。

//在i的儿子中选最大的,如果最大的大于i和i交换位置
    private void siftDown(int i) {
        if(i >= size / 2 ) return;		//如果i大于等于最后一个结点的叔叔结点,那么说明i是叶子结点,无需Down、返回。
        int index;      //  最大的儿子下标
        if((2 * i) + 2 >= size) {
            index = (2 * i) + 1;
        } else{
            index = (data[(2 * i) + 2] > data[(2 * i) + 1]) ? (2 * i) + 2 : (2 * i) + 1;
        }
        if(data[index] > data[i]) {
            int temp = data[i];
            data[i] = data[index];
            data[index] = temp;
            siftDown(index);
        }
    }

siftDown函数是将非叶子结点和其孩子结点进行比较,大根堆是将值大的选出来,值小的Down;小根堆是将值小的选出来,值大的Down。
这里写的是递归调用,退出条件是:i是叶子结点时退出。这里的写的(i >= size / 2)很巧妙。 写成(i > (size - 2) / 2)i大于最后一个结点的父节点的话,如果只有两个元素时(i == 0 , (size - 2 ) / 2 == 0),就不会对0结点进行比较。

	private void siftUp(int index) {
        if(index == 0) return;
        if(data[index] > data[(index - 1) / 2]){
            int temp = data[(index - 1) / 2];
            data[(index - 1) / 2] = data[index];
            data[index] = temp;
            siftUp((index - 1) / 2);
        }
    }

siftUp是将index结点和它的父节点进行比较,大根堆是将值大Up,小根堆是将值小的Up。
这个很好理解。递归调用,当传入的是根节点时退出。

堆的建立

初始输入为一个数组。
在这里插入图片描述

	public Heap (int[] data){
        this.data = data;
        size = data.length;
        heapify();			//将输入的data数组构建成大根堆
    }

构造函数,将一个数组构造成堆,调用堆化函数heapify(),初始化堆。

//堆化
    private void heapify() {
        for(int i = (size - 2) / 2; i >= 0; i--){
            siftDown(i);
        }
    }

从最后一个结点的父节点开始遍历,使所有非叶子结点都进行siftDown操作。
在这里插入图片描述

插入结点

	public void insert(int number){
        if(size == data.length)
            resize();
        data[size++] = number;
        siftUp(size-1);
    }
    private void resize(){
        data = Arrays.copyOf(data,data.length * 2);
    }

插入很简单,将要插入的结点加到最后,然后对新插入的结点进行siftUp操作就行了。

删除结点

	public int delete(int index){
        if(index >= size) return -1;		//下标超界返回-1
        int temp = data[index];				//存储要删去的值
        int record = data[size - 1];		//记录最后一个结点的值
        data[index] = data[--size];			//将最后一个结点覆盖要删去结点的位置
        siftDown(index, size);
        if(record == data[index])
            siftUp(index);
        return temp;
    }

因为不知道最后一个结点的值的大小和要删除的结点值的关系,所以先siftDown操作,如果没有变化,再进行siftUp操作。

看图说话,第三步如果如果Down了,说明最后一个元素比删除元素小,就没有Up的必要了。如果第3步没变,想想如果有四层,要删除的在倒数第二层,也有可能最后一个元素比删除的上一个元素大,所以就进行Up操作。

堆排序

重点就是将上面最大的元素放在最后一个位置,然后忽略它。
修改一下siftDown操作。

	//在i的儿子中选最大的,如果最大的大于i和i交换位置
    private void siftDown(int i,int size) {
        if(i >= size / 2 ) return;		//如果i大于等于最后一个结点的叔叔结点,那么说明i是叶子结点,无需Down、返回。
        int index;      //  最大的儿子下标
        if((2 * i) + 2 >= size) {
            index = (2 * i) + 1;
        } else{
            index = (data[(2 * i) + 2] > data[(2 * i) + 1]) ? (2 * i) + 2 : (2 * i) + 1;
        }
        if(data[index] > data[i]) {
            int temp = data[i];
            data[i] = data[index];
            data[index] = temp;
            siftDown(index, size);
        }
    }

巧就巧在将size修改为局部变量,这样就可以修改局部传入参数的值来忽略最后一个元素了。

	//大根堆默认升序排序
    public void Sort(){
        int mysize = size;
        for(; mysize > 0;){
            int temp = data[0];
            data[0] = data[--mysize];
            data[mysize] = temp;
            siftDown(0, mysize);			//交换第一个和最后一个元素后对下标为0的第一个元素进行siftDown操作。
        }
    }

遍历size 个元素所以时间复杂度为 nlogn

全部代码

import java.util.Arrays;
/*
* 默认是大根堆
* */
public class Heap<E extends Number & Comparable<E>> {
    private final int DEFAULT_CAPACITY = 10;
    private E[] data;
    private int size;
    public Heap(){
        data = (E[]) new Number[DEFAULT_CAPACITY];
        size = 0;
    }
    public Heap (E[] data){
        this.data = data;
        size = data.length;
        heapify();
    }
    //堆化
    private void heapify() {
        for(int i = (size - 2) / 2; i >= 0; i--){
            siftDown(i, size);
        }
    }
    //在i的儿子中选最大的,如果最大的大于i和i交换位置
    private void siftDown(int i, int size) {
        if(i >= size / 2 ) return;
        int index;      //  最大的儿子下标
        if((i << 1) + 2 >= size) {
            index = (i << 1) + 1;
        } else{
            index = (data[(i << 1) + 2].compareTo(data[(i << 1) + 1]) > 0) ? (i << 1) + 2 : (i << 1) + 1;
        }
        if(data[i].compareTo(data[index]) < 0) {
            E temp = data[i];
            data[i] = data[index];
            data[index] = temp;
            siftDown(index, size);
        }
    }

    public void insert(E number){
        if(size == data.length)
            resize();
        data[size++] = number;
        riseUp(size-1);
    }

    public E delete(E number){
        for(int i = 0; i < size; i++){
            if(data[i].equals(number))
                return delete(i);
        }
        return null;
    }

    public E delete(int index){
        if(index >= size) return null;
        E temp = data[index];
        E record = data[size - 1];
        data[index] = data[--size];
        siftDown(index, size);
        if(record == data[index])
            riseUp(index);
        return temp;
    }

    private void riseUp(int index) {
        if(index == 0) return;
        if(data[index].compareTo(data[(index - 1) / 2]) > 0){
            E temp = data[(index - 1) / 2];
            data[(index - 1) / 2] = data[index];
            data[index] = temp;
            riseUp((index - 1) / 2);
        }
    }
    //大根堆默认升序排序
    public void Sort(){
        int mysize = size;
        for(; mysize > 0;){
            E temp = data[0];
            data[0] = data[--mysize];
            data[mysize] = temp;
            siftDown(0, mysize);
        }
    }
    //还原排序后的大根堆
    public void reBack(){
        heapify();
    }

    private void resize(){
        data = Arrays.copyOf(data,data.length * 2);
    }

    @Override
    public String toString() {
        String res = "";
        res += "Heap{data = [";
        for (int i = 0; i < size - 1; i++){
            res += data[i].toString() + ", ";
        }
        if(size != 0){
            res += data[size - 1];
        }
        res +="]}";
        return res;
    }
}


  • 3
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

脸是真的白

如果对你有用的话,可以支持一下

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值