判断一棵树是否是完全二叉树
基于层次遍历。
在层次遍历中,如果遇到一个节点
只有右儿子,没有左儿子,
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;
}
三叉链表
在二叉链表的基础上增加了一个指向双亲的指针域。
实现
template<class T>
struct Node
{
T data;
Node<T> * lchild, *rchild,*parent;
};
按前序扩展遍历序列输入输入节点的值
如果输入节点之为“#”,则建立一棵空的子树
否则,根结点申请空间,将输入值写入数据域中,同时将三个指针赋空值
以相同方法的创建根节点的左子树,并设置子树的根的parent
以相同的方法创建根节点的右子树,并设置子树的根的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);
}
树和二叉树之间的对应关系:
1.兄弟加线
2.保留双亲与第一孩子连线,删去与其他孩子的连线.
3.顺时针转动,使之层次分明.
森林转换为二叉树
⑴ 将森林中的每棵树转换成二叉树;
⑵ 从第二棵二叉树开始,
依次把后一棵二叉树的根结点作为前一棵二叉树根结点的右孩子,
当所有二叉树连起来后,此时所得到的二叉树就是由森林转换得到的二叉树。
二叉树转换为树或森林
⑴ 加线——若某结点x是其双亲y的左孩子,则把结点x的右孩子、右孩子的右孩子、……,都与结点y用线连起来;
⑵ 去线——删去原二叉树中所有的双亲结点与右孩子结点的连线;
⑶ 层次调整——整理由⑴、⑵两步所得到的树或森林,使之层次分明。
森林的遍历
两种遍历方法:
⑴前序(根)遍历:前序遍历森林即为前序遍历森林中的每一棵树。
⑵后序(根)遍历:后序遍历森林即为后序遍历森林中的每一棵树。
最优二叉树-哈夫曼树及哈夫曼编码
哈夫曼树:给定一组具有确定权值的叶子结点,带权路径长度最小的二叉树。
哈夫曼树的特点:
- 权值越大的叶子结点越靠近根结点,而权值越小的叶子结点越远离根结点。
- 只有度为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;依次递增);
哈夫曼树应用——哈夫曼编码
编码:给每一个对象标记一个二进制位串来表示一组对象。
例:ASCII,指令系统
等长编码:表示一组对象的二进制位串的长度相等。
不等长编码:表示一组对象的二进制位串的长度不相等。
前缀编码:一组编码中任一编码都不是其它任何一个编码的前缀 。
前缀编码保证了在解码时不会有多种可能。
哈夫曼编码算法的实现:
从叶子结点到根, 逆向求每个叶子结点对应的哈夫曼编码
根据huffman树中叶子节点的个数,构造一个字符串数组,每个数组分量是一个字符串,用于存放该节点对应的huffman编码
对每个叶子节点i(i=0; i<n; i++),进行下面的工作:
1 cd[n-1]=′\0′; /cd是一个字符数组,共有n个元素,即每个字符编码的长度不超过n。从右向左逐位存放编码, 首先存放编码结束符/
2 start=n-1; /初始化编码起始指针/
3 for(c=i, p= huffTree [i].parent; p! =0; c=p, p= huffTree [p].parent) /i是要编码的叶子节点编号,从叶子到根结点求编码/
if(huffTree [p].LChild==c) cd[–start]=‘0’ /左分支标0/
else cd[–start]=‘1’; /右分支标1/
4 循环结束,完成一个字符的编码
5 重复上述工作,直到完成所有节点的编码
哈夫曼编码算法的实现:
从叶子结点到根, 逆向求每个叶子结点对应的哈夫曼编码
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]);
}
}
线索二叉树
二叉树的遍历运算是将二叉树中结点按一定规律线性化的过程。
当以二叉链表作为存储结构时,只能找到结点的左、右孩子信息,而不能直接得到结点在遍历序列中的前驱和后继信息。
要得到这些信息可采用以下两种方法:
第一种方法是将二叉树遍历一遍,在遍历过程中便可得到结点的前驱和后继,但这种动态访问浪费时间;
第二种方法是充分利用二叉链表中的空链域, 将遍历过程中结点的前驱、 后继信息保存下来。
线索链表:
线索:将二叉链表中的空指针域指向前驱结点和后继结点的指针被称为线索;
线索化:使二叉链表中结点的空链域存放其前驱或后继信息的过程称为线索化;
线索二叉树:加上线索的二叉树称为线索二叉树。
线索二叉树的存储结构:线索链表
enum flag {Child, Thread};
template <class T>
struct ThrNode
{
T data;
ThrNode<T> *lchild, *rchild;
flag ltag, rtag;
};
线索二叉树:
二叉树的遍历方式有4种,故有4种意义下的前驱和后继,相应的有4种线索二叉树:
⑴ 前序线索二叉树
⑵ 中序线索二叉树
⑶ 后序线索二叉树
⑷ 层序线索二叉树
中序线索链表类的声明
template <class T>
class InThrBiTree{
public:
InThrBiTree();
~ InThrBiTree( );
ThrNode *Next(ThrNode<T> *p);
void InOrder(ThrNode<T> *root);
private:
ThrNode<T> *root;
ThrNode<T> * Creat();
void ThrBiTree(ThrNode<T> *root);
};
中序线索链表的建立——构造函数
分析:建立线索链表,实质上就是将二叉链表中的空指针改为指向前驱或后继的线索,而前驱或后继的信息只有在遍历该二叉树时才能得到。
建立带有标志位的二叉链树:
template <class T>ThrNode<T>* InThrBiTree<T>::Creat( ){
ThrNode<T> *root;
T ch;
cout<<"请输入创建一棵二叉树的结点数据"<<endl;
cin>>ch;
if (ch=="#") root = NULL;
else{
root=new ThrNode<T>;
root->data = ch;
root->ltag = Child; root->rtag = Child;
root->lchild = Creat( );
root->rchild = Creat( );
}
return root;
}
中序线索化二叉树:递归实现
基本思想:
在遍历的过程中完成线索化
可以采用前序、中序、后序遍历建立前序线索二叉树、中序线索二叉树和后序线索二叉树。
中序线索二叉树的构造方法
中序线索化根结点的左子树;
对根结点进行线索化;
中序线索化根结点的右子树;
算法分析
函数设置形参root和全局变量pre,分别表示要处理的树的根结点和其前驱结点
如果root!=NULL
中序线索化root的左子树
中序线索化root本身
如果root->lchildNULL
root->left=pre,root->ltag=1;
如果root->rchildNULL,root->rtag=1,
如果pre!=NULL, 并且pre->rtag=1,
pre->rchild=root,;
pre=root
中序线索化root的右子树
中序线索化二叉树:递归实现
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的前驱线索
}
if (!root->rchild) root->rtag = Thread;
if(pre != NULL){
if (pre->rtag==Thread) pre->rchild = root;
}
pre = root;
ThrBiTree(root->rchild);
}
线索二叉树的建立:
ThrNode<T>* pre = NULL
template <class T>
InThrBiTree<T>::InThrBiTree( )
{
//ThrNode<T>* pre = NULL;
this->root = Creat( );
ThrBiTree(root);
}
线索链表的遍历算法:中序遍历中序线索树
基本思想
从中序序列中的第一个结点出发,进行遍历
?如何确定中序序列的第一个节点?
依次找前一个节点的后继
?如何确节点的后继?
查找第一个节点
先从穿线树的根出发,一直沿左指针,找到“最左”(它一定是中序的第一个结点);
结点后继的确定
一个结点的右指针如果是线索,则右指针就是下一个要遍历的结点,
如果右指针不是线索,则它的中序后继是其右子树的“最左”结点
遍历何时停止?
一个节点的右指针==NULL
在中序线索树中查找结点的中序遍历的后继:
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;
}