@@ -500,7 +500,7 @@ typedef struct {
500500 $$ h = \lceil \log_2(n) + 1 \rceil $$
501501 其中n为结点数。
502502
503- ### 5.3 二叉树的存储结构
503+ ** 二叉树的存储结构**
504504!!! abstract "二叉树存储结构"
505505 二叉树的存储结构主要有两种:顺序存储和链式存储。
506506
@@ -515,20 +515,378 @@ typedef struct BiTNode {
515515} BiTNode, *BiTree;
516516````
517517
518+ !!! warning
519+ 在含有n个结点的二叉链表中,含有n+1个空链域。
518520
521+ ### 5.3 二叉树的遍历
522+ !!! abstract "二叉树遍历"
523+ 二叉树的遍历是指按照一定的顺序访问二叉树的所有结点。
519524
520- ## ch6. 图
525+ !!! note "遍历方式"
526+ - ** 前序遍历** : 根结点 -> 左子树 -> 右
527+ - ** 中序遍历** : 左子树 -> 根结点 -> 右子树
528+ - ** 后序遍历** : 左子树 -> 右子树 -> 根
529+ - ** 层序遍历** : 按层次从上到下、从左到右访问结点
530+ > 前中后可以理解为根被遍历到的顺序
521531
522- !!! todo "待完善内容"
523- 图的基本概念、存储结构、遍历算法、最小生成树、最短路径、拓扑排序、关键路径等
532+ > 对于手写遍历,想象从根节点从上到下、从左到右环绕树,然后前序中序后序分别在左中右三个位置有标记,按照碰到标记的顺序作为顺序。
524533
525- ## ch7. 查找
534+ 前三种对应的递归算法如下:
535+ ``` c
536+ void PreOrder (BiTree T) {
537+ if (T != NULL) {
538+ visit(T); // 访问根结点
539+ PreOrder(T->lchild); // 递归访问左子树
540+ PreOrder(T->rchild); // 递归访问右子树
541+ }
542+ }
526543
527- !!! todo "待完善内容"
528- 线性查找、二分查找、分块查找、B树、B+树、散列表等
544+ void InOrder(BiTree T) {
545+ if (T != NULL) {
546+ InOrder(T->lchild); // 递归访问左子树
547+ visit(T); // 访问根结点
548+ InOrder(T->rchild); // 递归访问右子树
549+ }
550+ }
529551
530- ## ch8. 排序
552+ void PostOrder(BiTree T) {
553+ if (T != NULL) {
554+ PostOrder(T->lchild); // 递归访问左子树
555+ PostOrder(T->rchild); // 递归访问右子树
556+ visit(T); // 访问根结点
557+ }
558+ }
559+ ````
560+
561+ 前三种对应的非递归算法如下:
562+ ```c
563+ todo "非递归遍历"
564+ ```
565+
566+ **层序遍历**:
567+ ```c
568+ void LevelOrder(BiTree T) {
569+ InitQueue(Q); // 初始化队列
570+ BiTree p;
571+ EnQueue(Q, T); // 将根结点入队
572+ while (!QueueEmpty(Q)) {
573+ DeQueue(Q, &p); // 出队一个结点
574+ visit(p); // 访问该结点
575+ if (p->lchild != NULL) EnQueue(Q, p->lchild);
576+ if (p->rchild != NULL) EnQueue(Q, p->rchild);
577+ }
578+ }
579+ ```
580+
581+ **由遍历序列构造二叉树**
582+ 若已知中序序列,再给出其他三种遍历序列的其中一种,就可以唯一确定一棵二叉树。
583+
584+
585+ * 前序 + 中序
586+ 每次从前序找到根结点,然后在中序中找到根结点的位置,将中序分为左子树和右子树,递归构造。
587+
588+
589+ ```c
590+ BiTree CreateTree_PreIn(SString pre, SString in) {
591+ if (pre.length == 0 || in.length == 0) return NULL;
592+ BiTree T = (BiTree)malloc(sizeof(BiTNode));
593+ T->data = pre.ch[0]; // 前序的第一个字符是根结点
594+ int pos = LocateElem(in, T->data); // 在中序中找到根结点位置
595+ // 构造左子树:
596+ // 前序序列中,根结点后面的 pos 个元素对应左子树的前序序列(即 pre[1..pos]),
597+ // 中序序列中,根结点左侧的 pos 个元素对应左子树的中序序列(即 in[0..pos-1])
598+ T->lchild = CreateTree_PreIn(SubString(pre, 1, pos), SubString(in, 0, pos - 1));
599+
600+ // 构造右子树:
601+ // 前序序列中,左子树之后的剩余元素对应右子树的前序序列(即 pre[pos+1..end]),
602+ // 中序序列中,根结点右侧的剩余元素对应右子树的中序序列(即 in[pos+1..end])
603+ T->rchild = CreateTree_PreIn(SubString(pre, pos + 1), SubString(in, pos + 1));
604+
605+ return T;
606+ }
607+ ```
608+
609+
610+ * 后序 + 中序
611+ 每次从后序找到根结点,然后在中序中找到根结点的位置,将中序分为左子树和右子树,递归构造。
612+
613+ ```c
614+ BiTree CreateTree_PostIn(SString post, SString in) {
615+ if (post.length == 0 || in.length == 0) return NULL;
616+ BiTree T = (BiTree)malloc(sizeof(BiTNode));
617+ T->data = post.ch[post.length - 1]; // 后序的
618+ int pos = LocateElem(in, T->data); // 在中序中找到根结点位置
619+ // 构造左子树:
620+ // 后序序列中,根结点前面的 pos 个元素对应左子树的后序序列(即 post[0..pos-1]),
621+ // 中序序列中,根结点左侧的 pos 个元素对应左子树的中序序列(即 in[0..pos-1])
622+ T->lchild = CreateTree_PostIn(SubString(post, 0, pos - 1), SubString(in, 0, pos - 1));
623+ // 构造右子树:
624+ // 后序序列中,左子树之后的剩余元素对应右子树的后序序列(即 post[pos..end-1]),
625+ // 中序列中,根结点右侧的剩余元素对应右子树的中序序列(即 in[pos+1..end])
626+ T->rchild = CreateTree_PostIn(SubString(post, pos, post.length - 2), SubString(in, pos + 1));
627+ return T;
628+ }
629+ ```
630+
631+
632+ * 层序 + 中序
633+ 每次从层序找到根结点,然后在中序中找到根结点的位置,将中序分为左子树和右子树,递归构造。
634+ ```c
635+ BiTree CreateTree_LevelIn(SString level, SString in) {
636+ if (level.length == 0 || in.length == 0) return NULL;
637+ BiTree T = (BiTree)malloc(sizeof(BiTNode));
638+ T->data = level.ch[0]; // 层序的第一个字符
639+ int pos = LocateElem(in, T->data); // 在中序中找到根结点位置
640+ // 构造左子树:
641+ // 中序序列中,根结点左侧的 pos 个元素对应左子树的中序序列(即 in[0..pos-1])
642+ SString leftIn = SubString(in, 0, pos - 1);
643+ // 从层序中筛选出左子树的结点
644+ SString leftLevel = FilterLevel(level, leftIn);
645+ T->lchild = CreateTree_LevelIn(leftLevel, leftIn);
646+ // 构造右子树:
647+ // 中序序列中,根结点右侧的剩余元素对应右子树的中序序列(即 in[pos+1..end])
648+ SString rightIn = SubString(in, pos + 1);
649+ // 从层序中筛选出右子树的结点
650+ SString rightLevel = FilterLevel(level, rightIn);
651+ T->rchild = CreateTree_LevelIn(rightLevel, rightIn);
652+ return T;
653+ }
654+ ```
655+
656+
657+ #### 线索二叉树
658+
659+ !!! abstract "线索二叉树的概念"
660+ 利用二叉树中的空指针域,存放指向该结点在某种遍历次序下的前驱和后继结点的指针。
661+
662+ **基本思想:** 在含有n个结点的二叉树中,有n+1个空指针域,可以利用这些空指针存放前驱或后继的线索。
663+
664+ **结点结构:**
665+ ```c
666+ typedef struct ThreadNode {
667+ ElemType data; // 数据域
668+ struct ThreadNode *lchild, *rchild; // 左右孩子指针
669+ int ltag, rtag; // 左右线索标志
670+ } ThreadNode, *ThreadTree;
671+ ```
672+
673+ **标志位说明:**
674+ - `ltag = 0`:lchild指向左孩子;`ltag = 1`:lchild指向前驱
675+ - `rtag = 0`:rchild指向右孩子;`rtag = 1`:rchild指向后继
676+
677+ ##### 中序线索二叉树
678+
679+ **构造过程:**
680+ ```c
681+ // 中序线索化递归函数
682+ void InThread(ThreadTree &p, ThreadTree &pre) {
683+ if (p != NULL) {
684+ InThread(p->lchild, pre); // 递归线索化左子树
685+
686+ if (p->lchild == NULL) { // 左子树为空,建立前驱线索
687+ p->ltag = 1;
688+ p->lchild = pre;
689+ }
690+ if (pre != NULL && pre->rchild == NULL) { // 建立后继线索
691+ pre->rtag = 1;
692+ pre->rchild = p;
693+ }
694+ pre = p; // 更新前驱结点
695+
696+ InThread(p->rchild, pre); // 递归线索化右子树
697+ }
698+ }
699+
700+ // 主函数
701+ void CreateInThread(ThreadTree &T) {
702+ ThreadTree pre = NULL;
703+ if (T != NULL) {
704+ InThread(T, pre);
705+ if (pre->rchild == NULL) { // 处理最后一个结点
706+ pre->rtag = 1;
707+ }
708+ }
709+ }
710+ ```
711+
712+ **遍历操作:**
713+ ```c
714+ // 找到中序遍历的第一个结点
715+ ThreadNode *Firstnode(ThreadNode *p) {
716+ while (p->ltag == 0) // 沿左孩子走到底
717+ p = p->lchild;
718+ return p;
719+ }
720+
721+ // 找到中序遍历的后继结点
722+ ThreadNode *Nextnode(ThreadNode *p) {
723+ if (p->rtag == 0) // 有右子树
724+ return Firstnode(p->rchild); // 右子树的最左结点
725+ else
726+ return p->rchild; // 直接后继
727+ }
728+
729+ // 中序遍历线索二叉树
730+ void InOrder_Thread(ThreadTree T) {
731+ for (ThreadNode *p = Firstnode(T); p != NULL; p = Nextnode(p))
732+ visit(p);
733+ }
734+ ```
531735
532- !!! todo "待完善内容"
533- 插入排序、选择排序、交换排序、归并排序、基数排序、外部排序等
736+ ##### 其他线索二叉树
534737
738+ !!! note "先序和后序线索化特点"
739+
740+ **先序线索二叉树:**
741+ - 后继查找:有左孩子则后继为左孩子,否则为右孩子,叶结点的后继由线索指出
742+ - 前驱查找:较复杂,通常在线索化过程中处理
743+
744+ **后序线索二叉树:**
745+ - 后继查找规则:
746+ 1. 若为根结点,后继为空
747+ 2. 若为双亲的右孩子,或为双亲的左孩子且双亲无右子树,后继为双亲
748+ 3. 若为双亲的左孩子且双亲有右子树,后继为双亲右子树中后序遍历的第一个结点
749+
750+ ### 5.4 树和森林
751+
752+ #### 树的存储结构
753+
754+ **双亲表示法:**
755+ ```c
756+ typedef struct {
757+ ElemType data;
758+ int parent; // 双亲位置
759+ } PTNode;
760+
761+ typedef struct {
762+ PTNode nodes[MAXSIZE];
763+ int n; // 结点数
764+ } PTree;
765+ ```
766+
767+ **孩子表示法:**
768+ ```c
769+ typedef struct CNode {
770+ int child; // 孩子结点在数组中的位置
771+ struct CNode *next;
772+ } CNode;
773+
774+ typedef struct {
775+ ElemType data;
776+ CNode *firstchild; // 第一个孩子
777+ } CTBox;
778+
779+ typedef struct {
780+ CTBox nodes[MAXSIZE];
781+ int n, r; // 结点数和根的位置
782+ } CTree;
783+ ```
784+
785+ **孩子兄弟表示法(二叉树表示法):**
786+ ```c
787+ typedef struct CSNode {
788+ ElemType data;
789+ struct CSNode *firstchild, *nextsibling; // 第一个孩子和右兄弟
790+ } CSNode, *CSTree;
791+ ```
792+
793+ #### 树、森林与二叉树的转换
794+
795+ !!! tip "转换规则"
796+
797+ **树转二叉树:**
798+ 1. 加线:在所有兄弟结点之间加一条连线
799+ 2. 去线:树中每个结点,只保留与第一个孩子结点的连线,删除与其他孩子的连线
800+ 3. 层次调整:以树的根结点为轴心,将整棵树顺时针旋转一定角度
801+
802+ **森林转二叉树:**
803+ 1. 把每棵树转换为二叉树
804+ 2. 第一棵二叉树不动,其余二叉树依次作为前一棵二叉树根结点的右子树
805+
806+ **二叉树转树或森林:**
807+ 1. 若二叉树非空,则根结点的右子树为森林,左子树为第一棵树
808+ 2. 按照树转二叉树的逆过程进行
809+
810+ #### 树和森林的遍历
811+
812+ **树的遍历:**
813+ - **先根遍历**:先访问根结点,再依次遍历各子树
814+ - **后根遍历**:先依次遍历各子树,再访问根结点
815+
816+ **森林的遍历:**
817+ - **先序遍历**:依次对森林中每棵树进行先根遍历
818+ - **中序遍历**:依次对森林中每棵树进行后根遍历
819+
820+ !!! important "遍历等价性"
821+ - 树的先根遍历 ≡ 对应二叉树的先序遍历
822+ - 树的后根遍历 ≡ 对应二叉树的中序遍历
823+ - 森林的先序遍历 ≡ 对应二叉树的先序遍历
824+ - 森林的中序遍历 ≡ 对应二叉树的中序遍历
825+
826+ ### 5.5 哈夫曼树和哈夫曼编码
827+
828+ !!! abstract "基本概念"
829+ - **路径长度**:从树中一个结点到另一个结点之间的分支数目
830+ - **结点的权**:给每个结点赋予的一个有意义的数值
831+ - **结点的带权路径长度**:从根结点到该结点之间的路径长度与该结点权的乘积
832+ - **树的带权路径长度(WPL)**:树中所有叶结点的带权路径长度之和
833+
834+ **哈夫曼树(最优二叉树):** 在含有n个带权叶结点的二叉树中,带权路径长度最小的二叉树。
835+
836+ #### 哈夫曼树的构造
837+
838+ !!! example "哈夫曼算法"
839+ 1. 初始化:由给定的n个权值构成n棵只有根结点的二叉树,得到森林F
840+ 2. 选取和合并:在F中选取两棵根结点权值最小的树作为左右子树,构造一棵新的二叉树,新树根结点权值为左右子树权值之和
841+ 3. 删除和加入:从F中删除被选取的两棵树,把新构造的树加入F中
842+ 4. 重复步骤2-3,直到F中只剩一棵树为止
843+
844+ **算法实现:**
845+ ```c
846+ typedef struct {
847+ int weight; // 权值
848+ int parent, lch, rch; // 双亲、左孩子、右孩子下标
849+ } HTNode, *HuffmanTree;
850+
851+ void CreateHuffmanTree(HuffmanTree &HT, int *w, int n) {
852+ if (n <= 1) return;
853+ int m = 2 * n - 1; // 哈夫曼树总结点数
854+ HT = new HTNode[m + 1]; // 0号单元未用
855+
856+ // 初始化
857+ for (int i = 1; i <= m; ++i) {
858+ HT[i].parent = HT[i].lch = HT[i].rch = 0;
859+ }
860+ for (int i = 1; i <= n; ++i) {
861+ HT[i].weight = w[i-1];
862+ }
863+
864+ // 构造哈夫曼树
865+ for (int i = n + 1; i <= m; ++i) {
866+ int s1, s2;
867+ Select(HT, i - 1, &s1, &s2); // 选择权值最小的两个结点
868+ HT[s1].parent = HT[s2].parent = i;
869+ HT[i].lch = s1; HT[i].rch = s2;
870+ HT[i].weight = HT[s1].weight + HT[s2].weight;
871+ }
872+ }
873+ ```
874+
875+ #### 哈夫曼编码
876+
877+ **前缀编码:** 任何一个字符的编码都不是另一个字符编码的前缀。
878+
879+ !!! tip "哈夫曼编码特点"
880+ - 哈夫曼编码是前缀编码
881+ - 哈夫曼编码是最优前缀编码,即平均编码长度最短
882+ - 编码过程:从叶结点到根结点逆向求编码
883+ - 译码过程:从根结点开始,逐位读入编码,到达叶结点输出字符
884+
885+ ## ch6. 图
886+ todo "待补充内容"
887+
888+ ## ch7. 查找
889+ todo "待补充内容"
890+
891+ ## ch8. 排序
892+ todo "待补充内容"
0 commit comments