二叉树算法设计
简介
遍历二叉树是二叉树各种操作的基础,
遍历算法中对每个结点的访问操作可以是多种形式及多个操作,
根据遍历算法的框架,适当修改访问操作的内容,可以派生出很多关于二叉树的应用算法。
设计算法求二叉树结点设计
由中序遍历算法改造方案
void Count(BiNode *root){
if (root) {
Count(root->lchild);
number+ +; //number为数据成员
Count(root->rchild);
}
}
由递归思想 树结点数目=左子树结点数目+右子树结点数目+1
template<class T>
int BiTree<T>::cout(BiNode<T>* root){
int number=0;
if(root==NULL)
number=0;
else
number=cout(root->lchild)+cout(root->rchild)+cout;
return number;
二叉树的高度计算
递归思路定义:从根节点出发开始计算,
如果root==NULL, 高度为0;
否则,分别计算左子树的高度;右子树的高度;返回max(左子树高度,右子树高度)+1;
template<typename T>
int BiTree<T>::cal_height(BiTreeNode<T> * root){
int lheight=0,rheight=0;
if (root==0) return 0;
lheight=cal_height(root->lchild);
rheight=cal_height(root->rchild);
if (lheight>rheight) return lheight+1;
else return rheight+1;
}
输出中缀表达式
基本思想:
中序遍历。
中序遍历左子树前,输出左括号
中序遍历右子树后,输出右括号
如果遍历叶子结点的左右子树,不输出括号
如果遍历根节点的左右子树,不输出括号(否则,会得到形如(a+b)的表达式)
void BiTree<T>::In_Expression(BiNode<T>* root){
if(root)
{
if(root!=this->root&&root->lchild!=0&&root->rchild!=0)
cout<<"(";
In_Expression(root->lchild);
cout<<root->data;
In_Expression(root->rchild);
if(root!=this->root&&root->lchild!=0&&root->rchild!=0)
cout<<")";
}
}
输出二叉树旋转90度后的样子
思路:
按照从右向左的顺序,中序遍历
每行输出一个结点
按照结点的层次,进行缩进。
template <class T>
void BiTree<T>::Left_Rotate(BiNode<T>* root,int level){
if(root){
Left_Rotate(root->rchild, level+1);
for(int i=0;i<level;i++)
cout<<"\t";
cout<<root->data<<endl;
Left_Rotate(root->lchild, level+1);
}
}
计算二叉树的宽度
二叉树宽度定义:具有节点数最多的那一层的节点数;
利用层序实现:
struct q_element{ BiNode * root; int level;};
int BiTree::Width(){
queue< struct q_element > q;
int num[100]={0,1};
q_element s,child;
BiNode *root;
root=this->root;
if(root==NULL)
return 0;
s.root=root; s.level=1; q.push(s);
while(!q.empty()) {
s=q.front();
if(s.root->lchild){
num[s.level+1]++;
child.root=s.root->lchild;
child.level=s.level+1;
q.push(child);
}
if(s.root->rchild) {
num[s.level+1]++;
child.root=s.root->rchild;
child.level=s.level+1;
q.push(child);
}
q.pop();
}
int max=0,i=1;
while(num[i]>0){
if(max<num[i])
max=num[i];
i++;
}
return max;
}
判断一颗树是否为完全二叉树
基本思想:
基于层次遍历。
定义bool变量is_leaf,初值为false
如果is_leaf的值为true, 表示遍历的过程中遇到了叶子结点。
一旦在叶子结点之后再出现度为1、2的结点,则该树不是完全二叉树。
基于层次遍历。
在层次遍历中,如果遇到一个节点
只有右儿子,没有左儿子,
return false;
只有左,没有右
If (is_leaftrue) return false;
Else is_leaf=true;
两个儿子均为空
is_leaf=true
两个儿子均不空
If (is_leaftrue) return false;
将存在的儿子入队
能遍历完所有节点,即为完全二叉树
template<class T>
bool BiTree<T>::Is_Wanquan(BiNode<T> *root){
queue<BiNode<T>*> q;
BiNode <T>* pointer;
bool is_leaf=false;
if(!root)
return false;
q.push(root);
while(!q.empty()) {
pointer=q.front(); q.pop();
if(pointer->rchild!=NULL && pointer->lchild==NULL)
return false;
else if(pointer->rchild==NULL && pointer->lchild!=NULL )
if(is_leaf)
return false;
else //如果是完全二叉树,则,该结点之后的结点应为叶子节点
is_leaf=true;
else if(pointer->rchild==NULL && pointer->lchild==NULL )
is_leaf=true;
if(pointer->lchild!=NULL)
q.push(pointer->lchild);
if(pointer->rchild!=NULL)
q.push(pointer->rchild);
}
return true;
}
三叉链表
在二叉表中不方便求结点的双亲,这时在二叉表的基础上增加以个指向双亲的指针域。
按前序扩展遍历序列输入输入节点的值
如果输入节点之为“#”,则建立一棵空的子树
否则,根结点申请空间,将输入值写入数据域中,同时将三个指针赋空值
以相同方法的创建根节点的左子树,并设置子树的根的parent
以相同的方法创建根节点的右子树,并设置子树的根的parent
template<class T>
struct Node
{
T data;
Node<T> * lchild, *rchild,*parent;
};
template <class T>
BiNode<T> * BiTree<T>::Creat(BiNode<T> * &root ,BiNode<T> *parent){
T ch;
cout<<"请输入创建一棵二叉树的结点数据"<<endl;
cin>>ch;
if (ch=="#") root = NULL;
else{
root = new BiNode<T>; //生成一个结点
root->data=ch;
root->parent=parent;
Creat(root->lchild,root ); //递归建立左子树
Creat(root->rchild,root); //递归建立右子树
}
return root;
}
template<class T>
BiTree<T>::BiTree(int i)
{
number=0;
Creat(root,NULL);
}
树、森林与二叉树的转换
树和二叉树之间的对应关系
树的兄弟关系对应二叉树的双亲和右孩子
树的双亲关系和长子对应二叉树的双亲和左孩子
操作
树到二叉树
将树的兄弟之间加线,保留双亲和第一个孩子的连线删除与其余孩子的连线,顺时针转动使之层次分明。
树的前序遍历等价于二叉树的前序遍历,树的后续遍历等价于二叉树的中序遍历
森林转化为二叉树
⑴ 将森林中的每棵树转换成二叉树;
⑵ 从第二棵二叉树开始,
依次把后一棵二叉树的根结点作为前一棵二叉树根结点的右孩子,
当所有二叉树连起来后,此时所得到的二叉树就是由森林转换得到的二叉树。
二叉树转化为森林和树
⑴ 加线——若某结点x是其双亲y的左孩子,则把结点x的右孩子、右孩子的右孩子、……,都与结点y用线连起来;
⑵ 去线——删去原二叉树中所有的双亲结点与右孩子结点的连线;
⑶ 层次调整——整理由⑴、⑵两步所得到的树或森林,使之层次分明。
森林的遍历
1、前序遍历森林及前序遍历森林中的每一颗树。
2、后序遍历森林及后序遍历森林中的每一颗树。
最优树及哈夫曼编码
相关概念
叶子结点的权值:给每个叶子结点赋予一个有意义的值;
二叉树的带权路径长度:从根节点到每个叶子结点路劲长度与权值乘积之和称为二叉树的带权路径长度;
哈夫曼树
哈夫曼树的概念
给定一组权值确定的二叉树叶子结点,带权路径长度最短的二叉树。
特点:1、权值越大的结点里根结点越近,权值越小的结点离根结点越远;
2、只有度为0的结点(叶子结点)和度为2的结点(分支结点),没有度为1的结点。
哈夫曼算法基本思想:
⑴ 初始化:由给定的n个权值{w1,w2,…,wn}构造n棵只有一个根结点的二叉树,从而得到一个二叉树集合F={T1,T2,…,Tn};
⑵ 选取与合并:在F中选取根结点的权值最小的两棵二叉树分别作为左、右子树构造一棵新的二叉树,这棵新二叉树的根结点的权值为其左、右子树根结点的权值之和;
⑶ 删除与加入:在F中删除作为左、右子树的两棵二叉树,并将新建立的二叉树加入到F中;
⑷ 重复⑵、⑶两步,当集合F中只剩下一棵二叉树时,这棵二叉树便是哈夫曼树。
哈夫曼算法
1.数组huffTree初始化,所有元素结点的双亲、左
右孩子都置为-1;
2. 数组huffTree的前n个元素的权值置给定值w[n];
3. 进行n-1次合并
3.1 在二叉树集合中选取两个权值最小的根结点,
其下标分别为i1, i2;
3.2 将二叉树i1、i2合并为一棵新的二叉树k(初值为n;依次递增);
void HuffmanTree(element huffTree[ ], int w[ ], int n ) {
for (i=0; i<2*n-1; i++) {
huffTree [i].parent= -1;
huffTree [i].lchild= -1;
huffTree [i].rchild= -1;
}
for (i=0; i<n; i++)
huffTree [i].weight=w[i];
for (k=n; k<2*n-1; k++) {
Select(huffTree, &i1, &i2);
huffTree[k].weight=huffTree[i1].weight+huffTree[i2].weight;
huffTree[i1].parent=k;
huffTree[i2].parent=k;
huffTree[k].lchild=i1;
huffTree[k].rchild=i2;
}
}
哈夫曼编码
相关概念:
编码:给每一个对象标记一个二进制位串来表示一组对象。
例:ASCII,指令系统
等长编码:表示一组对象的二进制位串的长度相等。
不等长编码:表示一组对象的二进制位串的长度不相等。
前缀编码:一组编码中任一编码都不是其它任何一个编码的前缀 。
前缀编码保证了在解码时不会有多种可能。
例:一组字符{A, B, C, D, E, F, G}出现的频率分别是{9, 11, 5, 7, 8, 2, 3},设计最经济的编码方案。
优点:每个字符都是前缀编码,保证了解码不会出现其他可能;整体采用不等长编码,减少了数据量。
哈夫曼算法的实现
从叶子结点到根, 逆向求每个叶子结点对应的哈夫曼编码
根据huffman树中叶子节点的个数,构造一个字符串数组,每个数组分量是一个字符串,用于存放该节点对应的huffman编码
对每个叶子节点i(i=0; i<n; i++),进行下面的工作:
Char **hcode,*cd;
hcode=new char *[n];
cd=new char [n * sizeof(char )]; /*分配求当前编码的工作空间*/
cd[n-1]=’\0’; /*从右向左逐位存放编码,首先存放编码结束符*/
for(i=1; i<=n; i++) /*求n个叶子结点对应的哈夫曼编码*/
{
start=n-1; /*初始化编码起始指针*/
for(c=i, p=ht[i].parent; p! =0; c=p, p=ht[p].parent)
if(ht[p].LChild==c) cd[--start]=′0′;
else cd[--start]=′1′; /*右分支标1*/
hcode[i]=new char [n-start] /*为第i个编码分配空间*/
strcpy(hcode[i], &cd[start]);
}
}
线索二叉树
二叉树的遍历运算是将二叉树中结点按一定规律线性化的过程。
1、当以二叉链表作为存储结构时,只能找到结点的左、右孩子信息,而不能直接得到结点在遍历序列中的前驱和后继信息。
2、要得到这些信息可采用以下两种方法:
2.1、第一种方法是将二叉树遍历一遍,在遍历过程中便可得到结点的前驱和后继,但这种动态访问浪费时间;
2.2、第二种方法是充分利用二叉链表中的空链域, 将遍历过程中结点的前驱、 后继信息保存下来。
线索:将二叉链表中的空指针域指向前驱结点和后继结点的指针被称为线索;
线索化:使二叉链表中结点的空链域存放其前驱或后继信息的过程称为线索化;
线索二叉树:加上线索的二叉树称为线索二叉树
构造线索
template <class T> void ThrBiTree<T>::ThrBiTree (ThrNode<T>*root) {
if (root==NULL) return; //递归结束条件
ThrBiTree(root->lchild);
if (!root->lchild){ //对root的左指针进行处理
root->ltag = Thread;
root->lchild = pre; //设置pre的前驱线索,pre初始为空
}
if (!root->rchild) root->rtag = Thread;
if(pre != NULL){
if (pre->rtag==Thread) pre->rchild = root;
}
pre = root;
ThrBiTree(root->rchild);
}
中序遍历中序线索二叉树
template <class T> ThrNode<T>* InThrBiTree<T>::Next(ThrNode<T>* p)
{
ThrNode<T>* q; //要查找的p的后继
if (p->rtag==Thread) q = p->rchild;
else{
q = p->rchild;
while (q->ltag==Child) {
q = q->lchild;
}
}
return q;
}