zoukankan      html  css  js  c++  java
  • heap

    堆 heap


    利用完全二叉树的结构来维护一组数据,然后进行相关操作,一般的操作进行一次的时间复杂度在 O(1)~O(logn) 之间。

    完全二叉树

    • 若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。我们知道二叉树可以用数组模拟,堆自然也可以。
    • 从图中可以看出,元素的父亲节点数组下标是本身的1/2(只取整数部分),所以我们很容易去模拟,也很容易证明其所有操作都为log级别~~
    堆还分为两种类型:大根堆、小根堆

    顾名思义,就是保证根节点是所有数据中最大/小,并且尽力让小的节点在上方
    不过有一点需要注意:堆内的元素并不一定数组下标顺序来排序的!!很多的初学者会错误的认为大/小根堆中
    下标为1就是第一大/小,2是第二大/小……

    我们刚刚画的完全二叉树中并没有任何元素,现在让我们加入一组数据吧!
    下标从1到9分别加入:{8,5,2,10,3,7,1,4,6}。

    如下图所示

    现在我就来介绍一下堆的几个基本操作:

    • 上浮 shift_up;
    • 下沉 shift_down
    • 插入 push
    • 弹出 pop
    • 取顶 top
    • 堆排序 heap_sort

    小根堆 为例

    从上述未处理过的数据中可以很容易得出,根节点1元素8绝对不是最小的
    但是它的一个子节点3(元素2)比它小,我们可以将它放到最高点,直接进行交换。
    此外,子节点3的子节点7(元素1)似乎更适合在根节点
    此时,我们无法直接和根节点交换的,那么就是用上浮 shift_up操作来完成。

    操作过程如下

    从当前结点开始,和它的父亲节点比较,若是比父亲节点来的小,就交换,然后将当前询问的节点下标更新为原父亲节点下标;否则退出。

    伪代码如下:

    Shift_up( i )
    {
        while( i / 2 >= 1)
        {
            if( 堆数组名[ i ] < 堆数组名[ i/2 ] )
            {
                swap( 堆数组名[ i ] , 堆数组名[ i/2 ]) ;
                i = i / 2;
            }
            else break;
    }
    

    上浮操作结束后,节点3(元素8)与其子节点7(元素2)的位置并不正确。
    因此,需要节点3下沉

    节点的下沉策略如下所述

    小根堆是尽力要让小的元素在较上方的节点,而下沉与上浮一样要以交换来不断操作。
    让当前结点的子节点(如果存在)作比较,哪个比较小就和它交换,并更新询问节点的下标为被交换的子节点下标,否则退出。

    伪代码如下所示

    Shift_down( i , n )    //n表示当前有n个节点
    {
        while( i * 2 <= n)
        {
            T = i * 2 ;
            if( T + 1 <= n && 堆数组名[ T + 1 ] < 堆数组名[ T ])
                T++;
            if( 堆数组名[ i ] < 堆数组名[ T ] )
            {
               swap( 堆数组名[ i ] , 堆数组名[ T ] );
                i = T;
            }
            else break;
    }
    

    插入操作

    • 如何在插入的时候维护堆?

    每次进行数据插入的时候,往堆的最后插入,然后使用上浮操作。

    伪代码如下所示

    Push ( x )
    {
        n++;
        堆数组名[ n ] = x;
        Shift_up( n );
    }
    

    弹出操作

    使用根节点元素和尾节点进行交换,然后使现在的根元素下沉。

    伪代码如下所示

    Pop ( x )
    {
        swap( 堆数组名[1] , 堆数组名[ n ] );
        n--;
        Shift_down( 1 );
    }
    

    取顶操作

    返回节点数组[0]

    堆排序

    new 新数组,每次取堆顶元素放进去,然后弹掉堆顶

    伪代码如下所示

    Heap_sort( a[] )
    {
        k=0;
        while( size > 0 )
        {
            k++;
            a[ k ] = top();
            pop();    
        }        
    }
    

    堆排序的时间复杂度是O(nlogn)

    堆操作代码

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    
    #define maxn 100010   //这部分可以自己定义堆内存多少个元素 
    
    using namespace std;
    
    struct Heap {
        int size, queue[maxn];
        Heap() {       //初始化
            size=0;
            for(int i=0; i<maxn; i++)
                queue[i]=0;
        }
        void shift_up(int i) { //上浮
            while(i > 1) {
                if(queue[i] < queue[i>>1]) {
                    int temp = queue[i];
                    queue[i] = queue[i>>1];
                    queue[i>>1] = temp;
                }
                i >>= 1;
            }
        }
        void shift_down(int i) { //下沉
            while((i<<1) <= size) {
                int next = i<<1;
                if(next < size && queue[next+1] < queue[next])
                    next++;
                if(queue[i] > queue[next]) {
                    int temp = queue[i];
                    queue[i] = queue[next];
                    queue[next] = temp;
                    i = next;
                } else return ;
            }
        }
        void push(int x) { //加入元素
            queue[++size] = x;
            shift_up(size);
        }
        void pop() {       //弹出操作
            int temp = queue[1];
            queue[1] = queue[size];
            queue[size] = temp;
            size--;
            shift_down(1);
        }
        int top() {
            return queue[1];
        }
        bool empty() {
            return size;
        }
        void heap_sort() {  //另一种堆排方式,由于难以证明其正确性
            //我就没有在博客里介绍了,可以自己测试
            int m=size;
            for(int i = 1; i <= size; i++) {
                int temp = queue[m];
                queue[m] = queue[i];
                queue[i] = temp;
                m--;
                shift_down(i);
            }
        }
    };
    
    int main()
    {
        Heap Q;
        int n,a,i,j,k;
        cin>>n;
        for(i = 1; i <= n; i++) {
            cin >> a;
            Q.push(a); //放入堆内
        }
    
        for(i = 1; i <= n; i++) {
            cout << Q.top() << " ";  //输出堆顶元素
            Q.pop();        //弹出堆顶元素
        }
        return 0;
    }
    
    
  • 相关阅读:
    World Cup
    Eva's Problem
    Number-guessing Game
    WisKey的眼神
    Vowel Counting
    The 3n + 1 problem
    超级楼梯
    母牛的故事
    素数回文
    画8
  • 原文地址:https://www.cnblogs.com/openxyz/p/6923662.html
Copyright © 2011-2022 走看看