zoukankan      html  css  js  c++  java
  • 树和树结构(1) : 二叉堆和堆排序

    参考 百度词条 树结构
    参考书目 算法导论
    传送门 请在heap.h中找到完整的源码

    树结构
    树是一种重要的非线性数据结构,直观地看,它是数据元素(在树中称为结点)按分支关系组织起来的结构,很象自然界中的树那样。树结构在客观世界中广泛存在,如人类社会的族谱和各种社会组织机构都可用树形象表示。

    定义
    一棵树(tree)是由n(n>0)个元素组成的有限集合,其中:
    (1)每个元素称为结点(node);
    (2)有一个特定的结点,称为根结点或根(root);
    (3)除根结点外,其余结点被分成m(m>=0)个互不相交的有限集合,而每个子集又都是一棵树(称为原树的子树)
    一棵树可以直观的表示为

    1. 1
    2. /
    3. 2 3
    4. /
    5. 4 5

    树有一些重要的概念, 如:

     树的度——也即是宽度,简单地说,就是结点的分支数。以组成该树各结点中最大的度作为该树的度,如上图的树,其度为3;树中度为零的结点称为叶结点或终端结点。树中度不为零的结点称为分枝结点或非终端结点。除根结点外的分枝结点统称为内部结点。

    树的深度 ——组成该树各结点的最大层次,如上图,其深度为3;

    层次 根结点的层次为1,其他结点的层次等于它的父结点的层次数加1.

    完全二叉树 除最后一层外,每一层上的节点数均达到最大值;在最后一层上只缺少右边的若干结点。

    二叉堆
    堆(英语:heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:
    (1)堆中某个节点的值总是不大于或不小于其父节点的值;
    (2)堆总是一棵完全树。
    其中, 符合堆性质的完全二叉树称为二叉堆.根据定义, 二叉堆可以分为大根堆和小根堆.例如下图是一个小根堆:

    1. 1
    2. /
    3. 3 7
    4. / /
    5. 12 15 14

    不难发现, 堆可以用来高速的查询最大值(或最小值). 因此, 弗罗依德(Floyd)同他人一起发明了基于堆的堆排序算法. 其中, 堆的主要操作有三个--push(), pop(), top()

    代码与实现

    1, 堆的数据结构(代码来自TOCL)
    因为堆始终是一棵完全二叉树, 可以直接用数组存储, 即按照编号存储. 例如上一个图中的堆可以表示为:

    1. 1 3 7 12 15 14

    不难发现, 每个节点i的左孩子编号为2i, 右孩子为2i+1. 因此写出堆的数组存储(这里用指针实现)

    template <typename T>
    //泛型模板, 可以使用heap<int> h; 创建一个int类型的小根堆.
    class heap {
            private:
            T *h;
            int heap_size;
            int topp;
            /*堆顶指针*/
            public:
            /*构造/析构函数*/
            heap(int size){
                    /*必须指定堆的大小*/
                    heap_size = size;
                    topp = 0;
                    h = new T[size+1];
            }
            ~heap() {
                    delete[] h;
                    /*删除堆*/
            }
            ...
    };


    2, 入堆的操作push()

    将一个节点插入堆的方法是: 先将节点加入数组, 然后不断的向上比较. 如果已经满足堆的性质(这里指父节点已经比当前节点小), 即退出; 否则将此节点和父节点交换, 并再次向上比较, 直到满足性质或到达堆顶.

    void push (T data) 
    {
            h[++topp] = data;
            int now = topp;
            /*当前元素指针*/
            int next = half(now);
            /*当前元素的父节点*/
            while (now > 1) {
                    next = half (now);
                    /*定理 堆中节点i的左右孩子编号为2i,2i+1, 父节点编号为i/2*/
                    if (h[now] < h[next]) {
                            swap (h[now], h[next]);
                            now = next;
                    /*交换*/
                    } else {
                            return;
                            /*否则插入完成,结束*/
                    }
            }
    }


    3, 将优先级最大的节点退出堆 pop()

    退出堆的算法是: 将根节点用最末尾的节点代替, 并将堆的大小-1. 再自顶而下的选择较小的子节点. 若当前节点优先级小于其优先级较大的子节点, 则交换之, 并继续向下更新, 知道满足性质或到达堆的末尾.

    T pop () 
    {
            T res = h[1];
            h[1] = h[topp--];
            int now = 1;
            /*当前元素指针*/
            int next = twice(now);
            /*当前元素的左孩子节点*/
            while (twice(now) <= topp) {
                    next = twice (now);
                    /*见push解释*/
                    if (next < topp && h[next+1] < h[next]) 
                            next ++;
                    /*选择较小的孩子*/
                    if (h[now] < h[next] || h[now] == h[next])
                            return res;
                    swap (h[now], h[next]);
                            /*交换*/
                    now = next;
            }
            return res;
            //返回优先级最大的值
    }


    4, 获取堆顶(可以不写)

    很简单, 直接获取数组第一个即为根

    T top () 
    {
            return h[1];
    }
    


    5, 堆排序

    也很简单, 通过多次push建一个堆, 再用pop逐个取出.

    template <typename T> 
    void heap_sort (T array[], int begin, int end)
    {
            heap<T> h (end - begin + 1);
            /*建立一个堆*/
            for (int i = begin; i <= end; i++)
                    h.push (array[i]);
            /*通过push建小根堆*/
            for (int i = begin; i <= end; i++) 
                    array[i] = h.pop();
            /*逐个取出*/
    }


    6, 堆排序的效率和稳定性分析
    堆排序的时间,主要由建立初始堆和出堆这两部分的时间开销构成.建立堆的时间复杂度为O(nlogn), 出堆的复杂度也为O(nlogn), 总效率为O(nlogn). 而且堆排序不会出现快速排序的最坏情况, 是一种高效的排序算法(尤其是在数据量很大的情况下).
    它是不稳定的排序方法.

    7, 堆的其他用途
    由push和pop可以看到, 堆可以作为高效的优先队列.


    思考
    1, 算法导论中使用了一种更为高效的建堆方法, 可以探究一下.
    2, 试着用递归方法改写push和pop函数


  • 相关阅读:
    Title
    Title
    JS淘宝小广告
    JS淘宝浏览商品
    JS隐藏显示图片
    JS图片轮播
    C#连接数据库的方法
    让$(window).scroll()监听事件只执行一次
    axios发delete请求,后台收不到参数;
    .gitignore规则简介
  • 原文地址:https://www.cnblogs.com/ljt12138/p/6684403.html
Copyright © 2011-2022 走看看