[数据结构] 树、森林及二叉树的应用
树、森林
树的存储结构
双亲表示法
双亲表示法的存储结构
#define MAX_TREE_SIZE 100 typedef struct { int data; int parent; }PTNode; typedef struct { PTNode nodes[MAX_TREE_SIZE]; int n; }PTree;
【注】 区别树的顺序存储结构与二叉树的顺序存储结构。在树的顺序存储结构中,数组下标代表节点的编号,下标中所存的内容指示了节点之间的关系。而在二叉树的顺序存储结构中, 数组下标既表达了节点的编号,又指示了二叉树中节点之间的关系。当然,二叉树属于树,因此二叉树也可以用树的存储结构来存储,但树却不能都用都用二叉树的存储结构来存储。
孩子表示法
孩子表示法是将每个节点的孩子节点视为一个线性表,且以单链表作为存储结构,则
与双亲表示法相反,孩子表示法寻找孩子的操作非常方便,而寻找双亲的操作则需要遍历
孩子兄弟表示法
又称二叉树表示法,即以二叉链表作为树的存储结构。孩子兄弟表示法使每个节点包括三个部分的内容:节点值、指向节点第一个孩子节点的指针、指向节点下一个兄弟节点的指针
结构体如下:
typedef struct CSNode { inr data; struct CSNode *firstchild, *nextsibling; }CSNode, *CSTree;
树、森林、二叉树的转换
树转换为二叉树
(1)在兄弟节点之间画一条线;
(2)对每个节点,只保留它与第一个孩子的连线,而与其他孩子的连线全部删除;
(3)以树根为轴心,顺时针旋转45°。
以下代码引用我大佬同学:作者:Amαdeus,出处:https://www.cnblogs.com/MAKISE004/p/17089756.html //树 转化为 二叉树 BinaryTree CSTree_Transform_to_BinaryTree(CSTree ct){ if(!ct) return NULL; BinaryTree T = (BinaryTree)malloc(sizeof(BiNode)); T->data = ct->data; //相当于将left变为firstchild, 将right变为nextsibling 本质的形态没有改变 T->leftchild = CSTree_Transform_to_BinaryTree(ct->firstchild); T->rightchild = CSTree_Transform_to_BinaryTree(ct->nextsibling); return T; }
森林转换为二叉树
(1)将森林中的每棵树转换成相应的二叉树;
(2)每棵树的根视为兄弟关系,加上连线;
(3)以第一棵树的根为轴心顺时针旋转45°。
以下代码引用我大佬同学:作者:Amαdeus,出处:https://www.cnblogs.com/MAKISE004/p/17089756.html //森林 转化为 二叉树 BinaryTree Forest_Transform_to_BinaryTree(CSTree ct[], int low, int high){ if(low > high) return NULL; //每个树变成二叉树 BinaryTree T = CSTree_Transform_to_BinaryTree(ct[low]); //通过rightchild连接每一个二叉树的根节点 T->rightchild = Forest_Transform_to_BinaryTree(ct, low + 1, high); return T; }
树、森林的遍历
树的遍历
-
先根遍历。若树非空,则按如下规则遍历:
- 先访问根节点
- 再依次遍历根节点的每棵子树,遍历子树时仍遵循先根后子树的规则
-
后根遍历。若树非空,则按如下规则遍历:
- 先依次遍历根节点的每棵子树,遍历子树时仍遵循先子树后根的规则
- 再访问根节点
树的先根遍历与对应二叉树的先序序列相同,树的后根遍历与对应二叉树的中序序列相同。
森林的遍历
-
先序遍历森林。若森林非空,则按如下规则遍历:
- 访问森林中第一棵树的根节点
- 先序遍历第一棵树中根节点的子树森林
- 先序遍历除去第一棵树之后剩余的树构成的森林
-
中序遍历森林。若森林非空,则按如下规则遍历:
- 中序遍历森林中第一棵树的根节点的子树森林
- 访问第一棵树的根节点
- 中序遍历粗去第一棵树之后剩余的树构成的森林
树与二叉树的应用
哈夫曼树和哈夫曼编码
几个概念
-
路径:树中一个节点到另一个节点之间的分支构成
-
路径长度:路径上的分支数目
-
权:树中节点被赋予的一个表示某种意义的数值
-
带权路径长度:从树的根到一个节点的路径长度与该节点上权值的乘积
其中,
是第 个叶节点所带的权值, 是该叶节点到根节点的路径长度
在含有
下面看个算法:递归求WPL
int getWPL(struct TreeNode *root, int depth) { if (root == NULL) { // 如果节点为空,返回0 return 0; } if (root->left == NULL && root->right == NULL) { // 如果节点是叶子节点,返回带权路径长度 return depth * root->val; } // 如果节点不是叶子节点,递归计算左子树和右子树的WPL,并相加返回 return getWPL(root->left, depth + 1) + getWPL(root->right, depth + 1); }
接下来咱们举个栗子,来看一下哈夫曼编码
看题:来自北邮考研机试
题解:
// 优先队列求哈夫曼树最短带权路径长度 #include<bits/stdc++.h> using namespace std; int main() { int n; cin >> n; priority_queue<int, vector<int>, greater<int> > q; for(int i = 0; i < n; i ++) { int x; cin >> x; q.push(x); } int ans = 0; while(q.size() > 1) { int t1 = q.top(); q.pop(); int t2 = q.top(); q.pop(); q.push(t1 + t2); ans += t1 + t2; } cout << ans << endl; return 0; }
并查集
这里不想做太多解释,我们看一下y总的模版(写的时候已经快要零点了,第二天还要早起)(PS:第二天果然多睡了半个小时)
(1)朴素并查集: int p[N]; //存储每个点的祖宗节点 // 返回x的祖宗节点 int find(int x) { if (p[x] != x) p[x] = find(p[x]); return p[x]; } // 初始化,假定节点编号是1~n for (int i = 1; i <= n; i ++ ) p[i] = i; // 合并a和b所在的两个集合: p[find(a)] = find(b); (2)维护size的并查集: int p[N], size[N]; //p[]存储每个点的祖宗节点, size[]只有祖宗节点的有意义,表示祖宗节点所在集合中的点的数量 // 返回x的祖宗节点 int find(int x) { if (p[x] != x) p[x] = find(p[x]); return p[x]; } // 初始化,假定节点编号是1~n for (int i = 1; i <= n; i ++ ) { p[i] = i; size[i] = 1; } // 合并a和b所在的两个集合: size[find(b)] += size[find(a)]; p[find(a)] = find(b); (3)维护到祖宗节点距离的并查集: int p[N], d[N]; //p[]存储每个点的祖宗节点, d[x]存储x到p[x]的距离 // 返回x的祖宗节点 int find(int x) { if (p[x] != x) { int u = find(p[x]); d[x] += d[p[x]]; p[x] = u; } return p[x]; } // 初始化,假定节点编号是1~n for (int i = 1; i <= n; i ++ ) { p[i] = i; d[i] = 0; } // 合并a和b所在的两个集合: p[find(a)] = find(b); d[find(a)] = distance; // 根据具体问题,初始化find(a)的偏移量 作者:yxc 链接:https://www.acwing.com/blog/content/404/ 来源:AcWing 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
#include<bits/stdc++.h> using namespace std; const int N = 100010; int p[N];//定义多个集合 int find(int x) { if(p[x] != x) p[x] = find(p[x]); /* 经上述可以发现,每个集合中只有祖宗节点的p[x]值等于他自己,即: p[x]=x; */ return p[x]; //找到了便返回祖宗节点的值 }C int main() { int n, m; scanf("%d%d", &n, &m); for(int i = 1; i <= n; i ++) p[i]=i; while(m --) { char op[2]; int a, b; scanf("%s%d%d", op, &a, &b); if(*op == 'M') p[find(a)] = find(b);//集合合并操作 else if(find(a)==find(b)) //如果祖宗节点一样,就输出yes printf("Yes\n"); else printf("No\n"); } return 0; }
本文作者:拂云堂 诩言
本文链接:https://www.cnblogs.com/wanyy-home/p/18053227
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步