一、优先队列
优先队列是允许至少两种操作的数据结构:Insert(插入),DeleteMin(删除最小者)
简单的实现方式是使用二叉堆。
堆是一种非常实用的数据结构,其中以二叉堆最为常用。二叉堆可以看作一棵完全二叉树,每个节点的键值都大于(小于)其子节点,但左右孩子之间不需要有序。我们关心的通常只有堆顶的元素,而整个堆则被封装起来,保存在一个数组中。
下面分别介绍优先队列基本操作的实现
二、入队 (push)
基本实现步骤如下:
- 在下一个空闲处建立一个空穴
- 若空穴的父节点大于空穴的值,则将空穴向上行一步
流程图如下:
c 代码实现:
void Insert(int x, int *array, int num)
{
int i;
// i 表示空穴所在位置, i/2 表示空穴的父节点所在位置
// array[0] = 0 作为哨兵防止溢出
for (i = ++num; array[i/2] > x; i /= 2)
array[i] = array[i/2];
array[i] = x;
}
注意: 始终保持队列第一个元素为 0 是为了防止空穴上滤过程溢出
二、出队(pop)
需要考虑的是,由于堆现在少了一个元素,因此堆中最后一个元素 x 必须移动到该堆的某个地方。
基本实现步骤如下:
- 根处建立一个空穴(默认将最后一个元素放入空穴)
- 如果有比自己小的儿子,则将空穴的较小的儿子移入空穴
- 否则将最后一个元素放入空穴
流程图如下:
c 代码实现:
void DeleteMin(int *array, int *size)
{
int num = *size;
int lastVal = array[num--];
int child;
for (int i = 1; 2 * i < num; i *= 2)
{
child = 2 * i;
if (child != num && array[child + 1] < array[child])
child++;
if (lastVal > array[child])
array[i] = array[child];
else
break;
}
array[i] = lastVal;
}
三、建堆(buildHeap)
将任意数组调整成堆的过程如下
void buildHeap(int *array, int num)
{
for (int i = num/2; i > 0; i--)
minHeapfiy(i);
}
算法运行时间的界为 O(N)
,即虚线条数的界,可以通过计算堆中所有节点的高度的和来得到
流程图如下:
调整保持堆序结构
当遇到以节点i的左右儿子为根的两棵二叉树都是最大堆,而以节点i为根的二叉树可能不是最大堆情况的时候
我们需要一个辅助函数 minHeapfiy(int i)
来调整,过程如下
- 从元素
arr[i], arr[left(i)], arr[right(i)]
中找出最小的元素,将下标存在small
中; - 如果
arr[i]
是最小的,说明以节点 i 为根的二叉树是最小堆,无须调整,程序结束; - 否则将最小元交换到节点 i ,然后递归调用
minHeapfiy(int small)
c 代码实现:
void minHeapfiy(int *array, int num, int i)
{
int l = i * 2;
int r = l + 1;
int small = i;
int temp;
if (l < num && array[i] < array[small])
small = l;
if (r < num && array[i] < array[small])
small =r;
if (small != i)
{
temp = array[small];
array[small] = array[i];
array[i] = temp;
minHeapfiy(array, num, small);
}
}
四、所有优先队列的 c++ 实现以及简单测试
#include<iostream>
#include<vector>
using namespace std;
template <class T>
class PriorityQueue {
public:
// deleteMin
void pop() {
T val = m_data[m_data.size() - 1];
int i;
for (i = 1; i * 2 < m_data.size(); i *= 2) {
int child = i * 2;
// 将空穴儿子的较小者移入空穴
if (child != m_data.size() && m_data[child] > m_data[child + 1])
child++;
if (val > m_data[child])
m_data[i] = m_data[child];
else
break;
}
m_data[i] = val;
m_data.pop_back();
}
// insert
void push(const T &val) {
m_data.push_back(val);
int i = m_data.size() - 1;
// m_data[0] == 0 作为哨兵防止溢出
while (m_data[i/2] > val) {
m_data[i] = m_data[i/2];
i = i/2;
}
m_data[i] = val;
}
void buildHeap(const int *array, int num) {
m_data.push_back(0);
size_t i;
for (i = 1; i <= num; i++)
m_data.push_back(array[i - 1]);
cout << "befort : ";
for (i = 0; i < m_data.size(); i++)
cout << m_data[i] << ' ';
cout << endl;
for (i = m_data.size()/2; i > 0; i--)
minHeapfiy(i);
cout << "after : ";
for (i = 0; i < m_data.size(); i++)
cout << m_data[i] << ' ';
cout << endl;
}
void print() {
size_t i;
cout << "m_data : ";
for (i = 0; i < m_data.size(); i++)
cout << m_data[i] << ' ';
cout << endl;
}
private:
vector<T> m_data;
// 假设节点 i 的左右子树都为最小堆
void minHeapfiy(int i) {
int size = m_data.size();
int l = i * 2;
int r = l + 1;
int small = i;
if (l < size && m_data[l] < m_data[small])
small = l;
if (r < size && m_data[r] < m_data[small])
small = r;
if (small != i) {
T temp = m_data[small];
m_data[small] = m_data[i];
m_data[i] = temp;
minHeapfiy(small);
}
}
};
int main() {
int array[15] = {150,80,40,30,10,70,110,100,20,90,60,50,120,140,130};
PriorityQueue<int> pq;
pq.buildHeap(array, 15);
pq.pop();
pq.print();
pq.push(10);
pq.print();
return 0;
}
测试结果如下所示: