zoukankan      html  css  js  c++  java
  • 二叉堆

    在介绍堆之前, 先看一下一些概念

    完全二叉树(Complete Binary Tree)

    若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。

    维基百科的定义

    树的深度:对于任意节点n,n的深度为从根到n的唯一路径长,根的深度为0;
    树的高度:对于任意节点n,n的高度为从n到一片树叶的最长路径长,所有树叶的高度为0;

    二叉堆

    二叉堆是一个数组, 可以看成近似的完全二叉树,树上的每一节点对应数组中的一个数,除最低层外,该树是完全充满的,且从左往右填充

    下标计算

    由于数组下标从 0 开始,故处理方式:将把索引从 0 开始转化成从 1 开始,然后算出结果 x,则实际索引为 x-1

    // 先加1, 再减1
    left = ((i+1)x2-1 
    right = leftChild-1
    parent = (i+1)/2-1
    

    各个操作时间复杂度

    1. keep_heap: 时间复杂度O(lgn),维护最大堆性质的关键
    2. build_heap: 时间复杂度O(n), 作用是将一个无序数组建立成一个堆
    3. heap_sort: 时间复杂度O(nlgn),对一个数组原址排序
    4. pop_heap, push_heap: 在堆中, 插入删除元素时间复杂度都是 O(lgn)
    

    等比数列求和公式

    如果等比数列的公比为 (q), 等比数列的和为 (S_{n}=a_{1}+a_{2}+a_{3}+cdots +a_{n}), 求和如下

    [{S_{n}= egin{cases}{ frac {a_{1}-a_{n}q}{1-q}}&q eq 1\ na_{1}&q=1end{cases} } ]

    深度为 h 的满二叉树的节点个数为

    [egin{align*} S(n) &= 2^0 + 2^1+2^2 cdots+2^h \ &=frac{1-2^{h}cdot 2}{1-2} \ &= 2^{(h+1)}-1 end{align*} ]

    屏幕快照 2018-08-27 下午11.54.24-w656

    简单代码实现二叉堆

    #include<iostream>
    #include<vector>
    #include <map>
    using namespace std;
    
    // 这里操作的是大顶堆
    
    // keep_heap 维持堆的性质, 时间复杂度 O(lgn)
    // 1. 输入数组 nums 和下标 parent,假设左右孩子节点 left 和 right 的二叉树都是最大堆
    // 2. nums[parent] 可能小于其孩子,这时违反了最大堆的性质
    // 3. keep_heap 的作用就是让 A[parent] 在最大堆里逐级下降,
    //    最后使得每一个子堆都满足最大堆的性质
    
    void keep_heap(vector<int> &nums, int parent){
        int left = (parent+1)*2-1;
        int right = left + 1;
        int largest = parent;
    
        if (left < nums.size() && nums[left] > nums[largest]) largest = left;
        if (right < nums.size() && nums[right] > nums[largest]) largest = right;
    
        if (largest != parent) {
            swap(nums[largest], nums[parent]);
            keep_heap(nums, largest);
        }
    }
    
    // 时间复杂度为线性时间复杂度 O(n)
    // 证明: 当用数组表示存储 n 个元素的堆时, 叶节点的下标分别是 floor(n/2)+1, floor(n/2)+2, ..., n
    // 我们知道,堆是一个完全二叉树,最后一个节点 n 的父节点为 parent = floor(n/2) (索引从 1 开始);
    // 假设父节点的右兄弟存在, 那么父节点的右兄弟 (parent+1) 的左孩子节点为 2x(floor(n/2)+1);
    // 当 n 为偶数时, floor(n/2) > (n-1)/2;
    // 当 n 为奇数时, floor(n/2) = (n-1)/2,
    // 所以 floor(n/2) ≥ (n-1)/2
    // 故 2x(floor(n/2)+1) ≥ 2x((n-1)/2+1) = n+1, 而 n 是最大的索引, 故假设不成立
    // 与题设矛盾 所以说存储 n 个元素的堆的叶节点的下标分别是 floor(n/2)+1, floor(n/2)+2, ..., n
    
    void build_heap(vector<int> &nums) {
        for (int i = nums.size()/2-1; i >= 0; i--) {
            keep_heap(nums, i);
        }
    }
    
    // 顶端节点出队, 然后将最后一个元素放在顶部, 然后下滤
    int pop_heap(vector<int> &nums) {
        int answer = -1;
        if (nums.size() == 0) {
            cout << "heap is already empty!";
            return answer;
        }
        answer = nums[0];
        nums[0] = nums[nums.size()-1];
        nums.pop_back();
        keep_heap(nums, 0);
        return answer;
    }
    
    // 将插入元素放在数组尾部, 然后上滤
    void push_heap(vector<int> &nums, int val) {
        nums.push_back(val);
        int child = nums.size() - 1;
        while(child > 0) {
            // child > 0, father >= 0
            int father = (child+1)/2-1;
            if (nums[father] < nums[child]) {
                swap(nums[father], nums[child]);
                child = father;
            } else {
                break;
            }
        }
    }
    
    int main() {
    
        vector<int> v = {4, 5 ,7, 10};
        build_heap(v);
        for (auto e : v) {
            cout << e << " ";
        }
        cout << endl << endl;
    
        int size = v.size();
        for (int i = 0; i < size; i++) {
            cout << pop_heap(v) << " ";
        }
        cout << endl << endl;
    
        push_heap(v, 1);
        push_heap(v, 5);
        push_heap(v, 7);
        size = int(v.size());
        for (int i = 0; i < size; i++) {
            cout << v[i] << " ";
        }
        cout << endl;
    }
    

    C++ 自带的与堆相关的函数

    下面介绍 STL 中与堆相关的 4 个函数

    1. 建立堆 make_heap(_First, _Last, _Comp)

    默认是建立最大堆的。对int类型,可以在第三个参数传入greater()得到最小堆。

    2. 在堆中添加数据 push_heap (_First, _Last)

    要先在容器中加入数据,再调用 push_heap()

    3. 在堆中删除数据 pop_heap(_First, _Last)**

    要先调用 pop_heap() 再在容器中删除数据

    4. 堆排序 sort_heap(_First, _Last)**

    排序之后就不再是一个合法的heap了

    #include <iostream>
    #include <vector>
    #include <map>
    using namespace std;
    
    int main() {
        vector<int> vec = {2, 3, 5, 1, 34, 5};
        make_heap(vec.begin(), vec.end(), greater<int>());
        for (auto e : vec) {
            cout << e << " ";
        }
        cout << endl;
    
        // 不管是 push 还是 pop, 都要提供 comp 函数, 不然有错
    
        // 1. 先在容器中加入元素, 然后调用push_heap进行调整
        vec.push_back(10);
        push_heap(vec.begin(), vec.end(), greater<int>());
    
        for (auto e : vec) {
            cout << e << " ";
        }
        cout << endl;
        // 2. 删除容器元素, 先调用pop_heap, 将删除元素放到容器末尾, 然后删除
        pop_heap(vec.begin(), vec.end(), greater<int>());
        vec.pop_back();
        for (auto e : vec) {
            cout << e << " ";
        }
        return 0;
    }
    
  • 相关阅读:
    逆向工程工具介绍2-IDA
    汇编语言基础-1 基本语言元素
    Python常用标准库1-Turtle,Random,Time和Datetime
    Python的模块、包和库的概念
    Go语言的函数修饰符
    物理层2-物理层下面的传输媒体
    数据分析之两种用户分群方法(RFM和聚类)
    区间估计与假设检验公式
    源码分析过滤器与拦截器的区别
    Springboot拦截器使用及其底层源码剖析
  • 原文地址:https://www.cnblogs.com/nowgood/p/binaryheap.html
Copyright © 2011-2022 走看看