树和二叉树的应用
压缩与哈夫曼树
最优编码问题
设组成文件的字符集A={a1,a2,…an},其中ai出现的次数为ci,ai的编码成都为li,设计编码方案使min$\sum_{i=1}^{n}ci*li$
扩充二叉树

加权路径长度
外结点权值为wi,深度为Li
$WPL=\sum wi*Li$
一种文件编码方案可以映射为一颗扩充二叉树
字符 ==> 外结点
字符出现次数ci ==> 外结点权值wi
字符的编码长度li ==> 外结点深度Li
文件总编码长度 ==> 扩展二叉树WPL值
哈夫曼算法
自下而上
重复做:选择权值最小的两个结点来生成新结点,新结点作为原结点的父亲,权值是原来两个结点权值之和
编码:每个左分支标记为0,右分支标记为1

哈夫曼树
二子性 ==> n个叶结点,n-1个非叶结点,2n-1个总结点
同权不同构 ==> 哈夫曼树形态不唯一,编码不唯一,WPL唯一,最小编码长度唯一
哈夫曼算法实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| struct HuffmanNode { char data; int weight; HuffmanNode* left,*right; } const int N=300; HuffmanNode* H[N]; void CreateHuffmanNode(char ch[],int freq[],int n) { for(int i=0;i<n;i++) { H[i]=new HaffmanNode; H[i]->data=ch[i]; H[i]->weight=freq[i]; H[i]->left=H[i]->right=NULL; } sort(H,n); } HuffmanNode* CreateHuffmanTree(HuffmanNode* H[],int n) { for(int i=0;i<n-1;i++) { HuffmanNode* t=new HuffmanNode; t->weight=H[i]->weight+H[i+1]->weight; t->left=H[i]; t->right=H[i+1]; t->data=''; //把新结点t插入数组中 int j=i+2; while( j<n && H[j]->weight < t->weight) { H[j-1]=H[j]; j++; } H[j-1]=t; } return H[n-1]; }
|
哈夫曼树的遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| void traversal(HuffmanNode* t,vector<int>&path) { if(t->left==NULL&&t->right==NULL) { int n=path.size(); Cnt+=n*t->weight; for(int i=0;i<n;i++) { Path[(int)t->data][i]=path[i]; } Num[(int)t->data]=n; return; } if(t->left) { path.push_back(0); traversal(t->left,path); path.pop_back(); } if(t->right) { path.push_back(1); traversal(t->right,path); path.pop_back(); } }
|
译码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| void decode(HuffmanNode* t,vector<int>code) { vector<char>path; int n=code.size(); int i=0; HuffmanNode* origin=t; while(i<n) { while(t->left!=NULL||t->right!=NULL) { if(t==NULL||i>=n) { cout<<"INVALID"<<endl; return; } if(code[i]==0) t=t->left; else t=t->right; i++; } path.push_back(t->data); t=origin; } for(int i=0;i<(int)path.size();i++) cout<<path[i]; cout<<endl; }
|
不建树求WPL【北京邮电大学机试】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| int CreateHuffmanTree(int weight[],int n) { sort(weight,n); int WPL=0; for(int i=0;i<n-1;i++) { int t=weight[i]+weight[i+1]; WPL+=t; int j=i+2; while(j<n && t>weight[i]) weight[j-1]=weight[j]; weight[j-1]=t; } return WPL; }
|
给定树,求WPL
1 2 3 4 5 6 7 8 9
| int WPL=0; void WPLPreOrder(TreeNode* t,int k) { if(t==NULL) return; if(t->left==NULL && t->right==NULL) WPL+=t->weight * k; WPLPreOrder(t->left,k+1); WPLPreOrder(t->right,k+1); }
|
表达式树

根据后缀表达式构造表达式二叉树
从左到右扫描后缀表达式,扫描到一个符号就生成一个结点,符号位结点数据域值
- 操作数:将操作数结点压栈
- 运算符:从栈中弹出两个结点,分别作为当前运算符的左右孩子,再将当前运算符结点压栈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| TreeNode* buildExpTree(char* s,int n) { stack<TreeNode*>S; for(int i=0;i<n;i++) { TreeNode* p=new TreeNode; p->data=s[i]; if(s[i]>='a' && s[i] <='z') p->left=p->right=NULL; else { p->right=S.pop(); p->left=S.pop(); } S.push(p); } return S.peak(); }
|
计算表达式二叉树对应的值
1 2 3 4 5 6 7 8 9 10 11 12
| int Calc(TreeNode* t) { if(t->left==NULL && t->right==NULL) return t->data-'0'; int ans1=Calc(t->left); int ans2=Calc(t->right); char op=t->data; if(op=='+') return ans1+ans2; else if(op=='-') return ans1-ans2; else if(op=='*') return ans1*ans2; else return ans1/ans2; }
|
并查集
并查集的实现
- MAKE_SET(x):为x生成一颗单结点树,x为根节点
- UNION(x,y):合并x所在的树和y在的树,让其中一棵树的根的复制真只想另一棵树的根。
- FIND(x):查找x所在树的根结点
用Father数组表示集合
1 2 3 4 5 6 7 8 9 10 11 12 13
| void MS(int x) { Father[x]=0; } int FD(int x) { if(Father[x]==0) return x; return FD(Father(x)); } void UN(int x,int y) { Father[FD(y)]==FD(x); }
|
并查集的优化
- 路径压缩
在FIND(x)的操作中,找到元素x所在树的根fx之后,将x到fx路径上所有结点的父亲都改成fx1 2 3 4 5 6
| int FIND(int x) { if(Father[x]==0) return x; Father[x]=FIND(Father[x]); return Father[x]; }
|
- 按秩合并
每个结点维护一个秩,表示以该节点为根的子树的高度的上界
合并:秩大的根做秩小的根的父亲1 2 3 4 5 6 7 8 9 10 11 12 13 14
| void UNION(int x,int y) { int fx=FIND(x); int fy=FIND(y); if(fx==fy) return; if(rank[fx]>rank(fy)) Father[fy]=fx; else { Father[fx]=fy; if(rank[fx]==rank[fy]) rank[fy]++; } }
|
如果x是根结点,Father[x]存秩的相反数
否则,Father[x]存x的父亲地址
最终版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| void Make_Set(int x) { Father[x]=0; } int FIND(int x) { if(Father[x]<=0) return x; Father[x]=Find(Father[x]); return Father[x]; } void UNION(int x,int y) { int fx=Find(x); int fy=FIND(y); if(fx==fy) return; if(Father[fx]<Father[fy]) Father[fy]=fx; else { Father[fx]==fy; if(Father[fx]==Father[fy]) Father[fy]--; } }
|
并查集的应用–等价性问题
自反,对称,传递



