一些概念
普通队列:先进先出,后进先出(顺序已经确定)
优先队列:出队顺序与入队顺序无关,和优先级相关(动态确定顺序)
优先队列在处理动态问题上很有优势,同时处理静态问题也是比较好的,比如在100000000个元素中选出前100名?也就是,在N个元素中选出前M个元素。不同方法的时间复杂度分别是排序: NlogN ,优先队列: NlogM 。
动态
有新的元素会加入数据结构,同时又有元素会离开队列,也就是说入队和出队是有限队列的两个基本特征。
出队:取出优先级最高的元素。
堆的存储
1)节点值总是不大于父亲节点的值;
2)完全二叉树。
由于堆是一颗完全二叉树,所以可以用数组来存储一颗二叉树。
ShiftUp
入队时,从末端加入元素,然后要调整元素顺序,使其任然维持最大堆的定义,关键操作是每次和父亲相比较,逐层向上升直到满足堆的定义。
void shiftUp(int k){
while (k > 1 && data[k / 2] < data[k]){
swap(data[k / 2], data[k]);
k /= 2;
}
}
ShiftDown
出队只能取出根节点的元素,出队后为了保证完全二叉树,可以将最后一个元素与根节点位置交换,然后对根节点元素进行ShiftDown操作。
void shiftDown(int k){
while (2 * k <= count){
int j = 2 * k; // 在此轮循环中,data[k]和data[j]交换位置
if (j + 1 <= count && data[j + 1] > data[j])
j++;
// data[j] 是 data[2*k]和data[2*k+1]中的最大值
if (data[k] >= data[j]) break;
swap(data[k], data[j]);
k = j;
}
}
堆的数据结构
template<typename Item>
class MaxHeap{
private:
Item *data;
int count;
int capacity;
void shiftUp(int k){
while (k > 1 && data[k / 2] < data[k]){
swap(data[k / 2], data[k]);
k /= 2;
}
}
void shiftDown(int k){
while (2 * k <= count){
int j = 2 * k; // 在此轮循环中,data[k]和data[j]交换位置
if (j + 1 <= count && data[j + 1] > data[j])
j++;
// data[j] 是 data[2*k]和data[2*k+1]中的最大值
if (data[k] >= data[j]) break;
swap(data[k], data[j]);
k = j;
}
}
public:
MaxHeap(int capacity){
data = new Item[capacity + 1];
count = 0;
this->capacity = capacity;
}
~MaxHeap(){
delete[] data;
}
int size(){
return count;
}
bool isEmpty(){
return count == 0;
}
void insert(Item item){
assert(count + 1 <= capacity);
data[count + 1] = item;
shiftUp(count + 1);
count++;
}
Item extractMax(){
assert(count > 0);
Item ret = data[1];
swap(data[1], data[count]);
count--;
shiftDown(1);
return ret;
}
Item getMax(){
assert(count > 0);
return data[1];
}
};
Heapify
MaxHeap(Item arr[], int n){
data = new Item[n + 1];
capacity = n;
for (int i = 0; i < n; i++)
data[i + 1] = arr[i];
count = n;
for (int i = count / 2; i >= 1; i--)
shiftDown(i);
}
堆排序
template<typename T>
void heapSort2(T arr[], int n){
MaxHeap<T> maxheap = MaxHeap<T>(arr,n);
for( int i = n-1 ; i >= 0 ; i-- )
arr[i] = maxheap.extractMax();
}
template<typename T>
void heapSort1(T arr[], int n){
MaxHeap<T> maxheap = MaxHeap<T>(n);
for( int i = 0 ; i < n ; i ++ )
maxheap.insert(arr[i]);
for( int i = n-1 ; i >= 0 ; i-- )
arr[i] = maxheap.extractMax();
}
使用Heapify构建堆,时间复杂度是 O(n) ,而使用入队操作构建一个堆时间复杂度是 O(nlgn)
堆排序优化
原地堆排序,空间复杂度是 O(1) ,值得注意的是由于是在原地,因此堆的索引是从0就开始的,也就是要重新换算下父亲及叶子节点之间的关系。
template<typename T>
void __shiftDown(T arr[], int n, int k){
while( 2*k+1 < n ){
int j = 2*k+1;
if( j+1 < n && arr[j+1] > arr[j] )
j += 1;
if( arr[k] >= arr[j] )break;
swap( arr[k] , arr[j] );
k = j;
}
}
template<typename T>
void __shiftDown2(T arr[], int n, int k){
T e = arr[k];
while( 2*k+1 < n ){
int j = 2*k+1;
if( j+1 < n && arr[j+1] > arr[j] )
j += 1;
if( e >= arr[j] ) break;
arr[k] = arr[j];
k = j;
}
arr[k] = e;
}
template<typename T>
void heapSort(T arr[], int n){
// 首先进行Heapify操作
for( int i = (n-1)/2 ; i >= 0 ; i -- )
__shiftDown2(arr, n, i);
// 然后每次把堆顶元素与最后一个叶子交换,之后堆堆顶元素进行shiftDown操作
for( int i = n-1; i > 0 ; i-- ){
swap( arr[0] , arr[i] );
__shiftDown2(arr, i, 0);
}
}