admin管理员组

文章数量:1623792

一、STL队列、优先队列(priority_queue)

普通的队列是一种先进先出的数据结构,元素在队列尾追加,而从队列头删除。

在优先队列中,元素被赋予优先级。当访问元素时,具有最高优先级的元素最先删除。优先队列具有最高级先出 (first in, largest out)的行为特征。

首先要包含头文件#include<queue>, 他和queue不同的就在于我们可以自定义其中数据的优先级, 让优先级高的排在队列前面,优先出队。

优先队列具有队列的所有特性,包括队列的基本操作,只是在这基础上添加了内部的一个排序,它本质是一个堆实现的。

和队列基本操作相同:

  • top 访问队头元素
  • empty 队列是否为空
  • size 返回队列内元素个数
  • push 插入元素到队尾 (并排序)
  • emplace 原地构造一个元素并插入队列
  • pop 弹出队头元素
  • swap 交换内容

定义:priority_queue<Type, Container, Functional>
Type 就是数据类型,Container 就是容器类型(Container必须是用数组实现的容器,比如vector,deque等等,但不能用 list。STL里面默认用的是vector),Functional 就是比较的方式。

当需要用自定义的数据类型时才需要传入这三个参数,使用基本数据类型时,只需要传入数据类型,默认是大顶堆。
一般是:

//升序队列
priority_queue <int,vector<int>,greater<int> > q;
//降序队列
priority_queue <int,vector<int>,less<int> >q;

//greater和less是std实现的两个仿函数(就是使一个类的使用看上去像一个函数。其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了)
#include<iostream>
#include <queue>
using namespace std;
int main()
{
  //对于基础类型 默认是大顶堆
  priority_queue<int> a;
  //等同于 priority_queue<int, vector<int>, less<int> > a;

  //   这里一定要有空格,不然成了右移运算符↓↓
  priority_queue<int, vector<int>, greater<int> > c; //这样就是小顶堆
  priority_queue<string> b;

  for (int i = 0; i < 5; i++)
  {
    a.push(i);
    c.push(i);
  }

  while (!a.empty())
  {
    cout << a.top() << ' ';
    a.pop();
  }

  cout << endl;

  while (!c.empty())
  {
    cout << c.top() << ' ';
    c.pop();
  }

  cout << endl;
  b.push("abc");
  b.push("abcd");
  b.push("cbd");
  while (!b.empty())
  {
    cout << b.top() << ' ';
    b.pop();
  }

  cout << endl;
  return 0;
}

1、基本类型优先队列的例子:

运行结果:

4 3 2 1 0
0 1 2 3 4
cbd abcd abc
请按任意键继续. . .

2、用pair做优先队列元素的例子:

规则:pair的比较,先比较第一个元素,第一个相等比较第二个。

#include <iostream>
#include <queue>
#include <vector>
using namespace std;
int main()
{
  priority_queue<pair<int, int> > a;
  pair<int, int> b(1, 2);
  pair<int, int> c(1, 3);
  pair<int, int> d(2, 5);
  a.push(d);
  a.push(c);
  a.push(b);
  while (!a.empty())
  {
    cout << a.top().first << ' ' << a.top().second << '\n';
    a.pop();
  }
}


2 5运行结果:

1 3
1 2
请按任意键继续. . .

3、用自定义类型做优先队列元素的例子

 

#include <iostream>
#include <queue>
using namespace std;

//方法1
struct tmp1 //运算符重载<
{
  int x;
  tmp1(int a) {x = a;}
  bool operator<(const tmp1& a) const
  {
    return x < a.x; //大顶堆
  }
};

//方法2
struct tmp2 //重写仿函数
{
  bool operator() (tmp1 a, tmp1 b)
  {
    return a.x < b.x; //大顶堆
  }
};

int main()
{
  tmp1 a(1);
  tmp1 b(2);
  tmp1 c(3);
  priority_queue<tmp1> d;
  d.push(b);
  d.push(c);
  d.push(a);
  while (!d.empty())
  {
    cout << d.top().x << '\n';
    d.pop();
  }
  cout << endl;

  priority_queue<tmp1, vector<tmp1>, tmp2> f;
  f.push(b);
  f.push(c);
  f.push(a);
  while (!f.empty())
  {
    cout << f.top().x << '\n';
    f.pop();
  }
}

运行结果:

3
2
1
 
3
2
1
请按任意键继续. . .

二、STL优先队列(priority_queue) 与红黑树 对比

    先上代码

#include <iostream>
#include <string>
#include <stdio.h>
#include <sstream>
#include <set>
#include <chrono>
#include <stdint.h>
#include <queue>
#include <vector>
#include <functional>
#define LENGTH 10000000

int main()
{

	std::chrono::system_clock::time_point start = std::chrono::system_clock::now();
	std::chrono::system_clock::time_point end;
	srand(time(0));
#if 1
	std::set<int> temSet;

	for (int i = 0; i < LENGTH; ++i)
	{
		temSet.insert(rand());
	}

	std::set<int>::iterator it = temSet.begin();
	for (; it != temSet.end(); )
	{
		//std::cout << "element is " << *it << std::endl;
		temSet.erase(it++);
	}

	end = std::chrono::system_clock::now();
	std::cout << "RBtree use time is " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()
		<< " milliseconds" << std::endl;

	//getchar();
#endif

#if 1
	start = std::chrono::system_clock::now();
	std::priority_queue<int, std::vector<int>, std::greater<int> > priQueue;

	for (int i = 0; i < LENGTH; ++i)
	{
		priQueue.push(rand());
	}

	std::cout << std::endl;

	for (int i = LENGTH; i > 0; --i)
	{
		//std::cout << "element is " << priQueue.top() << std::endl;
		priQueue.pop();
	}

	end = std::chrono::system_clock::now();
	std::cout << "priority_queue use time is " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()
		<< " milliseconds" << std::endl;

#endif

	getchar();
	return 0;
}

可以看出红黑树的性能还是更优一些的。

三、红黑树 vs 最小堆

转自http://blog.sina/s/blog_56e6a0750101b0fo.html

不谈内存,从算法上来讲

红黑树插入是最坏情况要比较2logN次(最高的高度)外加不超过两次旋转,最小堆最坏情况是logN次

红黑树删除不需要比较只需要不超过3旋转,查找最小值需要遍历logN,如果删除最小值树调整一般很小

最小堆查询顶节点是O(1),而删除顶节点在任何情况下都是个最坏的情况,需要比较2logN次

红黑树的最坏情况在旋转中不断调整变化,插入性能比最小堆差,但删除最小性能却比最小堆好上几倍

如果插入+查找最小+删除最小总体来确定红黑树和最小堆,红黑树占优,性能波动相对较小,红黑树不过多的依赖比较,相对最小堆由比较带来的的性能影响较小(如果比较是调用自定义函数、比较的是字符串等,那么性能就牺牲很大)

最小堆最大的优势在于取最小值性能是O(1),如果插入的数据基本有序,那么最小堆能获得比较好的性能, 在频繁不断地取最小值的是否满足条件的应用中有更加好的性能(如超时队列),红黑树相应就要尽量避免不必要的查询次数才能在超时队列这种应用中获得更好的性能

从实际出发:

由于最小堆一般是采用堆的方式实现,元素访问速度远高于采用链表方式的红黑树,插入性能快红黑树好几倍,但最小堆的删除性能并不快于红黑树

最小堆的最大缺点就在于必须是连续的内存

四、总结

但是对于优先队列要是顺序值插入,比如1、2、3、4、......这种性能是由于红黑树的,但实际开发并不常见。

最后引用https://www.xuebuyuan/1727446.html

 当我们的程序中需要使用优先级队列时,常常考虑到两种数据结构,set和priority_queue,然后不知道用哪一个好。先分析一下我们的需求:

1.是否需要随时从容器中删除某项,比如一个待释放的资源队列,有时我们需要放弃释放,就要从待释放资源队列中删除。

2.是否需要维持队列并依次处理,还是说队列仅仅是为了一次性的排序。也就是是否需要遍历容器中的元素。

第一点,priority_queue是不提供删除任意一项的,它提供的方法非常有限,只有push,pop,top等几个,而set有erase方法。

第二点,可怜的priority_queue竟然没有提供iterator,想要遍历的唯一方法就是不断的top和pop直到容器为空。从性能上考虑,priority_queue遍历时必须删除,导致了额外的删除开销,而一个iterator迭代器却不需要对容器结构做任何修改,性能上更胜一筹。

第三点,还是性能,priority_queue实际上是一个wrapper,它需要底层的容器支持,默认的容器是vector,我们知道vector本身类似于数组,要插入数据的开销是O(n)。再来看一下set,它使用的是B树,我们知道,B树在插入的时候开销是O(nlogn),可见,孰高孰低也是不言而喻的。

 

那么,priority_queue到底有什么好处呢?答案是安全,仅提供了有限的push,pop,top几个函数,想要破坏内部结构都不行,呵呵,不过高手建议还是用set吧。

本文标签: 队列红黑性能stlpriorityqueue