数据结构之堆(Heap)及其用途
来源:互联网 发布:纳客软件如何 编辑:程序博客网 时间:2024/05/22 00:17
本文采用图文码结合的方式介绍堆来实现优先队列
什么是优先队列?
队列是一种先进先出(FIFO)的数据结构。虽然,优先队列中含有队列两个字,但是,他一点也不像队列了。个人感觉,应该叫他优先群。怎么说那,一群守秩序(FIFO)的人去排队买东西当然是队列结构。但是,一群不守秩序的人去买东西,当然谁的拳头大谁就先结账。这个拳头的大小就是我们所谓的优先级。哎~我拳头不大~
优先队列的实现方式有:
1.线性表
2.堆(Heap)
3.左高树(Leftist Tree)
本文先从堆(Heap)开始讨论实现优先队列。
介绍堆之前,先介绍一种叫做最大树和最小树的数据结构:
最大(小)树:
1.根的值大(小)于等于所有子树中所有的值
2.子树也是最大(小)树
最大(小)堆:
1.完全二叉树 2.最大(小)树
首先,我们要清楚,最大堆中最大的元素一定出现在根上,最小的元素一定在树的叶子上;第二大的元素一定在第二层上等等。
其次,因为堆是完全二叉树,所以,使用数组描述这种结构最好不过了。
上图是一棵最大堆。
我们想对这中数据结构进行插入、删除时,要如何完成?
插入(就是拉父亲,拉不拉得动就另一说了):
1)当插入5时,因为完全树,所以5要出现在第四层的第三个位置上。5放到这之后,父元素仍然比他大,所以仍然可以构成堆。
2)当插入20时,同样,要出现在4层的第三个上。但是,这时,7比20小了,那么,就把7拿下来,在判断20是不是可以在原7的位置上,不行的话,往下拉父节点,去侵占父节点的位置。
插入时,每一层操作一次,最多操作height次
于是时间复杂度为O(
删除(当然是要删最大的元素了,也就是要删根):
这也是一个最大堆
删掉20时,我们首先由完全树的定义知,第4层的8的未知将消失,我们不妨把8先拿到根上,再进行堆的重构(在左右树都是堆的情况下)。
删除时,每一层最多被修改一次,于是最多修改height次。
时间复杂度时O(
最大堆的初始化(对数组中的元素进行调序):
想一下最大堆,我们要想调序,要从最下层开始,但是,叶子需要调吗?暂时不需要。一个单独的节点就可以看成是一个最大堆了。由最大堆的性质可知,后面
对数组{1,2,3,4,5,6,7,8,9,10,11}进行调序
叶子暂时是不需要重构的。从5开始,对5进行重构和上面的重构方法类似不赘述。对4重构、3、2、1。
分析时间复杂性:
对第i层的某元素进行重构时,最复杂就是 从这个元素到叶子上的一条路径的节点都被修改,时间复杂度为O(height-i+1)。
我们一共需要修改height-1层;且第i层最多有
于是,总时间复杂度为
O(
============================================================================
古人云:“没有代码就是耍流氓。” 代码如下:
/*MaxHeap*/#include"xcept.h"#include<iostream>using namespace std;template<class T>class MaxHeap{public: MaxHeap(int _maxsize=10); ~MaxHeap(){ delete[] heap; }; MaxHeap<T>& Insert(T& t);//将元素t插入最大堆 MaxHeap<T>& Delete(T& t);//将最大堆的根删掉,返回到t中 void Initialize(T t[], int _currSize, int _maxSize);//初始化最大堆 void output(); void Deactive();private: int currentSize; int maxSize; T *heap;//数组存放};template <class T>MaxHeap<T>::MaxHeap(int _maxsize){//构造函数 currentSize = 0; maxSize = _maxsize; heap = new T[maxSize + 1];//第一个元素不使用}template <class T>MaxHeap<T>& MaxHeap<T>::Insert(T& t){//将元素t插入最大堆 if (currentSize == maxSize) //满了拒绝插入 throw NoMem(); int i = ++currentSize; while (i != 1 && t > heap[i / 2]){ heap[i] = heap[i / 2];//拉下父亲来 i = i / 2; } heap[i] = t; return *this;}template <class T>MaxHeap<T>& MaxHeap<T>::Delete(T& t){//删除最大元素 if (currentSize == 0){//堆是空的,拒绝 throw OutofBounds(); } t = heap[1]; T tail = heap[currentSize--]; //重构 int i = 1; int ci = 2;//记录i的孩子 while (ci<=currentSize) { //找最大的孩子 if (ci<currentSize && heap[ci+1]>heap[ci]){ ci = ci + 1; } //判断是不是可以在当前位置i if (tail > heap[ci]){//yes break; } //no heap[i] = heap[ci];//把最大的孩子拿上去 i = ci; //判断位置下移 ci = i * 2;//仍记录孩子 } heap[i] = tail; return *this;}template <class T>void MaxHeap<T>::Initialize(T t[], int _currSize, int _maxSize){ delete[]heap; heap = t; maxSize = _maxSize; currentSize = _currSize; //重构 for (int i = maxSize / 2; i > 0; i--){ T data = heap[i];//对第i个元素进行重构 int ci = i * 2; while (ci <= currentSize) { //找最大的孩子 if (ci<currentSize && heap[ci + 1]>heap[ci]){ ci = ci + 1; } //判断是不是可以在当前位置i if (data > heap[ci]){//yes break; } //no heap[ci/2] = heap[ci];//把最大的孩子拿上去 //判断位置下移 ci = ci * 2;//仍记录孩子 } heap[ci/2] = data; }}template<class T>void MaxHeap<T>::Deactive(){ heap = 0;}template<class T>void MaxHeap<T>::output(){ for (int i = 1; i <= currentSize; i++) { cout << heap[i] << " "; } cout << endl;}
class NoMem{public : NoMem(){}};class OutofBounds{public: OutofBounds(){ }};
这样,我们保证了每次出”队列”的都是最大的元素。即有了优先性。
========================================================================
堆这种基本的出入模式,可以完成一个人人皆知的排序算法—-堆排序。这是一种排序快且稳定的算法。
用堆来实现排序,看下面的代码:
#include"MaxHeap.h"template <class T>void heapSort(T arr[],int currSize,int maxSize){ MaxHeap<T> mh; mh.Initialize(arr, currSize, maxSize); for (int i = currSize; i >0; i--) { T temp; mh.Delete(temp); arr[i] = temp; } //不让mh删掉arr mh.Deactive(); for (int i = 1; i <= currSize; i++) { cout << arr[i] << " "; }}
调用
//第一个位置不放元素 int x[20] = {0,4,2,3,8,9,1,5}; heapSort(x, 7, 20);
结果
分析一下时间复杂度:
创建最大堆:n
删掉一个元素:
删掉全部的元素:
总的操作次数:
则总的时间复杂度****O(
================================================================
同样,我们也可类似的给出最小堆的生成方法。
使用最小堆结构我们还可以实现一个特殊的树结构–哈弗曼树(Haffman Tree)。
哈弗曼树可以用来给文本压缩、也可以解决一些优化问题。介绍一下文本的压缩吧,其实压缩就是一种特殊的优化。
有一个文本若是由1000个字符组成,且字符只有a、b、c、d组成,那就有1000个字节,即8000位数据。
若是将a表示成00,b表示成01,c表示成10,d表示成11。那么,我们表示这一个文件只需要2000位,即250字节就可以表示。节省了四分之三的空间,是不是很爆炸。
那我们要是把编码改成a:0、b:1、c:00、d:01,是不是可以那,要是可以那我们这样来编码是不是更省空间啊。看下面的例子:
abaccd=>010000001,这是将文本压缩的方式,但是我们能不能从这种编码将文本解压缩回去啊?010000001=>?显然是不能的。
那么,我们就需要对编码进行限制,任意编码不能是其他编码的前缀。于是,二叉树这种结构就很好用了,将节点前往左孩子的路径上标0,前往右孩子的路径上标1,因为任意两个叶子的路径一定不会是前缀的关系。但是,有四个叶子的二叉树的结构有很多,我们要选择哪种啊?不难想,我们要是尽量把文本压缩的小,那么我们就要让出现越多的字符替换的长度越短。于是,我们要统计文本中的各个字符的数目。于是,我们就需要来决定每个不同频率字符在树中的位置了。
我们给出haffman tree树的定义:
记WEP为加权外部路径的长度。L(i)跟根到外部节点i的距离,F(i)到外部节点的i的权值(也就是我们的频率)。
haffman tree就是对给定的频率建立的最小加权外部路径长度的二叉树。
进行文本压缩:
1.确定文本的字符数目。
2.构建字符对应的haffman tree
3.确定字符对应的haffman 编码
4.使用haffman 编码进行压缩
简单介绍一下如何构建一棵haffman tree:
1.将每一个字符建成二叉树的外部节点。
2.选取两棵权值最小的树进行合并。新树的权值为两个小树的权值之和。
3.重复2,直至剩下一棵树。
于是:我们可以得到haffman 编码
a:00
b:010
c:011
d:100
e:101
f:11
实现代码:
#Haffman.h#
#include"MinHeap.h"#include"BinaryTree.h"#include "Stack.h"class HaffmanNode{ //一个haffman tree的节点包含权值和二叉树public: bool operator>(HaffmanNode& a){ if (weight > a.weight) return true; else return false; } bool operator<(HaffmanNode& a){ if (weight < a.weight) return true; else return false; } int weight;//权值 BTree<int> btree;//二叉树};//创建haffman树BTree<int>& haffman(int a[], int n){ //a[1...n]为每一个字符的权值,n为有几个字符 HaffmanNode *hnodes = new HaffmanNode[n + 1];//从第一个开始使用 BTree<int> treeInHaff,zero; //将每个节点初始化成一个带权的haffman树 for (int i = 1; i <= n; i++) { treeInHaff.makeTree(1,zero,zero);//1没有意义随便写 hnodes[i].weight = a[i]; hnodes[i].btree = treeInHaff; } //存放haffman树的最小堆,用来找最小的haffman树 MinHeap<HaffmanNode> t(1); t.Initialize(hnodes, n, n); //合并两个最小haffman树,n个元素进行n-1次的合并 for (int i = 1; i <n; i++) { //找出两个最小的 HaffmanNode temp1; t.Delete(temp1); HaffmanNode temp2; t.Delete(temp2); //对这两棵树进行合并操作 int data = temp1.weight + temp2.weight; treeInHaff.makeTree(1,temp1.btree, temp2.btree);//1没有意义,随便写 HaffmanNode opResult;//两颗最小的树的合并结果 opResult.btree = treeInHaff; opResult.weight = data; t.Insert(opResult);//一定要存回 } HaffmanNode lastTree; //最后的一颗树就是结果 t.Delete(lastTree); return lastTree.btree;};//显示构造的haffman tree的haffman编码void show(BTNode<int> *t,Stack<int>& s){ if (t) { //当左右都变成空了,我们就输出这时的01序列 if (!t->lchild && !t->rchild) { s.showStatus(); } else { s.push(0);//进入左孩子,标0 show(t->lchild, s); int x; s.pop(x); s.push(1);//进入右孩子,标1 show(t->rchild, s); s.pop(x); } }}
#MinHeap.h#
#include<iostream>using namespace std;template<class T>class MinHeap{public: MinHeap(int _Minsize = 10); ~MinHeap(){ delete[] heap; }; MinHeap<T>& Insert(T& t);//将元素t插入最大堆 MinHeap<T>& Delete(T& t);//将最大堆的根删掉,返回到t中 void Initialize(T t[], int _currSize, int _maxSize);//初始化最大堆 void Deactive();//heap = 0private: int currentSize; int maxSize; T *heap;//数组存放};template <class T>MinHeap<T>::MinHeap(int _maxsize){//构造函数 currentSize = 0; maxSize = _maxsize; heap = new T[maxSize + 1];//第一个元素不使用}template <class T>MinHeap<T>& MinHeap<T>::Insert(T& t){//将元素t插入最大堆 if (currentSize == maxSize) //满了拒绝插入 throw NoMem(); int i = ++currentSize; while (i != 1 && t < heap[i / 2]){ heap[i] = heap[i / 2];//拉下父亲来 i = i / 2; } heap[i] = t; return *this;}template <class T>MinHeap<T>& MinHeap<T>::Delete(T& t){//删除最大元素 if (currentSize == 0){//堆是空的,拒绝 throw OutofBounds(); } t = heap[1]; T tail = heap[currentSize--]; //重构 int i = 1; int ci = 2;//记录i的孩子 while (ci <= currentSize) { //找最小的孩子 if (ci<currentSize && heap[ci + 1]<heap[ci]){ ci = ci + 1; } //判断是不是可以在当前位置i if (tail < heap[ci]){//yes break; } //no heap[i] = heap[ci];//把最小的孩子拿上去 i = ci; //判断位置下移 ci = i * 2;//仍记录孩子 } heap[i] = tail; return *this;}template <class T>void MinHeap<T>::Initialize(T t[], int _arrCurr, int _arrMax){ //_arrCurr传进数组的当前下标,_arrMax最大下表 delete[]heap; heap = t; maxSize = _arrMax; currentSize = _arrCurr; //重构 for (int i = maxSize / 2; i > 0; i--){ T data = heap[i];//对第i个元素进行重构 int ci = i * 2; while (ci <= currentSize) { //找最小的孩子 if (ci<currentSize && heap[ci + 1]<heap[ci]){ ci = ci + 1; } //判断是不是可以在当前位置i if (data < heap[ci]){//yes break; } //no heap[ci / 2] = heap[ci];//把最小的孩子拿上去 //判断位置下移 ci = ci * 2;//仍记录孩子 } heap[ci / 2] = data; }}template<class T>void MinHeap<T>::Deactive(){ heap = 0;}
#BinaryTree.h#
template<class T>class BTNode{public: BTNode(){ data = 0; lchild = 0; rchild = 0; } BTNode(T _data, BTNode *_lchild, BTNode *_rchild){ data = _data; lchild = _lchild; rchild = _rchild; } T data; BTNode *lchild; BTNode *rchild;};template <class T>class BTree {public: BTree(){ root = 0; } void makeTree(const T& data, BTree<T> &l, BTree<T> &r){ root = new BTNode<T>(data, l.root, r.root); l.root = 0; r.root = 0; }; BTNode<T> *root;};
#stack.h#
#include<iostream>using namespace std;/*使用数组来实现Stack*/template<class T>class Stack{public: Stack(int maxtop=20); ~Stack(){ delete[] element; } bool isEmpty(){ return _top == -1; }; bool isFull(){ return _top == MaxTop - 1; }; Stack<T>& push(const T& t);//入栈 Stack<T>& pop(T& r);//出栈 void showStatus();//显示栈的元素,从栈低private: int _top; //记录栈顶的位置 int MaxTop;//记录总的栈的大小 T *element;//存放元素};template<class T>Stack<T>::Stack(int maxtop){//构造函数 element = new T[maxtop]; MaxTop = maxtop; _top = -1;}template<class T>Stack<T>& Stack<T>::push(const T& t){//入栈 if (isFull())throw OutofBounds(); _top++; element[_top] = t; return *this;}template<class T>Stack<T>& Stack<T>::pop(T& r){//出栈 if (isEmpty()) throw OutofBounds(); r = element[_top]; _top--; return *this;}template<class T>void Stack<T>::showStatus(){ //显示栈的元素,从栈低开始显示 //因为我们是从根开始往里放的,那么,我们就要从下往上输出 for (int i = 0; i <= _top; i++){ cout << element[i] << " "; } cout << endl;}
#xcept.h#
//定义了两个异常类class NoMem{public : NoMem(){}};class OutofBounds{public: OutofBounds(){}};
测试代码:
#include"xcept.h"#include<iostream>using namespace std;#include"HaffmanTree.h"void main(){ //第一个位置不放元素的权值,第一个元素的权值放在x[1] int x[7] = { 0,1,2,4,5,6,7}; BTree<int> tree = haffman(x,6); Stack<int> s(20); show(tree.root, s);}
测试结果:
经过上面的代码,我们能生成haffman编码了。
相信有了编码,对屏幕前的你来说不能实现 压缩了。
==============================================================================
那么,我们想使用haffman树来解决优化问题也很简单,只需要把数组中的权值换成你要解决问题的权值(这个权值可能是需要你用一定的运算来算出)。我们就不对优化详细介绍了,因为,haffman编码的生成实际实际上就是优化问题。
- 数据结构之堆(Heap)及其用途
- 数据结构之堆Heap
- 数据结构之堆(Heap)
- 数据结构之堆(Heap)(9)
- 数据结构 之 二叉堆(Heap)
- 数据结构之堆(Heap)的实现
- Heap——数据结构之堆
- 数据结构之Binary Heap(二叉堆)
- 【数据结构】堆 Heap
- 【数据结构】堆(heap)
- 数据结构 《6》----堆 ( Heap )
- 数据结构-树-堆(heap)
- 数据结构-堆(heap)
- 【数据结构】堆(heap)
- 数据结构-堆(Heap)
- 数据结构-堆(heap)
- 数据结构:堆Heap
- 数据结构:堆(heap)
- windows:服务启动、暂停、停止按钮不能点击,是灰色如何解决
- 源码方式向openssl中添加新算法完整详细步骤(示例:摘要算法SM3)【非engine方式】
- GreenDao 3.2.0官网介绍的部分翻译(一)
- 腾讯云CentOS7.2 配置PHP7 MySQL 5.7.10
- 进程介绍及和线程的关系
- 数据结构之堆(Heap)及其用途
- 说说Xcode LLDB调试的那些事儿
- git常用指令
- ZJOI2012 network splay
- 非常简单,教你用OpenGL读入obj模型
- docker image保存和恢复
- 制作android中的红点数字提醒
- Android: app不被系统kill掉
- Redis设计与实现-客户端服务端与事件