树的应用
1.并查集
并查集是一种将元素划分到不同集合的数据结构,采用树的双亲表示法来实现。
/**@数据结构:树->并查集 **@作者:9761滴 **@存储结构:双亲表示法 **/ //本文件中实现了并查集的 /* 1.初始化 2.查找 3.合并 */ //等操作 #include<cstdio> #define Max_Size 100 int father[Max_Size]; //初始化 void init(){ for(int i=0;i<Max_Size;i++){ father[i]=i; } } //查找元素x所在的集合 int findParent(int x){ int a=x; while(x!=father[x]){ x=father[x]; } //压缩路径 while(a!=x){ int z=a; a=father[a]; father[z]=x; } return x; } //合并两个集合 void unify(int a,int b){ int fa=findParent(a); int fb=findParent(b); if(a!=b) father[fa]=fb; }
2.二叉排序树BST -> AVL树
定义:首先它是一颗二叉树,其次每个结点的左子树上的结点要小于该结点的值,右子树上结点的值要大于该结点的值。
性质:由于二叉排序树结点之间大小关系的特性,使得在二叉排序树中查找的效率会比较高(O(Logn)),但是如果对一棵BST进行一定次数的插入删除操作之后,它就会变成一个渐线性结构,从而使得查找效率降低(O(N)),因此插入删除结点之后需要保持二叉排序树的平衡,从而出现了AVL(平衡二叉树)树。
AVL树的实现:
/**@数据结构:树->AVL树 **@作者:9761滴 **@存储结构:链表 **/ //本文件中实现了AVL树的 /* 1.查找 2.插入 3.创建 */ //等操作 #include<cstdio> #include<stdlib.h> typedef int ElementType; typedef struct node{ int height; ElementType data; node* lchild; node* rchild; }node,AVLTree; //生成一个新的结点 node* newNode(ElementType x){ node* p=(node*)malloc(sizeof(node)); if(p==NULL) return NULL; p->data=x; p->height=1; p->lchild=NULL; p->rchild=NULL; return p; } //返回以root为根节点的子树的高度 int getHeight(node* root){ if(root==NULL) return 0; return root->height; } //计算root的平衡因子 int getBalanceFactor(node* root){ return getHeight(root->lchild)-getHeight(root->rchild); } int max(int a,int b){ if(a>b) return a; else return b; } //跟新以root为根节点的子树的高度 void updateHeight(node* root){ root->height=max(getHeight(root->lchild),getHeight(root->rchild))+1; } //查找AVL树中数据域为x的结点 node* search(node* root,ElementType x){ if(root==NULL) return NULL; if(root->data==x) return root; if(x>root->data) return search(root->rchild,x); else return search(root->lchild,x); } //左旋操作 void LeftRotation(node* &root){ node* temp=root->rchild; root->rchild=temp->lchild; temp->lchild=root; updateHeight(root); updateHeight(temp); root=temp; } //右旋操作 void RightRotation(node* &root){ node* temp=root->lchild; root->lchild=temp->rchild; temp->rchild=root; updateHeight(root); updateHeight(temp); root=temp; } //往AVL树中插入一个数据域为x的新结点,并保持AVL树特性 bool insert(node* &root,ElementType x){ node* p=newNode(x); if(p==NULL) return false; if(root==NULL){ root=p; return true; } if(x>root->data){ insert(root->rchild,x); updateHeight(root); if(getBalanceFactor(root)==-2){ if(getBalanceFactor(root->rchild)==-1){ LeftRotation(root); } else if(getBalanceFactor(root->rchild)==1){ RightRotation(root->rchild); LeftRotation(root); } } } else{ insert(root->lchild,x); updateHeight(root); if(getBalanceFactor(root)==2){ if(getBalanceFactor(root->lchild)==1){ RightRotation(root); } else if(getBalanceFactor(root->lchild)==-1){ LeftRotation(root->lchild); RightRotation(root); } } } } //创建AVL树 node* Create(ElementType data[],int n){ node* root=NULL; for(int i=0;i<n;i++){ insert(root,data[i]); } return root; } int main(){ return 0; }
3.哈夫曼树
首先搞清楚几个概念:
权:在很多应用中,树中结点常常被赋予一个表示某种意义的数值,称为该结点的权。
带权路径长度:从根节点到某个结点的路径长度(经过边的条数)与该结点权值的乘积
树的带权路径长度WPL:树中所有叶结点(注意是叶结点哦)的带权路径长度之和称为该树的带权路径长度。
哈夫曼树:含有n个带权叶节点的二叉树中,WPL最小的二叉树称为哈夫曼树。
将n个权值分别为w1,w2...wn的结点构造成一棵哈夫曼树的方法:
1.将这n个结点分别作为n棵仅含有一个结点的二叉树,构成森林F
2.构造一个新的结点,从F中选取两棵根节点权值最小的树作为新结点的左、右孩子,并且将新结点的权值置为左右子树上根节点的权值之和
3.从F中删除刚才选出的两棵树,同时将新得到的树加入F中
4.重复步骤 2、3, 直到F中只剩下一棵树。
哈夫曼树性质:
每个初始结点最终都称为叶节点,且权值越小的结点到根节点的路径长度越大
构造过程中一共新建了n-1个结点,因此哈夫曼树的结点总数为2n-1
每次构造都选择2棵树作为新结点的孩子,因此哈夫曼树中不存在度为1的结点
n个权值结点构造的哈夫曼树不唯一
4.哈夫曼编码
固定长度编码:在字符编码中,常用长度相等的二进制位表示每个字符,这种编码方式称为固定长度编码。
可变长度编码:用不等长的二进制位表示不同字符
哈夫曼编码:可变长度编码的一种,用于数据压缩。
前缀编码:没有一个编码是另一个编码的前缀,这样的编码叫做前缀编码。如对a,b,c编码,a=0,b=101,c=100, 那么串码就00101100就可以唯一的识别为aabc,而不产生歧义
哈夫曼编码过程:
将每个字符当作一个独立的结点,其权值为它出现的频度,构造哈夫曼树。
构造好哈夫曼树之后,所有字符都出现在了叶节点中,可以将字符的编码解释为从根到该节点字符的路径上边标记的序列,其中0表示“转向左孩子”的边,1表示转向有孩子的边。