在前面的几篇文章中,介绍了线性表的三种数据结构:链表、队列和栈。他们因为各自的特性,都可以方便的支持某一种运算。比如链表相比于数组,其插入和删除的时间代价更为优化。
除了这些数据结构之外,今天和大家分享需要支持如下两种运算的数据结构:插入元素和寻找最大元素。这两种运算的数据 结构称为优先队列,其有效实现便是通过堆。下面给出大顶堆的定义:
一个(二叉)堆是一棵几乎完全的二叉树,它的每个节点都满足如下特性:存储在父节点中的数据项键值不小于存储在节点中数据项键值。
从上述特性中,我们可以知道:沿着每条路径,元素的键值以非升序排列。定义如下在堆上的运算:
- int deleteMax():删除最大值并返回
- void insertElement(int x):插入数据x到堆中
- int deleteElement(int i):删除其中i数据项并返回
- void makeHeap():将普通数组转换为大顶堆
- void heapSort():堆排序,将堆中数据按升序排列
我们用数组H来表示堆,则有:
- 根节点存储在H[1]中
- 对于某个节点H[j],若其拥有左右子节点,则左子节点在H[2j]中,右子节点在H[2j+1]中
- H[j]的父节点如果不是根节点,则在H[(int)j/2]中
在正式实现上述定义的运算之前,先介绍两个辅助运算,这两个辅助运算又是不可或缺、尤为重要的:sift-up和sift-down运算。
- sift-up运算:假设对于某个H[i],其键值大于父节点键值时,这样就不满足大顶堆的特性,故而需要进行动态维护。sift-up运算就是沿着H[i]到根节点的唯一路径将其移动到合适的位置上。具体方法就是:在沿着路径的每一步上,将H[i]和其父节点H[(int)i/2]比较,若不满足堆特性,则交换以使其满足。
- sift-down运算:对于i<=(int)n/2,若H[i]中元素键值小于H[2i]和H[2i+1]的最大值,则违反堆的特性,需要动态维护。实现过程与sift-up类似,不再赘述。
以下给出大顶堆的C++实现,通过类封装上述操作:
class MaxHeap
{
private:
int *heap;
int heapSize;
public:
MaxHeap(int numOfElenment)
{
heapSize = numOfElenment;
int length = heapSize + 1;
heap = new int[length];
}
~MaxHeap()
{
delete[]heap;
}
void initialHeap()
{
heap[0] = -1;
for (int i = 1;i <= heapSize;i++)
cin >> heap[i];
makeHeap();
}
//to input an array and build a max heap
void printHeap()
{
for (int i = 1;i <= heapSize;i++)
cout << heap[i] << " ";
cout << endl;
}
//tp print the heap
int getHeapSize()
{
return heapSize;
}
//return heap size
void swapValues(int i, int j)
{
int middleVariable = 0;
middleVariable = heap[i];
heap[i] = heap[j];
heap[j] = middleVariable;
}
//to swap heap[i] and heap[j]
void siftUp(int i)
{
bool done = false;
if (i == 1)
return;
//a root
do
{
if (heap[i] > heap[(int)(i / 2)])
swapValues(i, (int)(i / 2));
else
done = true;
i = (int)(i / 2);
} while (i != 1 && done == false);
}
//to sift-up heap[i]
void siftDown(int i)
{
bool done = false;
if (2 * i > heapSize)
return;
//leaf
do
{
i = i * 2;
if ((i + 1 <= heapSize) && (heap[i + 1] > heap[i]))
i += 1;
if (heap[(int)(i / 2)] < heap[i])
swapValues(i, (int)(i / 2));
else
done = true;
} while ((2 * i <= heapSize) && done == false);
}
//to sift-down heap[i]
void insertElement(int x)
{
heapSize += 1;
heap[heapSize] = x;
siftUp(heapSize);
}
//to insert x to the heap
int deleteElement(int i)
{
int x = heap[i];
int y = heap[heapSize];
heapSize -= 1;
if (i == heapSize + 1)
return x;
heap[i] = y;
if (y >= x)
siftUp(i);
else
siftDown(i);
return x;
}
//to delete heap[i] and return it
int deleteMax()
{
int x = heap[1];
deleteElement(1);
return x;
}
//to delete the max value and return it
void makeHeap()
{
for (int i = (int)(heapSize / 2);i > 0;i--)
{
siftDown(i);
}
}
//to transfer "heap" to a max heap
void heapSort()
{
int heapSizeTemp = heapSize;
for (int j = heapSize;j > 1;j--)
{
swapValues(1, j);
heapSize--;
siftDown(1);
heapSize = heapSizeTemp;
}
//to sort heap[] asc
};