面试必知必会:堆和优先队列

栏目: IT技术 · 发布时间: 4年前

内容简介:通过本文将了解到以下内容:由于优先队列的元素既可以是基础类型,也可以是复合类型,因此在2.优先队列的实现

通过本文将了解到以下内容:

  • 优先队列的概念

  • 优先队列的实现

  • 优先队列的应用

1.优先队列的概念

优先队列是计算机科学中的一类抽象数据类型。

优先队列中的每个元素都有各自的优先级,优先级最高的元素最先得到服务;

优先级相同的元素按照其在优先队列中的顺序得到服务。

优先队列至少需要支持下述操作:

a.插入带优先级的元素

b.取出具有最高优先级的元素

c.查看最高优先级的元素。

综合考虑插入和删除的性能 优先队列一般采用堆来实现。

维基百科-优先队列

由于优先队列的元素既可以是基础类型,也可以是复合类型,因此在 C++中一般使用模板来定义优先队列,从而提高其可扩展性 ,简单定义:

template<class T>

class priqueue {

public:

//构造函数 初始化

priqueue(int maxsize);

//将T类型新元素添加到队列中

void insert(T t);

//获取队列中最小的元素

T extractmin();

};

2.优先队列的实现

实现优先队列的候选数据结构包括: 有序序列、无序序列、堆

  • 有序序列

有序序列中存储的数据都是有序的, 在执行extractmin获取最小值时复杂度O(1) ,但是 在添加新元素时就存在大量的移动和查找正确的位置最大复杂度O(N) ,因此在 insert和extactmin同时执行N次 时,最大复杂度为 O(N^2)

  • 无序序列

无序序列中存储的元素是无序的,在 执行insert操作复杂度为O(1) ,但是在 extractmin时每次都要进行一次遍历复杂度为O(N) ,因此在 insert和extractmin同时执行N次 时,最大复杂度为 O(N^2)

  • 堆结构

从上一节我们知道堆的insert不如无序序列那么随意,extractmin也没有有序序列那么容易,每次都 需要进行siftup和siftdn操作进行调整 ,但是同时执行 insert和extractmin时,复杂度是O(nlogn) ,优于O(N^2)。

  • 复杂度对照

面试必知必会:堆和优先队列

数据量超过较大时O(N^2)的执行时间要比O(nlogn)大非常多 ,有兴趣可以试试,引用参考4中的数据曲线:

面试必知必会:堆和优先队列

  • 小结

综上可以知道, 堆结构在实现优先队列的插入和删除操作时复杂性都很稳定,在大量数据的场景下优于有序序列和无序序列 ,因此权衡之下现在堆作为优先队列的底层数据结构。

3.优先队列的实现

  • 基于模板化和堆的优先队列的简单实现

template<class T>

class priqueue {

private:

int n,maxsize;

T *x;

void swap(int i,int j)

{ T t = x[i]; x[i] = x[j]; x[j] = t; }

public:

//初始化

priqueue(int m)

{

maxsize = m;

x = new T[maxsize+1];

n = 0;

}

//插入新数据

void insert(T t)

{

int i,p;

x[++n] = t;

//末尾添加新数据 执行siftup操作

for (i = n; i > 1 && x[p=i/2] > x[i]; i = p)

swap(p,i);

}

//提取操作

T extractmin()

{

int i,c;

//提取堆顶元素

T t = x[1];

//将尾部元素放到堆顶

x[1] = x[n--];

//针对新堆顶进行siftdn操作

for (i = 1; (c = 2*i) <= n; i = c) {

if (c+1 <= n && x[c+1] < x[c])

c++;

if (x[i] <= x[c])

break;

swap(c,i);

}

return t;

}

};

上述代码摘自 编程珠玑 并不是STL关于优先队列的实现,只是一部分简洁的代码,旨在表现insert和extract操作时如何 运用堆的siftup和siftdn操作 来封装成优先队列类的基础成员函数。

  • 优先队列的自定义优先级

模板化的优先队列扩展了使用场景,但是也产生了新的问题,就是 默认的优先级比较函数不一定满足 所有要求,因此很多时候都 需要自己来定义优先级判定函数

实现了一个模板优先队列 需要三个参数

  • 容器元素的类型

  • 存储数据所用的容器

  • 比较函数 缺省情况是less<T>

#include<quque>

// 队列和优先队列的声明

std::queue<T> pq;

std::priority_queue<T, std::vector<T>, cmp> pq;

//模板化优先队列的主要参数

priority_queue<Type, Container, Func>

//举例

priority_queue<pair<char,int>,vector<pair<char,int>>,compare> pq;

//自定义比较函数

struct compare

{

bool operator()(const pair<char,int> a,const pair<char,int> b){

return b.second > a.second;

}

};

4.优先队列的应用

上面的介绍可以认为优先队列是对堆的 工具 化封装,加上模板和自定义比较函数两个利器加持,优先队列让使用者不再苦于堆 排序 的原始造轮子。

  • TopN问题

仍然以LeetCode 215题为例,获取数组的第K大元素,上一节中我们直接使用堆,但是构建的是小顶堆,并且大小是K个元素,遍历完成之后直接取堆顶即可,但是在数据量不大或者内存足够的情况下,可以直接建立优先队列。

默认的优先队列本质上是大顶堆 ,那么堆顶就不是第K大元素了,但是 从堆顶开始依次pop出K-1个元素 ,堆顶也就是第K大元素了。

当然也可以 修改比较函数实现小顶堆 ,取堆顶元素也是可以的,要灵活运用。

使用优先队列实现LeetCode 第215题,代码如下:

//默认的比较函数是less 也就是优先队列相当于最大堆

//堆顶元素为最大值

priority_queue<int,vector<int>,less<int>> q;

int findKthLargest(vector<int>& nums, int k)

{

priority_queue<int> q(nums.begin(),nums.end());

for(int i=0;i<k-1;i++)

q.pop();

return q.top();

}

  • 优先队列和贪心算法

贪心算法又称贪婪算法,是一种在每一步选择中都采取在当前状态下最好或最优的选择,从而希望导致结果是最好或最优的算法。

贪心算法在有最优子结构的问题中尤为有效。

最优子结构的意思是局部最优解能决定全局最优解。

简单地说问题能够分解成子问题来解决,子问题的最优解递推到最终问题的最优解。

维基百科-贪心算法

优先队列是贪心算法的重要组成部分 ,借助于优先队列贪心算法可以解决非常多的实际问题其中包括:

  • 旅行商TSP问题

  • 01背包问题

  • 霍夫曼编码问题

  • 最短路径 Dijkstra算法

  • 最小生成树Prim算法

5.参考资料

  • 《编程珠玑》

  • http://www.laicar.com/book/echapter/5cb0a77e739207662ac8710c/links/item10/OEBPS/Text/part0009.xhtml

  • 维基百科

  • https://my.oschina.net/u/1246663/blog/227115

6.往期精彩

二叉树及其四大遍历


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Redis 深度历险:核心原理与应用实践

Redis 深度历险:核心原理与应用实践

钱文品 / 电子工业出版社 / 2019-1 / 79

Redis 是互联网技术架构在存储系统中使用得最为广泛的中间件,也是中高级后端工程师技术面试中面试官最喜欢问的工程技能之一,特别是那些优秀的互联网公司,通常要求面试者不仅仅掌握 Redis 基础用法,还要理解 Redis 内部实现的细节原理。《Redis 深度历险:核心原理与应用实践》作者老钱在使用 Redis 上积累了丰富的实战经验,希望帮助更多后端开发者更快、更深入地掌握 Redis 技能。 ......一起来看看 《Redis 深度历险:核心原理与应用实践》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具