赞
踩
堆(heap)分为二叉堆、二项式堆、斐波那契堆,堆是非线性数据结构,相当于一维数组,有两个直接后继。堆又被称为优先队列,尽管名为优先队列,但堆并不是队列。因为队列遵循First in, First out,但是堆是按照元素的优先级取出元素。所以“堆”是实现调度器的理想数据结构。
堆排序与快速排序、归并排序一样都是时间复杂度为O(N*logN)的排序方法。
在这里讲二叉树是因为堆通常是一个可以被看做一棵完全二叉树的数组对象。
二叉树(Binary Tree)是每一个节点最多有两个分支的树结构,通常分支被称作左子树和右子树,分支具有左右次序,不能随意颠倒。二叉树第i
层最多拥有 2 ^ (i - 1)
个节点,深度为k
的二叉树最多共有2 ^ (k + 1) - 1
个节点。
假设某个二叉树深度为 k
,第 i
层拥有 2 ^ (i - 1)
个节点,且总共拥有 2 ^ (k + 1) - 1
个节点,这样的二叉树称为满二叉树。简单来说就是,二叉树的每一层都是满的,除了最后一层上的节点,每一个节点都具有左节点和右节点。
完全二叉树是由满二叉树引出来的,是效率很高的数据结构,若设二叉树的深度为h
,除第 h
层外,其它各层 (1~h-1
) 的结点数都达到最大个数,第 h
层所有的结点都连续集中在最左边,这就是完全二叉树。
对于深度为K
的,有n
个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1
至n
的结点一一对应时称之为完全二叉树。
K
层或K - 1
层(层次最大的两层)L
,则其左子树的最大层次为L或L + 1
。这里有一点需要强调:满二叉树以一定是完全二叉树,但完全二叉树不一定是满二叉树。
二叉堆是一种特殊的堆,二叉堆是完全二元树(二叉树)或者是近似完全二元树(二叉树)。
二叉堆分为两种:大顶堆和小顶堆
因为二叉堆是一个完全二叉树,所以其必须满足上面讲述的完全二叉树的条件。再而,为了实现堆的操作,我们额外增加一个要求: 任意节点的优先级不小于它的子节点
。
所以总结,二叉堆满足以下两个特性:
在下面的讲述中采用大顶堆来进行实现。
一般都用数组来表示堆,i
为数组的下标,那么该节点的父节点下标为i / 2
。它的左右子节点下标分别为2 * i
和2 * i + 1
。
说到这里,大家应该可以将堆和树联系在一起了,同时又因为堆通常是一个可以被看做一棵完全二叉树的数组对象。为了方便后面的操作(像插入、删除…),所以堆的结构就并不是左指针和右指针了,而是以数组的形式。
在实现堆的过程中,我们需要先创建一个堆,堆的底层是数组,由于其第一个位置不存放真实的值,所以在申请空间的时候多申请一个。
#include <iostream> #include <algorithm> #include <string> #include <ctime> #include <cmath> #include <cassert> using namespace std; template<typename Item> class MaxHeap{ private: Item* data; int count; public: MaxHeap(int capacity){ data = new Item[capacity + 1]; count = 0; this->capacity = capacity;//避免数组长度越界 } ~MaxHeap(){ delete [] data; } int size(){ return count; } bool isEmpty(){ return count == 0; } }; int main() { MaxHeap<int> maxHeap = MaxHeap<int>(100); }
向其中插入元素
void insert(Item item){
//避免数组长度越界,如越界则不执行
assert(count + 1 <= capacity);
data[count + 1] = item;
count ++;
//在进行插入操作后,可能破坏堆的结构,所以进行上溯操作
percolate_up( count );
}
//考虑索引越界的问题,k必须大于1才能有父节点
void percolate_up (int k){
while (k > 1 && data[k / 2] < data[k]){
swap(data[k / 2],data[k]);
k = k / 2;
}
}
测试插入元素
int main() {
MaxHeap<int> maxHeap = MaxHeap<int>(100);
srand(time(NULL));
for (int i = 0; i < 15; i++) {
maxHeap.insert(rand()%100);
}
maxHeap.testPrint();
return 0;
}
从堆中取出(删除操作)
Item extractMax(){ assert(count > 0); Item ret = data[1]; swap(data[1],data[count]); count --; percolate_down(1); return ret; } void percolate_down(int k){ while (2 * k <= count){ int j = 2 * k; //判断此节点是否有右孩子节点,如果有,并且右孩子节点的值大于左孩子节点, //那么与k交换的就是右孩子节点 if (j + 1 <= count && data[j + 1] > data[j]){ j = j + 1; } if (data[k] >= data[j]) break; swap(data[k],data[j]); k = j; } }
测试取出元素
maxHeap.extractMax();
通过上面的学习我们知道了堆的存储结构以及如何用代码来实现堆的插入和删除,在进行堆插入的时候,按照堆的结构进行存储,当我们弹出元素的时候,永远弹出的是堆中的最大值。我们看一下下面的代码以及他的结果。
while( !maxHeap.isEmpty()){
cout << maxHeap.extractMax()<<" ";
}
我们发现了是一组有序的数据,这里就引申出了堆排序。
#include <iostream>
#include "Heap.h"
using namespace std;
template <typename T>
void headSort1(T arr[], int n){
MaxHeap<T> maxHeap = MaxHeap<T>(n);
for (int i = 0; i < n; i++) {
maxHeap.insert(arr[i]);
}
for (int j = n - 1; j >= 0; j --) {
arr[j] = maxHeap.extractMax();
}
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。