记录作业题
一、题目要求
假设定义最大堆为满足如下性质的完全三叉树:
- 空树为堆;
- 每个结点最多有3棵子树,且结点的值不小于所有子树根的值,且所有子树均为最大堆。
按上述述要求完成三叉堆的存储结构设计,再实现三叉堆的向下调整算法,最后完成利用三叉堆进行排序的算法。要求利用大数据量进行测试,同原有堆排序算法在时间上进行对比分析。
二、二叉堆代码
本题算法逻辑与二叉堆代码一致,因此先放出课本上二叉堆代码。
要注意的是,长度为n的堆中各元素的序号是从0开始,到n-1结束。因此对于序号为i的节点而言,其左孩子序号为2*i+1,右孩子序号为2*i+2。
template <class ElemType>
void HeapSort(ElemType elem[], int n)
// 操作结果:对数组elem进行堆排序
{
int i;
for (i = (n-2)/2; i >= 0; --i) // 初始建堆,将elem[0 .. n - 1]调整成最大堆
FilterDown(elem, i, n - 1);
for (i = n - 1; i > 0; --i)
{ // 第i趟堆排序
Swap(elem[0], elem[i]);
// 将堆顶元素和当前未经排序的子序列elem[0 .. i]中最后一个元素交换
FilterDown(elem, 0, i - 1); // 将elem[0 .. i - 1]重新调整为最大堆
}
}
template <class ElemType>
void FilterDown(ElemType elem[], int low, int high)
// 操作结果:将以low为根的子树调整成为一个最大堆
{
int f = low, i = 2 * low + 1;
ElemType e = elem[low];
while (i <= high)
{ // f为被调整结点,i为f的最大孩子
if (i < high && elem[i] < elem[i + 1])
i++; // f有右孩子,且右孩子的值更大, i指向右孩子
if (e < elem[i])
{ // 孩子的值大于其双亲,则进行向下调整
elem[f] = elem[i];
f = i;
i = 2 * f + 1;
}
else
break;
}
elem[f] = e;
}
三、思路
对于排序部分,三叉堆与二叉堆的整体逻辑一模一样:都是将堆中具有最大关键字的元素elem[0]与堆的最后一个元素交换位置,然后调整堆中除最后一个元素以外的元素,使之成为最大堆,再将新的elem[0]与堆的最后一个元素交换位置……不断重复此过程直到将所有元素排好序。由于逻辑相同,所以只需要根据完全三叉树的性质对相应数字做修改即可,也就是在寻找最后一个分支节点i时,采取计算公式i=(n-1)/3,其余部分代码完全相同。
三叉堆和二叉堆最大的不同之处出现在调整函数。因为三叉堆的一个根节点最多会有三个孩子,所以双亲结点与孩子结点的序号对应会有不同;在寻找被调整结点的最大的孩子时,也要比二叉堆的对应环节更加复杂。具体改变如下:
- 对于序号为f的被调整结点,其最左边的孩子序号为3*f+1。
- 为了找到三个孩子中关键字最大的孩子j,需要使用多重判断。如果最左边的孩子小于中间的孩子,则再比较中间的孩子与最右边的孩子,j表示后两者中较大的孩子;如果最左边的孩子大于中间的孩子且小于最右边的孩子,则j表示最右边的孩子;如果以上两种情况都不成立,则最左边的孩子为最大的孩子,j表示最左边的孩子。
四、三叉堆代码
经过以上分析,通过仿照二叉堆代码,得到三叉堆代码如下:
template <class ElemType>
void HeapSort3(ElemType elem[], int n)
{
int i;
for(i = (n-1)/3; i >= 0; --i)
FilterDown3(elem, i, n - 1);
for(i = n - 1; i > 0; --i)
{
Swap(elem[0], elem[i]);
FilterDown3(elem, 0, i - 1);
}
}
template <class ElemType>
void FilterDown3(ElemType elem[], int low, int high)
{
int f = low, j = 3 * low + 1;
ElemType e = elem[low];
while(j <= high)
{
if(j < high && elem[j] < elem[j + 1])
{
j++;
if(j < high && elem[j] < elem[j + 1])
j++;
}
else if(j + 1 < high && elem[j] < elem[j + 2])
j = j + 2;
if(e < elem[j])
{
elem[f] = elem[j];
f = j;
j = 3 * f + 1;
}
else
break;
}
elem[f] = e;
}
用大数据量进行测试后发现,三叉堆的排序算法比原先二叉堆排序算法的速度要更快一些。分析认为这是由于在三叉堆向下调整函数的每一次循环中,比较的节点数量更多,因此将数组化为最大堆所需的循环更少,消耗的时间也就更少。