zoukankan      html  css  js  c++  java
  • 堆(heap)详解

    堆就是一棵完全二叉树

    堆算法的简介:

    堆的主要作用就是是排序,分了大根堆和小根堆,大根堆顾名思义就是任何父节点都比(large{ ext{它的}})子节点大的完全二叉树,小根堆反之。堆是为了能够很方便第找出或者更新一个一维数组的最大/小值。在一些找最大/小值的算法里就特别好用,而堆的算法的核心当然就是维护这个性质了(当然也可以使用(STL)中自带的函数,当然这个后面ge也会说):

    维护堆的操作:

    [ ext{以小根堆为例} ]

    1.上浮(push_up/float)

    上浮指的是将任意一个节点从下向上挪并且保证小根堆性质不改变(即保证任何父节点都比(large{ ext{它的}})子节点小),过程很简单,就是(large{ ext{一步步}})地从下往上挪。注意:一定是一步步的,不能一下子从叶子节点变成根节点(不能一口吃个胖子)。既然是一步步的,交换当前的父节点和子节点即可完成!

    [ ext{很简单吧,但还是证明一下不然我会怀疑} ]

    那么为什么一步步挪就能满足条件呢,因为一个小根堆是肯定满足父节点比(large{ ext{它的}})子节点小,所以如果新的节点(A)需要上浮(比如 (A) 是一个新插入的叶子节点)那么当它的父节点 (B) 比它大时,将 (B) 换为叶子节点,此时 (B) 没有儿子,满足条件。然后现在 (A) 的父节点是 (C) 那么肯定有 (C<B),(因为 (B) 之前当子节点时是满足父节点比它的子节点小的),如果此时 (A) 有另一个子节点(D),(即原来(B)的子节点)所以(B<D),又因(C<B),所以(C<D),所以此时如果是 (C) 做它们的父节点不会破坏这一部分的性质,而 $ A$ 也是如此,因为 (A<B,B<D),所以(A<D)。那么此时就可以随意地去比较 (A,C) 了,因此只要一步步地往上爬就能保留堆的原有性质不变。(我要一步一步往上爬~)

    [ ext{只要不急,随你挪啦!} ]

    那么上浮就很容易搞定了,完全二叉树最好的地方就是可以直接用(int(frac{i}{2}))来表示一个点的父节点,在程序里就更方便了,自动帮你取整,所以代码就很容易实现了!

    void push_up(int i)//将i点上浮 
    {
         while(i>1)
    	 {
    	     if(heap[i]<heap[i>>1])
    		 {
    		 	swap(heap[i],heap[i>>1]);
    		 	i>>=1; 
    		 }
    		 else
    		 {
    		 	return;
    		 }
    	 }
    }
    

    2.下沉(push_down/sink)

    下沉一般是用来删除节点用的,比如你想要迫害一个中间的节点,你就可以让它下沉到底部,然后一刀剪掉(R.I.P)。这个比上浮还简单,因为下沉只要比较子节点中较小的一个并交换即可。

    [ ext{还是证明一下:} ]

    我们假设要迫害点 (A),我们看到 (A) 有俩儿子(BC)(B<C),因为此时如果用 (C) 来顶替 (A) 就会导致 (C)作为父节点应当比 (B) 小,然后我们已知(B<C),所以我们只能选(B),于是我们将 (A)(B) 交换即可。然后设此时B有儿子 (DE) ,而我们选 (min(D,E))(A) 交换(设 (D<E)),那么因为 (B) 原本是它们的父亲,所以 (B<D),将 (D) 换上去是毫无问题的。由此可得,只要选两个儿子中最小的那个交换即可满足咯!代码实现:

    void push_down(int i)//将i点下沉 
    {
    	while((i<<1)<=n)
    	{
    		int next=(i<<1);
    	    if(heap[next]>heap[next+1]&&next+1<=n)next++;
    		if(heap[next]<heap[i])swap(heap[i],heap[next]);
    		else break;
    		i=next;
    	}
    }
    

    那么基本操作就到这里咯,然后还有STL的做法值得参考,先上代码(洛谷的堆模板):

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    using namespace std;
    int heap[5000005],n;
    void push_up(int i)//将i点上浮 
    {
         while(i>1)
    	 {
    	     if(heap[i]<heap[i>>1])
    		 {
    		 	swap(heap[i],heap[i>>1]);
    		 	i>>=1; 
    		 }
    		 else
    		 {
    		 	return;
    		 }
    	 }
    }
    void push_down(int i)//将i点下沉 
    {
    	while((i<<1)<=n)
    	{
    		int next=(i<<1);
    	    if(heap[next]>heap[next+1]&&next+1<=n)next++;
    		if(heap[next]<heap[i])swap(heap[i],heap[next]);
    		else break;
    		i=next;
    	}
    }
    
    void add(int i)//加值为i的点 
    {
    	heap[++n]=i;
    	push_up(n);
    }
    
    void pop(int i)//删除处于i位置的点 
    {
    	swap(heap[i],heap[n--]);
    	push_down(i);
    }
    //上面是堆操作,下面根据题目自行改变 
    int main()
    {
    	int m;
    	scanf("%d",&m);
    	for(int i=1;i<=m;++i)
        {
        	int ope,num;
        	scanf("%d",&ope);
        	switch(ope)
        	{
        		case 1:
        			scanf("%d",&num);
        			add(num);
        			break;
        		case 2:
        			printf("%d
    ",heap[1]);
        			break;
        		case 3:
        			if(n>0)
        			pop(1);
        			break;
        	}
        }
        return 0;
    }
    

    [large{ ext{然后我们来看一下$STL$:}} ]

    (STL)中的堆人称"优先队列"(@Edmundino的最爱):

    //优先队列身份证:
    
    I.住址(头文件):<queue>
    
    II.座机电话(调用):priority_queue<int>a;(大根堆)
    
    III.手机电话(调用):priority_queue<int,vector<int>,greater<int> > a;(小根堆)
    
    IV.不动产(基本操作):
    
      a.size():返回堆内元素个数;
      a.empty():如果堆为空,返回true,否则返回false;
      a.top():返回堆顶元素。
      a.pop():删除堆顶元素,保姆STL自动帮忙整理;
      a.push(x):插入一个元素x,同上自动整理。
    
    

    注意!!!:

    [ ext{结构体中比较某一变量} ]

    struct node
    {
        int compare,another;
        bool operator<(const node &x)const
        {
            return x.compare<compare
        }//小根堆,大根堆则将'<'改成'>'即可
    };
    priority_queue<node>q;
    

    那么我们用优先队列来 (A) 一下模板(真的是......非常非常非常简单啊QAQ):

    #include<iostream>
    #include<queue>
    #include<cstring>
    #include<cstdio>
    using namespace std;
    priority_queue<int,vector<int>,greater<int> > heap;//> >之间注意空格 
    int main()
    {
    	int m;
    	scanf("%d",&m);
    	for(int i=1;i<=m;++i)
        {
        	int ope,num;
        	scanf("%d",&ope);
        	switch(ope)
        	{
        		case 1:
        			scanf("%d",&num);
        			heap.push(num);
        			break;
        		case 2:
        			printf("%d
    ",heap.top());
        			break;
        		case 3:
        			if(!heap.empty())
        			heap.pop();
        			break;
        	}
        }
        return 0;
    }
    
    

    [ ext{诶,早知道就不手打了QAQ} ]

    填坑第(huge{4})站完成!!!

    //Good luck
  • 相关阅读:
    CShop Project 082: 获取分页数据模型并传递到页面上
    CShop Project 08: 展示不同类型的商品
    CShop Project 08: 开发商品分类的查询和展示
    119 类和数据类型
    118 对象的绑定方法
    117 对象的属性查找顺序
    116 定制对象独有特征
    115 类和对象
    114 面向对象编程介绍
    113 面向对象程序设计的由来(了解)
  • 原文地址:https://www.cnblogs.com/tale365/p/14206137.html
Copyright © 2011-2022 走看看