美文网首页
图解二叉堆

图解二叉堆

作者: Taonce | 来源:发表于2019-06-04 23:42 被阅读0次

二叉堆本质上其实就是一种完全二叉树(不熟悉二叉树的可以看前面的文章:图解二叉树),它分为两种类型:

  • 最大堆:堆中任何一个父节点的值都大于等于它左右子节点的值
  • 最小堆:显然和最大堆相反,堆中任何一个父节点的值都小于等于它左右子节点的值

对于一个二叉堆的操作主要包含了两个:插入节点和删除节点;接下来会对这两种操作进行具体的说明,说明的对象都是最小堆,如果理解了最小堆的操作,那么最大堆的操作就易如反掌了。

插入节点 (最小堆)

插入一个节点主要的逻辑就是在二叉树的最后节点上安置新的节点,然后比较此节点和父节点的大小。如果是小于父节点,那么就不用操作,直接生成此二叉堆;如果是大于父节点,那么将此节点和父节点位置互换(也就是上浮操作),将互换后的父节点作为新的节点,再进行新节点和父节点的大小比较,直到新节点小于父节点或者新节点为根节点,结束。文字的表现力依旧比较复杂,下面就看插入的流程图吧,结合流程图会更加贴切的理解插入过程。

  1. 在现有的二叉堆 ( 0, 3, 2, 7, 9, 5, 6, 10, 8 ) 中插入新的节点 1
  2. 新节点与父节点对比,新节点 1 小于 父节点 9,那么就互换位置
  3. 互换后的节点 1 再与其父节点对比,1 小于其父节点 3,再次互换位置
  4. 再看此时的新节点 1,对于其现有父节点 0,它是大于其父节点的,所以终止上浮操作,生成最终的二叉堆 ( 0 1 2 7 3 5 6 10 8 9 ),如上图所示。
    下面看具体的代码实现:
    /**
     * 添加一个结点
     *
     * @param element 结点值
     */
    public void addNode(Integer element) {
        _data.add(element);
        up();
    }

    /**
     * 进行上浮操作
     */
    private void up() {
        int tempIndex = _data.size() - 1;
        int parentIndex = (tempIndex - 1) / 2;
        Integer temp = _data.get(tempIndex);

        while (tempIndex > 0) {
            int cmp = _data.get(parentIndex) - temp;
            if (cmp <= 0) {
                break;
            } else {
                _data.set(tempIndex, _data.get(parentIndex));
                tempIndex = parentIndex;
                parentIndex = (parentIndex - 1) / 2;
            }
        }
        _data.set(tempIndex, temp);
    }

删除节点 (最小堆)

删除节点的操作和逻辑要比添加节点稍微复杂那么一丢丢,但是其本质还是一样的,比较子节点和父节点的大小进行下沉操作。删除某个节点,先将此节点移除,然后将最后一个节点暂时安放在删除节点的位置;开始比较最后一个节点和其左右子节点的大小,如果是比两个子节点都小,那么终止下沉操作,生成新的二叉堆,如果是比其中任何一个子节点大,那么就将此节点和其子节点中最小的那个互换位置,依次循环,直到没有子节点或者比子节点小,终止下沉操作,生成最终的新二叉堆。下面请看图解:

  1. 在原二叉堆 ( 0 1 2 7 3 5 6 10 8 9 ) 中删除其根节点 0,第一步就是将最后节点 9 暂时移到根节点处
  2. 第二步对比 9 这个节点和其子节点中最小的大小,最小节点为 1,而且 9 比其子节点 1 要大,那么就将其互换位置,得到如下图所示:
  3. 第三步继续对比此时的 9 节点和其子节点的大小,此时它的子节点中最小的是 3 ,而 9 节点又比 3 要大,那么继续互换位置,如下图:
  4. 第四步准备继续对比,发现此时的 9 节点没有子节点了,那么就终止对比,生成新的二叉堆 ( 1 3 2 7 9 5 6 10 8 ): 代码实现起来如下:
/**
     * 删除一个结点,同时进行下沉操作
     *
     * @param element 删除的结点
     */
    public void deleteNode(Integer element) {
        if (_data.isEmpty()) {
            return;
        }
        int index = _data.indexOf(element);
        if (index == -1) {
            return;
        }
        if (index == _data.size() - 1) {
            _data.remove(_data.size() - 1);
            return;
        }
        int size = _data.size();
        _data.set(index, _data.get(size - 1));
        _data.remove(size - 1);
        if (size > 1) {
            down(index, _data.size() - 1);
        }
    }

    /**
     * 下沉
     *
     * @param start 下沉起始坐标
     * @param end   下沉终点坐标
     */
    private void down(int start, int end) {
        int left = start * 2 + 1;
        Integer temp = _data.get(start);
        while (left <= end) {
            boolean max = true;
            // 防止index越界
            if (left + 1 <= end) {
                // 对比左右子节点,如果右子节点比左小,那么left取右子节点的下标
                max = (_data.get(left) - _data.get(left + 1)) < 0;
            }
            if (!max && left <= end) {
                left++;
            }
            // 如果子节点小于父节点,那么交换位置
            // 并且继续检查子节点和其子节点的大小关系
            max = (temp - _data.get(left)) > 0;
            if (!max) break;
            else {
                _data.set(start, _data.get(left));
                start = left;
                left = left * 2 + 1;
            }
        }
        _data.set(start, temp);
    }

二叉堆在理解二叉树的知识之后,上手理解还是比较轻松的,而且二叉堆的应用很广,比如我们使用的堆排序,就是完美的使用了二叉堆中删除节点的操作,可以试想一下,对于一个最小堆来说,依次删除根节点,拿到顺序不就是从最小值开始的么。而且我们还可以取一个数组或者列表中前K个最小值。所以二叉堆的知识还是有必要熟练掌握的!
下面我把整体代码贴出来,以便大家在熟悉过程中可以跟着代码结果来对比。

import java.util.ArrayList;
import java.util.List;

class BinaryHeap {

    private List<Integer> _data;

    public BinaryHeap() {
        _data = new ArrayList<>();
    }

    /**
     * 添加一个结点
     *
     * @param element 结点值
     */
    public void addNode(Integer element) {
        _data.add(element);
        up();
    }

    /**
     * 进行上浮操作
     */
    private void up() {
        int tempIndex = _data.size() - 1;
        int parentIndex = (tempIndex - 1) / 2;
        Integer temp = _data.get(tempIndex);

        while (tempIndex > 0) {
            int cmp = _data.get(parentIndex) - temp;
            if (cmp <= 0) {
                break;
            } else {
                _data.set(tempIndex, _data.get(parentIndex));
                tempIndex = parentIndex;
                parentIndex = (parentIndex - 1) / 2;
            }
        }
        _data.set(tempIndex, temp);
    }

    /**
     * 删除一个结点,同时进行下沉操作
     *
     * @param element 删除的结点
     */
    public void deleteNode(Integer element) {
        if (_data.isEmpty()) {
            return;
        }
        int index = _data.indexOf(element);
        if (index == -1) {
            return;
        }
        if (index == _data.size() - 1) {
            _data.remove(_data.size() - 1);
            return;
        }
        int size = _data.size();
        _data.set(index, _data.get(size - 1));
        _data.remove(size - 1);
        if (size > 1) {
            down(index, _data.size() - 1);
        }
    }

    /**
     * 下沉
     *
     * @param start 下沉起始坐标
     * @param end   下沉终点坐标
     */
    private void down(int start, int end) {
        int left = start * 2 + 1;
        Integer temp = _data.get(start);
        while (left <= end) {
            boolean max = true;
            // 防止index越界
            if (left + 1 <= end) {
                // 对比左右子节点,如果右子节点比左小,那么left取右子节点的下标
                max = (_data.get(left) - _data.get(left + 1)) < 0;
            }
            if (!max && left <= end) {
                left++;
            }
            // 如果子节点小于父节点,那么交换位置
            // 并且继续检查子节点和其子节点的大小关系
            max = (temp - _data.get(left)) > 0;
            if (!max) break;
            else {
                _data.set(start, _data.get(left));
                start = left;
                left = left * 2 + 1;
            }
        }
        _data.set(start, temp);
    }

    @Override
    public String toString() {
        StringBuilder result = new StringBuilder();
        _data.forEach(element -> result.append(element).append(" "));
        return result.toString();
    }

    public static void main(String[] args) {
        Integer[] data = {0, 3, 2, 7, 9, 5, 6, 10, 8};
        BinaryHeap binaryHeap = new BinaryHeap();
        for (Integer element : data) {
            binaryHeap.addNode(element);
        }
        binaryHeap.addNode(1);
        System.out.println(binaryHeap.toString());
        binaryHeap.deleteNode(0);
        System.out.println(binaryHeap.toString());
    }
}

如果本文章你发现有不正确或者不足之处,欢迎你在下方留言或者扫描下方的二维码留言也可!

相关文章

  • 图解二叉堆

    二叉堆本质上其实就是一种完全二叉树(不熟悉二叉树的可以看前面的文章:图解二叉树),它分为两种类型: 最大堆:堆中任...

  • 二叉搜索树

    二叉搜索树 图解二叉树搜索算法图解:二叉搜索树算法二叉查找树(Binary Search Tree),(又:二叉搜...

  • 用go实现二叉堆插入删除构建

    二叉堆的插入: 二叉堆的删除: 二叉堆的构建:

  • [数据结构与算法-iOS 实现]二叉堆原理实现及top k 问题

    二叉堆 demo 二叉堆其实就是一颗完全二叉树,所以又可以叫做完全二叉堆 二叉堆的底层可以用数组来实现 对于二叉堆...

  • 二叉堆

    二叉堆定义 二叉堆是一种特殊的堆, 二叉堆是完全二叉树或者近似完全二叉树. 二叉堆满足堆特性: 父节点的键值总是保...

  • 二叉堆(Binary Heap)

    二叉堆这个数据结构有点意思,自己做了个总结,内容结构如下: 二叉堆性质 二叉堆操作 应用 二叉堆性质: 堆(Hea...

  • 堆排序(下):最大堆

    二叉堆,简称堆 Heap 尖的完全二叉树。也有三叉堆以及普通堆,但大部分时候堆就是指二叉堆 二叉堆的定义 一棵完全...

  • 二叉堆的Python实现

    二叉堆(binary heap)是一种特殊的堆,二叉堆是完全二叉树或者是近似完全二叉树。二叉堆满足堆特性:父节点的...

  • java堆排序

    什么是堆排序:图解堆排序堆排序:利用了堆这种数据结构堆数据结构:特殊的完全二叉树,因为具有以下的特点:1)每个结点...

  • Sort.堆排序(heapsort) & 优先队列

    二叉堆 这个二叉堆和先进后出的那个堆不是一个。而且这个二叉堆从下标1开始存储元素。 这里的二叉堆是个数组,也可以看...

网友评论

      本文标题:图解二叉堆

      本文链接:https://www.haomeiwen.com/subject/mnlrxctx.html