广工数据结构Anyview - 第六章 - 二叉树

广工数据结构Anyview - 第六章 - 二叉树


本篇为广东工业大学数据结构Anyview题库中的第六章的二叉树部分
头文件参阅请点击 -> 传送门
请注意,以下的题目顺序并不会按照题库中的题目顺序,想要快速查找题目请善用 Ctrl + F 呀

一、借用栈结构的二叉树遍历

二叉树的遍历最快捷的还是使用递归来进行
但是,有时候为了提高效率,可以通过其他手段来对二叉树进行遍历操作
在此使用栈结构来进行对二叉树的遍历

1)中序遍历

代码如下(来自课本):

// 中序非递归遍历二叉树 T,visit 是对数据元素操作的应用函数
void InorderTraverse_T(BiTree T, Status(*visit) (TElemType e)) {
     
	Stack S;
	InitStack(S);

	BiTree p;
	// 找到最左下的结点,并将沿途结点的指针入栈 S
	p = GoFarLeft(T, S);
	while (p != NULL) {
     
		// 访问结点
		visit(p->data);
		if (p->rchild != NULL) {
     
			// 令 p 指向其右孩子为根的子树的最左下结点
			p = GoFarLeft(p->rchild, S);
		}
		else if (StackEmpty(S) != TRUE) {
     
			// 栈不空时退栈
			Pop(S, p);
		}
		else {
     
			// 栈空表明遍历结束
			p = NULL;
		}
	}
}

BiTNode* GoFarLeft(BiTree T, Stack& S) {
     
	// 从T结点出发,沿左分支走到底,沿途结点的指针入栈S,返回左下结点的指针
	if (T == NULL) {
     
		return NULL;
	}

	while (T->lchild != NULL) {
     
		Push(S, T);
		T = T->lchild;
	}
	return T;
}

解析(来自课本):

  • 指针 T 从根节点出发,向左走到底,并依次将指向沿途结点的左孩子指针入栈
  • 重复下面步骤,直到 T 为空:
    • 访问 T 结点
    • 若 T 结点的右孩子存在,则令 T 指向右孩子,然后向左走到底,并依次将指向沿途结点的指针入栈。若不存在,则判断栈是否为空。若非空则将栈顶的指针退栈并赋予 T;若空,则强制 T 为空(遍历结束)

2)先序遍历

题目说明:
【题目】试利用栈及其基本操作写出二叉树T的非递归的先序遍历算法。

二叉链表类型定义:

typedef struct BiTNode {
	TElemType data;
	struct BiTNode  *lchild, *rchild;
} BiTNode, *BiTree;

可用栈类型Stack的相关定义:

typedef BiTree SElemType;   // 栈的元素类型

Status InitStack(Stack &S); 
Status StackEmpty(Stack S); 
Status Push(Stack &S, SElemType e);
Status Pop(Stack &S, SElemType &e); 
Status GetTop(Stack S, SElemType &e);

要求实现下列函数:

void PreOrder(BiTree T, void (*visit)(TElemType)); 
/* 使用栈,非递归先序遍历二叉树T,     */
/* 对每个结点的元素域data调用函数visit */

先序遍历的顺序:中间结点 -> 左孩子 -> 右孩子
代码如下(来自本人):

void PreOrder(BiTree T, Status (*visit)(TElemType)){
      
    // Add your code here
    
    Stack stack;
    InitStack(stack);
    
    BiTree middleNode = T;
    while (middleNode != NULL) {
     
        // 1.先访问中间结点
        visit(middleNode -> data);
        
        // 2.因为顺序是:中 -> 左 -> 右,所以要先把右结点压入栈中保存(先进后出)
        if (middleNode -> rchild != NULL) {
     
            Push(stack, middleNode -> rchild);
        }
        
        // 3.把左结点压入栈中
        if (middleNode -> lchild != NULL) {
     
            Push(stack, middleNode -> lchild);
        }
        
        // 4.弹出,然后重复循环
        if (StackEmpty(stack) != TRUE) {
     
            Pop(stack, middleNode);
        } else {
     
            middleNode = NULL;
        }
    }
} 

解析(来自本人):

  • ①对结点进行操作(本例中为 visit 函数)
  • ②如果右孩子不为 NULL,则加入栈中
  • ③如果左孩子不为 NULL,则加入栈中
  • ④如果栈不为空,则弹出栈顶元素;为空,则遍历结束
  • ⑤不断重复①②③④

二、不借用栈结构的二叉树非递归遍历

不使用栈结构,但是可以使用栈的思想(?)

1)后序遍历

题目说明:
【题目】假设在三叉链表的结点中增设一个标志域(mark取值0,1或2)以区分在遍历过程中到达该结点时应继续向左或向右或访问该结点。试以此存储结构编写不用栈辅助的二叉树非递归后序遍历算法。

带标志域的三叉链表类型定义:

typedef struct TriTNode {
 	TElemType data;
 	struct TriTNode *lchild, *rchild, *parent;
 	int mark;  // 标志域
} TriTNode, *TriTree;

要求实现以下函数:

void PostOrder(TriTree T, Status (*visit)(TElemType));

先序遍历的顺序:左孩子 -> 右孩子 -> 中间结点

I.不太符合题目要求的做法

  • 使用数组(本质上还是使用栈的思想(先进后出))
  • 没有用到三叉的特性(没有用到父节点parent)
  • 重新定义标志域:0-表示还不用访问,1-表示要进行访问操作

代码如下(来自本人):

void PostOrder(TriTree T, Status (*visit)(TElemType)){
      
    // Add your code here
    
    // 使用数组(本质上还是使用栈的思想(先进后出))
    // 没有用到三叉的特性(没有用到父节点parent)
    // 使用到标志域:0-表示还不用访问,1-表示要进行访问操作
    
    // array:定义一个数组,长度最小为树的所有结点数量(保证足够容纳所有结点)
    TriTree array[36];
    // pos:指示位置(相当于栈顶位置)
    int pos = 0;
    TriTree temp;
    
    if (T == NULL) {
     
        return ;
    } else {
     
        array[pos] = T;
    }
    
    while (pos >= 0) {
     
        temp = array[pos];
        if (temp -> mark == 1) {
     
            visit(temp -> data);
            pos--;
        } else {
     
            temp -> mark = 1;
            
            // 先放入右结点
            if (temp -> rchild != NULL) {
     
                array[++pos] = temp -> rchild;
            }
            
            // 再放入左结点(因为遍历顺序是:左->右->中,而在这里取array中数据的顺序是“后进先出”)
            if (temp -> lchild != NULL) {
     
                array[++pos] = temp -> lchild;
            }
            
            // 如果是叶子结点,则直接进行访问操作
            if (temp -> rchild == NULL && temp -> lchild == NULL) {
     
                visit(temp -> data);
                pos--;
            }
        }
    }
    
    return ;
} 

三、根据条件求树的深度

题目说明:
【题目】编写递归算法:求二叉树中以元素值为x的结点为根的子树的深度。

二叉链表类型定义:

typedef struct BiTNode {
 	TElemType data;
 	struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;

要求实现以下函数:

int Depthx(BiTree T, TElemType x);
/* 求二叉树中以值为x的结点为根的子树深度 */

代码如下(来自本人):

int Depthx(BiTree T, TElemType x){
      
    // Add your code here
    
    // 补充一点:T 为 NULL 的话返回0
    // ???为什么不在题目中说清楚呢?返回0或者返回-1(非法值)都有各自的合理说法啊
    if (T == NULL) {
     
        return 0;
    }
    
    int l_count = 0;
    int r_count = 0;
    if (T -> data == x) {
     // 求深度的分支
        if (T -> lchild == NULL && T -> rchild == NULL) {
     
            return 1;
        } 
        
        if (T -> lchild != NULL) {
     
            l_count = Depthx(T -> lchild, T -> lchild -> data) + 1;
        } 
        
        if (T -> rchild != NULL) {
     
            r_count = Depthx(T -> rchild, T -> rchild -> data) + 1;
        }
        
        return l_count > r_count ? l_count : r_count;
    } else {
     // 找结点的分支
        if (T -> lchild == NULL && T -> rchild == NULL) {
     
            return 0;
        }
      
        if (T -> lchild != NULL) {
     
            l_count = Depthx(T -> lchild, x);
        }
        
        if (T -> rchild != NULL) {
     
            r_count = Depthx(T -> rchild, x);
        }
        
        return l_count > r_count ? l_count : r_count;
    }
} 

解析(来自本人):
首先一个前提是:会求一个二叉树的深度。
“求一个二叉树的深度”,用本题来描述就是“求二叉树中以根节点为根的子树的深度”。
所以说,本题和一般的“求二叉树深度”的区别就在于多了一个寻找“元素值为x的结点”的过程
但是,但是,但是
因为题目要求使用递归算法,我们不可能在每一次进入递归之后都寻找一次“元素值为x的结点”,所以,要转变一下思路:

  • 如果还没有找到“元素值为x的结点”,则进入递归之后继续寻找它
  • 如果已经找到了“元素值为x的结点”,则进入递归之后,改为寻找“元素值为y的结点”。这个y是当前结点的左孩子结点的值或者右孩子结点的值。这样就不会耗费时间在寻找的过程了

四、判定完全二叉树

1)满二叉树是啥?

一棵深度为 k 且有 2k-1 个结点的二叉树称为满二叉树

一棵满二叉树如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

2)完全二叉树是啥?

一个约定:约定从根起,自上而下,自左而右,给满二叉树中的每个结点从 1 到 n 连续编号,编号为 i 的结点称为 i 结点(如上图所示)
深度为 k 且含 n 个结点的二叉树,如果其每个结点都与深度为 k 的满二叉树中编号从 1 至 n 的结点一一对应,则称为完全二叉树

一棵完全二叉树如下所示:

1
2
3
4
5
6
7
8
9
10

一棵非完全二叉树如下所示:

1
2
3
4
6
7
9

因为markdown的渲染问题,不能很好的地表现出树结构
可以看到,上面的非完全二叉树比完全二叉树少了 5、8、10 号结点

3)判定一棵二叉树是否为完全二叉树

判定方法:用层次遍历算法遍历一棵完全二叉树的所有结点,如果中间过程中遇到了一个空结点(NULL),并且在此之后还有不为空的结点(不为NULL),则此二叉树不是完全二叉树;否则,此二叉树是一棵完全二叉树

题目说明:
【题目】编写算法判别给定二叉树是否为完全二叉树。

二叉链表类型定义:

typedef struct BiTNode {
  	TElemType data;
  	struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;

可用队列类型Queue的相关定义:

typedef BiTree QElemType; // 设队列元素为二叉树的指针类型
Status InitQueue(Queue &Q);
Status EnQueue(Queue &Q, QElemType e);
Status DeQueue(Queue &Q, QElemType &e);
Status GetHead(Queue Q, QElemType &e);
Status QueueEmpty(Queue Q);

要求实现下列函数:

Status CompleteBiTree(BiTree T);
/* 判别二叉树T是否为完全二叉树 */

代码如下(来自本人):

Status CompleteBiTree(BiTree T){
      
    // Add your code here

    if (T == NULL) {
     
        return TRUE;
    }
    
    Queue queue;
    InitQueue(queue);
    EnQueue(queue, T);
    
    BiTree temp;
    bool hasNullBefore = false;
    while (DeQueue(queue, temp) == OK) {
     
        if (temp -> lchild != NULL) {
     
            if (hasNullBefore) {
     
                return FALSE;
            } else {
     
                EnQueue(queue, temp -> lchild);
            }
        } else {
     
            hasNullBefore = true;
        }
        
        if (temp -> rchild != NULL) {
     
            if (hasNullBefore) {
     
                return FALSE;
            } else {
     
                EnQueue(queue, temp -> rchild);
            }
        } else {
     
            hasNullBefore = true;
        }
    }
    
    return TRUE;
} 

解析(来自本人):

  • 定义了一个布尔变量 hasNullBefore 用于标记是否遇到了空结点(指左结点为 NULL 或者右结点为 NULL)
  • 如果 hasNullBefore 为 true,且当前结点的左结点不为 NULL 或者右结点不为 NULL,那么可以判定此二叉树不是完全二叉树
  • 当然,根结点为 NULL(一棵空树)时也是一棵完全二叉树

你可能感兴趣的:(广工数据结构Anyview,数据结构,二叉树,栈)