您的位置:首页 > 理论基础 > 数据结构算法

数据结构与算法C++描述(12)---堆及最大堆

2017-10-30 19:22 731 查看

1、有关堆的相关概念

最大堆(最小树):最大(最小)的完全二叉树。

最大树(最小树):每个节点的值都大于(小于)或等于其子节点(如果有的话)值的数。注意,最大(小)数不一定是二叉树。

完全二叉树:从满二叉树中删除k个元素,其编号为2h−i,1<=i<=k,所得到的二叉树成为完全二叉树。

一个最大堆如下图所示:



2、最大堆的相关操作

这里,最大堆的操作包括:堆顶元素的删除、向最大堆中插入一个元素、将一个数组转化为最大堆、最大堆的输出、获取最大堆的堆顶元素等。

下面利用C++语言分别实现之。

2.1 最大堆类的声明与简单函数实现

最大堆类有3个私有成员:heap指针为堆中元素数组、CurrentSize为最大堆当前大小、MaxSize为最大堆的最大容量(通过构造函数赋值)。

template <class T>
class MaxHeap
{
public:
MaxHeap(int HeapSize=10)
{
MaxSize = HeapSize;                          //堆的最大容量
heap = new T[MaxSize + 1];                   //新建堆元素数组
CurrentSize = 0;                             //设置当前容量为0
}
~MaxHeap() { delete[] heap; }
//获取当前堆的大小
int Size() const { return CurrentSize; }
//获取堆顶元素
T Max()
{
if (CurrrentSize == 0)
throw OutOfRange();
else
return heap[1];                        //heap中的元素从1开始
}
//向堆中插入元素x,并返回操作之后的堆
MaxHeap<T> &Insert(const T &x);
//删除堆顶元素,并返回操作后的堆
MaxHeap<T> &DeleteMax(T &x);
//初始化函数,将一个数组构造成最大堆,size为数组中元素个数,ArrayMaxSize为数组最大容量
MaxHeap<T> &Initialize(T a[], int size, int ArraySize);
//输出最大堆
void Output(ostream &out)
{
for (int i = 1; i <= CurrentSize; i++)
out << heap[i] << "  ";
cout << endl;
}
private:
T *heap;                                     //元素数组,索引值从1开始
int CurrentSize,                             //当前堆大小
MaxSize;                                 //堆的最大规模
};


2.2 向最大堆中插入一个元素

若插入一个到最大堆中,可以先将元素放入最大堆的末尾,然后在调整插入元素后的完全二叉树,使其成为一个最大堆。调整规则可以根据最大堆的定义来设定。

由于二叉树中最后一个父节点的索引值满足[CurrentSize/2],其中CurrentSize为当前二叉树中元素个数。当插入一个元素后,原二叉树中元素个数加1。此时,可以从最后一个父节点开始,依次访问每个父节点,直到访问完第一个父节点(即根节点)。在访问每个父节点时,分别判断父节点与其左右孩子的大小关系。先判断其左孩子(若存在),若大于父节点,则与父节点交换;再访问其右孩子(若存在),若大于父节点,则交换。

//交换a和b
template <class T>
void Swap(T &a, T &b)
{
T c;
c = a; a = b; b = c;
}
//向堆中插入元素x,并返回操作之后的堆
template <class T>
MaxHeap<T> &MaxHeap<T>::Insert(const T &x)
{
if (CurrentSize == MaxSize)
throw NoMerm();
else
{
heap[CurrentSize + 1] = x;                     //取x的值作为堆的最后一个元素
CurrentSize++;
int root_i = (CurrentSize + 1) / 2;            //取当前元素的根节点
//依次访问各个根节点,调整成为最大堆
while (root_i > 0)
{
//若左孩子(若存在)大于根节点,则交换
if (2 * root_i <= CurrentSize && heap[root_i] < heap[2 * root_i])
Swap(heap[root_i], heap[2 * root_i]);
//若右孩子(若存在)大于根节点,则交换
if (2 * root_i + 1 <= CurrentSize && heap[root_i] < heap[2 * root_i+1])
Swap(heap[root_i], heap[2 * root_i + 1]);
root_i--;                                   //访问下一个根节点
}
}
return *this;
}


2.3 删除堆顶元素,并赋给x,返回操作后的最大堆

删除堆顶元素后,为了节省重建最大堆的时间,可以将最大堆的最后一个元素移至堆顶。从堆顶开始,依次访问各个父节点,直至最后一个父节点(索引值为[(CurrentSize-1)/2])。在依次访问父节点过程中,将父节点与其左右孩子(若存在)中的最大者交换。

//删除堆顶元素,赋给x,并返回操作后的堆
template <class T>
MaxHeap<T> &MaxHeap<T>::DeleteMax(T &x)
{
if (CurrentSize == 0)
throw OutOfRange();
else
{
x = heap[1];                                   //将堆顶元素给z
heap[1] = heap[CurrentSize];                   //将堆的最后一个元素移到堆顶
CurrentSize--;                                 //当前堆大小-1
int Max_root_i = CurrentSize / 2;              //当前最后一个根节点的索引值
int i = 1;
while (i <= Max_root_i &&(2*i<=CurrentSize || 2*i+1<=CurrentSize))
{
//不满足最大堆的定义
if (heap[i] < heap[2 * i] || heap[i] < heap[2 * i + 1])
{
int max_child_index;
//寻找左右孩子最大者的索引值
if (2 * i < CurrentSize)               //根节点左右孩子都存在
//最大者索引值为左右孩子较大者的索引值
max_child_index = (heap[2 * i] >= heap[2 * i + 1]) ? 2 * i : 2 * i + 1;
else if (2 * i <= CurrentSize)         //仅存在左孩子,最大值索引值为左孩子
max_child_index = 2 * i;
//根节点和左右孩子的较大者交换
Swap(heap[i], heap[max_child_index]);
}
i++;                                      //访问下一个根节点
}
}
return *this;
}


2.4 将数组转化成最大堆

对于一个给定数组,首先删除原来heap数组中的数据,然后为heap分配与给定数组大小相同的空间,并将数组元素赋值给heap数组。接着,采用了正反向两次访问父节点的策略,将heap中元素调整成为最大堆。

首先从最后一个父节点开始(索引值为[CurrentSize/2]),依次访问父节点,直到第一个父节点(即根节点)。在访问每个父节点时,分别判断父节点与其左右孩子的大小关系。先判断其左孩子(若存在),若大于父节点,则与父节点交换;再访问其右孩子(若存在),若大于父节点,则交换。

接着,再从第一个父节点开始,直至访问到最后一个父节点。期间,元素调整策略相同。

//将一个数组转化为最大堆,size为数组中元素个数,ArrayMaxSize为数组最大容量
template <class T>
MaxHeap<T> &MaxHeap<T>::Initialize(T a[], int size, int ArrayMaxSize)
{
delete[] heap;                                 //删除heap中的所有元素
MaxSize = ArrayMaxSize;                        //最大容量
heap = new T[size];
CurrentSize = size-1;                          //当前容量为数组中元素个数
for (int i = 1; i < size; i++)                 //将数组a的值赋给数组heap
heap[i] = a[i];
int root_i = CurrentSize / 2;                  //最后一个根节点索引值
while (root_i>0)                               //从后向前依次访问每个根节点
{
//若根节点小于其左孩子,则交换
if (2 * root_i <= CurrentSize && heap[root_i] < heap[2 * root_i])
Swap(heap[root_i], heap[2 * root_i]);
//若根节点小于其右孩子,则交换
if (2 * root_i + 1 <= CurrentSize && heap[root_i] < heap[2 * root_i + 1])
Swap(heap[root_i], heap[2 * root_i + 1]);
//访问上一个根节点
root_i--;
}
int i = 1;
while (i <= int(CurrentSize / 2))              //从前向后依次访问每个根节点
{
//若根节点小于其左孩子,则交换
if (2 * i <= CurrentSize && heap[i] < heap[2 * i])
Swap(heap[i], heap[2 * i]);
//若根节点小于其右孩子,则交换
if (2 * i + 1 <= CurrentSize && heap[i] < heap[2 * i + 1])
Swap(heap[i], heap[2 * i + 1]);
//访问下一个根节点
i++;
}
return *this;
}


2.5 重载操作符“<<”

//重载输出操作符"<<"
template <class T>
ostream &operator<<(ostream &out, MaxHeap<T> &MH)
{
MH.Output(out);
return out;
}


2.6 测试

#include "MaxHeap.h"

int main()
{
try
{
int a[] = { 12,32,43,56,74,13,9 };
MaxHeap<int> MH;
//测试Insert函数
MH.Insert(3).Insert(4).Insert(10).Insert(6);
cout <<"当前最大堆为:      " << MH<<endl;
//测试DeleteMax函数
int x;
MH.DeleteMax(x);
cout << "删除元素为:        " << x << endl << endl;
cout <<"当前最大堆为:      " << MH << endl;
//测试Initialize函数
MH.Initialize(a, 7, 10);
cout << "输入的数组为:     ";
for (int i = 1; i < 7; i++)
cout << a[i] << "  ";
cout<< endl << endl;
cout <<"转化为最大堆为:   "<< MH << endl;
}
catch (OutOfRange)
{
cout << "越界!!!" << endl;
}
catch (NoMerm)
{
cout << "内存不足!!!" << endl;
}
}


测试结果:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息