一文搞懂数据结构之堆

完全二叉树

定义:一棵二叉树,除最后一层外都是满的。最后一层的节点集中在左边。
完全二叉树

完全二叉树的节点可以按层序遍历的顺序放入数组。注意数组的第0位不存放信息,根节点放在第1位。
上图中的完全二叉树就可以以一个数组的形式存储:{#,1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
为什么要这么存放?因为从下标为1的位置开始存放(也称为基1) ,当一个结点的下标为k时,下标为 [k/2] 的结点 就是它的根节点。下标为[2k] 和 [2k+1] 的结点就是它的左孩子结点和右孩子结点。

堆是一个完全二叉树。
堆分为两种:
当堆的每个结点都大于其两个子结点时,这种堆叫大根堆。
当堆的每个结点都小于其两个子结点时,这种堆叫小根堆。

我们先来看大根堆。

1. 插入元素

当我们向一个堆中插入一个元素时,分为两步:

  1. 将新元素插入到堆的尾端。
  2. 不断比较插入结点和其父节点的值,若大于其父节点就交换位置。

不断向上交换的操作我们可以很容易用一个循环写出:

	int N;//N表示当前堆内的节点数
	int[] tree;//保存堆内元素
    public void swap(int index1,int index2)//将两个下标所在的元素交换位置
    {
        int t = tree[index1];
        tree[index1] = tree[index2];
        tree[index2] = t;
    }

    public void swim(int index)//通过不断上浮,将指定下标位置的元素更换到到合适的位置上
    {
        if(index>N)
            return;
        while(index>1)
        {
            if(tree[index]>tree[index/2])
            {
                swap(index,index/2);
                index = index/2;
            }
            else
                break;
        }
    }

	public void insert(int n)//插入操作
    {
        tree[++N] = n;//将新节点放入堆的尾部
        swim(N);//不断将该节点上浮,直到放置到合适位置
    }
    
删除堆顶最大元素
  1. 把最后一个元素与根节点交换。
  2. 把最后一个元素删除。
  3. 循环判断根节点是否小于其子结点,是则将其与子结点交换。
    public void sink(int index)//将指定下标的元素不断下沉交换到合适位置
    {
        while(2*index<=N)
        {
            int swapIndex = 0;
            if(2*index+1>N)
                swapIndex = 2*index;
            else
                swapIndex = tree[2*index]>tree[2*index+1]?2*index:2*index+1;
            if(tree[index]<tree[swapIndex])
            {
                swap(index,swapIndex);
                index = swapIndex;
            }
            else
                break;
        }
    }

    public int delMax()//删除堆顶结点并返回
    {
        int Max = tree[N];
        swap(1,N--);
        sink(1);
        return Max;
    }

完整代码
class Heap
{
    int[] tree;
    int N = 0;
    public newHeap(int capacity)
    {
        this.tree = new int[capacity+1];
        tree[0] = 0;

    }

    public newHeap(int[] list)
    {
        this(list.length);
        for(int i:list)
        {
            this.insert(i);
        }
    }

    public void swap(int index1,int index2)
    {
        int t = tree[index1];
        tree[index1] = tree[index2];
        tree[index2] = t;
    }

    public void swim(int index)
    {
        if(index>N)
            return;
        while(index>1)
        {
            if(tree[index]>tree[index/2])
            {
                swap(index,index/2);
                index = index/2;
            }
            else
                break;
        }
    }

    public void sink(int index)
    {
        while(2*index<=N)
        {
            int swapIndex = 0;
            if(2*index+1>N)
                swapIndex = 2*index;
            else
                swapIndex = tree[2*index]>tree[2*index+1]?2*index:2*index+1;
            if(tree[index]<tree[swapIndex])
            {
                swap(index,swapIndex);
                index = swapIndex;
            }
            else
                break;
        }
    }

    public void insert(int n)
    {
        tree[++N] = n;
        swim(N);
    }

    public int delMax()
    {
        int Max = tree[N];
        swap(1,N--);
        sink(1);
        return Max;
    }
}

堆排序

将一个数组从大到小排序可以用到大根堆。逐一将数组元素插入堆,再删除堆顶元素直到堆空。每次删除的堆顶元素构成的序列就是这个从大到小排列的数组。

但是还有一个巧妙的方法可以节省时间复杂度:将待排序数组拷贝到堆内。然后从这个新数组的中间开始,从后往前遍历并将每个元素执行sink下沉交换操作。遍历完以后这个堆就是一个大根堆。然后不断删除堆顶结点,直到堆为空。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值