/*二叉链表存储*/
typedef struct BTNode{
char data;
struct BTNode *lChild; // 左孩子指针
struct BTNode *rChild; // 右孩子指针
}BTNode;
问题:
为什么非递归遍历效率高于递归遍历效率?
答:
因为非递归遍历使用的是自定义栈,而递归遍历使用的是系统栈,系统栈是所有递归函数都通用的栈,所以效率会低。
void preOrder(BTNode *p){
if(p != NULL){
cout<<p->data<<" ";
preOrder(p->lChild); // 先序遍历左子树
preOrder(p->rChild); // 先序遍历右子树
}
}
分析:
1)非递归遍历用到了栈(自定义栈)
2)首先访问根结点,并让其入栈,再出栈(并输出根节点值)
3)出栈后,访问当前结点 ,让其右孩子先入栈,左孩子再入栈(因为栈先进后出,而我们先访问的是左孩子)
4)依次重复执行 2)和 3) 步骤,直到栈空为止
void preOrder(BTNode *bt){
/*根结点不空,执行遍历*/
if(bt != NULL){
// 1)
BTNode *stack[maxSize]; // 定义一个栈(存的都是BTNode类型指针)
int top = -1;
BTNode *p;
// 2)
stack[++top] = bt; // 根节点入栈
while(top != -1){ // 栈不空
p = stack[top--]; // p 指向根结点,并执行出栈
visit(p); // visit 访问结点
if(p->rChild != NULL){
stack[++top] = p->rChild;
}
if(p->lChild != NULL){
stack[++top] = p->lChild;
}
}
}
}
void inOrder(BTNode *p){
if(p != NULL){
inOrder(p->lChild); // 中序遍历左子树
cout<<p->data<<" ";
inOrder(p->rChild); // 中序遍历右子树
}
}
分析:
1)自定义栈,栈存放结点指针
2)左孩子结点一直入栈,直到无左孩子为止
3)然后判断栈是否空,不空,执行左孩子出栈、并输出,再将右孩子执行入栈
4)直到栈空且当前指针为空,结束
void inOrder(BTNode *bt){
if(bt){
/*创建栈,存储结点指针*/
BTNode *stack[maxSize]; // maxSize 已定义常量
int top = -1;
BTNode *p = bt; // p 指针指向根结点
while(top != -1 || p != NULL){
/*左孩子结点一直入栈*/
while(p != NULL){
stack[++top] = p;
p = p->lChild;
}
/*此时栈顶元素就是最左端结点*/
if(top != -1){
p = stack[top--];
visit(p); // visit 是访问函数,假设已定义
p = p->rChild;
}
}
}
}
void postOrder(BTNode *p){
if(p != NULL){
postOrder(p->lChild); // 后序遍历左子树
postOrder(p->rChild); // 后序遍历右子树
cout<<p->data<<" ";
}
}
分析:
1)先序遍历: 1 2 3 5 4 后序遍历:3 5 2 4 1 逆后序遍历:1 4 2 5 3
2)我们可以看到 逆后序遍历其实就是先序遍历改变左右子树遍历顺序,先右再左
3)所以我们可以定义两个栈,stack1存储逆后序遍历,stack2存储从stack1中的数据,然后再执行stack2出栈即可得到后序遍历
4)循环结束还是栈空的时候为止
void posOrder(BTNode *bt){
if(bt != NULL){
BTNode *stack1[maxSize];
BTNode *stack2[maxSize];
int top1 = -1;
int top2 = -1;
BTNode *p;
/*根结点入栈*/
stack1[++top1] = bt;
while(top1 != -1){
/*结点出栈,并访问*/
p = stack1[top1--];
/*stack1结点出栈后,进入stakc2中*/
stack2[++top2] = p;
/*先存入左子树结点再存入右子树结点*/
if(p->lChild != NULL){
stack1[++top1] = p->lChild;
}
if(p->rChild != NULL){
stack1[++top1] = p->rChild;
}
}
/*遍历输出stack2*/
while(top2 != -1){
p = stack2[top2--];
visit(p);
}
}
}
分析:
1)层次遍历要用到 队列,在这里我们使用循环队列,定义队列
2)根结点入队列,然后根节点出队列(并输出访问),然后检查当前结点的左右子树,左非空,左子树先入队列,右非空,右子树再入队列
3)然后当队列不空的时候一直循环,将队头元素弹出,重复 2)
void level(BTNode *bt){
/*定义队列,并初始化*/
BTNode *que[maxSize];
int front , rear;
front = rear = 0;
BTNode *p;
if(bt != NULL){
rear = (rear + 1)%maxSize;
que[rear] = bt;
while(front != rear){ // 队列不空
front = (front + 1)%maxSize;
p = que[front];
visit(p); // 访问 p
if(p->lChild != NULL){ // 左子书进入队列
que[(rear + 1)%maxSize] = p->lChild;
}
if(p->rChild != NULL){ // 右子树进入队列
que[(rear + 1)%maxSize] = p->rChild;
}
}
}
}
表达式 (a-(b+c))*(d/e) 存储在一棵以二叉链表为存储结构的二叉树中(二叉树结点的data域为字符型),编写程序求出该表达式的值(表达式中的操作数都是一位的整数)。
分析:
1) (a-(b+c))*(d/e) 可以看作 A * B
2)然后我们 可以先去计算 表达式 A ,再计算表达式 B,最后 A * B ,对应着先左 再右 再后,我们二叉树的后序遍历
3) 对于表达式,度要么为2 都要么为 0 ,没有度为 1 的
int comp(BTNode *p){
int A,B;
if(p != NULL){ // 根结点不空时
if(p->lchild != NULL && p->rchild !=NULL){ // 非叶子结点进行计算
A = comp(p->lchild);
B = comp(p->rchild);
return op(A, B,p->data); // 根据求得的 A,B 进行计算
}else{ // 叶子结点,是数值
return p->data - '0';
}
}else{
return 0; // 空树,表达式值为 0
}
}
op 是返回 A p->data B 计算得来的函数值
写一个算法求一棵二叉树的深度,二叉树以二叉链表为存储方式。
分析:
1)求二叉树的深度,我们可以去分别计算出 左子树 和 右子树的高度,取其中的最大值 加1 就是二叉树的深度
2)计算左子树深度 计算右子树深度 根,可以采用后序遍历
int high(BTNode *p){
int Lh,Rh;
if(p == NULL){
return 0
}else{
Lh = high(p->lchild);
Rh = high(p->rchild);
return (Lh>Rh?Lh:Rh) + 1; // 返回左右子树深度最大值 加1
}
}
在一棵以二叉链表为存储结构的二叉树中,查找data 域值等于 key 的结点是否存在(找到任何一个满足要求的结点即可),如果存在,则将 q 指向该结点,否则 q 赋值为 NULL ,假设 data 为 int 型。
分析:
1)随意一种遍历方式都可以
2)为了提高效率,假设我们在左子树中找到了,我们就不在右子树找了
/*先序遍历*/
void search(BTNode *p , BTNode *&q ,int key){ // q 改变用引用型
if(p != NULL){ // p 为空,则 q 保持NULL
if(p->data == key){
q = p;
}else{
search(p->lchild,q,key);
search(p->rchild,q,key);
}
}
}
/*先序遍历高效*/
void search(BTNode *p , BTNode *&q , int key){
if(p != NULL){
if(p->data == key){
q = p;
}else{
search(p->lchild,q,key);
if(q == NULL){ // 左子树没找到,再去右子树找
search(p->rchild,q,key);
}
}
}
}
假设二叉树采用二叉链表存储结构存储,编写一个程序,输出先序遍历中 第 k 个结点的值,假设 k 不大于总的结点数(结点 data 域类型为 char 型)。
分析:
1)要输出第 k 个结点的值,所以要采用计数器 num,全局变量
2)题目要求先序遍历,先访问根结点 再左子树,再访问右子树
int num = 0; // 定义计数器并初始化,全局变量
void trave(BTNode *p , int k){
if(p != NULL){
++num;
if(num == k){
cout<<p->data<<endl;
return; // 找到了,就无需再遍历了
}
trave(p->lchild,k);
trave(p->rchild,k);
}
}
假设二叉树采用二叉链表存储结构存储,设计一个算法,求出该二叉树的宽度(具有结点数最多的那一层上的结点个数)。
分析:
1)求二叉树宽度,可以定义队列,进行层次遍历
2)可以根据双亲结点找到其左右孩子结点的层号,这样知道所有结点层号
3)通过遍历找到宽度最大的那个,即是二叉树的宽度
4)用到非循环队列,设置 maxSize足够大
typedef struct St{
BTNode *p;
int lno; // 记录每个结点的层号
}St;
int maxNode(BTNode *bt){
/*创建一个队列,并初始化*/
St qu[maxSize]; // 非循环队列
int front,rear;
front=rear=0; // 队列置空
int Lno = 0; // 层号
int i,j,n,max = 0; // i,j循环变量,n max 找最大值
BTNode* q;
if(bt != NULL){
qu[++rear].p = bt; // 结点入队列
qu[rear].lno = 1; // 根结点在第一层
while(front != rear){
q = qu[++front].p;
Lno = qu[front].lno;
if(q->lchild != NULL){
qu[++rear].p = q->lchild;
qu[rear].lno = Lno+1; // 根据当前层号推测 子树层号
}
if(q->rchild != NULL){
qu[++rear].p = q->rchild;
qu[rear].lno = Lno + 1;
}
}
/*循环结束后,Lno 中保存着最大层号,因为我们是将其存入一个足够大的队列中,rear 是二叉树结点的个数*/
for(i=1;i<=Lno;++i){ // 从第一层开始遍历到最后一层,i层号
n=0;
for(j=0;j<rear;++j){ // j 相当于是从第一个元素到最后一个
if(qu[j].lno == i){ // qu[j].lno 存当前结点的层号
++n;
}
if(max < n){
max = n;
}
}
}
return max;
}else{
return 0; // 空树 返回 0
}
}
假设二叉树采用二叉链表存储结构,设计一个算法,计算一棵给定二叉树的所有结点数。
分析:
1)方法一:创建计数器,通过遍历,没遇到一个结点,计数器加1,这样就可以得到所有结点数
2)方法二:分冶,先去计算左子树个数,再去计算右子树个数,最后,左子树个数 + 右子树个数 + 1
int num = 0; // 定义全局变量,用来计数
void count(BTNode *p){
if(p != NULL){
++num;
count(p->lchild);
count(p->rchild);
}
}
int count(BTNode* p){
int A,B;
if(p == NULL){
return 0;
}else{
A = count(p->lchild); // 查左子树结点个数
B = count(p->rchild); // 查右子树结点个数
return A + B + 1; // 左子树 + 右子树 + 根
}
}
假设二叉树采用二叉链表存储结构,设计一个算法,计算一棵给定二叉树的所有叶子结点数。
分析:
1)方法一:计数器方式,条件是左右子树都为NULL ,计数器加1
2)方法二:分冶,先计算左子树叶子结点个数 再计算右子树叶子结点个数,两个相加
int num = 0;
void treeCount(BTNode *p){
if(p != NULL){
if(!p->lchild && !p->rchild){ // 左右子树均空
++num;
}
treeCount(p->lchild);
treeCount(p->rchild);
}
}
void treeCount(BTNode *p){
int A,B;
if(p == NULL){
return 0;
}else if(p->lchild == NULL && p->rchild == NULL){ // 是叶子结点返回 1
return 1;
}else{
A = treeCount(p->lchild);
B = treeCount(p->rchild);
return A + B; // 计算叶子结点个数
}
}
假设二叉树采用二叉链表存储结构,设计一个算法,利用结点的右孩子指针 rchild 将一棵二叉树的叶子结点按照从左往右的顺序串成一个单链表(在题目中定义两个指针 head 和 tail ,其中 head 指向第一个叶子结点,head 的初值为 NULL ,tail 指向最后一个叶子结点)。
分析:
1)想要链接起所有叶子结点,我们第一步要去遍历二叉树,遇见叶子结点链入链表中
2)让head 指向第一个叶子结点,tail 一直指向最后一个叶子结点,遇见叶子结点修改 右孩子指针
3)尾插法
void link(BTNode *p,BTNode *&head , BTNode *&tail){
if(p != NULL){
if(!p->lchild && !p->rchild){ // 遇见叶子结点
if(head == NULL){ // 判断是否是第一个叶子结点
head = p;
tail = p;
}else{
tail->rchild = p; // 上一个叶子结点链接当前叶子结点
tail = p; // 让 tail 一直指向最后一个叶子结点
}
}
link(p->lchild , head , tail);
link(p->rchild , head , tail);
}
}
在二叉树的二叉链式存储结构中,增加一个指向双亲结点的 parent 指针,设计一个算法,给这个指针赋值,并输出所有结点到根节点的路径。
分析:
1)首先定义新的二叉链式存储结构,增加一个 parent 双亲指针
2)去遍历二叉树,走到某个结点,即可通过 parent 指针向上找到根结点
3)直到 parent 双亲指针为空,为止,过程中,输出 路径
/*结构体*/
typedef struct BTNode{
char data;
struct BTNode *lchild;
struct BTNode *rchild;
struct BTNode *parent; // 双亲指针
}BTNode;
/*(1)给各个结点的 parent 双亲指针赋值*/
void pTree(BTNode *p , BTNode *q){ // q 指向当前结点的双亲结点
if(p != NULL){
p->parent = q; // q 刚开始为 NULL
q = p; // q 指向根结点
pTree(p->lchild,q);
pTree(p->rchild,q);
}
}
/*(2)打印单个结点到根节点的路径*/
void print(BTNode *p){
while(p != NULL){ // 最终当指向根节点上的 NULL 时结束
cout<<p->data<<" "<<endl;
p = p->parent;
}
}
/*(3)打印所有结点*/
void printAll(BTNode *p){
if(p != NULL){
print(p); // 每到一个结点,都打印一下路径
printAll(p->lchild);
printAll(p->rchild);
}
}
假设满二叉树 b 的先序遍历序列已经存在于数组 A 中,长度为 n ,设计一个算法,将其转换为后序遍历序列。
分析:
1)满二叉树,所以先序遍历第一个结点是根结点,后边等分,是左子树和右子树
2)先序遍历中第一结点是后序遍历中最后一结点
3)用分冶方法,一层一层递归的处理,重复 1)和 2)
void change(char pre[] , int L1 , int R1 , char post[] , int L2 , int R2){
if(L1 <= R1){
/*将pre中第一个结点(先序遍历根结点)放入post最后位置,即后序遍历根结点*/
post[R2] = pre[L1];
/*递归处理 pre 前一半序列,将其存在 post 对应的前一半位置*/
change(pre,L1+1,(L1+1+R1)/2,post,L2,(L2+R2-1)/2);
/*递归处理 pre 后一半序列,将其存在 post 对应的后一半位置*/
change(pre,(L1+1+R1)/2+1,R1,post,(L2+R2-1)/2+1,R2-1);
}
}
假设二叉树采用二叉链式存储结构,设计一个算法,求二叉树 b 中值为 x 的结点的层号。
分析:
1)根据双亲结点可以找到子结点的层号
2)技巧图,我们定义一个层号变量,让其随着遍历 增或者减
int L = 1; // 代表着第一层
void leno(BTNode *p , char x){
if(p != NULL){
if(p->data == x){
cout<<L<<endl; // 输出 x 所在层数
}
++L;
leno(p->lchild,x);
leno(p->rchild,x);
--L;
}
}
假设二叉树采用二叉链式存储结构,设计一个算法,输出根节点到每个叶子结点的路径。
int i;
int top = 0;
char path[maxSize];
void allPath(BTNode *p){
if(p != NULL){
path[top] = p->data; // 入栈,也是 p 自上向下走的过程
++top;
if(p->lchild == NULL && p->rchild == NULL){
for(i=0;i<top;++i){
cout<<path[i];
}
}
allPath(p->lchild);
allPath(p->rchild);
--top; // 出栈,也是 p 自下向上走的过程
}
}