概述
我所知道的,广义的队列有三种:stack栈是一种,queue队列是一种,还有一种比较特殊,priority queue优先队列
为什么特殊,因为前面两种队列,往队列push元素的时候,push过程就是很简单的把元素放在头部或者尾部就行了,pop的时候也是简单的取出头尾数据。而优先队列更加“智能化”,你把数据push进队尾的时候,push过程要在内部把数据进行整理,使队首元素总保持是最大值,所以你pop元素的时候都能拿到队列的最大元素了,另外pop的时候也需要整理数组,毕竟还得从剩下的数据中选一个最大的数顶上来,不然下一次pop队首元素,怎么能保证它是最大元素呢。
如果你要对一组数据进行排序,除了插入排序、快速排序等方式,还可以借助优先队列这个性质,把数据全部装进去,一个一个取出来不就有序了么。这就是堆排序。为啥是堆排序不叫“优先队列排序”呢,前面说,priority queue的核心算法就是push与pop,其实就是赋予普通数组堆的性质,也可以说是基于堆的性质来整理数组,这样才能让你每次pop得到的都是排好序的元素啊。什么是堆的性质呢,下面就来讲下这个堆到底是什么。
堆的性质
堆是一种抽象数据结构,同时也是一种性质,是普通数组 + 一种关联
这种关联以数组下标表示:下标n的元素为父节点,2n、2n+1为它左右两个子节点。
说得像是二叉树一样是吧,不过他就是内置数据类型---数组,不是二叉树结构,只是加上了元素间的一层关系而已。
通过这种关系,元素i就可以直接访问到距离它很远的2i、2i+1的元素了。
如果我们在这层关联上面建立一个大小关系(n > 2n && n > 2n + 1),那么数组就成为了堆。
用堆排序
假设数组已满足了堆定义,添加队尾元素后,如何让首元素保持是最大元素?
这个不就是push接口里面该做的事情么,答案是对其进行排序么?单独看排序的话,想到的排序肯定是快排之类的nlogn级别的排序,不过没有这个必要,因为我们想的是只找出最大元素更新到数组的首位,而不是把整个数组排序,显然这个工作比兴师动众的快排更加轻量级,时间复杂度肯定比nlogn小得多,怎么实现这个push呢,假设这个新的尾部元素索引是8并且他就是最大元素,他需要如何“上位”呢,一路swap,他需要经过的索引是:4->2->1,最后就替代了1这个首元素成为老大了,为什么可以这样跳着排,正是因为数组原本就满足了堆的性质,并且“上位”之后,这个性质依然保持。每次交换,索引都会折半,那么时间复杂度就和折半查找一样是logn了,注意logn只是使首元素成为最大/小,并没有对数组进行排序。
假设数组已满足了堆定义,取出队首元素后,如何让首元素保持是最大元素?
其实就是pop的实现,队首元素没有了,肯定要让其他元素去填充它,最方便的就是让队尾元素去填充,填充了之后它变成队首元素,为了最大的元素“上位”,它还得与左右子节点比较,如果它本身就是数组中最小的元素,它得从队首位置开始一路swap,比如经过1->3->7,最后7这个位置就是它最终位置,最后数组保持了堆的性质。
这样一来,就实现了概述里面的的排序功能了。
以最小堆为例,来看一个优先队列的接口:
template<typename T>
class MinPQ
{
public:
public:
MinPQ(); //默认构造空队列
MinPQ(int max); //初始容量为max
MinPQ(T a[], int n); //用a[]数组创建
void insert(T v); //末尾插入
T delMin(); //头部删除
T min(); //返回最小元素
bool isEmpty(); //判空
int size(); //返回队列元素个数
private:
void swim(int k); //尾部新数据重排
void sink(int k); //头部新数据重排
void minPQCheck(); //检查队列是否满足最小堆
T* pq; //内置数组
int N; //当前数据量\当前队尾索引
int C; //数据容量
public:
static int MinPQ<T>::testMinPQ();
};
实际用法
以下给出实现代码:
template<typename T>
MinPQ<T>::MinPQ() {
//
//
}
template<typename T>
MinPQ<T>::MinPQ(int max) :N(0), C(max) {
pq = new T[max + 1];//索引要从1开始
memset(pq, 0, sizeof(int) *(max + 1));
}
template<typename T>
MinPQ<T>::MinPQ(T a[], int n) : N(n), C(n) {
//
//
}
template<typename T>
void MinPQ<T>::insert(T v) {
pq[++N] = v;
swim(N);
minPQCheck();
}
template<typename T>
T MinPQ<T>::delMin() {
T ret = min();
pq[1] = pq[N];
pq[N--] = NULL;
sink(1);
minPQCheck();
return ret;
}
template<typename T>
bool MinPQ<T>::isEmpty() {
return N == 0;
}
template<typename T>
int MinPQ<T>::size() {
return N;
}
template<typename T>
T MinPQ<T>::min() {
return pq[1];
}
template<typename T>
void MinPQ<T>::swim(int k) {
while ((k > 1) && (pq[k] < pq[k / 2])) {
swap(pq[k], pq[k / 2]);
k = k / 2;
}
}
template<typename T>
void MinPQ<T>::sink(int k) {
while (2 * k <= N) {
int s = 2 * k;//s is the final child element in swap
if ((2 * k < N) && (pq[2 * k + 1] < pq[2 * k])) {
if (pq[k] > pq[2 * k + 1]) {
s++;
}
}
if (pq[k] > pq[s]) {
swap(pq[k], pq[s]);
k = s;
}
else
break;
}
}
template<typename T>
void MinPQ<T>::minPQCheck(void) {
for (int i = 1; i <= N / 2; i++) {
if ((2 * i + 1 <= N) && (pq[i] > pq[2 * i] || pq[i] > pq[2 * i + 1])) {
assert(false);
}
}
}
template<typename T>
int MinPQ<T>::testMinPQ() {
MinPQ<int>* pObj = new MinPQ<int>(2);
srand(time(NULL));
for (int i = 0; i < 3; i++)
pObj->insert(rand() % 2000);
vector<int> arr;
for (int i = 0; i < 2000; i++) {
arr.push_back(pObj->delMin());
}
for (auto n : arr) {
cout << n << " ";
}
return 0;
}
template class MinPQ<int>;