6-1 求二叉树高度
题目
思路
要知道整棵树的高度即求以A为根结点的树的高度,我们可以利用分治的思想,将问题分解。然后遍历树我们采用树的先序遍历。
如题中的样例,A的高度可以等效为:以B与C为根结点的两颗子树高度的最大值再加1,写成方程 Height(A) = Max(Height(B), Height(C)) + 1。知道发现当前的这个结点为NULL时,让其返回0。
于是如题中样例,D的左右孩子为空给D返回0,则D取两个孩子返回值中最大的一个,仍然为0,并将其加1作为返回值返回给B,于是B收到了它左孩子D给他的返回值1,再遍历到B的右子树,同理B会收到它右孩子的返回值2,于是B从D和F返回的两个返回值中取最大的一个加1并作为返回值返回给A,以此类推。直接上代码。
代码
int GetHeight(BinTree BT)
{
if (BT == NULL) return 0;
int a = GetHeight(BT->Left);
int b = GetHeight(BT->Right);
return (a > b ? a : b) + 1;
}
6-2 统计二叉树各类结点个数
题目
思路
题中提供了三个全局变量参数n0,n1,n2,分别代表没有孩子,只有一个孩子和两个孩子都有的结点的个数。要求各种结点的个数,只需要遍历一遍整棵树,然后对每个结点进行判断,若左孩子和有孩子都为NULL叶子结点计数器n0就加1,左孩子和右孩子只有其中一个就让计数器n1加1,左右孩子都有就让计数器n2加1。直接上代码。
代码
void count(BiTree root)
{
if (root == NULL) return;
if (root->LChild && root->RChild) ++n2;
else if (!root->LChild && !root->RChild) ++n0;
else ++n1;
count(root->LChild);
count(root->RChild);
}
6-3 哈夫曼树及哈夫曼编码
题目
思路
通过浏览裁判测试程序样例,可以发现输入的权值全部作为了数组w的各个元素。同时HT,HC两个指针参数也是没有初始化的,没有指向的空间。
然后分析两个函数的作用,通过字面可以发现SelectTwoMin函数是为了寻找两个最小值,分析传入的参数,HT在没有建成哈夫曼树前是个森林,所以这个函数的目的是寻找,HT森林中树的根权值最小的两个,s1和s2是int引用类型的变量,所以s1和s2的值可以推测出是为了存找到的最小的两个根节点在HT森林的表中的下标。upbound是HT森林表中的最后一项的下标。
HuffmanCoding函数参数中HT在没有建成哈夫曼树前是个森林,HC是要存储哈夫曼编码的二维字符数组,可以看作是字符串的一维数组。这两个指针变量在传参进来前是没有被初始化指向空间的,传参类型是引用类型,所以我们可以对这两个指针变量在函数中进行初始化。还有一个参数n则代表的是需要编码的对象的个数,也就是w数组的大小。
在HuffmanCoding函数里我们要先将HT建成一个完整的哈夫曼树,再将这棵树进行编码。
建立哈夫曼树
在建立这棵树之前我们要把HT初始化了,分配的空间大小应分到最大,一共有n个需要编码的对象,那么最多这棵树需要有2n-1个结点,我打算跟书上一样不用第一个空间即下标为0的空间所以要创建2n个节点,对HT分配2n个HTNode大小的空间。然后对其进行初始化,从下标为i(1<=i<=n)的结点,weight值设为w[i],其他量parent,lchild,rchild全部设为0。然后让upbound为最后一个元素的下标。
然后通过SelectTwoMin函数找到权值最小的两个结点下标,我们用分别用min1和min2存下来,然后对HT表进行修改,首先让upbound指向下一个元素,然后将其的weight设为min1和min2指向的元素的两个weight之和,并将其的左右孩子分别指向min1和min2,还要将其parent指向设为0。然后修改min1和min2指向的元素的parent为upbound。这就是建树的过程了,不过还有一些细节。
这个建树的过程放在循环里,那么这个循环的出口判断,我选择判断最后一个元素也就是upbound指向的元素的weight与所有需要编码的结点的权值之和相等时,就跳出循环。
然后是SelectTwoMin的编写。我们从1开始upbound结束遍历整个表,寻找权值最小的两个结点,不过这两个结点必须是根结点,根结点,也就意味着它的parent值必须为0,这样就可以找到我们需要的两个最小权值的节点了。
于是最终建好的哈夫曼树表就是这样
对节点进行编码
编码的过程我没有选择书上给出的从每个叶子结点开始向上遍历,我选择了更高效的dfs回溯法遍历,名字听起来很复杂,其实就是先序遍历直到叶子结点,便给叶子结点对应的字符串赋值。
为此我将建立一个char类型的code数组,并引入一个参数length作为code数组的长度,同时方便我对这个数组的末尾进行增删,可以完全把code数组当作一个未进行包装的栈。于是遍历这颗哈夫曼树,往左遍历我就给code数组后面增添一个0,往右遍历我就增添一个1,当走到叶子结点了,给code数组最后加一个\0然后strcpy给当前叶子结点对应下标的字符串即HC对应当前叶子结点的下标的地方开辟一块存储编码字符串的空间。这个过程我采用了先序遍历,所以我选择再写一个专门来编码的函数,方便我递归操作。直接上代码
代码
void SelectTwoMin(int upbound, HuffmanTree HT, int& s1, int& s2)
{
int min1 = 999999, min2 = 999999;
s1 = 1, s2 = 1;
for (int i = 1; i <= upbound; ++i)
{
if (HT[i].parent == 0 && HT[i].weight < min1)
{
min1 = HT[i].weight;
s1 = i;
}
}
for (int i = 1; i <= upbound; ++i)
{
if (HT[i].parent == 0 && HT[i].weight < min2 && i != s1)
{
min2 = HT[i].weight;
s2 = i;
}
}
}
void EncodingHuffman(HuffmanTree& HT, HuffmanCode& HC, int root, char* code, int length)
{
if (HT[root].lchild == 0)
{
code[length] = '\0';
HC[root] = new char[length + 1];
strcpy(HC[root], code);
return;
}
code[length] = '0';
EncodingHuffman(HT, HC, HT[root].lchild, code, length + 1);
code[length] = '1';
EncodingHuffman(HT, HC, HT[root].rchild, code, length + 1);
}
void HuffmanCoding(HuffmanTree& HT, HuffmanCode& HC, int* w, int n)
{
// Tree constructing process
int m = 2 * n - 1, upbound = n, sum = 0;
HT = new HTNode[m + 1];
for (int i = 1; i <= n; ++i)
{
sum += w[i - 1];
HT[i].weight = w[i - 1];
HT[i].parent = 0;
HT[i].lchild = 0;
HT[i].rchild = 0;
}
int min1 = 0, min2 = 0;
while (HT[upbound].weight != sum)
{
SelectTwoMin(upbound, HT, min1, min2);
HT[++upbound].weight = HT[min1].weight + HT[min2].weight;
HT[upbound].parent = 0;
HT[upbound].lchild = min1;
HT[upbound].rchild = min2;
HT[min1].parent = upbound;
HT[min2].parent = upbound;
}
// Encoding process
HC = new char*[n];
char* code = new char[m];
EncodingHuffman(HT, HC, upbound, code, 0);
}
6-4 朋友聚会
题目
如下给出题目的机翻:
今天是伊格内修斯的生日。他邀请了很多朋友。现在是晚餐时间。伊格内修斯想知道他至少需要多少张桌子。你必须注意,并不是所有的朋友都认识对方,所有的朋友都不想和陌生人呆在一起。
这个问题的一个重要规则是,如果我告诉你 A 认识 B,而 B 认识 C,这意味着 A、B、C 彼此认识,所以他们可以呆在一张桌子上。
例如:如果我告诉你 A 知道 B,B 知道 C,D 知道 E,所以 A、B、C 可以留在一张桌子上,而 D、E 必须留在另一张桌子上。所以 Ignatius 至少需要 2 张桌子。
输入
输入以整数 T(1<=T<=25) 开头,表示测试用例的数量。然后是 T 测试用例。每个测试用例都以两个整数 N 和 M(1<=N,M<=1000) 开头。N表示好友数量,好友从1到N标记。然后是 M 行。每行由两个整数 A 和 B(A!=B) 组成,这意味着朋友 A 和朋友 B 彼此认识。两个案例之间将有一个空行。
输出
对于每个测试用例,只需输出 Ignatius 至少需要多少个表即可。不要打印任何空白。
思路
这道题是考察并查集,将所有人熟络的好友保存到pre数组中比如:1认识2,则pre[1] = 2。依次完善数组,不过,要进行一定程度上的路径压缩,即如果1认识2,2认识3,3认识1,就应该设计成pre[1] = 2, pre[2] = 3, pre[3] = 3; 让pre[3]找认识的时让pre[3]指向与1能形成好友联系而pre[i] == i的人,也就是3自己。为什么要这么做,因为在代码里,cnt计数器判断加1的条件是pre[i] == i,所以如果形成了1认识2,2认识3,3认识1,类似于这样的环路,要路径压缩让3指向自己才能然这一桌人有一个pre[i] = i,同样在这样的基础上如果再来一个4认识1,则也直接路径压缩让pre[4] = 3,他们仍然是一桌。好了,直接上代码。因为pre数组是全局变量,我们可以直接在函数中使用。
代码
int find(int x)
{
if (pre[x] != x) return find(pre[x]);
return x;
}
为什么写博客
这是我第一次在平台上这样写,分享我的学习成果,对于我来说,这样写一篇博客能帮助我梳理我在一次作业中所运用的知识,以教学的口吻去讲题更能让我对知识点融会贯通,也可能会有其他人看到我写的博客并对我写的代码,我的思路提出建议。我将尽量多在学习之余,总结自己所学,写成文章发表出来。谢谢