diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..f6572ed --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,16 @@ +{ + "cSpell.words": [ + "infty", + "inorder", + "lchild", + "ltag", + "nextval", + "nlog", + "postorder", + "preorder", + "rchild", + "rclild", + "rtag", + "struct" + ] +} \ No newline at end of file diff --git a/ch2/single-link/README.md b/ch2/single-link/README.md index be8a2e7..019199f 100644 --- a/ch2/single-link/README.md +++ b/ch2/single-link/README.md @@ -406,7 +406,7 @@ int LocateElem(LinkList L, int e) { p = p->next; } - // 找到后返回该节点指针,否则返回 NULL + // 找到后返回该结点指针,否则返回 NULL return p; } ``` diff --git a/ch2/single-link/with.cpp b/ch2/single-link/with.cpp index 802c6be..9f3dfd9 100644 --- a/ch2/single-link/with.cpp +++ b/ch2/single-link/with.cpp @@ -164,7 +164,7 @@ LNode *LocateElem(LinkList L, int e) { p = p->next; } - // 找到后返回该节点指针,否则返回 NULL + // 找到后返回该结点指针,否则返回 NULL return p; } diff --git a/ch2/static-link/README.md b/ch2/static-link/README.md index cb60e99..02e0dcf 100644 --- a/ch2/static-link/README.md +++ b/ch2/static-link/README.md @@ -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. 删除 diff --git a/ch5/README.md b/ch5/README.md index acdf0ed..ec5de14 100644 --- a/ch5/README.md +++ b/ch5/README.md @@ -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-线索二叉树的构造) diff --git a/ch5/binary-tree-storage/README.md b/ch5/binary-tree-storage/README.md new file mode 100644 index 0000000..6dd953c --- /dev/null +++ b/ch5/binary-tree-storage/README.md @@ -0,0 +1,30 @@ +# 二叉树的存储结构 + +## 1. 二叉树的顺序存储 + +用一组连续的存储单元依次从上而下、从左至右存储[完全二叉树](../binary-tree/README.md#32-完全二叉树)上的结点元素。 + +![二叉树的顺序存储](sequential-storage-of-binary-tree.png) + +在完全二叉树中依次编号,对于结点 $i$: + +- 若存在左孩子,则编号为 $2i$; +- 若存在右孩子,则编号为 $2i+1$; + +![二叉树的顺序存储的缺点](disadvantages-of-sequential-storage-of-binary-tree.png) + +## 2. 二叉树的链式存储 + +用链表存放一棵二叉树,二叉树的每个结点用链表的一个链结点来存储。 + +```cpp +typedef struct BiTNode +{ + ElemType data; + struct BiTNode *Lchild, *rclild; +} BiTNode, *BiTree; +``` + +含有 $n$ 个结点的二叉链表中,有 $n+1$ 个空链域。 + +![二叉树的链式存储](link-storage-of-binary-tree.png) diff --git a/ch5/binary-tree-storage/disadvantages-of-sequential-storage-of-binary-tree.png b/ch5/binary-tree-storage/disadvantages-of-sequential-storage-of-binary-tree.png new file mode 100644 index 0000000..02362d6 Binary files /dev/null and b/ch5/binary-tree-storage/disadvantages-of-sequential-storage-of-binary-tree.png differ diff --git a/ch5/binary-tree-storage/link-storage-of-binary-tree.png b/ch5/binary-tree-storage/link-storage-of-binary-tree.png new file mode 100644 index 0000000..b3709ea Binary files /dev/null and b/ch5/binary-tree-storage/link-storage-of-binary-tree.png differ diff --git a/ch5/binary-tree-storage/link.cpp b/ch5/binary-tree-storage/link.cpp new file mode 100644 index 0000000..df2c6d4 --- /dev/null +++ b/ch5/binary-tree-storage/link.cpp @@ -0,0 +1,11 @@ +#include +typedef struct BiTNode +{ + int data; + struct BiTNode *Lchild, *rclild; +} BiTNode, *BiTree; + +int main(int argc, char const *argv[]) +{ + return 0; +} diff --git a/ch5/binary-tree-storage/sequential-storage-of-binary-tree.png b/ch5/binary-tree-storage/sequential-storage-of-binary-tree.png new file mode 100644 index 0000000..5ae6f2c Binary files /dev/null and b/ch5/binary-tree-storage/sequential-storage-of-binary-tree.png differ diff --git a/ch5/binary-tree-traversal/README.md b/ch5/binary-tree-traversal/README.md new file mode 100644 index 0000000..e5bd0ba --- /dev/null +++ b/ch5/binary-tree-traversal/README.md @@ -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. 若无右子树,则将右指针指向其后继结点。 + +![二叉树遍历举例](binary-tree-traversal-example.png) + +- 先序遍历:$124536$ + +![先序线索二叉树](clue-binary-tree-with-preorder-traversal.png) + +- 中序遍历:$425163$ + +![中序线索二叉树](clue-binary-tree-with-inorder-traversal.png) + +- 后序遍历:$452631$ + +![后序线索二叉树](clue-binary-tree-with-postorder-traversal.png) + +线索二叉树的结点结构 + +| 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; +``` + +这种结点结构构成的二叉链表作为二叉树的存储结构,称为**线索链表**。 + +![三种线索二叉树](three-kinds-of-clue-binary-trees.png) + +### 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; + } +} +``` + +![中序线索二叉树的线索化](cueing-of-inorder-clue-binary-tree.png) + +![中序线索二叉树的线索化2](cueing-of-inorder-clue-binary-tree2.png) + +```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); + } +} +``` diff --git a/ch5/binary-tree-traversal/binary-tree-traversal-example.png b/ch5/binary-tree-traversal/binary-tree-traversal-example.png new file mode 100644 index 0000000..a1c5ab6 Binary files /dev/null and b/ch5/binary-tree-traversal/binary-tree-traversal-example.png differ diff --git a/ch5/binary-tree-traversal/clue-binary-tree-with-inorder-traversal.png b/ch5/binary-tree-traversal/clue-binary-tree-with-inorder-traversal.png new file mode 100644 index 0000000..601bd30 Binary files /dev/null and b/ch5/binary-tree-traversal/clue-binary-tree-with-inorder-traversal.png differ diff --git a/ch5/binary-tree-traversal/clue-binary-tree-with-postorder-traversal.png b/ch5/binary-tree-traversal/clue-binary-tree-with-postorder-traversal.png new file mode 100644 index 0000000..6a2bb77 Binary files /dev/null and b/ch5/binary-tree-traversal/clue-binary-tree-with-postorder-traversal.png differ diff --git a/ch5/binary-tree-traversal/clue-binary-tree-with-preorder-traversal.png b/ch5/binary-tree-traversal/clue-binary-tree-with-preorder-traversal.png new file mode 100644 index 0000000..fc79976 Binary files /dev/null and b/ch5/binary-tree-traversal/clue-binary-tree-with-preorder-traversal.png differ diff --git a/ch5/binary-tree-traversal/cueing-of-inorder-clue-binary-tree.png b/ch5/binary-tree-traversal/cueing-of-inorder-clue-binary-tree.png new file mode 100644 index 0000000..fb81b3c Binary files /dev/null and b/ch5/binary-tree-traversal/cueing-of-inorder-clue-binary-tree.png differ diff --git a/ch5/binary-tree-traversal/cueing-of-inorder-clue-binary-tree2.png b/ch5/binary-tree-traversal/cueing-of-inorder-clue-binary-tree2.png new file mode 100644 index 0000000..e561ecc Binary files /dev/null and b/ch5/binary-tree-traversal/cueing-of-inorder-clue-binary-tree2.png differ diff --git a/ch5/binary-tree-traversal/three-kinds-of-clue-binary-trees.png b/ch5/binary-tree-traversal/three-kinds-of-clue-binary-trees.png new file mode 100644 index 0000000..3968f4d Binary files /dev/null and b/ch5/binary-tree-traversal/three-kinds-of-clue-binary-trees.png differ diff --git a/ch5/binary-tree/README.md b/ch5/binary-tree/README.md index cc6e3d2..27afb25 100644 --- a/ch5/binary-tree/README.md +++ b/ch5/binary-tree/README.md @@ -3,15 +3,15 @@ 二叉树是 $n(n \geq 0)$ 个结点的有限集合。 1. $n=0$ 时,二叉树为空。 -2. $n>0$ 时,由根节点和两个互不相交的被称为根的左子树和右子树组成。左子树和右子树也分别是一棵二叉树。 +2. $n>0$ 时,由根结点和两个互不相交的被称为根的左子树和右子树组成。左子树和右子树也分别是一棵二叉树。 ## 1. 五种基本形态 - 空树 - 仅有根结点 -- 根节点+左子树 -- 根节点+右子树 -- 根节点+左子树+右子树 +- 根结点+左子树 +- 根结点+右子树 +- 根结点+左子树+右子树 ![二叉树的五种基本形态](five-basic-form-of-binary-tree.png) @@ -42,7 +42,7 @@ - 若 $i<[n/2]$,则结点 $i$ 为分支结点,否则为叶子结点。 - 叶子结点只可能出现在层次最大的两层上出现。对于最大层次的叶子结点,都依次排在最左边的位置上。 -- 度为 $1$ 的结点若存在,则可能有一个,且是编号最大的分支结点,并孩子结点一定是左节点。 +- 度为 $1$ 的结点若存在,则可能有一个,且是编号最大的分支结点,并孩子结点一定是左结点。 ![完全二叉树的性质](character-of-complete-binary-tree.png) @@ -50,7 +50,7 @@ 一颗二叉树,若树非空,则有如下性质: -对任意结点若存在左子树或右子树,则其左子树上所有结点的关键字均小于该节点。右子树上所有结点的关键字均大于该结点。 +对任意结点若存在左子树或右子树,则其左子树上所有结点的关键字均小于该结点。右子树上所有结点的关键字均大于该结点。 ![二叉排序树](binary-sort-tree.png) @@ -62,7 +62,7 @@ ## 4. 二叉树的性质 -- 非空二叉树上的叶子结点数等于度为 $2$ 的节点数加 $1$,即 $n_0=n_2+1$ +- 非空二叉树上的叶子结点数等于度为 $2$ 的结点数加 $1$,即 $n_0=n_2+1$ ![二叉树性质一](nature-1-of-binary-tree.png) @@ -72,7 +72,7 @@ ![二叉树性质二和性质三](nature-2-and-3-of-binary-tree.png) - 对完全二叉树按从上到下、从左到右的顺序依次编号 $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$。