本文采用图文码结合的方式介绍堆来实现优先队列

什么是优先队列?
队列是一种先进先出(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( log2n log_2n)

删除(当然是要删最大的元素了,也就是要删根):


这也是一个最大堆

删掉20时,我们首先由完全树的定义知,第4层的8的未知将消失,我们不妨把8先拿到根上,再进行堆的重构(在左右树都是堆的情况下)。



删除时,每一层最多被修改一次,于是最多修改height次。
时间复杂度时O( log2n log_2n)。

最大堆的初始化(对数组中的元素进行调序):
想一下最大堆,我们要想调序,要从最下层开始,但是,叶子需要调吗?暂时不需要。一个单独的节点就可以看成是一个最大堆了。由最大堆的性质可知,后面 ⌈n/2⌉ \lceil n/2\rceil个元素是叶子。那么,我们就从第[n/2]个元素开始对其进行调序。

对数组{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层最多有 2i−1 2^{i-1}个元素。
于是,总时间复杂度为
O( (height−i+1)∗(height−1)∗2i−1∀i∈1,2,3,...,height−1 (height-i+1)*(height-1)*2^{i-1}\quad\forall i\in\mathbb {1,2,3,...,height-1})=O( 2h 2^h)=O(n)

============================================================================

古人云:“没有代码就是耍流氓。” 代码如下:

/*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;}//判断是不是可以在当前位置iif (tail > heap[ci]){//yesbreak;}//noheap[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;}//判断是不是可以在当前位置iif (data > heap[ci]){//yesbreak;}//noheap[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删掉arrmh.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
删掉一个元素: log2n log_2n
删掉全部的元素: n∗log2n n*log_2n
总的操作次数: n+n∗log2n n+n*log_2n
总的时间复杂度****O( nlogn nlogn)(这是最快的排序了吧,好像是)

================================================================

同样,我们也可类似的给出最小堆的生成方法。

使用最小堆结构我们还可以实现一个特殊的树结构–哈弗曼树(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的权值(也就是我们的频率)。

WEP=∑i=1nL(i)∗F(i)

WEP=\sum_{i=1}^n L(i)*F(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;elsereturn false;}bool operator<(HaffmanNode& a){if (weight < a.weight)return true;elsereturn 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);//进入左孩子,标0show(t->lchild, s);int x;s.pop(x);s.push(1);//进入右孩子,标1show(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 = 0
private: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;}//判断是不是可以在当前位置iif (tail < heap[ci]){//yesbreak;}//noheap[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;}//判断是不是可以在当前位置iif (data < heap[ci]){//yesbreak;}//noheap[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)及其用途相关推荐

  1. codeforces 贪心+优先队列_算法与数据结构基础 - 堆(Heap)和优先级队列(Priority Queue)...

    堆基础 堆(Heap)是具有这样性质的数据结构:1/完全二叉树 2/所有节点的值大于等于(或小于等于)子节点的值:

  2. 算法与数据结构基础 - 堆(Heap)和优先级队列(Priority Queue)

    堆基础 堆(Heap)是具有这样性质的数据结构:1/完全二叉树 2/所有节点的值大于等于(或小于等于)子节点的值: 图片来源:这里 堆可以用数组存储,插入.删除会触发节点shift_down.shif ...

  3. 数据结构之堆Heap

    1. 概述 堆(也叫优先队列),是一棵完全二叉树,它的特点是父节点的值大于(小于)两个子节点的值(分别称为大顶堆和小顶堆).它常用于管理算法执行过程中的信息,应用场景包括堆排序,优先队列等. 2. 堆 ...

  4. Java数据结构之堆(Heap)

    文章目录 一.基本概念 二.上浮操作(siftUp) 三.下沉操作(siftDown) 四.数组堆化 五.实现大根堆 提示:以下是本篇文章正文内容,Java系列学习将会持续更新 一.基本概念 堆在逻辑 ...

  5. 数据结构-堆(Heap)

    数据结构-堆(Heap) 我认识的堆: 1.建立在完全二叉树的基础上 2.排序算法的一种,也是稳定效率最高的一种 3.可用于实现STL中的优先队列(priority_queue)  优先队列:一种特殊 ...

  6. 数据结构--堆Heap

    数据结构:堆(Heap) 堆就是用数组实现的二叉树,所以它没有使用父指针或者子指针.堆根据"堆属性"来排序,"堆属性"决定了树中节点的位置. 堆的常用方法: 构 ...

  7. 数据结构:堆(Heap)

    堆就是用数组实现的二叉树,所有它没有使用父指针或者子指针.堆根据"堆属性"来排序,"堆属性"决定了树中节点的位置. 堆的常用方法: 构建优先队列 支持堆排序 快 ...

  8. 数据结构之——堆(Heap)

    堆(Heap) 堆(Heap)是计算机科学中一类特殊的数据结构的统称.堆通常是一个可以被看做一棵完全二叉树的数组对象.同时堆是一种特殊的"队列" 完全二叉树 既然说堆是完全二叉树, ...

  9. heap python_数据结构-堆(Heap) Python实现

    堆(Heap)可以看成近似完全二叉树的数组,树中每个节点对应数组中一个元素.除了最底层之外,该树是完全充满的,最底层是从左到右填充的. 堆包括最大堆和最小堆:最大堆的每一个节点(除了根结点)的值不大于 ...

最新文章

  1. 2021年大数据Flink(十二):流批一体API Transformation
  2. 重磅:国拨概算5.34亿!“新一代人工智能”重大项目项目申报指南发布
  3. LVS+Keepalived-DR模式负载均衡高可用集群
  4. idea 添加 VUE 的语法
  5. Winform中设置DevExpress的RadioGroup的items从配置文件中加载
  6. [iOS]iOS AudioSession详解 Category选择 听筒扬声器切换
  7. VTK:可视化之BackgroundTexture
  8. POJ2299 逆序数
  9. 软件:常用 Linux 软件汇总,值得收藏!
  10. Orleans入门例子
  11. android system读写权限设置,当然需要root访问权限才能写入Android的system目录
  12. java实现千米与经纬度度数的转换(画圆左右有精度缺失)
  13. 1371. 每个元音包含偶数次的最长子字符串
  14. Variational Mode Decomposition(变分模态分解),介绍,算法流程,作用,优缺点
  15. 高斯加权滤波matlab,简单易懂的高斯滤波
  16. CocosCreator查找图片引用
  17. 黑苹果虚拟机安装教程
  18. 微信小程相对图片路径_微信小程序加载本地图片时“../”的使用
  19. 基于历史使用数据的虚拟机动态整合研究( 文献阅读与问题理解)
  20. @RestController当中的value 含义

热门文章

  1. PHP 将xml文件解析为数组
  2. GPIO模块常用的一些方法
  3. 从像素之间谈起:像素游戏的画面增强(上)
  4. 戳对地方,葛优躺秒变工作狂
  5. 悼念512汶川大地震遇难同胞——来生一起走(dfs+打表)
  6. 误将桌面的计算机图标删除,如何恢复误删除的"桌面图标"
  7. 汇编指令学习(JMP、JE、JS、JP,JO,JB)
  8. HX=JE,HX-JE芯片,无感升压ic
  9. linux ftp 解压缩命令,常用五种Linux环境中的压缩和解压命令示范 | OPS技术联盟
  10. Hive分析、窗口函数