数据结构与算法(七)堆

优先级队列:按照事先约定的优先级,可以始终高效查找并访问优先级最高数据项的数据结构。

可以将堆继承于向量的数据结构,优先级队列ADT的size(),insert(),getMax(),delMax()四个接口。

堆的成员:词条也是向量的元素,有大小N确定。

应用接口:可反复使用delMax接口实现排序算法。

如何确保insert(),getMax(),delMax()的时间复杂度均可达到o(logn);

无论是向量还是列表均无法实现该要求,无需保证全体词条之间的全序关系。

有限偏序集的极值必然存在,故因此借助堆结构来实现,也就是树结构特例。

完全二叉堆的其中一种:完全二叉树,条件二:根节点均不大于或不小于子节点。

大顶堆:优先级最高的词条在堆顶为大顶堆。优先级最低的词条在堆顶,为小顶堆。

基于向量结构实现堆结构。完全二叉树可用向量实现。节点之间的关系由位置RANK确定。

1.词条插入堆中:将词条添加到向量末尾,再对堆执行上滤操作。

2.获取优先级最高或最低的词条:返回向量的首单元。

3.删除优先级最高的的词条:堆顶词条=末尾词条,舍弃末尾词条,对新堆顶实现下滤。

堆的上滤操作:判断该元素是否有父亲;
            若有,看是否逆序,若逆序,则调换
            直到不再逆序或达到堆顶。

由于完全二叉堆中元素均按照层次遍历存储在向量结构中,所以各节点在物理上连续,可利用RANK判断父子关系。
只要i>0,则有父节点=i/2-1;
i*2+1满足[0,N)之间,则合法,且左孩子为i*2+1

i*2+2满足[0,N)之间,则合法,且右孩子为i*2+2

堆的下滤操作: 判断i是否有两个孩子,有,则与最大的那个交换,
             若只有一个左孩子,则与左孩子看最大的那个交换。
       
建堆:给定一组词条,如何高效地将他们建成堆。
方法一:insert()插入法,O(NLOGN);
方法二:将所有词条插入向量中,自顶向下对每个词条进行上滤操作。O(LOGN).深度
方法三:Floyd算法,自下而上,依次对每一个内部节点进行下滤。O(N)。高度
方法三优于方法二的原因是:在完全二叉树树中,深度小的点,远远少于高度小的节点。
堆的构建:
#define Rank int
#parent(i) (i-1)>>1
#define InHeap(n,i)    -1<i && i<n
#define LastInternal(n) Parent(i);
#define orderNot(i,j)  i<j or j<i 大顶堆与小顶堆之区别。
#define parentvalid(i) 0<i
#define best(heap,n,i) RchildValid(n,i) ? big(heap,big(heap,i,Lchild(i),Rchild(i))):
Lchildvalid(n,i)?big(heap ,i,Lchild(i)):i
template<typename T>class Heap:{
        private: 
                vector<T>heap;
        protcted:
                RANK downfind(rank n,rank i);
                Rank upfind(rank i);
                void heapify(rank n);
                void copyFrom(T const*A,rank lo,rank hi);
        public:
               Headp(){}
               Heap(T*A,rank n){copyFrom(A,0,n);heapify(n);}
               void insert(T);
               T getMax();
               T delmax();

};
template<typename T>Heap<T>::copyFrom(T const*A,rank lo,rank hi){
    while(lo<hi){
        heap.clear();
        heap.push_back(A[lo++]);
    }
    heapify(hi);
}
template<typename T>Heap<T>::heapify(rank n){
    for(int i=LastInternal(n);InHeap(n,i);i--){
        downfind(n,i);
    }
}
template<typename T>rank Heap<T>::upfind(rank i){
    while(parentvalid(i)){
        rank j=parent(i);
        if(orderNot(heap[i],heap[j])) break;
        swap(i,j);i=j;
    }
    return i;
}SWAP在utility中。
template<typename T>rank Heap<T>::downfind(rank n,rank i){
    rank j;
    while(i!=(j=(best(heap,n,i)))){
        swap(heap[i],heap[j]);i=j;
    }
    return i;
}

堆排序:反复调用delmax()方法,实际运行时间往往高于O(nlogn)时间。

也就证明堆结构优于向量结构。

左式堆:除了标准的插入和删除操作,堆结构的另一个常见操纵为合并。由列表构成。

方法一:反复取出堆的最大词条并插入另外一个堆中。

建堆方法;

优先级容器priority_queue<int,vector<T>,less<T>>。priority_queue 模板有 3 个参数,其中两个有默认的参数;第一个参数是存储对象的类型,第二个参数是存储元素的底层容器,第三个参数是函数对象,它定义了一个用来决定元素顺序的断言。当然,如果指定模板的最巵一个参数,就必须提供另外的两个模板类型参数。

std::vector<int> values{21, 22, 12, 3, 24, 54, 56};

std::priority_queue<int,vector<T>,less<T>> numbers {std::less<int>(),values};

方法如下:

  • push(const T& obj):将obj的副本放到容器的适当位置,这通常会包含一个排序操作。
  • push(T&& obj):将obj放到容器的适当位置,这通常会包含一个排序操作。
  • emplace(T constructor a rgs...):通过调用传入参数的构造函数,在序列的适当位置构造一个T对象。为了维持优先顺序,通常需要一个排序操作。
  • top():返回优先级队列中第一个元素的引用。
  • pop():移除第一个元素。
  • size():返回队列中元素的个数。
  • empty():如果队列为空的话,返回true。
  • swap(priority_queue<T>& other):和参数的元素进行交换,所包含对象的类型必须相同。
  • priority_queue 是一个堆。在底层,一个 priority_queue 实例创建了一个堆它可以自动保持元素的顺序;但我们不能打乱 priority_queue 的有序状态,因为除了第一个元素,我们无法直接访问它的其他元素。如果需要的是一个优先级队列,这一点非常有用。

make_heap():堆(heaps)不是容器,而是一种特别的数据组织方式。堆一般用来保存序列容器。默认使用的是 < 运算符,可以生成一个大顶堆。类似sort()。例如:

std::make_heap(std::begin(numbers), std:rend(numbers),greater<>());

添加元素和移除元素到堆:push_heap()会默认最后一个元素是新添加到堆中的,所以会执行上滤或下滤操作。

  1. numbers.push_back(11); // Result: 12 10 3.5 6.5 8 2.5 1.5 6 11
  2. std::push_heap(std::begin(numbers), std::end(numbers));
  3. std::pop_heap(std::begin(numbers),std::end(numbers));
  4. // Result:10 8 3.5 6.5 6 2.5 1.5 12
  5. numbers.pop_back();// Result:10 8 3.5 6.5 6 2.5 1.5
  6. std::is_heap(std::begin(numbers),std::end(numbers),std::greater<>())。

STL 提供的最后一个操作是 sort_heap(),它会将元素段作为堆来排序。如果元素段不是堆,程序会在运行时崩溃。所以需要判断该向量是否为堆。这个函数有以两个迭代器为参数的版本,迭代器指向一个假定的大顶堆(用 less<> 排列),然后将堆中的元素排成降序。结果当然不再是大顶堆。尽管堆并不是全部有序的,但任何全部有序的序列都是堆。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值