完成线索二叉树
16
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"infty",
|
||||
"inorder",
|
||||
"lchild",
|
||||
"ltag",
|
||||
"nextval",
|
||||
"nlog",
|
||||
"postorder",
|
||||
"preorder",
|
||||
"rchild",
|
||||
"rclild",
|
||||
"rtag",
|
||||
"struct"
|
||||
]
|
||||
}
|
||||
@@ -406,7 +406,7 @@ int LocateElem(LinkList L, int e)
|
||||
{
|
||||
p = p->next;
|
||||
}
|
||||
// 找到后返回该节点指针,否则返回 NULL
|
||||
// 找到后返回该结点指针,否则返回 NULL
|
||||
return p;
|
||||
}
|
||||
```
|
||||
|
||||
@@ -164,7 +164,7 @@ LNode *LocateElem(LinkList L, int e)
|
||||
{
|
||||
p = p->next;
|
||||
}
|
||||
// 找到后返回该节点指针,否则返回 NULL
|
||||
// 找到后返回该结点指针,否则返回 NULL
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ for (int i = 1; i < a.length; i++)
|
||||
|
||||
1. 找到一个空的结点,存入数据元素。
|
||||
2. 从头结点出发,找到位序为 $i-1$ 的结点。
|
||||
3. 修改新节点的 `next`。
|
||||
3. 修改新结点的 `next`。
|
||||
4. 修改 $i-1$ 结点的 `next`。
|
||||
|
||||
## 5. 删除
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
## 1. 树的基本概念
|
||||
|
||||
树是 $n(n \geq 0)$ 个节点的有限集合,$n=0$ 时,称为空树。
|
||||
树是 $n(n \geq 0)$ 个结点的有限集合,$n=0$ 时,称为空树。
|
||||
|
||||
而任意非空树应满足:
|
||||
|
||||
1. 有且仅有一个特定的称为**根**的结点。
|
||||
2. 当 $n>0$ 时,其余结点可分为 $m(m \geq 0)$ 个互不相交的有限集合,其中每一个集合本身又是一棵树,称为根节点的**子树**。
|
||||
2. 当 $n>0$ 时,其余结点可分为 $m(m \geq 0)$ 个互不相交的有限集合,其中每一个集合本身又是一棵树,称为根结点的**子树**。
|
||||
|
||||
$n$ 个结点的树中只有 $n-1$ 条边。
|
||||
|
||||
@@ -22,7 +22,7 @@ $n$ 个结点的树中只有 $n-1$ 条边。
|
||||
- 叶子结点:度为 $0$ 的结点。
|
||||
- 结点的层次:根结点为第一层(或第零层)。
|
||||
- 结点的高度:从叶子结点(第一层)开始逐层累加。
|
||||
- 结点的深度:从根节点(第一层)开始逐层累加。
|
||||
- 结点的深度:从根结点(第一层)开始逐层累加。
|
||||
- 树的高度(深度)是树中结点的最大层数。
|
||||
- 有序树和无序树
|
||||
- 路径:树中两个结点之间的路径是由这两个结点之间所经过的**结点序列**构成的。(树的分支是有向的,即从双亲结点指向孩子结点,所以路径一定是自上而下的。)
|
||||
@@ -60,3 +60,26 @@ $n$ 个结点的树中只有 $n-1$ 条边。
|
||||
- [平衡二叉树](binary-tree/README.md#34-平衡二叉树)
|
||||
|
||||
### 4.4. [二叉树的性质](binary-tree/README.md#4-二叉树的性质)
|
||||
|
||||
### 4.5. [二叉树的存储结构](binary-tree-storage/README.md#二叉树的存储结构)
|
||||
|
||||
- [二叉树的顺序存储](binary-tree-storage/README.md#1-二叉树的顺序存储)
|
||||
- [二叉树的链式存储](binary-tree-storage/README.md#2-二叉树的链式存储)
|
||||
|
||||
### 4.6. [二叉树的遍历](binary-tree-traversal/README.md#二叉树的遍历)
|
||||
|
||||
- [先序遍历](binary-tree-traversal/README.md#1-先序遍历)
|
||||
- [中序遍历](binary-tree-traversal/README.md#2-中序遍历)
|
||||
- [后序遍历](binary-tree-traversal/README.md#3-后序遍历)
|
||||
- [层次遍历](binary-tree-traversal/README.md#5-层次遍历)
|
||||
- [由遍历序列构造二叉树](binary-tree-traversal/README.md#6-由遍历序列构造二叉树)
|
||||
|
||||
### 4.7. [线索二叉树](binary-tree-traversal/README.md#7-线索二叉树)
|
||||
|
||||
#### 4.7.1. [线索二叉树的概念](binary-tree-traversal/README.md#71-线索二叉树的概念)
|
||||
|
||||
- 先序线索二叉树
|
||||
- 中序线索二叉树
|
||||
- 后序线索二叉树
|
||||
|
||||
#### 4.7.2. [线索二叉树的构造](binary-tree-traversal/README.md#72-线索二叉树的构造)
|
||||
|
||||
30
ch5/binary-tree-storage/README.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# 二叉树的存储结构
|
||||
|
||||
## 1. 二叉树的顺序存储
|
||||
|
||||
用一组连续的存储单元依次从上而下、从左至右存储[完全二叉树](../binary-tree/README.md#32-完全二叉树)上的结点元素。
|
||||
|
||||

|
||||
|
||||
在完全二叉树中依次编号,对于结点 $i$:
|
||||
|
||||
- 若存在左孩子,则编号为 $2i$;
|
||||
- 若存在右孩子,则编号为 $2i+1$;
|
||||
|
||||

|
||||
|
||||
## 2. 二叉树的链式存储
|
||||
|
||||
用链表存放一棵二叉树,二叉树的每个结点用链表的一个链结点来存储。
|
||||
|
||||
```cpp
|
||||
typedef struct BiTNode
|
||||
{
|
||||
ElemType data;
|
||||
struct BiTNode *Lchild, *rclild;
|
||||
} BiTNode, *BiTree;
|
||||
```
|
||||
|
||||
含有 $n$ 个结点的二叉链表中,有 $n+1$ 个空链域。
|
||||
|
||||

|
||||
|
After Width: | Height: | Size: 368 KiB |
BIN
ch5/binary-tree-storage/link-storage-of-binary-tree.png
Normal file
|
After Width: | Height: | Size: 319 KiB |
11
ch5/binary-tree-storage/link.cpp
Normal file
@@ -0,0 +1,11 @@
|
||||
#include <stdio.h>
|
||||
typedef struct BiTNode
|
||||
{
|
||||
int data;
|
||||
struct BiTNode *Lchild, *rclild;
|
||||
} BiTNode, *BiTree;
|
||||
|
||||
int main(int argc, char const *argv[])
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
BIN
ch5/binary-tree-storage/sequential-storage-of-binary-tree.png
Normal file
|
After Width: | Height: | Size: 343 KiB |
288
ch5/binary-tree-traversal/README.md
Normal file
@@ -0,0 +1,288 @@
|
||||
# 二叉树的遍历
|
||||
|
||||
按照某条搜索路径访问树中的每个结点,树的每个结点均被访问一次,而且只访问一次。
|
||||
|
||||
## 1. 先序遍历
|
||||
|
||||
若二叉树非空:
|
||||
|
||||
1. 访问根结点。
|
||||
2. 先序遍历左子树。
|
||||
3. 先序遍历右子树。
|
||||
|
||||
```cpp
|
||||
void PreOrder(BiTree T)
|
||||
{
|
||||
if (T != NULL)
|
||||
{
|
||||
visit(T);
|
||||
PreOrder(T.lchild);
|
||||
PreOrder(T.rchild);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
时间复杂度:$O(n)$。
|
||||
|
||||
## 2. 中序遍历
|
||||
|
||||
若二叉树非空:
|
||||
|
||||
1. 中序遍历左子树。
|
||||
2. 访问根结点。
|
||||
3. 中序遍历右子树。
|
||||
|
||||
```cpp
|
||||
void InOrder(BiTree T)
|
||||
{
|
||||
if (T != NULL)
|
||||
{
|
||||
InOrder(T.lchild);
|
||||
visit(T);
|
||||
InOrder(T.rchild);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
时间复杂度:$O(n)$。
|
||||
|
||||
## 3. 后序遍历
|
||||
|
||||
若二叉树非空:
|
||||
|
||||
1. 后序遍历左子树。
|
||||
2. 后序遍历右子树。
|
||||
3. 访问根结点。
|
||||
|
||||
```cpp
|
||||
void PostOrder(BiTree T)
|
||||
{
|
||||
if (T != NULL)
|
||||
{
|
||||
PostOrder(T.lchild);
|
||||
PostOrder(T.rchild);
|
||||
visit(T);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
时间复杂度:$O(n)$。
|
||||
|
||||
## 4. 中序遍历非递归算法
|
||||
|
||||
借助**栈**。算法思想:
|
||||
|
||||
1. 初始时依次扫描根结点的所有左侧结点并将他们一一进栈;
|
||||
2. 出栈一个结点,**访问**它;
|
||||
3. 扫描该结点的右孩子结点并将其进栈;
|
||||
4. 依次扫描右孩子结点的所有左侧结点,并一一进栈;
|
||||
5. 反复该过程直到栈空为止。
|
||||
|
||||
```cpp
|
||||
void InOrder2(BiTree T)
|
||||
{
|
||||
InitStack(S);
|
||||
BiTree p = T;
|
||||
while (p || IsEmpty(S))
|
||||
{
|
||||
if (p)
|
||||
{
|
||||
Push(S, p);
|
||||
p = p.lchild;
|
||||
}
|
||||
else
|
||||
{
|
||||
Pop(S, p);
|
||||
visit(p);
|
||||
p = p.rchild;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 5. 层次遍历
|
||||
|
||||
借助**队列**。算法思想:
|
||||
|
||||
1. 初始将根入队并访问根结点;
|
||||
2. 若有左子树,则将左子树的根入队;
|
||||
3. 若有右子树,则将右子树的根入队;
|
||||
4. 然后出队,访问该结点;
|
||||
5. 反复该过程直到队空为止。
|
||||
|
||||
```cpp
|
||||
void levelOrder(BiTree T)
|
||||
{
|
||||
InitQueue(Q);
|
||||
BiTree p;
|
||||
EnQueue(Q, T);
|
||||
while (!isEmpty(Q))
|
||||
{
|
||||
DeQueue(Q, p);
|
||||
visit(p);
|
||||
if (p->lchild != NULL)
|
||||
{
|
||||
EnQueue(Q, p->lchild);
|
||||
}
|
||||
if (p->rchild != NULL)
|
||||
{
|
||||
EnQueue(Q, p->rchild);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 6. 由遍历序列构造二叉树
|
||||
|
||||
- 先序(或后序)遍历序列+中序遍历序列可以确定一棵二叉树。
|
||||
- 而先序遍历序列+后序遍历序列不可以确定一颗二叉树。
|
||||
|
||||
1. 在先序序列中,第一个结点是根结点;
|
||||
2. 根结点将中序遍历序列划分为两部分;
|
||||
3. 然后在先序序列中确定两部分的结点,并且两部分的第一个结点分别为左子树的根结点和右子树的根结点;
|
||||
4. 在子树中递归重复该过程,边能唯一确定一棵二叉树。
|
||||
|
||||
> 层次遍历序列+中序遍历序列也可以确定一棵二叉树。
|
||||
|
||||
## 7. 线索二叉树
|
||||
|
||||
### 7.1. 线索二叉树的概念
|
||||
|
||||
线索化:
|
||||
|
||||
1. 若无左子树,则将左指针指向其前驱结点;
|
||||
2. 若无右子树,则将右指针指向其后继结点。
|
||||
|
||||

|
||||
|
||||
- 先序遍历:$124536$
|
||||
|
||||

|
||||
|
||||
- 中序遍历:$425163$
|
||||
|
||||

|
||||
|
||||
- 后序遍历:$452631$
|
||||
|
||||

|
||||
|
||||
线索二叉树的结点结构
|
||||
|
||||
| ltag | lchild | data | rclild | rtag |
|
||||
| ---- | ------ | ---- | ------ | ---- |
|
||||
|
||||
$$
|
||||
标志域l(r)tag=
|
||||
\begin{cases}
|
||||
0,l(r)child域指示结点的左(右)孩子\\
|
||||
1,l(r)child域指示结点的前驱(后继)
|
||||
\end{cases}
|
||||
$$
|
||||
|
||||
```cpp
|
||||
typedef struct ThreadNode
|
||||
{
|
||||
ElemType data;
|
||||
struct ThreadNode *lchild, *rclild;
|
||||
int ltag, rtag;
|
||||
}ThreadNode, *ThreadTree;
|
||||
```
|
||||
|
||||
这种结点结构构成的二叉链表作为二叉树的存储结构,称为**线索链表**。
|
||||
|
||||

|
||||
|
||||
### 7.2. 线索二叉树的构造
|
||||
|
||||
常用的是**中序线索二叉树**。
|
||||
|
||||
寻找前驱结点:
|
||||
|
||||
- 若左指针为线索,则其指向结点为前驱结点。
|
||||
- 若左指针为左孩子,则其左子树的最右侧结点为前驱结点。
|
||||
|
||||
寻找后继结点:
|
||||
|
||||
- 若右指针为线索,则其指向结点为后继结点。
|
||||
- 若右指针为右孩子,则其右子树的最左侧结点为后继结点。
|
||||
|
||||
中序线索二叉树线索化
|
||||
|
||||
```cpp
|
||||
// p 为线索二叉树的根结点
|
||||
// pre 为对应结点的前驱结点
|
||||
void InThread(ThreadTree &p, ThreadTree &pre)
|
||||
{
|
||||
if (p != NULL)
|
||||
{
|
||||
InThread(p->lchild, pre);
|
||||
if (p->lchild == NULL)
|
||||
{
|
||||
p->lchild = pre;
|
||||
p->ltag = 1;
|
||||
}
|
||||
if (pre != NULL && pre->lchild == NULL)
|
||||
{
|
||||
p->rchild = p;
|
||||
p->rtag = 1;
|
||||
}
|
||||
InThread(p->rchild, pre);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```cpp
|
||||
void CreateInThread(ThreadTree T)
|
||||
{
|
||||
ThreadTree pre = NULL;
|
||||
if (T != NULL)
|
||||
{
|
||||
InThread(T, pre);
|
||||
pre->rchild = NULL;
|
||||
pre->rtag = 1;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
```cpp
|
||||
// p 为线索二叉树的根结点
|
||||
ThreadNode *FirstNode(ThreadNode *p)
|
||||
{
|
||||
while (p->ltag == 0)
|
||||
{
|
||||
p = p->lchild;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
```
|
||||
|
||||
```cpp
|
||||
// p 为结点
|
||||
ThreadNode *NextNode(ThreadNode *p)
|
||||
{
|
||||
if (p->rtag == 0)
|
||||
{
|
||||
return FirstNode(p.rchild); // 如果有孩子结点,则后继结点就是右子树最左侧的结点
|
||||
}
|
||||
else
|
||||
{
|
||||
return p.rchild;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```cpp
|
||||
// 遍历
|
||||
void InOrder(ThreadNode *T)
|
||||
{
|
||||
for (ThreadNode *p = FirstNode(T); p != NULL; p = NextNode(p))
|
||||
{
|
||||
visit(p);
|
||||
}
|
||||
}
|
||||
```
|
||||
BIN
ch5/binary-tree-traversal/binary-tree-traversal-example.png
Normal file
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 113 KiB |
|
After Width: | Height: | Size: 121 KiB |
|
After Width: | Height: | Size: 122 KiB |
BIN
ch5/binary-tree-traversal/cueing-of-inorder-clue-binary-tree.png
Normal file
|
After Width: | Height: | Size: 156 KiB |
|
After Width: | Height: | Size: 251 KiB |
BIN
ch5/binary-tree-traversal/three-kinds-of-clue-binary-trees.png
Normal file
|
After Width: | Height: | Size: 500 KiB |
@@ -3,15 +3,15 @@
|
||||
二叉树是 $n(n \geq 0)$ 个结点的有限集合。
|
||||
|
||||
1. $n=0$ 时,二叉树为空。
|
||||
2. $n>0$ 时,由根节点和两个互不相交的被称为根的左子树和右子树组成。左子树和右子树也分别是一棵二叉树。
|
||||
2. $n>0$ 时,由根结点和两个互不相交的被称为根的左子树和右子树组成。左子树和右子树也分别是一棵二叉树。
|
||||
|
||||
## 1. 五种基本形态
|
||||
|
||||
- 空树
|
||||
- 仅有根结点
|
||||
- 根节点+左子树
|
||||
- 根节点+右子树
|
||||
- 根节点+左子树+右子树
|
||||
- 根结点+左子树
|
||||
- 根结点+右子树
|
||||
- 根结点+左子树+右子树
|
||||
|
||||

|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
|
||||
- 若 $i<[n/2]$,则结点 $i$ 为分支结点,否则为叶子结点。
|
||||
- 叶子结点只可能出现在层次最大的两层上出现。对于最大层次的叶子结点,都依次排在最左边的位置上。
|
||||
- 度为 $1$ 的结点若存在,则可能有一个,且是编号最大的分支结点,并孩子结点一定是左节点。
|
||||
- 度为 $1$ 的结点若存在,则可能有一个,且是编号最大的分支结点,并孩子结点一定是左结点。
|
||||
|
||||

|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
|
||||
一颗二叉树,若树非空,则有如下性质:
|
||||
|
||||
对任意结点若存在左子树或右子树,则其左子树上所有结点的关键字均小于该节点。右子树上所有结点的关键字均大于该结点。
|
||||
对任意结点若存在左子树或右子树,则其左子树上所有结点的关键字均小于该结点。右子树上所有结点的关键字均大于该结点。
|
||||
|
||||

|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
|
||||
## 4. 二叉树的性质
|
||||
|
||||
- 非空二叉树上的叶子结点数等于度为 $2$ 的节点数加 $1$,即 $n_0=n_2+1$
|
||||
- 非空二叉树上的叶子结点数等于度为 $2$ 的结点数加 $1$,即 $n_0=n_2+1$
|
||||
|
||||

|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||

|
||||
|
||||
- 对完全二叉树按从上到下、从左到右的顺序依次编号 $1,2,...,n$,则有以下关系:
|
||||
- 当 $i>1$ 时,结点 $i$ 的双亲节点编号为:$\left \lfloor i/2 \right \rfloor$。即当 $i$ 为偶数时,其双亲结点的编号为 $i/2$,它是双亲结点的左孩子 0;当 $i$ 为奇数时,其双亲结点的编号的为 $(i-1)/2$,它是双亲结点的右孩子。
|
||||
- 当 $i>1$ 时,结点 $i$ 的双亲结点编号为:$\left \lfloor i/2 \right \rfloor$。即当 $i$ 为偶数时,其双亲结点的编号为 $i/2$,它是双亲结点的左孩子 0;当 $i$ 为奇数时,其双亲结点的编号的为 $(i-1)/2$,它是双亲结点的右孩子。
|
||||
- 当 $2i \leq n$ 时,结点 $i$ 的左孩子编号为 $2i$,否则无左孩子。
|
||||
- 当 $2i+1 \leq n$ 时,结点 $i$ 的右孩子编号为 $2i+1$,否则无右孩子。
|
||||
- 结点 $i$ 所在的层次为 $\left \lfloor log_2i \right \rfloor+1$。
|
||||
|
||||