文章目录
- 优先队列
- C++用法
- 声明
- 基本操作
- Java用法
- 声明
- 基本用法
- Python
- heapq
- PriorityQueue
- Go
- 手写一个最大堆
优先队列
通常是语言内已经实现好了的一种数据结构。
首先,优先队列显然是一种“队列”,而队列(Queue)的特点是“先进先出,后进后出”。
优先队列区别于普通队列的点在于能够对进队的数据自动排序,始终维持着队列从小到大排或者从大到小排。也可以理解为数据的优先级不一样,每次出队都是最高优先级的先出队。
比如维持着从大到小排序的优先队列,能保证把插入的数据从大到小排,每次从队列中取数都是当前队列中最大的。
优先队列的实现通常都是基于堆(heap)。当一个完全二叉树满足父节点比左右子树都大于等于 或者 都小于等于 的时候,就称为堆。
一般优先队列的插入和删除时间复杂度为O(logn);
C++用法
声明
添加头文件
#include<queue>
通常声明一个优先队列的方法是
priority_queue<类型> 队列名
最常用的几个包括:
//1.当最大堆用,元素按照从大到小排列,注意空格
priority_queue <int,vector<int>,less<int> >Q;
//2.当最小堆用,int等基本元素按照从小到大排列
priority_queue <int,vector<int>,greater<int> > Q;
记住默认的less由小到大,greater由大到小!
// 3.类型是结构体,其中重载了比较符号。更适合放不能直接比较大小的对象
//1)在结构体李重载比较符号
struct a_struct
{
int x,y;
bool operator < (const node & a) const
{
return x<a.x;
}
};
priority_queue<a_struct> Q;
//2) 外面单独写比较函数,声明时传入比较函数
struct a_struct
{
int x, y;
}
struct tmp //重写比较函数
{
bool operator() (a_structa, a_structb)
{
return a.x < b.x; //大顶堆
}
};
priority_queue<a_struct, vector<a_struct>, tmp> Q;
基本操作
优先队列的基本操作,和队列差不多,就是插入、查看队首元素、删除队首元素
Q.push(x); // 插入元素,始终维持内部秩序
x = Q.top(); //查看当前队首,或者说是优先级最高的元素
Q.pop(); //删除最高优先级的元素
int s = Q.size(); // 返回优先队列大小
bool e = Q.empty(); //判断队列是否为空
Java用法
Java集合框架中提供了PriorityQueue和PriorityBlockingQueue两种类型的优先级队列,PriorityQueue是线程不安全的,PriorityBlockingQueue是线程安全的,这里主要使用PriorityQueue。
声明
需要导入
import java.util.PriorityQueue
Java时面向对象的,优先队列里放的也是对象,且对象必须可比较,而且不能为空
1.基本用法,默认是从小到大排序的
Queue<String> q = new PriorityQueue<>();
Queue<Integer> queue1 = new PriorityQueue<Integer>();
如果想要从大到小排列呢?就需要自己override比较函数
Comparator<Integer> comparator = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2.compareTo(o1);
}
};
Queue<Integer> queue1 = new PriorityQueue<Integer>(comparator )
// 也可以写成内部类的形式
Queue<Integer> queue2 = new PriorityQueue<Integer>(new Comparator<Integer>(){
@Override
public int compare(Integer o1, Integer o2){
return o2-o1; //和上面的比较是等效的
}
})
2.如果要放其他对象,那必须是可比较的对象,在Java中意味着该对象的类实现Comparator 或者额外传入Comparator对象。
Comparator<Student> comparator = new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return (o1.id - o2.id);
}
};
Queue<Student> queue2 = new PriorityQueue<Student>(comparator);
queue2.add(new Student(2, "B"));
queue2.add(new Student(1, "A"));
queue2.add(new Student(3, "C"));
3.其实还支持将ArrayList转化为PriorityQueue,当然也得是可以比较的
ArrayList<Integer> list = new ArrayList<>();
list.add(4);
list.add(0);
PriorityQueue<Integer> queue3 = new PriorityQueue<>(list);
基本用法
Q.offer(object);
// 插入,基本等同于Q.add(object),不过add插入失败会异常,offer只是返回false
o = Q.poll(); // 获取而且删除队首元素
o = Q.peek();//获取但不删除队首元素
Q.isEmpty(); //是否为空
Q.size(); //大小
Python
提供了两种结构,一个是heapq一个是PriorityQueue
heapq
import heapq
把列表转化为堆:
a_list = [1,2,2,9,3,4]
heapq.heapify(a_list)
基本操作
heapq.heappush(q, 1) # 插入
heapq.heappop(heap) # 删除并返回最小的元素,用heap[0]可以访问而不删除
heapq.heapreplace(heap, item)
# 弹出并返回 heap 中最小的一项,同时推入新的 item。
默认是最小堆。想要最大堆最简单的操作是list中所有数据取反,这样的最小堆其实是原来数字的最大堆。
自己重写比较函数要重写不少,可以参考 链接 有一些未公开的最大堆的方法,但是并不能保证新加入的数据还能维持最大堆,详见链接
PriorityQueue
这个其实就是基于heapq来实现的,并且线程安全(也就是有锁开销),不过接口更舒服
from Queue import PriorityQueue
pq = PriorityQueue()
for i in range(3,0,-1):
pq.put(i)
while not pq.empty():
print pq.get()
基本用法
Q.put(object)
Q.get()
Q.empty()
Q.qsize()
Go
有heap包实现堆的操作,不过优先队列就得自己用heap实现一个,具体可以参考 链接
手写一个最大堆
我们用C++自己实现一个构建堆和维护堆,以最大堆为例。
堆虽然逻辑上是二叉树结构,但是不需要真的存成二叉树。以数组就可以标识。从索引0开始放root节点,父节点的索引为i,则左子树为2i+1,右子树为2(i+1)。最后一个非叶子节点的索引为(size-2)/2(毕竟堆是完全二叉树,前面不会出现空了节点的)。
构造堆:
基本意思是,从最后一个非叶子节点开始,把每个非叶子节点都调整成比它的两个子节点要大
void build_heap(int arr[], int size){
for(int i = (size-2)/2; i>=0; i--){
heap_adjust(arr, i, size);
}
}
调整最大堆
void heap_adjust(int arr[], int i, int size){
if(i<=(size-2)/2){ // 只有非叶子节点才有调整的必要
int left = 2*i+1;
int right = 2*i+2;
int maxx = i;
// 找到左右子节点中更大的那个
if(left < size && arr[left]>arr[maxx])
{
maxx = left;
}
if(right < size && arr[right] > arr[maxx){
maxx = right;
}
if(maxx != i){
swap(arr[i], arr[maxx]);
heap_adjust(arr, maxx, size);//递归处理
}
}
}
然后就可以用这个做堆排序了!