From 7d7e4e446bc76af3d7fa0dbd052e33e38a37b1c9 Mon Sep 17 00:00:00 2001 From: Didnelpsun <2675350965@qq.com> Date: Wed, 22 Sep 2021 23:32:59 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=A0=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Code/Code/head/link_stack.h | 2 +- Code/Code/head/link_tree.h | 134 +++++++++++++++++++++++++++++++- Code/Code/head/sequence_stack.h | 2 +- Code/Code/head/thread_tree.h | 11 +++ Code/Code/source/test.cpp | 1 + Data-Structrue/7-tree-ex.md | 54 +++++++++++++ Data-Structrue/7-tree.md | 88 ++++++++++++--------- 7 files changed, 254 insertions(+), 38 deletions(-) create mode 100644 Code/Code/head/thread_tree.h diff --git a/Code/Code/head/link_stack.h b/Code/Code/head/link_stack.h index 44f40d7..5a4edfb 100644 --- a/Code/Code/head/link_stack.h +++ b/Code/Code/head/link_stack.h @@ -6,7 +6,7 @@ typedef struct LinkStackNode{ element_type data; // 指针 LinkStackNode *next; -} LinkStackNode, *LinkStack; +} *LinkStack; // 初始化 LinkStack InitLinkStack(){ diff --git a/Code/Code/head/link_tree.h b/Code/Code/head/link_tree.h index ea1ff7e..541c54a 100644 --- a/Code/Code/head/link_tree.h +++ b/Code/Code/head/link_tree.h @@ -1,7 +1,139 @@ #include "head.h" +#include +#include // 二叉树结点 typedef struct BinaryTreeNode { element_type data; BinaryTreeNode *left_child, *right_child; -} BinaryTreeNode, *BinaryTree; \ No newline at end of file +} BinaryTreeNode, *BinaryTree; + +// 前序遍历 +bool PreOrder(BinaryTree &tree, bool( *function)(BinaryTree&)){ + if(tree!= nullptr){ + if(!function(tree)) + return false; + PreOrder(tree->left_child, function); + PreOrder(tree->right_child, function); + } + return true; +} + +// 中序遍历 +bool InOrder(BinaryTree &tree, bool(* function)(BinaryTree&)){ + if(tree!= nullptr){ + PreOrder(tree->left_child, function); + if(!function(tree)) + return false; + PreOrder(tree->right_child, function); + } + return true; +} + +// 后序遍历 +bool PostOrder(BinaryTree &tree, bool(* function)(BinaryTree&)){ + if(tree!= nullptr){ + PreOrder(tree->left_child, function); + PreOrder(tree->right_child, function); + if(!function(tree)) + return false; + } + return true; +} + +// 非递归先序遍历 +bool PreOrderNonRecursive(BinaryTree &tree, bool(* function)(BinaryTree&)){ + // 初始化栈,栈元素为tree指针 + std::stack stack; + // 赋值一个新树 + BinaryTree new_tree = tree; + // 如果栈不空或new_tree不空时循环 + while(new_tree||!stack.empty()){ + // 一直向左 + if(new_tree){ + // 访问当前结点 + if(!function(new_tree)){ + printf("PreOrderNonRecursive:访问结点失败!"); + return false; + } + // 当前结点入栈 + stack.push(new_tree); + // 左孩子不空则一直向左 + new_tree = new_tree->left_child; + } + // 出栈,并转向右子树 + else{ + // 返回栈顶的元素,但不删除该元素 + new_tree = stack.top(); + // 删除栈顶元素但不返回其值 + stack.pop(); + // 向右 + new_tree = new_tree->right_child; + // 返回while + } + } + return true; +} + +// 非递归中序遍历 +bool InOrderNonRecursive(BinaryTree &tree, bool(* function)(BinaryTree&)){ + // 初始化栈,栈元素为tree指针 + std::stack stack; + // 赋值一个新树 + BinaryTree new_tree = tree; + // 如果栈不空或new_tree不空时循环 + while(new_tree||!stack.empty()){ + // 一直向左 + if(new_tree){ + // 当前结点入栈 + stack.push(new_tree); + // 左孩子不空则一直向左 + new_tree = new_tree->left_child; + } + // 出栈,并转向右子树 + else{ + // 返回栈顶的元素,但不删除该元素 + new_tree = stack.top(); + // 删除栈顶元素但不返回其值 + stack.pop(); + // 访问出栈结点 + if(!function(new_tree)){ + printf("InOrderNonRecursive:访问结点失败!"); + return false; + } + // 向右 + new_tree = new_tree->right_child; + // 返回while + } + } + return true; +} + +// 层序遍历 +bool LevelOrder(BinaryTree &tree, bool(* function)(BinaryTree&)){ + // 初始化辅助队列 + std::queue queue; + // 初始化树结点 + BinaryTree new_tree=tree; + // 根结点入队 + queue.push(new_tree); + // 若队列非空则循环 + while(!queue.empty()){ + // 队头结点出队 + new_tree = queue.front(); + queue.pop(); + // 访问出队结点 + if(!function(new_tree)){ + printf("LevelOrder:访问结点失败!"); + return false; + } + // 如果左子树不空则其根结点入队 + if(new_tree->left_child!= nullptr){ + queue.push(new_tree); + } + if(new_tree->right_child!= nullptr){ + queue.push(new_tree); + } + } + return true; +} \ No newline at end of file diff --git a/Code/Code/head/sequence_stack.h b/Code/Code/head/sequence_stack.h index 9e0f909..cef216f 100644 --- a/Code/Code/head/sequence_stack.h +++ b/Code/Code/head/sequence_stack.h @@ -1,7 +1,7 @@ #include "head.h" // ˳ջ -typedef struct { +typedef struct SequenceStack { // ջԪ element_type *data; // ջָ diff --git a/Code/Code/head/thread_tree.h b/Code/Code/head/thread_tree.h new file mode 100644 index 0000000..15666b2 --- /dev/null +++ b/Code/Code/head/thread_tree.h @@ -0,0 +1,11 @@ +#include "head.h" + +// 线索二叉树结点 +typedef struct ThreadTreeNode{ + // 数据 + element_type data; + // 左右孩子结点 + ThreadTreeNode *left_child, *right_child; + // 左右线索指针 + int left_tag, right_tag; +} ThreadTreeNode, *ThreadTree; diff --git a/Code/Code/source/test.cpp b/Code/Code/source/test.cpp index dd89f4d..dc09085 100644 --- a/Code/Code/source/test.cpp +++ b/Code/Code/source/test.cpp @@ -12,6 +12,7 @@ #include "../head/sequence_string.h" #include "../head/link_string.h" #include "../head/link_tree.h" +#include "../head/thread_tree.h" using namespace std; diff --git a/Data-Structrue/7-tree-ex.md b/Data-Structrue/7-tree-ex.md index 309fcd6..d7ca572 100644 --- a/Data-Structrue/7-tree-ex.md +++ b/Data-Structrue/7-tree-ex.md @@ -13,3 +13,57 @@ $C.113$ $D.122$ 解:$B$。设树中度为$i$($i=0,1,2,3,4$)的结点数分别为$n_i$,树中结点总数为$n$,则$n$=分支数$+1$,而分支数又等于树中各结点的度之和,即$n=1+0n_0+n_1+2n_2+3n_3+4n_4=n_0+n_1+n_2+n_3+n_4$。依题意,$n_1+2n_2+3n_3+4n_4=10+2+30+80= 122$,$n_1+n_2+n_3+n_4=10+1+10+20=41$,可得出$n_0=82$,即树$T$的叶结点的个数是$82$。 + +## 二叉树 + +### 二叉树性质 + +**例题** 下列关于二叉树的说法中,正确的是()。 + +$A.$度为$2$的有序树就是二叉树 + +$B.$含有$n$个结点的二叉树的高度$\lfloor\log_2n\rfloor+1$ + +$C.$在完全二叉树中,若一个结点没有左孩子,则它必是叶结点 + +$D.$在任意一棵非空二叉排序树中,删除某结点后又将其插入,则所得二叉排序树与删除前原二叉排序树相同 + +解:$C$。在二叉树中,若某个结点只有一个孩子,则这个孩子的左右次序是确定的,而在度为$2$的有序树中,若某个结点只有一个孩子,则这个孩子就无须区分其左右次序,$A$错误。选项$B$仅当是完全二叉树时才有意义,对于任意一棵二叉树,高度可能为$\lfloor\log_2n\rfloor+1\sim n$。在二叉排序树中插入结点时,一定插入在叶结点的位置,故若先删除分支结点再插入,则会导致二叉排序树的重构,其结果就不再相同,$D$错误。根据完全二叉树的定义,在完全二叉树中,若有度为$1$的结点,则只可能有一个,且该结点只有左孩子而无右孩子,选项$C$正确。 + +**例题** 设二叉树有$2n$个结点,且$mrtag==1,则next=p->rchild。 - + 若p右孩子指针指向右子树根结点:p->rtag==0,则next=p右子树中最左下结点。 ++ 中序线索二叉树中找到结点$*P$的中序后继$next$: + + 若$p$右孩子指针指向后继:$p->rtag==1$,则$next=p->rchild$。 + + 若$p$右孩子指针指向右子树根结点:$p->rtag==0$,则$next=p$右子树中最左下结点。 + 所以可以利用线索对二叉树实现非递归的中序遍历。 -+ 中序线索二叉树中找到结点*P的中序前驱pre: - + 若p左孩子指针指向前驱:p->ltag==1,则pre=p->lchild。 - + 若p左孩子指针指向左子树根结点:p->ltag==0,则pre=p左子树中的最右下结点。 ++ 中序线索二叉树中找到结点$*P$的中序前驱$pre$: + + 若$p$左孩子指针指向前驱:$p->ltag==1$,则$pre=p->lchild$。 + + 若$p$左孩子指针指向左子树根结点:$p->ltag==0$,则$pre=p$左子树中的最右下结点。 + 所以可以利用线索对二叉树实现非递归的逆向中序遍历。 -+ 先序线索二叉树中找到结点*P的先序后继next: - + 若p右孩子指针指向后继:p->rtag==1,则next=p->rchild。 - + 若p右孩子指针指向右子树根结点:p->rtag==0,如果p有左孩子,则p->next=p->lchild,如果p没有左孩子,则p->next=p->rchild。 ++ 先序线索二叉树中找到结点$*P$的先序后继$next$: + + 若$p$右孩子指针指向后继:$p->rtag==1$,则$next=p->rchild$。 + + 若$p$右孩子指针指向右子树根结点:$p->rtag==0$,如果$p$有左孩子,则$p->next=p->lchild$,如果$p$没有左孩子,则$p->next=p->rchild$。 + 所以可以利用线索对二叉树实现非递归的先序遍历。 -+ 先序线索二叉树中找到结点*P的中序前驱pre: - + 若p左孩子指针指向前驱:p->ltag==1,则pre=p->lchild。 - + 若p左孩子指针指向左子树根结点:p->ltag==0,先序遍历中左右子树的根结点只可能是后继,所以这时候就找不到p的前驱,如果没有父结点只能从头开始先序遍历。 ++ 先序线索二叉树中找到结点$*P$的中序前驱$pre$: + + 若$p$左孩子指针指向前驱:$p->ltag==1$,则$pre=p->lchild$。 + + 若$p$左孩子指针指向左子树根结点:$p->ltag==0$,先序遍历中左右子树的根结点只可能是后继,所以这时候就找不到$p$的前驱,如果没有父结点只能从头开始先序遍历。 + 如果有父结点,则又有三种情况: - + p为左孩子,则根据根左右,p的父结点为根所以在p的前面,p->pre=p->parent。 - + p为右孩子,其左兄弟为空,则根据根左右,顺序为根右,所以p->pre=p->parent。 - + p为右孩子且有左兄弟,根据根左右,p的前驱就是左兄弟子树中最后一个被先序遍历的结点,即在p的左兄弟子树中优先右子树遍历的底部。 -+ 后序线索二叉树中找到结点*P后中序后继next: - + 若p右孩子指针指向后继:p->rtag==1,则next=p->rchild。 - + 若p右孩子指针指向右子树根结点:p->rtag==0,则根据左右根顺序,左右孩子结点必然是p的前驱而不可能是后继,所以找不到后序后继,如果没有父结点只能使用从头开始遍历的方式。 + + $p$为左孩子,则根据根左右,$p$的父结点为根所以在$p$的前面,$p->pre=p->parent$。 + + $p$为右孩子,其左兄弟为空,则根据根左右,顺序为根右,所以$p->pre=p->parent$。 + + $p$为右孩子且有左兄弟,根据根左右,p的前驱就是左兄弟子树中最后一个被先序遍历的结点,即在p的左兄弟子树中优先右子树遍历的底部。 ++ 后序线索二叉树中找到结点$*P$后中序后继$next$: + + 若$p$右孩子指针指向后继:$p->rtag==1$,则$next=p->rchild$。 + + 若$p$右孩子指针指向右子树根结点:$p->rtag==0$,则根据左右根顺序,左右孩子结点必然是$p$的前驱而不可能是后继,所以找不到后序后继,如果没有父结点只能使用从头开始遍历的方式。 + 如果有父结点则又有三种情况: - + p为右孩子,根据左右根,所以p->next=p->parent。 - + p为左孩子,右孩子为空,根据左右根,所以p->next=p->parent。 - + p为左孩子,右孩子非空,根据左右根,所以p->next=右兄弟子树中第一个被后序遍历的结点。 -+ 后序线索二叉树中找到结点*P后中序前驱pre: - + 若p左孩子指针指向前驱:p->ltag==1,则pre=p->lchild。 - + 若p左孩子指针指向左子树根结点:p->ltag==0,则又有两种情况: - + 若p有右孩子,则按照左右根的情况遍历,右在根的前面,所以p->pre=p->rchild。 - + 若p没有右孩子,按照左根的顺序,则p->pre=p->lchild。 + + $p$为右孩子,根据左右根,所以$p->next=p->parent$。 + + $p$为左孩子,右孩子为空,根据左右根,所以$p->next=p->parent$。 + + $p$为左孩子,右孩子非空,根据左右根,所以$p->next=$右兄弟子树中第一个被后序遍历的结点。 ++ 后序线索二叉树中找到结点$*P$后中序前驱$pre$: + + 若$p$左孩子指针指向前驱:$p->ltag==1$,则$pre=p->lchild$。 + + 若$p$左孩子指针指向左子树根结点:$p->ltag==0$,则又有两种情况: + + 若$p$有右孩子,则按照左右根的情况遍历,右在根的前面,所以$p->pre=p->rchild$。 + + 若$p$没有右孩子,按照左根的顺序,则$p->pre=p->lchild$。 ### 二叉排序树 @@ -450,7 +468,7 @@ AL(H) CL(H-1) CR(H) BR(H) ### 树的存储结构 + 双亲表示法:是一种顺序存储方式,每个结点中保存指向双亲的指针。查找双亲方便,但是查找孩子就只能从头遍历。 -+ 孩子表示法:是顺序加链式存储方法,顺序存储所有元素,添加一个firstChild域,指向第一个孩子结构体的指针,孩子结构体包括元素位置索引与指向下一个孩子结构体的next指针。 ++ 孩子表示法:是顺序加链式存储方法,顺序存储所有元素,添加一个firstChild域,指向第一个孩子结构体的指针,孩子结构体包括元素位置索引与指向下一个孩子结构体的$next$指针。 + 孩子兄弟表示法:是一种链式存储方式,定义了两个指针,分别指向第一个孩子与右兄弟,类似于二叉树,可以利用二叉树来实现对树的处理 。 ### 森林与树的转换