构造基本二叉树
- 用括号表示法表示的字符串创建二叉树
采用栈这种结构。基本思想:
利用ch逐一扫描这个字符串,扫描结果分以下几种情况:
- 若ch为‘(’,则将前面刚创建的结点作为双亲结点进栈,并置k = 1,表示其后创建的结点作为这个结点的左孩子结点。
- 若ch为‘)’,表示栈中结点的左右孩子结点处理完毕,退栈。
- 若ch为‘,’,表示其后创建的结点为右孩子结点,置k = 2。
- 其他情况,表示要创建一个结点,并根据k值建立它与栈中结点之间的关系,当k = 1时,表示这个结点作为栈中结点的左孩子结点;当k = 2时,表示这个结点作为栈中结点的右孩子结点。如此循环直到ch扫描完毕。
- 本算法的时间和空间复杂度均为O(n).
#include "LinkStack.h" //包含链式栈头文件
void CreateBT(BTNode *&b, char *str) //对数根b进行操作,所以需要引用
{
LinkStack<BTNode *> St(MaxSize);
BTNode *p=NULL;
int k, j = 0;
char ch;
b = NULL; //初始二叉树为空
ch = str[j]; //ch逐个扫描
while(ch!='\0') //为扫描完毕继续循环
{ switch(ch)
{
case '(':St.push(p); k = 1; break; //入栈,k置1
case ')':St.pop(); break; //退栈
case ',':k = 2; break; //k置2
default: p = new BTNode(ch); //创建新结点
if(b==NULL) b = p; //若b为空则b=p
else
{ switch(k) //判断k的值从而判断是左孩子还是右孩子
{ case '1':St.GetTop()->lChild = p; break;
case '2':St.GetTop()->rChild = p; break;
}
}
}
ch = str[++j]; //继续扫描重新循环
}
}
- 前序和中序序列字符串创建二叉树
void CreateBT1(BTNode *&r, char pre[], char in[], int preLeft, int preRight, int inLeft, int inRight)
// 操作结果:已知二叉树的先序序列pre[preLeft..preRight]和中序序列in[inLeft..inRight]构造以r为根的二叉树
{
if (inLeft > inRight) // 二叉树无结点,空二叉树
r = NULL; // 空二叉树根为空
else { // 二叉树有结点,非空二叉树
r = new BTNode(pre[preLeft]);// 生成根结点
int mid = inLeft;
while (in[mid] != pre[preLeft]) // 查找pre[preLeft]在in[]中的位置,也就是中序序列中根的位置
mid++;
CreateBinaryTree(r->leftChild, pre, in, preLeft+1, preLeft + mid - inLeft, inLeft, mid - 1);
// 生成左子树
CreateBinaryTree(r->rightChild, pre, in, preLeft + mid - inLeft + 1, preRight, mid + 1,
inRight); // 生成右子树
}
}
- 后序和中序序列字符串创建二叉树
void CreateBT2(BTNode *&r, char pre[], char in[], int postLeft, int postRight, int inLeft, int inRight)
// 操作结果:已知二叉树的后序序列post[postLeft..postRight]和中序序列in[inLeft..inRight]构造以r为根的二叉树
{
if (inLeft > inRight) // 二叉树无结点,空二叉树
r = NULL; // 空二叉树根为空
else { // 二叉树有结点,非空二叉树
r = new BTNode(post[postRight]);// 生成根结点
int mid = inLeft;
while (in[mid] != post[postRight]) // 查找pre[preLeft]在in[]中的位置,也就是中序序列中根的位置
mid++;
CreateBinaryTree(r->leftChild, pre, in, postLeft, postLeft + mid - inLeft - 1, inLeft, mid - 1);
// 生成左子树
CreateBinaryTree(r->rightChild, pre, in, postLeft + mid - inLeft, postRight - 1, mid + 1,
inRight); // 生成右子树
}
}
思考:为什么前序序列和后序序列不能构建一个二叉树??
解答:前序序列和后序序列的根节点都在两端,中间并不能判断左右子树的边界。
二叉树的遍历
- 先序遍历
1.递归
递归算法的特点就是层层递进,最后再层层回溯,就像一条蛇,在通过某条路径后再沿着原路径返回。
所以需要注意在递归过程中对结点的操作是在哪个时间节点,在回溯后要不要“清理现场”。
void PreOrder(BTNode *b) //先序序列的递归算法
{
if(b!=NULL) //根节点不为空才开始遍历
{
cout << b->data; //对当前结点进行操作
PreOrder(b->lChild); //遍历它的左子树
PreOrder(b->rChild); //遍历它的右子树
}
}
2.数组递归
这里只在先序遍历举例,后面不再重复。
数组存储二叉树分两种情况:1.完全二叉树 2.非完全二叉树
对于非完全二叉树数组要如何表示那些空的结点?
解决方案:采用两个同样大小的数组。一个数组放结点,另一个数组在空结点下标处做标记。如图所示:
//求左孩子
int *getLchild(int p[], int q[], int index){
if(index*2 + 1 > SIZE || q[index*2 + 1] == 1){ //超出数组范围或者结点不存在就返回空
cout << "结点不存在!!!"
return NULL;
}
return &p[index*2];
}
//求右孩子
int *getRchild(int p[], int q[], int index){
if(index*2 + 2 > SIZE || q[index*2 + 2] == 1){ //超出数组范围或者结点不存在就返回空
cout << "结点不存在!!!"
return NULL;
}
return &p[index*2+2];
}
//先序遍历
void Preorder1(int p[], int q[], int len){
cout << p[len];
if(getLchild(p, q, len) != NULL)
perorder1(arr,*getLchild(p, q, len));
if(getRchild(p, q, len) != NULL)
perorder1(arr,*getRchild(p, q, len));
}
3.半非递归
这只是一种采用尾递归转变成迭代算法的举例,后面便不再重复。
void PreOrder2(BTNode *b) //消去第二个递归语句,按尾递归改为迭代处理
{
while(b!=NULL)
{
cout << b->data;
PreOrder1(b->lChild);
t = t->rChild; //向右子树循环
}
}
4.非递归
#include "LinkStack.h" //包含链式栈头文件
void PreOrder3(BTNode *b) //先序序列的非递归算法
//利用栈辅助结构来实现非递归
{
LinkStack<BTNode *> St[MaxSize];
BTNOde *p;
if(b!=NULL) //不是空树
{
St.push(b); //根节点进栈
while(!St.IsEmpty()) //只要栈不为空
{
p = St.pop();
cout << p;
if(p->rChild!=NULL) //右孩子进栈
{
St.push(p->rChild);
}
if(p->lChild!=NULL) //左孩子进栈
{
St.push(p->lChild);
}
}
}
}
- 中序遍历
1.递归
void InOrder(BTNode *b) //中序遍历递归算法
{
if(b!=NULL) //不是空树
{
InOrder(b->lChild);
cout << b->data; //在遍历完左子树后,输出当前结点
InOrder(b->rChild);
}
}
2.非递归
#include "LinkStack.h" //包含链式栈头文件
void InOrder1(BTNode *b) //中序遍历非递归算法
{
LinkStack<BTNode *> St(MaxSize);
BTNode *p;
if(b!=NULL)
{
p = b;
while(!St.IsEmpty() || p!=NULL) //栈不空或p不空时循环
{
while(p!=NULL) //扫描(注意!!而非访问!!)p的所有左下孩子进栈
{
St.push(p);
p = p->lChild;
}
if(!St.IsEmpty())
{
p = St.pop(); //此时的栈顶为最左下角结点
cout << p->data;
p = =->rChild; //扫描p的右孩子结点
}
}
cout << endl;
}
}
- 后序遍历
1.递归
void PostOrder(BTNode *b) //后序遍历递归算法
{
if(b!=NULL) //不是空树
{
PostOrder(b->lChild);
PostOrder(b->rChild);
cout << b->data; //遍历完左右子树后才访问当前结点
}
}
2.非递归
#include "LinkStack.h" //包含链式栈头文件
void PostOrder1(BTNode *b) //后序遍历非递归算法1
{
LinkStack<BTNode *> St(MaxSize);
BTNode *p;
if(b!=NULL) //不是空树
{
do
{ while(b!=NULL) //将 *b的所有左下结点进栈
{ St.push(b);
b = b->lChild;
}
p = NULL; //p是指向栈顶结点的前一个已访问的结点
flag = 1; //flag=1表示栈顶元素的处理
while(!St.IsEmpty() && flag)
{ b = St.GetTop(); //取出当前的栈顶元素
if(b->rChild == p) //当右孩子不存在或者右孩子已经被访问过
{ cout << b->data;
St.pop(); //将*b退栈
p = b; //p指向刚被访问的结点
}
else
{ b = b->rChild; //b指向它的右孩子
flag = 0; //flag=0代表退出栈顶元素的处理
}
}
}while(!St.IsEmpty()) //栈不空的时候进行循环
cout << endl;
}
}
中序遍历和后序遍历非递归算法心得:连续性左下取点,间歇性栈顶处理。
这里是另一种后序遍历非递归算法:
#include "LinkStack.h"
void PostOrder2(BTNode *b) //后序遍历非递归算法2
//采用两个栈,两个栈操作要同步,一个存放结点,一个存放其右孩子是否访问的标记。
{
LinkStack<BTNode *> St1(MaxSize); //存放进栈之树结点
LinkStack<bool> St2(MaxSize); //存放进栈之标志
do
{
while(b!=NULL) //不是空树
{ St1.push(b); //不断取左孩子入栈1,同时标志false入栈2.
St2.push(false);
b = b->lChild;
}
if(!St2.IsEmpty()) //标志栈不为空
{ if(St2.GetTop() == false) //预出栈
{ b = St1.GetTop();
St2.pop();
St2.push(true); //把标志栈栈顶改为true
b = b->rChild;
}
else //出栈并打印
{ b = St1.pop();
St2.pop();
cout << b->data;
b = NULL; //b指向空,继续判断是否要出栈
}
}
}while(!St1.IsEmpty()) //结点栈不空时循环
cout << endl;
}
- 双序遍历
双序遍历的意思就是先访问当前结点,再双序遍历左子树,再访问当前结点,再双序遍历右子树。
void DoubleOrder(BTNode *b)
{
if(b!=NULL)
{ cout << b->data;
DoubleOrder(b->lChild);
cout << b->data;
DoubleOrder(b->rChild);
}
}
- 层序遍历
层序遍历主要采用队列这种辅助结构,利用这种结构也可以求二叉树结点的层次信息。
#include "LinkQueue.h" //包含链式队列头文件
void LevelOrder(BTNode *b) //层序遍历算法
{
BTNode *p;
LinkQueue<BTNode *> qu(MaxSize);
qu.EnQueue(b); //将根节点进队
if(!qu.IsEmpty()) //若队列不为空
{
p = qu.OutQueue(); //将队头出队并访问
cout << p;
if(p->lChild!=NULL) //若左孩子不为空,则将左孩子进队
qu.EnQueue(p->lChild);
if(p->rChild!=NULL) //若右孩子不为空,则将右孩子进队
qu.EnQueue(p->rChild);
}
}
遍历的应用
- 中缀表达式
1.中缀表达式存储和计算
仅包含二元运算符+ - * /的算术表达式二叉树以二叉链形式存储,并计算表达式的值。
double ExpValue(BTNode *b)
{
double lv, rv, value = 0;
if(b!=NULL) //如果b为空就直接返回0
{ if(b->data!='+' && b->data!='-' && b->data!='*' && b->data!='/') //b是数就返回值
return b->data;
lv = ExpValue(b->rChild); //b是算数符号就要判断进行哪种运算
rv = ExpValue(b->rChild);
switch(b->data)
{
case '+': value = lv+rv; break;
case '-': value = lv-rv; break;
case '*': value = lv*rv; break;
case '/': if(rv!=0) value = lv+rv; //考虑除数为0的情况
else exit(0); break;
}
}
return value;
}
2.中缀表达式输出
中缀表达式输出。当根节点和左(或右)孩子结点都是运算符时,比较它们的优先级,当根节点优先级高于孩子结点时,要加上括号。
int precede(char op1, char op2)
{
if(op1!='+' && op1!='-' && op1!='*' && op1!='/') return -1; //判断两个结点是否都是运算符
if(op2!='+' && op2!='-' && op2!='*' && op2!='/') return -1;
switch(op1)
{
case '+':
case '-':return 0;
case '*':
case '/':if(op2=='*' || op2=='/') return 0;
else return 1; //当op1优先级大于op2优先级时返回1,其余返回0
}
}
void InorderExp(BTNode *b) //输出中缀表达式
{
int bracket;
if(b!=NULL)
{ if(b->lChild!=NULL)
{ bracket = precede(b->data, b->lChild->data);
if(bracket==1) cout<<'('; //如果左孩子优先级低,加括号
InorderExp(b->lChild); //输出左孩子
if(bracket==1) cout<<')';
}
cout<<b->data; //输出当前结点
if(b->rChild!=NULL)
{ bracket = precede(b->data, b->rChild->data);
if(bracket==1) cout<<'('; //如果右孩子优先级低,加括号
InorderExp(b->rChild); //输出右孩子
if(bracket==1) cout<<')';
}
}
}
- 后缀表达式
首先对二叉树进行后序遍历得到后缀表达式,接着对后缀表达式求值。
过程是:从左到右扫描后缀表达式,遇到数字符的操作数就转化为数值再进栈,遇到运算符就从栈中取出两个操作数,进行相应的运算后再进栈,如此直到后缀表达式结束,这时栈中就只有一个数,这个数就是表达式的值。
由于后序遍历的特性,栈中的运算操作符一定是位于两个数字操作符的上边。
#include "LinkStack.h"
char postexp[MaxSize]; //存放后缀表达式(数组)
int n=0; //后缀表达式长度
void PostExp(BTNode *b) //后缀遍历表达式数产生后缀表达式postexp
{
if(b!=NULL)
{ PostExp(b->lChild);
PostExp(b->rChild);
post[n++]=b->data;
}
}
double CompValue() //计算后缀表达式postexp的值
{
LinkStack<double> St(MaxSize);
double opnd, opnd1, opnd2;
char ch;
int i=0;
while(i<n)
{ ch = postexp[i++];
switch(ch)
{
case '+':opnd1 = St.pop();
opnd2 = St.pop();
opnd = opnd1 + opnd2;
St.push(opnd);
break;
case '-':opnd1 = St.pop();
opnd2 = St.pop();
opnd = opnd1 - opnd2;
St.push(opnd);
break;
case '*':opnd1 = St.pop();
opnd2 = St.pop();
opnd = opnd1 * opnd2;
St.push(opnd);
break;
case '/':opnd1 = St.pop();
opnd2 = St.pop();
if(opnd1==0) exit(0); //测试除0错误
opnd = opnd2 / opnd1; //这里的操作数顺序与上面相反
St.push(opnd);
break;
default:St.push(ch-'0'); //转化成double后入栈
break;
}
}
return St.GetTop();
}
double ExpValue1(BTNode *b) //求表达式树b的值
{
PostExp(b);
return(CompValue());
}
- 满二叉先序遍历转化为后序遍历
b是一颗满二叉树,所以任一结点的左右子树均含有相等个数的结点。同时先序遍历的第一个结点等于后序遍历的最后一个结点。
void PreToPost(ElemeType pre[], int l1, int h1, ElemType post[], int l2, int h2)
{
int half;
if(h1>=l1)
{
post[h2] = pre[l1];
half = (h1 - l1)/2; //任一结点的左右子树个数相等
PreToPost(pre, l1+1, l1+half, post, l2, l2+half-1); //转换左子树
PreToPost(pre, l1+half+1, h1, post, l2+half, h2-1); //转换右子树
}
}
- 求最小值结点
void FindMinNode(BTNode *b, ElemType &min)
{
if(b->data<min) min = b->data;
FindMinNode(b->lChild, min); //在左子树中找最小结点
FindMinNode(b->rChild, min); //在右子树中找最小结点
}
void MinNode(BTNode *b)
{
if(b!=NULL)
{ ElemType min = b->data;
FindMinNode(b,min);
cout<<"min="<<min;
}
}
求二叉树的信息
- 求序列第k个结点的值
1.先序序列
用全局变量n保存先序遍历访问结点的序号。当二叉树为空时返回特殊字符‘ ’,当k=n时返回该值,当k不等于n时则先往左子树查找,若左子树查找到,就返回左子树的查找结果;若左子树没有查找到,就往右子树查找,最后返回右子树的查找结果。
具体代码实现:
int n = 1; //全局变量
ElemType PreNode(BTNode *b, int k)
{
ElemType ch;
if(b = NULL) return ' ';
if(n==k) return b->data;
n++; //遍历一个结点记录个数加1
ch = PreNode(b->lChild, k); //遍历左子树
if(ch!=' ') return ch; //如果左子树遍历结果不为空就返回遍历结果
else ch = PreNode(b->rChild, k); //左子树遍历结果为空就遍历右子树
return ch; //返回右子树的遍历结果
}
先序遍历的非递归算法:
#include "LinkStach.h"
ElemType PreNode1(BTNode *b, int k)
{
LinkStack<BTNode *> St(MaxSize);
BTNode *p;
int n = 0;
if(b!=NULL)
{ St.push(b);
while(!St.IsEmpty())
{ p = St.pop(); //栈顶元素出栈
n++; //出栈访问一个结点记录加1,因为所有结点扫描进一次栈,访问出一次栈
if(n==k) return p->data;
if(b->rChild!=NULL)
St.push(b->rChild); //遍历左子树
if(b->lChild!=NULL)
St.push(b->lChild); //遍历右子树
}
}
return (' '); //若都没有找到就返回‘ ’
}
2.中序序列
int n = 1; //全局变量
ElemType InNode(BTNode *b, int k)
{
ElemType ch;
if(b = NULL) return ' ';
ch = InNode(b->lChild, k); //遍历左子树
if(ch!=' ') return ch;
if(n==k) return b->data;
n++; //遍历一个结点记录个数加1
return PreNode(b->rChild, k); //返回右子树的遍历结果
中序序列非递归算法:
#include "LinkStack.h"
Elemtype InNode1(BTNode *b, int k)
{
LinkStack<BTNode *> St(MaxSize);
if(b!=NULL)
{ while(!St.IsEmpty() && b!=NULL)
{ while(b!=NULL) //扫描所有左结点进栈
{ St.push(b);
b = b->lChild;
}
if(!St.IsEmpty())
{ b = St.pop();
n++; //出栈访问对应n加1
if(n==k) return b->data;
b = b->rChild; //扫描右孩子结点
}
}
}
return (' '); //一无所获返回‘ ’
}
3.后序序列
int n = 1; //全局变量
ElemType PostNode(BTNode *b, int k)
{
ElemType ch;
if(b = NULL) return ' ';
ch = InNode(b->lChild, k); //遍历左子树
if(ch!=' ') return ch;
ch = InNode(b->rChild, k); //遍历右子树
if(ch!=' ') return ch;
if(n==k) return b->data;
n++; //遍历一个结点记录个数加1
return ' '; //一无所获返回‘ ’
后序序列的非递归算法:
#include "LinkStack.h"
void PostNode1(BTNode *b)
{
LinkStack<BTNode *> St(MaxSize);
BTNode *p;
if(b!=NULL)
{
do
{ while(b!=NULL) //将 *b的所有左下结点进栈
{ St.push(b);
b = b->lChild;
}
p = NULL; //p是指向栈顶结点的前一个已访问的结点
flag = 1; //flag=1表示栈顶元素的处理
while(!St.IsEmpty() && flag)
{ b = St.GetTop(); //取出当前的栈顶元素
if(b->rChild == p) //当右孩子不存在或者右孩子已经被访问过
{ n++;
if(n==k) return p->data;
St.pop(); //将*b退栈
p = b; //p指向刚被访问的结点
}
else
{ b = b->rChild; //b指向它的右孩子
flag = 0; //flag=0代表退出栈顶元素的处理
}
}
}while(!St.IsEmpty()) //栈不空的时候进行循环
cout << endl;
}
return ' ';
}
思考:n的初值应该如何选取?
解答:先操作后加1的话n就取1,先加1后操作的话n就取0。
- 计算一颗给定二叉树结点数
1.所有结点数
int Nodes(BTNode *b)
{
int num1, num2;
if(b==NULL) return 0;
else
{ num1 = Nodes(b->lChild); //左子树所有结点数
num2 = Nodes(b->rChild); //右子树所有结点数
return (num1+num2+1); //左右子树结点数加上自己
}
}
2.叶子结点数
int LeafNodes(BTNode *b)
{
int num1, num2;
if(b==NULL) return 0;
else if(b->lChild==NULL && b->rChild==NULL) //若b是叶子节点则返回1
return 1;
else //若b不为叶子结点且不为空,则返回它左右子树叶子结点数之和
{ num1 = LeafNodes(b->lChild);
num2 = LeafNodes(b->rChild);
return (num1+num2);
}
}
3.双分支结点数
int DoubleNodes(BTNode *b)
{
int num1, num2, n;
if(b==NULL) return 0;
else if(b->lChild==NULL || b->rChild==NULL) //为单分支或者是叶子结点时,不计
n = 0;
else n = 1; //为双分支结点时计1
num1 = DoubleNodes(b->lChild); //递归左子树
num2 = DoubleNodes(b->rChild); //递归右子树
return (num1+num2+n);
}
4.值为x的结点数
int FindCount(BTNode *b, ElemType x)
{
if(b==NULL) return 0;
else if(b->data==x) return (1+FindCount(b->lChild)+FindCount(b->rChild));
else return (FindCount(b->lChild)+FindCount(b->rChild));
}
5.某一层k(k>1)的叶子结点个数
跟层有关的,都可以用队列解决。
记录每一层的最右结点,然后记录叶子结点个数。
#include "LinkQueue.h"
int LeafKLevel(BTNode *b, int k)
{
LinkQueue<BTNode *> Qu(MaxSize);
int leaf = 0, level; //leaf叶子结点数,level层数
BTNode *last; //last指向每一层最右结点
if(b==NULL || k<=1) return 0; //条件错误返回0
Qu.EnQueue(b);
last = Qu.GetRear(); //队尾指针指向每一层最右结点
level = 1;
while(!Qu.IsEmpty())
{ b = Qu.OutQueue(); //队头出队
if(level = k && b->rChild==NULL && b->lChild==NULL) leaf++; //如果满足条件leaf就加1
if(b->lChild!=NULL) Qu.EnQueue(b->lChild); //左右孩子进队
if(b->rChild!=NULL) Qu.EnQueue(b->rChild);
if(Qu.GetFront()==last) //队头到了这一层的最右结点,表示这一层处理完毕
{ level++;
last = Qu.GetRear(); //last重新指向下一层的最右结点
}
if(level>k) return leaf; //当层数大于k时返回leaf,不再继续
}
}
- 寻找结点双亲
在二叉树中求指定值为x的结点的双亲。其中根节点双亲为NULL,未找到双亲也为NULL。二叉树中每个结点值都不相同。
void findparent(BTNode *b, ElemType x, BTNode *&p)
{
if(b!=NULL)
{ if(b->data==x) p=NULL; //只会在第一次调用中执行,是根节点则没有双亲
else if(b->lChild!=NULL &&b->lChild==x) //若左右孩子有x,则双亲就为b
p=b;
else if(b->rChild!=NULL &&b->rChild==x)
p=b;
else
{ findparent(b->lChild,x,p); //遍历左子树
findparent(b->rChild,x,p); //遍历右子树
}
}
else p=NULL;
}
- 求二叉树高
int Height(BTNode *b)
// 操作结果:返回以b为根的二叉树的高
{
if(r == NULL) // 空二叉树高为0
return 0;
else { // 非空二叉树高为左右子树的高的最大值再加1
int lHeight, rHeight;
lHeight = Height(r->leftChild); // 左子树的高
rHeight = Height(r->rightChild); // 右子树的高
return (lHeight > rHeight ? lHeight : rHeight) + 1;
// 非空二叉树高为左右子树的高的最大值再加1
}
}
若一棵树的存储表示为子女——兄弟链表,则求此树的高度算法为:
int Height1(CSTNode *RT)
{
if(RT==NULL) return 0;
if(RT->firstChild==NULL) return 1; //若第一个孩子为空则返回1
CSTNode *p = RT.firstChild; //p为根节点的第一个孩子
int height, maxheight = 0;
while(p!=NULL)
{ height = Height1(p); //递归算法,一直取结点的第一个孩子,直到为空
if(height>maxheight) maxheight = height;
p = p->nextSibling; //取结点的下一个兄弟
}
return maxheight+1;
}
- 求二叉树最大宽度
两个队列结构:
#include "LinkStack.h"
int BTWidth(BTNode* b) //求二叉树最大宽度
{
LinkQueue<BTNode *> q, r; //创建两个链队列
BTNode* s;
int max = -1, i = 0; //定义最大宽度 max 和计数变量 i
if (p == NULL) return 0;
q.EnQueue(p);
while (!q.IsEmpty() || !r.IsEmpty()) //循环结束条件为当q r都为空
{
if (!q.IsEmpty() && r.IsEmpty()) //若q不为空,r为空,则先记录q中元素个数,再让q中元素出队,
//这个元素的左孩子和右孩子(若存在)进r队。
{
i=q.GetLength();
if (i >= max) max = i; //判断最值
while (!q.IsEmpty())
{
q.DelQueue(s);
if (s->leftChild != NULL)
{
r.EnQueue(s->leftChild);
}
if (s->rightChild != NULL)
{
r.EnQueue(s->rightChild);
}
}
}
if (q.IsEmpty() && !r.IsEmpty()) //若r不为空,q为空,则先记录r中元素个数,再让r中元素出队,
//这个元素的左孩子和右孩子(若存在)进q队。
{
i = r.GetLength();
if (i >= max) max = i; //判断最值
while (!r.IsEmpty())
{
r.DelQueue(s);
if (s->leftChild != NULL)
{
q.EnQueue(s->leftChild);
}
if (s->rightChild != NULL)
{
q.EnQueue(s->rightChild);
}
}
}
}
return max;
}
一个队列结构,并实现输出各节点的层次:
#include "SeqQueue.h"
int BTWidth1(BTNode *b)
{
struct node //结构体(层次编号+指针p)
{
int lno;
BTNode *p;
};
LinkStack<node> Qu(MaxSize);
int lnum, max, i, n;
if(b!=NULL)
{ Qu.EnQueue(b,1); //根节点的层次编号为1
while(!Qu.IsEmpty())
{ b = Qu.GetFront().p; //取队头指针
lum = Qu.GetFront().lno; //取队头层次编号
Qu.OutQueue(); //队头出队
if(b->lChild!=NULL) Qu.EnQueue(b->lChild, lum+1); //左孩子层次编号为队头指针层次编号+1
if(b->lChild!=NULL) Qu.EnQueue(b->rChild, lum+1); //右孩子层次编号为队头指针层次编号+1
}
cout<<"各节点的层编号是:";
for(i = 0; i <=Qu.GetRear(); i++)
cout<<Qu.GetElem(i).p->data<<Qu[i].lno; //输出各节点值和层次编号
max =0; lnum =1; i=1;
while(i <=rear)
{ n = 0;
while(i<=rear && Qu[i].lno) //若lno=lnum
{ n++;
i++;
}
lnum = Qu[i].lno; //lnum等于下一层的层次编号
if(n>max) max = n;
}
return max;
}
else return 0;
}
递归算法求解最大宽度(用数组存储):
void BTWidth2(BTNode* b,int * &a,int h) //求二叉树最大宽度(递归)
//以p为根节点,层次为h,a[]记录每一层的宽度
{
if (b!=NULL) //若根节点为空则不进行任何操作
{
a[h] += 1;
BTWidth2(p->leftChild, a, h + 1); //求其左孩子那一层的宽度并放入数组a[h+1].
BTWidth2(p->rightChild, a, h + 1); //求其右孩子那一层的宽度并放入数组a[h+1].
}
}
这样我们就可以得到一个数组,里面记录着每一个层次的结点数。那么接下来我们只需要去数组里找最大值就行了。
- 求共同最近祖先
1.顺序储存
void NearAncestor(int* C,int i, int j, int n) //求出 A[i] 和 A[j] 的最近的公共祖先
{
int* A=new int [n];
for (int i = 0; i < n ; i++)
A[i] = C[i]; //拷贝C数组数据
if (i >= n || j >= n) //超出二叉树范围,则报错,直接返回
{ cout << "结点不存在于二叉树里!" << endl;
return;
}
else if (i == 1 || j == 1) //其中至少一个结点为根节点,那么他们就不存在公共祖先
{ cout << "不存在公共祖先!" << endl;
return;
}
else if (i == j) //若输入数据时i j相等,则直接返回他们的双亲。
cout << "最近公共祖先结点编号为:" << i/2 << endl << "元素值为:" << A[i/2-1] << endl;
else
{ while (i && j)
{ if (i > j) i = i/ 2; //若i>j,则让i变成他自己的双亲
if (i < j) j = j/ 2; //若i<j,则让j变成他自己的双亲
if (i == j) //若i=j,就代表已经找到他们的公共祖先
{ cout << "最近公共祖先结点编号为:" << i << endl<<"元素值为:"<<A[i-1]<<endl;
return;
}
}
}
if (i == 1 || j == 1)
cout << "最近公共结点为根节点,其元素值为:" << A[0] << endl;
}
2.链式存储
用非递归后序遍历树b。假设r结点在s结点的左边,当后序遍历到r时,栈St中所有结点均是r的祖先,此时将其复制到anor中,然后继续后序遍历访问到s结点,同样,此时St中的所有结点也均是s的祖先。这时候只需要比较r和s的祖先的公共部分,就可以找到r和s的最近公共祖先。
#include "LinkStack.h"
int NearAncestor1(BTNode *b, BTNode *r, BTNode *s)
{
LinkStack<BTNode *> St(MaxSize);
BTNode *p;
ElemType anor[MaxSize];
int i,flag;
do
{ while(b!=NULL) //将所有b左孩子进栈
{ St.push(b);
b = b->lChild;
}
p = NULL; //p为指向当前结点的前一个已经访问过的结点
flag = 1; //设置b的访问标记为已访问过
while(!St.IsEmpty() && flag)
{ b = St.pop(); //取出栈顶元素
if(b->rChild==p) //若右子树不存在或者已经被访问过
{ if(b==r) //结点为要找的结点
{ for(i = 0; i<=St.GetLen();i++)
anor[i] = St.GetElem(i)->data; //将路径存入anor[]中
p = b;
}
else if(b==s) //匹配anor[]与b的路径
{ i = 0;
while(anor[i]==St.GetElem(i)->data) //相等则继续循环
i++;
cout<<"最近公共祖先为:"<<anor[i-1]; //找到最近一个相等的元素也就是最近的公共祖先
return 1;
}
else p = b;
}
else
{ b = b->rChild; //b指向右子树
flag = 0; //设置未被访问的标记
}
}
}while(!St.IsEmpty())
return 0;
}
同样也可以用上述的后序遍历的第三种方法(预出栈)进行解决,关键代码(anor的复制和匹配)只需要插入到真正出栈访问的代码中。具体操作可以自己实现。
- 判断两颗二叉树是否相似
bool similar(BTNode *b1, BTNode *b2)
{
// 采用递归算法判断两个二叉树是否相似
bool left = false, right = false;
if (b1 == NULL && b2 == NULL) // 两树皆空
return true;
else if (b1 == NULL || b2 == NULL) // 只有一个树为空
return false;
else
{
left = similar(b1->lChild, b2->lChild); //判断左子树是否相似
right = similar(b1->rChild, b2->rChild); //判断右子树是否相似
return left && right;
}
}
- 判断是否是完全二叉树
1.顺序存储
顺序存储的判断非常简单,只需判断第一个结点到最后一个结点之间没有空结点即可。
int CompBTNode(BTNode *b)
{
int i, j;
for(i = 1; i<MaxSize; i++) //找到第一个空结点
if(b[i] == '#') break;
for(j = i+1; j<MaxSize; j++) //判断第一个空结点后面还有没有空结点
if(b[j]!= '#') return 0;
return 1;
}
2.链式存储
根据完全二叉树的定义,对完全二叉树进行层次遍历时应该满足以下条件。
(1)若某结点没有左孩子,则一定没有右孩子。
(2)若某结点缺左或者右孩子,则其后继结点一定无孩子
若不满足以上任何一条,均不为完全二叉树
#include "LinkQueue.h"
int CompBTNode1(BTNode *b)
{
LinkQueue<BTNode *> Qu(MaxSize);
BTNode *p;
int cm = 1; bj = 1; //cm=1代表是一颗完全二叉树,bj=1代表目前为止所有结点都有左右孩子
if(b!=NULL)
{ Qu.EnQueue(b);
while(!Qu.IsEmpty()) //队列不空
{ p = Qu.OutQueue(); //出队
if(p->lChild==NULL) //如果p没有左孩子
{ bj = 0;
if(p->rChild!=NULL) cm = 0; //如果p没有左孩子却有右孩子,违反(1)
}
else //如果p有左孩子
{ if(bj==1) //如果到目前为止所有结点都有左右孩子
{ Qu.EnQueue(p->lChild);
if(p->rChild==NULL) bj = 0; //p有左孩子但没有右孩子
else Qu.EnQueue(p->rChild);
}
else cm = 0; //到目前为止已经有结点没有左孩子或者右孩子,可p却有左孩子,违反(2)
}
}
return cm;
}
return 1; //空二叉树可以看成是完全二叉树
}
- 判断二叉树是否平衡
二叉树平衡的概念是左右子树的高度差不超过1.
#include <stdlib.h>
bool Balance((BTNode *b), int &height) //引用实参可以把值传回到上一级
{
if(b!=NULL)
{ int lh, rh;
bool lb = Balance(b->lChild, lh); //判断左子树是否平衡,同时把lh传回
bool rb = Balance(b->rChild, rh); //判断右子树是否平衡,同时把rh传回
height = (lh>rh)? 1+lh:1+rh; //lh,rh大者加一为本节点为根的树高
if(lb && rb && abs(lh-rh)<=1) return true; //左子树,右子树都平衡同时左右子树高度差<=1,就返回true
else return false; //否则就返回false
}
else {height = 0;return true;} //空树自然为平衡二叉树
}
- 判断二叉树是不是二叉排序树
思路:二叉排序树的特点是,若左子树非空,则左子树上结点的值均小于根结点的值;
若右子树非空,则右子树上结点的值均大于根结点的值。所以根据这一特点,可以看出
二叉排序树的中序遍历是一个递增序列。
利用中序遍历进行判断。
bool flag=true; //循环标记
ElemType prev = Min; //树的最左下结点,也就是二叉排序树里的最小结点
bool InOrderSort(BTNode *T)
{
if(T->lchild != NULL && flag)
{
InOrderTraverse(T->lchild); //遍历左子树
}
if(T->data<prev) //刚从左子树回溯来,值应该比根节点小
{
flag = false; //如果大说明这不是二叉排序树(不满足递增序列)
}
prev = T->data;
if(T->rchild != NULL && flag){ //遍历右子树
InOrderTraverse(T->rchild);
}
return flag;
}
对二叉树进行操作
- 交换一棵树的左右子树
1.空间复杂度O(n)(不破坏原树)
相当于拷贝了一份一模一样的树,只不过左右子树的顺序发生了交换。
void Swap1(BTNode *b, BTNode *&t)
{
if(b==NULL) t = NULL;
esle
{ t = new BTNode(); //复制根节点
t->data = b->data;
Swap1(b->lChild, t->rChild); //交换左子树
Swap1(b->rChild, t->lChild); //交换右子树
}
}
2.空间复杂度O(1)(所需空间更少)
不去拷贝树,而是直接改动指向左右子树的指针。
void Swap2(BTNode *b)
{
BTNode *temp;
if(b!=NULL)
{ Swap2(b->lChild); //交换左子树
Swap2(b->rChild); //交换右子树
temp = b->lChild; //将指针的左右指针域进行交换
b->lChild = b->rChild;
b->rChild = temp;
}
}
- 利用右孩子指针将叶子结点从左到右串成单链表
采用先序遍历的递归算法求解,在遍历过程中采用尾插法构建叶子结点的单链表,head指向建立单链表的首节点(初值为NULL),tail指向单链表的尾结点。
void Link(BTNode *b, BTNode *&head, BTNode *&tail)
//初始调用时head=NULL
{
if(b!=NULL)
{ if(b->lChild==NULL && b->rChild==NULL) //如果是叶子结点
if(head==NULL) //如果是第一个叶子结点
{ head =b;
tail = head;
}
else
{ tail->rChild = b; //尾插法,这里利用了叶子结点的空余指针域
tail = b;
}
if(b->lChild!=NULL) Link(b->lChild, head, tail); //遍历左子树
if(b->rChild!=NULL) Link(b->rChild, head, tail); //遍历右子树
}
}
寻径问题
- 根节点到指定结点的路径
采用后序非递归遍历树b,当后序遍历访问到s所指结点时,此时栈St中所有结点均为p所指结点的祖先,由这些祖先则构成了从根节点到p所指结点之间的路径。
#include "LinkStack.h"
int AncestorPath(BTNode *b, BTNode *s)
{
LinkStack<BTNode *> St(MaxSize);
BTNode *p;
int i, flag;
do
{ while(b!=NULL) //不断取b的左孩子
{ St.push(b);
b = b->lChild;
}
p = NULL;
flag = 1;
while(!St.IsEmpty() && flag)
{ b = St.GetTop();
if(b->rChild==p) //当右子树不存在或者已被访问(满足后序遍历的标准)
{ if(b==s) //当前结点为要找的结点,输出路径
{ for(i=0;i<=top;i++)
cout<<St.GetElem(i)->data;
return 1;
}
else
{ b = St.pop();
p = b;
}
}
else
{ b = b->rChild;
flag = 0;
}
}
}while(!St.IsEmpty())
return 0; //其他情况返回0
}
思考:为什么是后序遍历,其他遍历可以吗?
解答:后序遍历是左右根的遍历顺序,而栈的特点是先进后出。所以通过后序遍历的非递归算法,就可以从栈底到栈顶取出这个结点的路径。其他遍历自然也可以,要分别利用不同遍历之间的特点。
- 输出从叶子结点到根节点的逆路径
1.层次遍历
设计队列为非循环队列Qu,将所有扫描过的结点指针进队,并在队列中保存双亲结点的位置。当找到一个叶子结点时,在队列中通过双亲结点的位置输出该叶子结点到根节点的逆路径。
#include "SeqQueue.h"
void AllPath(BTNode *b)
{
struct snode
{ BTNode *node; //存放当前结点指针
int parent; //存放其双亲结点下标
};
SeqQueue<snode> Qu(MaxSize); //存放snode的队列
int p;
Qu.EnQueue(b,-1); //根节点进队,没有双亲,所以parent置-1
while(!Qu.IsEmpty()) //队列不为空
{ b = Qu.OutQueue();
if(b->lChild==NULL && b->rChild==NULL) //如果*b是叶子结点
{ cout<<b->node->data<<"到根节点逆路径为:";
p = Qu.GetFront(); //取队头下标
while(Qu.GetNode(p)->parent!=-1) //只要结点有双亲,就输出结点
{ cout<<Qu.GetNode(p).node->data;
p = Qu.GetNode(p).parent; //p重新赋值为双亲结点的双亲下标
}
cout<<Qu.GetNode(p).node->data; //输出根节点
}
if(b->lChild!=NULL) //左孩子进队,双亲下标指向b
Qu.EnQueue(b->lChild,front);
if(b->rChild!=NULL) //右孩子进队,双亲下标指向b
Qu.EnQueue(b->rChild,front);
}
}
2.先序遍历
用path存放数组,pathlen存放数组长度,利用形参只在本函数内起作用的特点实现遍历和路径的存储。
void AllPath1(BTNode *b, ElemType path[], int pathlen)
//初始调用时path为空,pathlen为0
{
int i;
if(b!=NULL)
{ if(b->lChild==NULL && b->rChild==NULL) //如果*b是叶子结点
{ cout<<b->data<<"到根节点路径为:"<<b->data;
for(i=panthlen-1;i>=0;i--)
cout<<path[i];
cout<<endl;
}
else
{ path[pathlen] = b->data; //把当前结点放入路径中
pathlen++; //路径长度+1
AllPath1(b->lChild, path, pathlen); //递归扫描左子树
AllPath1(b->rChild, path, pathlen); //递归扫描右子树
}
}
}
/*这里的调用path和pathlen都不是引用调用,而是拷贝了一个副本进入到递归函数中*/
/*这就代表添加到路径中的操作在回退到上一个递归函数中时是对上一个递归函数的路径没有任何影响*/
/*这就起到了在回溯过程中“清理现场”的作用*/
3.后序遍历非递归
这个做法和上述的从根节点到指定结点的路径做法一样,只不过改成了从栈顶到栈底,而不是从栈底到栈顶。
具体代码实现可以略微修改上述函数。
- 输出第一条最长的路径长度
由于二叉树的最长路径一定是从根节点到某个叶子结点的路径,因此可以求出所有叶子结点到根节点的逆路径,通过比较长度得到最长路径。用形参maxpath[]存放最长路径,maxpathlen存放最长路径长度。
对应算法可以参考上面三种:
1.层次遍历
#include "SeqQueue.h"
void AllPath(BTNode *b, ElemType path[], int &maxpathlen)
//maxpathlen初值为0
{
struct snode
{ BTNode *node; //存放当前结点指针
int parent; //存放其双亲结点下标
};
SeqQueue<snode> Qu(MaxSize); //存放snode的队列
ElemType path[MaxSize];
int p, pathlen;
Qu.EnQueue(b,-1); //根节点进队,没有双亲,所以parent置-1
while(!Qu.IsEmpty()) //队列不为空
{ b = Qu.OutQueue();
if(b->lChild==NULL && b->rChild==NULL) //如果*b是叶子结点
{ pathlen = 0;
p = Qu.GetFront(); //取队头下标
while(Qu.GetNode(p)->parent!=-1)
{ path[pathlen] = Qu.GetNode(p).node->data; //把路径存入到路径数组里
pathlen++;
p = Qu.GetNode(p).parent; //p重新赋值为双亲结点的双亲下标
}
path[pathlen] = Qu.GetNode(p).node->data; //存入根节点
pathlen++;
if(pathlen>maxpathlen) //通过比较求最长路径
{ for(i = 0; i<pathlen; i++)
maxpath[i] = path[i];
maxpathlen = pathlen;
}
}
if(b->lChild!=NULL) //左孩子进队,双亲下标指向b
Qu.EnQueue(b->lChild,front);
if(b->rChild!=NULL) //右孩子进队,双亲下标指向b
Qu.EnQueue(b->rChild,front);
}
}
2.先序遍历
void AllPath1(BTNode *b, ElemType path[], int pathlen, ElemType maxpath[], int &maxpathlen)
//初始调用时pathlen和maxpathlen均为0
{
int i;
if(b!=NULL)
{ if(b->lChild==NULL && b->rChild==NULL) //如果*b是叶子结点
{ if(pathlen>maxpathlen) //通过比较求最长路径
{ for(i = 0; i<pathlen; i++)
maxpath[i] = path[i];
maxpathlen = pathlen;
}
}
else
{ path[pathlen] = b->data; //把当前结点放入路径中
pathlen++; //路径长度+1
AllPath1(b->lChild, path, pathlen); //递归扫描左子树
AllPath1(b->rChild, path, pathlen); //递归扫描右子树
}
}
}
3.后序遍历非递归
其做法也跟上述类似,这里不再重复。
二叉树的表示
- 水平表示
void DisplayBTWithTreeShape(BTNode *b, int level)
if(r != NULL) { // 空树不显示,只显式非空树
DisplayBTWithTreeShape(b->rChild, level + 1);//显示右子树
cout << endl; //显示新行
for(int i = 0; i < level - 1; i++)
cout << " "; //确保在第level列显示结点
cout << r->data; //显示结点
DisplayBTWithTreeShape(b->lChild, level + 1);//显示左子树
}
}
显示结果为:
- 广义表表示
对于一个非空二叉树b,先输出一个元素值,当存在左孩子结点或者是右孩子结点时,输出一个 ‘(’ 符号,然后递归处理左子树,再输出一个 ‘,’ 符号,递归处理右子树,最后输出一个 ‘)’ 符号。对应递归算法如下:
void DispBTNode(BTNode *b)
{
if(b!=NULL)
{ cout<<b->data;
if(b->lChild!=NULL || b->rChild!=NULL)
{ cout<<'(';
DispBTNode(b->lChild); //递归处理左子树
if(b->rChild!=NULL)
cout<<',';
DispBTNode(b->rChild); //递归处理右子树
cout<<')';
}
}
}
显示结果为:
- 凹入表表示
void DisplayBTWithTreeShape(BTNode *b, int level)
// 操作结果:按树状形式显示以r为根的二叉树,level为层次数,可设根结点的层次数为1
{
if (b!=NULL)
{
DisplayBTWithTreeShape(b->rChild, level + 1);
for (int i = 0; i <= 8 * level; i++)
{
cout << '-';
}
cout << r->data << endl; //换行
DisplayBTWithTreeShape(b->lChild, level + 1);
}
显示结果为: