最大堆的性质就是每个非叶子节点都要比它的两个儿子(或是一个)要大,因此我们在插入与删除时必定会对插入点的祖先或是后代进行访问,这种访问方式不宜使用顺序遍历来实现,显然通过层序遍历直接访问下标的方式更加容易实现,对于节点i,i/2即为它的父节点,2*i,2*i+1,即为它的子节点,所以这里使用数组实现而不是链表。
基本功能插入,删除,遍历
class MaxTree
{
public:
vector<int>tree;
int getHight();//获取树高
void Insert(int val);
void put_in(int *arr,int n);
void Erase(int val);
void printTree();
};
为了方便直接使用vector容器装数据了,就不额外写那些接口了
树的高度即为log2(n+1)
int MaxTree::getHight()
{
int n = tree.size();
return log(n + 1) / log(2) + 1;
}
不要忘了包含math.h头文件
由于最大堆是完全二叉树,新数据一律插在最后,然后再向上与其父节点对比,比父节点大了就与父节点换位,然后继续向上重复这个操作直到比父节点小
void MaxTree::Insert(int val)
{
vector<int>::iterator it = find(tree.begin(), tree.end(), val);
if (it != tree.end())
{
cout << "插入失败" << endl;
return;
}
tree.push_back(val);
int n = tree.size();
while (n / 2 != 0)
{
if (tree[n / 2 - 1] > tree[n - 1])
{
return;
}
else
{
int temp = tree[n - 1];
tree[n - 1] = tree[n / 2 - 1];
tree[n / 2 - 1] = temp;
n = n / 2;
}
}
}
要使用find函数先包含头文件algorithm,如果找到与插入数据相同的数据,就插入失败,如果没有就先尾插这个数据,然后获得这个容器的元素个数,即为新元素的下标,然后循环的与其父节点对比,直到它比父节点小或是它成为了根。
还有一种插入方法是先传入一个数组,然后把这个数组变成最大堆,由于要求每个子树都是最大堆,那么就从倒数第二层开始,从后向前的依次将每个堆变成最大堆,这样,从最后一个非叶子结点到根,都进行了算法处理,使得每个结点都比它的儿子大
void MaxTree:: put_in(int *arr,int n)
{
for (int i = 0; i < n; i++)
{
this->tree.push_back(arr[i]);
}
int pos = n / 2;//这个位置是最后一个非叶子结点
while (pos > 0)//对每个非叶子结点做一遍算法
{
int m = pos;
while (m * 2 <= n)
{
int _m;
if (m * 2 + 1 > n)
_m = m * 2;
else
_m = tree[m * 2 - 1] > tree[m * 2] ? (m * 2) : (m * 2 + 1);
if (tree[m - 1] < tree[_m - 1])
{
int temp = tree[m - 1];
tree[m - 1] = tree[_m - 1];
tree[_m - 1] = temp;
}
m = _m;
}
pos--;
}
}
对于删除操作,我们只能对根进行删除,因为我们只对根进行了维护(即根为最大值),其他元素没有顺序可言,而删除一个不确定位置的元素对我们来说没有任何意义,因此不能随意的删除元素。
void MaxTree::Erase()
{
int n = tree.size();
if (n - 1 == 0)
{
tree.pop_back();
return;
}
int temp = tree[n - 1];
tree[n - 1] = tree[0];
tree[0] = temp;
tree.pop_back();
n--;
int m = 1;
while (m * 2 <= n)
{
int _m;
if (m * 2 + 1 > n)
{
_m = m * 2;
}
else
{
_m = tree[m * 2 - 1] > tree[m * 2] ? (m * 2) : (m * 2 + 1);
}
if (tree[m - 1] < tree[_m - 1])
{
temp = tree[m - 1];
tree[m - 1] = tree[_m - 1];
tree[_m - 1] = temp;
m = _m;
}
else
{
return;
}
}
}
与插入操作相反,删除操作需要将根与最后一个元素互换,再尾删它,最后一个元素就成为了新的根,但是新的根并不是最大值,那就要将它与两个儿子对比,与其中大的一个交换,再循环往复直到它比两个儿子都大
在主函数中进行若干插入删除操作后,打印输出来看一下是否满足要求,为了打印出的数据更加直观,这里使用iomanip头文件中的setw()函数调整输出格式,这个函数限定了输出位数,不足位数的以空格代替
void MaxTree::printTree()
{
int n = 0;
int p = 1;
int h = getHight();
for (auto it = tree.begin(); it != tree.end(); it++)
{
cout << setw(pow(2,h + 1 - p)) << *it <<setw(pow(2, h + 1 - p))<< " ";
n++;
if (n == pow(2, p) - 1)
{
p++;
cout << endl;
}
}
cout << endl;
输出效果如图,形象的展示了树的结构。