非递归后序遍历二叉树
算法分析
定义一个栈;从根节点出发开始遍历,p=root,如果,root==NULL, 不进行遍历;
无条件进行下面的工作
如果指针不空,指针打上left标记,并将指针进栈,执行②;否则,执行③
p=p->lchild,重复①
栈顶元素出栈P
查看P的标志,如果标志为right,进行下面的工作,否则,执行⑤
访问当前节点P
如果栈空 ,算法结束;
否则,栈顶元素出栈,转④
修改P的标志,让P重新入栈,p=P->rchild,执行2
栈中的元素类型定义StackElement
enum Tags{Left,Right}; //特征标识定义
template <class T>
class StackElement //栈元素的定义
{
public:
BiTreeNode<T>* pointer; //指向二叉树结点的指针
Tags tag; //特征标识申明
};
#include <stack>
Using namespace std;
template<class T>
void BiTree<T>::PostOrderWithoutRecusion(BiTreeNode<T>* root){
StackElement<T> element;
stack<StackElement<T > > aStack;//栈申明
BiTreeNode<T>* pointer;
if(root==NULL)
return;//空树即返回
else pointer=root;
while(true){
while(pointer!=NULL){//进入左子树
element.pointer=pointer;
element.tag=Left; //沿左子树方向向下周游
aStack.push(element);
pointer=pointer->lchild;
}
element=aStack.pop();
pointer=element.pointer;
while(element.tag==Right){
cout<<pointer->data;
if(aStack.empty()) return;
else{
element=aStack.pop();
pointer=element.pointer;
}//end else
} //endwhile
element.tag=Right;
aStack.push(element);
pointer=pointer->rchild();
}//end while
}
二叉树的非递归遍历总结
都是沿着左分支访问,直到左分支为空时,再依次对栈中节点的右分支进行处理。(遵循从左至右的遍历原则,体现深度优先搜索的思想)
前序遍历:每个节点只进栈一次,在进栈前访问节点
中序遍历:每个节点进栈一次,在出栈时访问节点
后序遍历:每个节点进栈两次,在第二次出栈时访问节点
二叉树算法设计练习
设计算法求二叉树的结点个数。
void Count(BiNode *root)
{
if (root) {
Count(root->lchild);
number+ +; //number为数据成员
Count(root->rchild);
}
}
左子树中节点的数目+右子树中节点的数目+1
template<class T>
int BiTree<T>::count(BiNode<T>* root)
{
int number=0;
if (root==NULL)
number=0;
else
number=count(root->lchild)+count(root->rchild)+1;
return number;
}
统计叶子节点的数目
增加一个数据成员,leafcount, 初值为0
对树进行遍历。 如果一个节点是叶子,则将leafcount+1;
可以在前序、中序或后序的遍历过程中进行计算。
算法分析
从根节点出发
判断当前节点是否是叶子节点,如果是,则叶子数+1
否则,在左子树中遍历,并统计叶子节点的数目,在右子树中进行遍历,并统计叶子节点的数目
template<typename T>
void BiTree<T>:: countleaf(BiTreeNode<T> * root){
if (root) {
if (root->lchild==0 && root->rchild==0)
leafcount=leafcount+1;
else
{
countleaf(root->lchild);
countleaf(root->rchild);
}
}
return;
}
树中叶子节点的数目
左子树中叶子节点的数目+右子树中叶子节点的数目
template<class T>
int BiTree<T>::leafcount(BiNode<T>* root)
{
int number=0;
if (root==NULL)
number=0;
else if(root->lchild==NULL && root->rchild==NULL)
number=1;
else
number=leafcount(root->lchild)+leafcount(root->rchild);
return number;
}
计算树的高度
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;
}
计算二叉树的宽度
所谓宽度是指在二叉树的各层上,具有结点数最多的那一层上的结点总数
基本思想
统计每层节点个数
利用队列是实现
int level_tail=0;//记录每层最右边结点在顺序队列中的位置
int count[MaxSize];//记录每层结点的个数,初值为0;
int width=0;// 记录二叉树的宽度
int level_num=0;//记录层数
如果root NULL,不进行计算
Root入队;并记住第一层的最后一个元素在队中的位置(用非循环的顺序队列,rear=1,level_tail=rear;)
在队不空的情况下,进行如下工作
队首出对,并将其孩子入队, 并count[level_num]++;
如果 (frontlevel_tail),表示一层已经处理完毕
如果width< count[level_num]++; width= count[level_num]++;
层数增1 (level_num++);
否则,继续进行出队操作;
template <class T>
int BiTree<T>::Tree_Width(BiNode<T> *root)
{
const int MaxSize = 100;
int front = 0;
int rear = 0; //采用顺序队列,并假定不会发生上溢
BiNode<T>* Q[MaxSize];
BiNode<T>* q;
int level_tail=0;//记录每层最右边结点在顺序队列中的位置,
int count[MaxSize];//记录每层结点的个数
int width=0;// 记录二叉树的宽度
int level_num=0;//记录层数
if (root==NULL) return 0;
else
{
Q[rear++] = root; level_tail=rear; count[level_num]=0;
while (front != rear) {
q = Q[front++];//队首元素出队 cout<<q->data<<" ";
if (q->lchild != NULL) {
Q[rear++] = q->lchild; count[level_num]++; }
if (q->rchild != NULL) {
Q[rear++] = q->rchild;count[level_num]++; }
if(front==level_tail) {
if (width<count[level_num])
width=count[level_num];
level_num++; level_tail=rear;count[level_num]=0;
}
}
}
return width;
}
左右子树的交换
将每个节点的左、右子树进行交换
可以在遍历过程中进行交换。
算法分析
从根出发开始操作
如果要操作的节点是空值,则不进行任何操作;
否则,则交换左右儿子
对左子树进行处理
对右子树进行处理
template<typename T>
void Binarytree<T>::swap_LR(binarytreenode<T> * root){
binarytreenode<T> * temp;
if (root==0) return ;
else{
temp=root->rchild;
root->rchild=root->lchild;
root->lchild=temp;
swap_LR (root->lchild);
swap_LR (root->rchild);
}
}
}
判断两棵二叉树是否相同
从根节点出发
判断两棵树的根是否相同,
是,
判断左子树是否相同
判断右子树是否相同
否
结束
判断一棵树是否是完全二叉树
能给出递归定义吗?
基本思想:
基于层次遍历。
定义bool变量is_leaf,初值为false
如果is_leaf的值为true, 表示遍历的过程中遇到了叶子结点。
一旦在叶子结点之后再出现度为1、2的结点,则该树不是完全二叉树。
基于层次遍历。
在层次遍历中,如果遇到一个节点
只有右儿子,没有左儿子,
false;
只有左,没有右
If (is_leaftrue) return false;
Else is_leaf=true;
两个儿子均为空
is_leaf=true
两个儿子均不空
If (is_leaftrue) return false;
将存在的儿子入队
能遍历完所有节点,即为完全二叉树
template
bool BiTree::Is_Wanquan(BiNode root)
{
queue<BiNode> q;
BiNode * 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->lchildNULL)
return false;
else if(pointer->rchildNULL && pointer->lchild!=NULL )
if(is_leaf)
return false;
else
is_leaf=true;
else if(pointer->rchildNULL && pointer->lchildNULL )
is_leaf=true;
else if(is_leaf)
return false;
if(pointer->lchild!=NULL)
q.push(pointer->lchild);
if(pointer->rchild!=NULL)
q.push(pointer->rchild);
}
return true;
}
给定二叉树中的一个结点,输出从该结点的到根的路径
基本思想:
借助于遍历(非递归的遍历)
从根出发进行遍历(寻找路径),把访问过程中的每个结点入栈,如果遍历到目标结点,依次输出栈中结点
为完成路径的输出,需要将每个结点入栈两次(
如果结点入栈一次的话,则如果路径在右分支上的话,栈中不能记录跟到目标的结点序列)
输出根到每个叶子的路径
借助于前序遍历的过程完成
用栈存储遍历过的节点
一旦访问到叶子,则输出栈中的所有结点
统计二叉树中第k层节点个数
借助于层次遍历思想完成
将根节点打上标记(层号,1)入队
在队列不空的情况下,
队首元素出队
检查出队元素的编号,如果==K,退出循环(此时,队中存储着所有k层的元素)
否则,出队元素的孩子打上标记(父亲所在的层号+1)入队,
重复
输出队中元素的个数
template <class T>
struct Q_Element { BiNode <T> * root; int level;};
template<class T>
int BiTree<T>::Count_Level(BiNode <T> *root,int k){
queue<Q_Element<T> > q;
Q_Element<T> temp;
int level=1;
if (!root ) return 0;
temp.root=root; temp.level=level;
q.push(temp);
while(!(q.empty())) {
temp=q.front();
level=temp.level;
if(level==k) break;
q.pop();
root=temp.root;
if(root->lchild!=NULL)
{temp.root=root->lchild; temp.level=level+1;q.push(temp); }
if(root->rchild!=NULL)
{temp.root=root->rchild;temp.level=level+1;q.push(temp);
}
}
int number=0; number=q.size(); cout<<number<<endl;
return number;
}
三叉链表的实现
5.6 树、森林与二叉树的转换
树和二叉树之间的对应关系
树:兄弟关系
二叉树:双亲和右孩子
树:双亲和长子
二叉树:双亲和左孩子
1.兄弟加线.
2.保留双亲与第一孩子连线,删去与其他孩子的连线.
3.顺时针转动,使之层次分明.
树的后序遍历等价于二叉树的中序遍历!
森林转换为二叉树
⑴ 将森林中的每棵树转换成二叉树;
⑵ 从第二棵二叉树开始,
依次把后一棵二叉树的根结点作为前一棵二叉树根结点的右孩子,
当所有二叉树连起来后,此时所得到的二叉树就是由森林转换得到的二叉树。
⑴ 加线——若某结点x是其双亲y的左孩子,则把结点x的右孩子、右孩子的右孩子、……,都与结点y用线连起来;
⑵ 去线——删去原二叉树中所有的双亲结点与右孩子结点的连线;
⑶ 层次调整——整理由⑴、⑵两步所得到的树或森林,使之层次分明。
森林的遍历
森林有两种遍历方法:
⑴前序(根)遍历:前序遍历森林即为前序遍历森林中的每一棵树。
⑵后序(根)遍历:后序遍历森林即为后序遍历森林中的每一棵树。
(1)先根序遍历:
若森林 F 为空, 返回;否则
访问 F 的第一棵树的根结点;
先根次序遍历第一棵树的子树森林;
先根次序遍历其它树组成的森林。
** 哈夫曼树及哈夫曼编码**
叶子结点的权值:对叶子结点赋予的一个有意义的数值量。
二叉树的带权路径长度:设二叉树具有n个带权值的叶子结点,从根结点到各个叶子结点的路径长度与相应叶子结点权值的乘积之和。 记为:
哈夫曼树:给定一组具有确定权值的叶子结点,带权路径长度最小的二叉树。
例:给定4个叶子结点,其权值分别为{2,3,4,7},可以构造出形状不同的多个二叉树。
哈夫曼树的特点:
- 权值越大的叶子结点越靠近根结点,而权值越小的叶子结点越远离根结点。
- 只有度为0(叶子结点)和度为2(分支结点)的结点,不存在度为1的结点.
哈夫曼算法基本思想:
⑴ 初始化:由给定的n个权值{w1,w2,…,wn}构造n棵只有一个根结点的二叉树,从而得到一个二叉树集合F={T1,T2,…,Tn};
⑵ 选取与合并:在F中选取根结点的权值最小的两棵二叉树分别作为左、右子树构造一棵新的二叉树,这棵新二叉树的根结点的权值为其左、右子树根结点的权值之和;
⑶ 删除与加入:在F中删除作为左、右子树的两棵二叉树,并将新建立的二叉树加入到F中;
⑷ 重复⑵、⑶两步,当集合F中只剩下一棵二叉树时,这棵二叉树便是哈夫曼树。
伪代码
数组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;
}
}
哈夫曼编码算法的实现
从叶子结点到根, 逆向求每个叶子结点对应的哈夫曼编码
根据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].LChildc) 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].LChildc) 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 void ThrBiTree::ThrBiTree (ThrNode*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);
}