数据结构复习

数据结构复习期末速成

Author:itsbrqs

前言

​ 本笔记仅针对 2023 年数据结构期末考试内容进行复习总结，考点时效性与准确性不保证，由 itsbrqs 编撰整理。本笔记为 itsbrqs 为应付期末考试所作。

知识点与考点

考点总结

第一章 绪论：

抽象数据类型

时间复杂度

第二章 线性表：

顺序表

后向单链表

第三章 栈和队列

栈和队列的结构特征

栈和队列的基本操作

第五章 数组和广义表

地址计算公式

三元组的存储结构

第六章 树和二叉树

二叉树的性质

二叉树的遍历算法及其应用

遍历二叉树和线索二叉树

树和森林的转换

树和森林的遍历算法（序列如何写）

第七章 图

图的性质

图的存储结构

邻接矩阵

邻接表

图的遍历

书写遍历序列

深度优先遍历和广度优先遍历

最小生成树

图的应用

拓扑排序

关键路径

最短路径

题型

​ 选择题五道 + 填空题五道 + 应用大题四道 + 算法书写两道

第一章绪论

知识点 1 数据结构的定义

学科定义：数据结构是一门研究非数值计算的程序设计问题中计算机的操作对象以及它们之间的关系和操作等等的学科。

数据结构的定义：数据元素及其相互之间的联系。

数据结构的简单分类：逻辑结构和存储结构。

知识点 2 四种基本逻辑结构

集合：数据元素之间除了 “同属于一个集合” 的关系外，别无其它关系。

线性结构：数据元素之间存在一对一的联系。

树形结构：数据元素之间存在一对多的联系

图状结构：数据元素之间存在多对多的联系。

​ 其中集合结构、树结构、图结构皆属于非线性结构

​ 非线性结构：树、二叉树、有向图、无向图、集合

​ 线性结构：线性表、栈和队列、数组、广义表

知识点 3 数据

定义：在计算机领域中是指所有能输入到计算机中并能被计算机处理的符号的总称，是计算机程序加工的 “原料”。它包括数值、字符、图像、声音等。

操作：输入、输出、排序、查找、插入、删除。

知识点 4 数据结构的分类

逻辑结构：数据元素之间的逻辑关系、相互联系。

物理结构（存储结构）：数据结构在计算机中的表示。存储结构又分成顺序存储结构和链式存储结构。

知识点 5 数据存储结构

定义：数据对象在计算机中的存储表示成为数据的存储结构，也称为物理结构。

数据元素在计算机中有两种基本的存储结构，分别是顺序存储结构和链式存储结构。

顺序存储结构

定义：顺序存储结构是借助元素在存储器中的相对位置来表示数据元素之间的逻辑关系，通常借助程序设计语言的数组类型来描述。

邻接存放，借助元素在存储器中的相对位置来表示数据元素之间的逻辑关系。

优点：占用较少的存储空间，存储密度高。缺点：产生碎片现象，做插入或删除时需要移动元素。

链式存储结构

顺序存储结构要求所有的元素依次存放在一片连续的存储空间当中，而链式存储结构勿需占用一整块存储空间。但为了表示节点之间的关系需要给每个节点附加指针字段，用于存放后继元素的存储地址，所以链式存储结构通常借助于程序设计语言的指针类型来描述。

非邻接存放，借助存放元素地址的指针来表示数据元素之间的逻辑关系。

优点：空间利用率高，操作简单。缺点：占用较多的存储空间，数据密度低。

​ 两种结构的关系：数据的逻辑结构和物理结构是密不可分的两个方面，一个算法的设计取决于所选定的逻辑结构，而算法的实现依赖于所采用的存储结构。

知识点 6 抽象数据类型

​ 抽象数据类型一般指由用户定义的、表示应用问题的数学模型，以及定义在这个模型上的一组操作的总称，具体包括三部分：数据对象、数据对象上关系的集合、以及对数据对象基本操作的集合。

知识点 7 算法

算法是为了解决某类问题而规定的一个有限长的操作序列。

算法必须满足的五个特性：

有穷性

确定性：对于每种情况下所应执行的操作，在算法之中都有确切的规定，不会产生二义性，使算法的执行者或阅读者都能明确其含义及如何执行。

可行性

输入

输出

知识点 8 算法的评价

正确性

可读性

健壮性

高效性：高效性包括时间和空间两个方面，时间高效是指算法设计合理，执行效率高，可以用时间复杂度来衡量；空间高效是指算法占用存储容量合理，可以用空间复杂度来衡量。时间和空间复杂度时衡量算法的两个主要指标。

知识点 9 时间复杂度

​ 衡量算法效率的方法主要有两类：事后统计法和事前估计法。事后统计法需要先将算法实现，然后测算其时间和空间开销。这种方法的缺陷很明显，一是必须要把算法转换为可执行的程序，而是时空开销的测算结果依赖于计算机的软硬件等环境因素，这容易掩盖算法本身的优劣。所以普遍采用的方法是事前分析估算法，通过计算算法的渐进复杂度来衡量算法的效率。

算法时间复杂度求法

​ 寻找最深层循环次数，只需要求出最高阶即可。

算法的时间复杂度分析举例

常数阶 O (1)

Copy
{x ++ ;}

线性阶 O (n)

Copy
for( int i = 0 ; i < n ; i ++ )
s ++ ;

平方阶 O (n^2)

Copy
#冒泡排序也是平方阶
for (i = 0; i < size-1;i ++)//size-1是因为不用与自己比较，所以比的数就少一个
{
int count = 0;
for (j = 0; j < size-1 - i; j++) //size-1-i是因为每一趟就会少一个数比较
{
if (arr[j] > arr[j+1])//这是升序排法，前一个数和后一个数比较，如果前数大则与后一个数换位置
{
tem = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tem;
count = 1;

}
}
if (count == 0) //如果某一趟没有交换位置，则说明已经排好序，直接退出循环
break;
}

第二章 线性表

** 线性表：** 由 n (n≥0) 个数据特性相同的元素构成的有限序列。

线性表的存储结构：顺序存储结构和链式存储结构

数据结构的基本运算：修改、插入、删除、查找、排序。

知识点 10 顺序存储空间动态分配线性表

顺序表存储结构的优缺点：

​ 优点： 逻辑相邻，物理相邻；可随机存取任一元素；存储空间使用紧凑；

​ 缺点： 插入、删除操作需要移动大量的元素；

存储结构

Copy
typedef struct{
ElemType *elem;//首地址
int length;//当前长度
int listsize;//当前所占存储容量
}

初始化

Copy
/*算法思路
*1.空间分配
*2.判断分配是否成功
*3.初始化其他参数
*/
status Initlist_sq(Sqlist &L){
//分配空间
L.elem=(ElemType)* malloc(L * sizeof(ElemType));
//判断分配是否成功
if(!L.elem) retrun -2;
//初始化其他参数
L.length=0;
L.listszie=0;
return 0;
}

插入

Copy
/*算法思路
*1.检查i值合法性
*2.检查是否空间溢出及溢出空间再分配
*3.检查再分配是否成功
*4.取插入位置元素，整体向后移位
*5.表长加1
*/
status ListInsert_sq(Sqlist* L,int i,int e){
int* newbase,q,p;
//检查 i 值是否合法，是否会越界
if(i<1||i>(L->length+1))
return ERROR;
//长度超限时分配内存新空间用于存储
if(L->length>=L->listsize){
newbase=(int*)realloc(L->elem,(L->listsize+LISTINCREMENT)*
sizeof(int));
//检查存储是否分配失败
if(!newbase)
return (OVERFLOW);
L->elem=newbase;
L->listsize+=LISTINCREMENT;
}
q=&(L->elem[i-1]);//q 为插入位置
if(L->length>0){
for(p=&(L->elem[(L->length)-1]); p>=q; --p){
*(p+1)=*p;
}
}
*q=e;
++(L->length);
return OK;
};

插值

Copy
q=&(L->elem[i-1]);//q 为插入位置元素
if(L->length>0){//检查是否为空
for(p=&(L->elem[(L->length)-1]); p>=q; --p){
*(p+1)=*p;//元素整体后移
}
}
*q=e;//插入元素

删除

Copy
/*算法思路
*1.检查i值合法性
*2.取被删除元素位置
*3.被删除元素赋值给e
*4.取被删除元素的元素，整体向前移位
*5.表长减1
*/
//核心代码： 删除移位
q=&(L->elem[i-1]);//取被删除元素位置
*e=*p;//被删除元素赋值给e
q=L->elem+L->length-1;//表尾
for(++p;p<=q;++p)
*(p-1)=*p;//向前移位

知识点 11 链式存储后项单链线性表

单链表特点：

它是一种动态结构，整个存储空间为多个链表共用； 不需预先分配空间； 指针占用额外存储空间； 不能随机存取，查找速度慢。

存储结构

Copy
typedef struct LNode{
Elemtype data;//数据域
struct LNode *next;//指针域
}

初始化

Copy
Status InitList_L(LNode *L){
L->next=NULL;//头结点next域置空非常重要
return OK;
};

由于并未使用 c++ 引用参数而是将结构体指针传入所以不需要申请空间。

插值

Copy
/*算法思路
*1.取插值前结点
*2.检查i值
*3.生成插值结点
*4.断开老结点插入插值结点
*/
Status ListInsert_L(LNode *L,int i,Elemtype e){
int j=0;
LNode *p;
p=L;
while(p&&j<i-1){
p=p->next;
j++;
}
if(!p||j>i-1){
return ERROR;//i值检验，负值或大于表长加1值
}

LNode *s;
s=(LinkLIst) malloc(sizeof(LNode));//空间分配！！！生成插值结点
s->data=e;
//开始插值
s->next=p->next;//中部插值时必须
p->next=s;
return OK;
};

示意图如下：

删除

Copy
/*算法思路
*1.取删除值前结点
*2.检查i值
*3.生成删除结点
*4.断开老结点链接删除值后结点
*5.返回删除值，释放删除结点
*/
Status ListDelete_L(LNode *L,int i,Elemtype *e){
int j=0;
LNode *p;
p=L;

while(p&&j<i-1){
p=p->next;
j++;
}
if(!(p->next)||j>i-1){
return ERROR;//i值检验，负值或大于表长加1值
}

LNode *q;
q=(LinkLIst) malloc(sizeof(LNode));//空间分配！！！

q=p->next;//获得被删除结点
p->next=q->next;//越过被删除结点实现删除

*e=q->data;
free(q);
return OK;
};

示意图如下：

第三章 栈和队列

从数据结构角度来讲，栈和队列也是线性表，其操作是线性表操作的子集，属操作受限 的线性表。但从数据类型的角度看，它们是和线性表大不相同的重要抽象数据类型。

知识点 12 栈的结构，定义与操作

** 定义：** 限定仅在表尾进行插入或删除操作的线性表。

结构：

Copy
typedef struct stack{
int stacksize; //栈的容量
struct stack *head; //栈顶指针
struct stack *base; //栈底指针
}

操作：

入栈push口诀：堆栈指针 top “先压后加” : S[top++]=an+1

出栈pop口诀：堆栈指针 top “先减后弹” : e=S[--top]

Copy
//插值 Push
Status Push(SqStack *S,SElemType e){
if(S->top-S->base>S->stacksize){
S->base=(SElemType *)realloc(S->base,(S->stacksize+STACKINCREMENT)*sizeof(SElemType));//栈满追加分配空间
if(!S->base)return -2;//分配失败
S->top=S->base+S->stacksize;//栈顶指针移动
S->stacksize+=STACKINCREMENT;
}
*(S->top)++=e;//插值之后栈顶指针上移，这里实际为两步操作
}
//删除 Pop
Status Pop(SqStack *S,SElemType *e){
if(S->base==S->top){
printf("error: SqStack NULL");
return 0;//栈空
}
*e=*(S->top-1);
S->top--;
return 1;
}

** 特点：** 后入先出，当表中没有元素时称为空栈。

知识点 13：队列的结构定义和操作

** 定义：** 只允许在表的一端（队头）进行插入，而在另一端（队尾）删除元素。先入先出。

结构：

Copy
typedef struct QNode{
QElemType data;
struct QNode *next;
}QNode,*QueuePtr;

typedef struct
{
QueuePtr front;//队头指针
QueuePtr rear;//队尾指针
int len;
}LinkQueue;

操作：

入队EnQueue（尾部插入）：rear->next=S; rear=S;

出队DEQueue（头部删除）：front->next=p->next;

Copy
//插值EnQueue 删除DEQueue
Status EnQueue(LinkQueue *Q,QElemType e){
QNode *p=(QueuePtr)malloc (sizeof(QNode));
p->data=e;
p->next=NULL;
if(Q->front==NULL){
Q->front=Q->rear=p;
}else{
Q->rear->next=p;
Q->rear=p;
}
Q->len++;
return 1;
}
Status DeQueue(LinkQueue *Q,QElemType *e){
int isEmpty=QueueEmpty(Q);
if(!isEmpty){
QNode *p=Q->front;
Q->front=p->next;
printf("info: Queue delete head elem %d success\n",p->data);
free(p);
return 1;
}{
printf("error:Queue Empty \n");
return 0;
}
Q->len--;
}

** 特点：** 先入先出，空链队的特征：front=rear。链队列空的条件是首尾指针相等，而循环队列满的条件的判定，则有队尾加 1 等于队头 和设标记两种方法。

知识点 14：线性表、栈、队的异同点：

相同点：

逻辑结构相同，都是线性的；

都可以用顺序存储或链表存储；

栈和队列是两种特殊 的线性表，即受限的线性表（只是对插入、删除运算加以限制）。

不同点：

运算规则不同：

线性表为随机存取； 而栈是只允许在一端进行插入和删除运算，因而是后进先出表 LIFO；

队列是只允许在一端进行插入、另一端进行删除运算，因而是先进先出表 FIFO。

用途不同，线性表比较通用；堆栈用于函数调用、递归和简化设计等；队列用于离散事 件模拟、OS 作业调度和简化设计等。

第五章 数组和广义表

知识点 15 数组地址计算公式

ji 表示 n 维数组中该元素在第 i 维中的坐标
ai 表示 n 维数组中第 i 维的起始坐标
bi 表示第 i 维度的长度
L 表示一个元素所占的字节数

例：一个顺序表第一个元素存储地址是 100，每个元素长度为 2，则第五个元素地址是108

一维下上述公式为loc(j1)=loc(a1)+L*(j1-a1)

loc=100+2*(0+(5-1))=108

例：设二维数组中，每个元素的长度为 4 个字节，行下标 i 从 1 到 8，列下标从 1 到 10，从首地址 SA 开始以行序列连续存放再存储器内，那么元素A[8][6]的起始地址为SA+300

公式？狗都不用，直接画出来数第几个，A[8][6]是第 76 个元素

loc=SA+4*(76-1)=SA+300

从首地址 SA 开始以列序列连续存放再存储器内，那么元素A[8][6]的起始地址为SA+168

按列数A[8][6]是 48 个元素

总结：对于二维数组A[X][Y], 设行元素个数为 a，列元素个数为 b，首元素地址为 A，元素长度为 s，按行序则地址为A+s*((X-1)*b+Y-1), 按列序则地址为A+s*((Y-1)*a+X-1)。

知识点 16 三元组的存储结构

Copy
typedef struct
{
int i,j;
ElemType e;
}Triple
#三元组顺序表的C语言描述,稀疏矩阵
#define MAXSIZE 125000
typedef struct
{
Triple data[MAXSIZE+1;//矩阵包含的三元组表，data[0]未用
int mu,nu,t）;//mu行数nu列数tu非0元素数
}TSMatrix

第六章 树和二叉树

知识点 17 树

树的概念及术语

树：n（n≥0）个结点的有限集合。当 n＝0 时，称为空树；任意一棵非空树满足以下条件：

有且仅有一个特定的称为根的结点；

当 n＞1 时，除根结点之外的其余结点被分成 m（m>0）个互不相交的有限集合 T1,T2,… ,Tm，其中每个集合又是一棵树，并称为这个根结点的子树。

结点的度：结点所拥有的子树的个数。

树的度：树中所有结点的度的最大值。

叶子结点：度为 0 的结点，也称为终端结点。

分支结点：度不为 0 的结点，也称为非终端结点。

孩子、双亲：树中某结点的子树的根结点称为这个结点的孩子结点，这个结点称为它孩子结点的双亲结点；

兄弟：具有同一个双亲的孩子结点互称为兄弟。

路径：如果树的结点序列 n1, n2, …, nk 有如下关系：结点 ni 是 ni+1 的双亲（1<=i<k），则把 n1, n2, …, nk 称为一条由 n1 至 nk 的路径；路径上经过的边的个数称为路径长度。

祖先、子孙在树中，如果有一条路径从结点 x 到结点 y，那么 x 就称为 y 的祖先，而 y 称为 x 的子孙。

结点所在层数根结点的层数为 1；对其余任何结点，若某结点在第 k 层，则其孩子结点在第 k+1 层。树的深度：树中所有结点的最大层数，也称高度。

层序编号：将树中结点按照从上层到下层、同层从左到右的次序依次给他们编以从 1 开始的连续自然数。

有序树、无序树：如果一棵树中结点的各子树从左到右是有次序的，称这棵树为有序树；反之，称为无序树。数据结构中讨论的一般都是有序树

树通常有前序（根）遍历、后序（根）遍历和层序（次）遍历三种方式（树，不是二叉树，没中序遍历。)

关于树的高度与深度

树的深度是根结点到这个结点所经历的边的个数，所以高度等于深度加 1。

知识点 18 二叉树的定义与性质

二叉树的定义

二叉树是 n（n≥0）个结点的有限集合，该集合或者为空集（称为空 二叉树），或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树的二叉树组成。

满二叉树：在一棵二叉树中，如果所有分支结点都存在左子树和右子树，并且所有叶子都在 同一层上。

（满二叉树的特点：叶子只能出现在最下一层；只有度为 0 和度为 2 的结点。）

完全二叉树：对一棵具有 n 个结点的二叉树按层序编号，如果编号为 i（1≤i≤n）的结点与同样深度的满二叉树中编号为 i 的结点在二叉树中的位置完全相同。

完全二叉树的特点：

在满二叉树中，从最后一个结点开始，连续去掉任意个结点，即是一棵完全二叉树。

叶子结点只能出现在最下两层，且最下层的叶子结点都集中在二叉树的左部；

完全二叉树中如果有度为 1 的结点，只可能有一个，且该结点只有左孩子。

深度为 k 的完全二叉树在 k-1 层上一定是满二叉树。

满二叉树一定是完全二叉树，完全二叉树不一定是满二叉树，换言之满二叉树更加严格。

二叉树的性质

性质 1：二叉树的第 i 层上最多有 2i-1 个结点（i≥1）。

性质 2：一棵深度为 k 的二叉树中，最多有 2^k-1 个结点，最少有 k 个结点。深度为 k 且具有 2^k-1 个结点的二叉树一定是满二叉树

性质 3：在一棵二叉树中，如果叶子结点数为 n0，度为 2 的结点数为 n2，则有: n0＝n2 ＋1。（一个结点的度就是指它放出的射线）

性质 4：具有 n 个结点的完全二叉树的深度为 log2n +1。

性质 5：对一棵具有 n 个结点的完全二叉树中从 1 开始按层序编号，则对于任意的序号为 i（1≤i≤n）的结点（简称为结点 i），有：

（1）如果 i＞1，则结点 i 的双亲结点的序号为 i/2；如果 i＝1，则结点 i 是根结点，无双亲结点。

（2）如果 2i≤n，则结点 i 的左孩子的序号为 2i；如果 2i＞n，则结点 i 无左孩子。

（3）如果 2i＋1≤n，则结点 i 的右孩子的序号为 2i＋1；如果 2i＋1＞n，则结点 i 无右孩子。

知识点 19 二叉树的遍历

前序遍历是指，对于树中的任意节点来说，先打印这个节点，然后再打印它的左子树，最后打印它的右子树。
中序遍历是指，对于树中的任意节点来说，先打印它的左子树，然后再打印它本身，最后打印它的右子树。
后序遍历是指，对于树中的任意节点来说，先打印它的左子树，然后再打印它的右子树，最后打印这个节点本身。

层次遍历就是按照每一层从左向右的方式进行遍历。

知识点 20 二叉树的存储

Copy
/*------二叉树的二叉链表存储结构------*/
typedef struct BiTNode
{
TElemType data;
struct BiTNode* left;
struct BiTNode* right;
}BTNode,*BiTree;

知识点 21 线索二叉树与存储

创建二叉树时我们会发现其中一共有两个指针域，有的指针域指向的结构为空，这也就浪费了很多空间。所以为了不去浪费这些空间我们采取了一个措施。就是利用那些空地址，存放指向结点在某种遍历次序之下的前驱和后继结点的地址。我们把这种指向前驱和后继的指针成为线索，加上线索的二叉链表称为线索链表，相应的二叉树就成为线索二叉树。

如下图就是一棵中序遍历线索二叉树，其中序遍历为 1，3，6，8，10

线索是一种对二叉树的操作，意思是对二叉树进行线索化，其目的是使线索化后的二叉树具有方便被遍历的特点，即不使用递归和栈也可以对线索化之后的树进行中序遍历。

将对二叉树以某种次序遍历使其变为线索二叉树的过程称为线索化 。

线索二叉树的存储

Copy
struct Tree
{
char data; //需要存储的数据
int num;
int LisThread;//左右的标志,值为0或1
int RisThread;
struct Tree *lchild,*rchild; //左右孩子的指针
};

知识点 22 二叉树的应用 - 哈夫曼树与哈夫曼编码

哈夫曼树的基本概念：

哈夫曼树：给定一组具有确定权值的叶子结点，带权路径长度最小的二叉树。

哈夫曼树的特点：

1. 权值越大的叶子结点越靠近根结点，而权值越小的叶子结点越远离根结点。

2. 只有度为 0（叶子结点）和度为 2（分支结点）的结点，不存在度为 1 的结点。

哈夫曼树遍历：

构造 Huffman 树的步骤（即 Huffman 算法）：

(1) 由给定的 n 个权值 { w1, w2, …, wn } 构成 n 棵二叉树的集合 F = { T1, T2, …, Tn }（即森林），其中每棵二叉树 Ti 中只有一个带权为 wi 的根结点，其左右子树均空。

(2) 在 F 中选取两棵根结点权值最小的树做为左右子树构造一棵新的二叉树，且让新二叉树根结点的权值等于其左右子树的根结点权值之和。 (3) 在 F 中删去这两棵树，同时将新得到的二叉树加入 F 中。

(4) 重复 (2) 和 (3) , 直到 F 只含一棵树为止。这棵树便是 Huffman 树。

计算带权路径长度（WPL）

带权路径长度 = 叶结点的权值 * 路径长度，以上图哈夫曼树为例叶子结点有 7 8 5 3 11 23，WPL=7*4+8*4+3*4+5*4+11*3+23*2=167

哈夫曼编码计算

哈夫曼编码的定义

哈夫曼编码就是规定哈夫曼树中的左分支为 0，右分支为 1，从根节点到每个叶节点所经过的分支对应的 0 和 1 组成的序列便为该节点对应字符的编码。这样的编码称为哈夫曼编码。哈夫曼编码是一种无前缀编码。解码时不会混淆。其主要应用在数据压缩，加密解密等场合。

知识点 23 树与森林的转换

树到二叉树

加线：树中所有相邻兄弟加一条线

去线：对于树中的每个结点，只保留与其第一个孩子结点之间的连线，删去与其他孩子结点的连线

旋转：以树的根结点为轴心，将整棵树顺时针旋转一定角度，使之结构层次分明，即可得到二叉树

森林和二叉树的相互转换

二叉树转换成森林

加线：将二叉树中每个结点和其左孩子结点的右孩子加线相连

去线：去掉树中的每个结点和右孩子的连线

旋转：以树的根结点为轴心，将整棵树逆时针旋转一定角度，使之结构层次分明，即可得到森林。

森林转换成二叉树

加线：森林中所有相邻兄弟加一条线

去线：对于森林中的每个结点，只保留与其第一个孩子结点之间的连线，删去与其他孩子结点的连线

旋转：以树的根结点为轴心，将整棵树顺时针旋转一定角度，使之结构层次分明，即可得到二叉树

知识点 24 树和森林的遍历

树的遍历

树只有前序，后序，层次遍历没有中序遍历

前序遍历

和二叉树一样先根后左再右，优先级依次降低。树非空，则先访问根节点，然后按从左向右的顺序，前序遍历根节点的每一棵子树。（像是对树进行类似二叉树的先序遍历）如上图的树前序遍历为 A B E F C D G I H

后序遍历

树非空，则按从左向右的顺序，后根遍历根节点的每一棵子树，然后访问根节点。（像是对树进行类似二叉树的后序遍历步骤）如上图的树后序遍历为 E F B C I G H D A

森林的遍历

可以直接按照先序遍历二叉树，也可以先转换为二叉树再遍历

例如上图森林的前序遍历为 A B C D E F G H J I

第七章 图

知识点 25 图的定义与性质

图的定义：图 (Graph)：图 G 是由两个集合 V (G) 和 E (G) 组成的，记为：G=(V,E)

其中：V (G) 是顶点的非空有限集；E (G) 是边的有限集合，边是顶点的无序对或有序对。

有向图，无向图

顶点的度：

​ 无向图中，顶点的度为与每个顶点相连的边数；

​ 有向图中，顶点的度分成入度与出度

​ ** 入度：** 以该顶点为头的弧的数目

​ ** 出度：** 以该顶点为尾的弧的数目

路径：路径是顶点的序列

路径长度：沿路径边的数目或沿路径各边权值之和

回路：第一个顶点和最后一个顶点相同的路径

简单路径：序列中顶点不重复出现的路径

简单回路：除了第一个顶点和最后一个顶点外，其余顶点不重复出现的回路

连通：从顶点 V 到顶点 W 有一条路径，则说 V 和 W 是连通的

连通图：图中任意两个顶点都是连通

连通分量：非连通图的每一个连通部分

强连通图：有向图中，如果对每一对 Vi,Vj!=V, Vi!=Vj, 从 Vi 到 Vj 和从 Vj 到 Vi 都存在路径，则称 G 是强连通图

知识点 26 图的存储结构

邻接矩阵

图的邻接矩阵表示法，又称数组表示法。它采用两个数组来表示图：

1、用于存储顶点信息的一维数组；

2、用于存储图中顶点之间关联关系的二维数组，称为邻接矩阵。

无权图的邻接矩阵

有权图的邻接矩阵

Copy
#图结构邻接矩阵存储结构代码实现
#define MAXVEX 20 //顶点最大数
#define INFINITY 65535 //代指无限大
typedef struct
{
datatype data[MAXVEX]; //顶点表
edgetype arc[MAXVEX][MAXVEX]; //存储边的权的邻接矩阵
int numVertexes,numEdges; //顶点数,边或弧的个数
}MGraph;//图结构邻接矩阵
int visited[MAXVEX]; //访问数组

邻接表

邻接表表示法是图的一种链式存储结构，基本思想是只存储图中存在的边的信息。

Copy
#define MAX_VERTEX_NUM 20 /* 最多顶点个数 */
typedef enum{DG, DN, UDG, UDN} GraphKind; /* 图的种类 */

/* 定义表头结点的类型 */
typedef struct VertexNode {
VertexData data; //数据域，存放顶点信息
ArcNode * firstarc; //指针域，指向边表中第一个结点
} VertexNode;

/* 定义边结点的类型 */
typedef struct ArcNode {
AdjType adjvex; //邻接点域，边的终点在顶点表中的下标
OtherInfo info; //数据域，存放其它相关信息
struct ArcNode * nextarc; //指针域，指向边表中的下一个结点
} ArcNode;

邻接表与邻接矩阵有什么异同之处？

联系：邻接表中每个链表对应于邻接矩阵中的一行， 链表中结点个数等于一行中非零元素的个数。

区别： 对于任一确定的无向图，邻接矩阵是唯一的（行列号与顶点编号一致）， 但邻接表不唯一（链接次序与顶点编号无关）。

用途： 邻接矩阵多用于稠密图的存储 而邻接表多用于稀疏图的存储

知识点 27 图的遍历

图的深度优先遍历（DFS）

方法：从图的某一顶点 V0 出发，访问此顶点；然后依次从 V0 的未被访问的邻接点出发，深度优先遍历图，直至图中所有和 V0 相通的顶点都被访问到；若此时图中尚有顶点未被访问，则另选图中一个未被访问的顶点作起点，重复上述过程，直至图中所有顶点都被访问为止。

所谓 DFS，就是从起点开始，找准一个方向直到走不了为止，然后再原路返回，再找到一个能走的地方继续走的思路。

深度优先遍历结果是： A B E F C D G H I

必须说明，若不给定存储结构，深度优先遍历的结果不唯一。因为哪个顶点是第一邻接点未确定。给定存储结构后，深度优先遍历的结果是唯一的。

图的广度优先遍历（BFS）

广度优先遍历可定义如下：首先访问出发点 v，接着依次访问 v 的所有邻接点 w1、w2......wt，然后依次访问 w1、w2......wt 邻接的所有未曾访问过的顶点。以此类推，直至图中所有和源点 v 有路径相通的顶点都已访问到为止。此时从 v 开始的搜索过程结束。

广度优先遍历结果是： A B C D E F G H I

广度优先遍历按层次优先搜索最近的结点，一层一层往外搜索。

知识点 28 图的应用 - 最小生成树

最小生成树（MST）的性质如下：若 U 集是 V 的一个非空子集，若 (u0, v0) 是一条最小权值的边，其中 u0 U，v0 V-U；则：(u0, v0) 必在最小生成树上。

求 MST 最常用的是以下两种：Kruskal（克鲁斯卡尔）算法、Prim（普里姆）算法

Kruskal 算法特点：将边归并，适于求稀疏网的最小生成树。

Prime 算法特点：将顶点归并，与边数无关，适于稠密网。

Prim 算法（逐点加入）

Prim 算法更适用于稠密图（点少边多）。

过程图解

Kruskal 算法（逐边加入）

连通图的最小生成树不是唯一的，但最小生成树边上的权值之和是唯一的。应熟练掌握 prim 和 kruscal 算法，特别是手工分步模拟生成树的生成过程。

知识点 29 图的应用 - 拓扑排序

拓扑排序是一个 ** 有向无环图（有向图、弧不形成闭环）** 的所有顶点的线性序列。该线性序列中，图的每个顶点只出现一次，若顶点 A 到顶点 B 之间存在有向弧 <v1,v2>, 则顶点 A 一定在顶点 B 前面。

访问图的顶点时，保证每次访问的顶点前面没有别的顶点 (入度为 0)，即访问的顶点只作为弧的弧头。满足拓扑排序的前提：图中没有环

选入度为 0 的节点 A， 输出 A。 删除 AB AC 的边（B 入度为 1-1=0，C 入度为 2-1=1）

选入度为 0 的节点 B， 输出 B。删除 BC，BE 的边（C 入度为 1-1=0，E 入度 - 1）

选入度为 0 的节点 C， 输出 C。删以 C 开始的边（对应节点入度 - 1）

继续重复即得拓扑排序 ABCDEF（也可以是 ABCEDF）

注： 拓扑排序结果不唯一（同时有多个入度为 0）, 所以题目可以要求你写出全部的拓扑排序。

知识点 30 图的应用 - 关键路径

关键路径：若有向图中，各顶点表示事件，各有向边表示活动持续事件，则该图为活动边网络，简称 AOE 网。AOE 网中的关键路径，就是完成整个网络所需的最短时间，亦最长路径，AOE 网中，往往有若干项活动可以平行的进行，因此，从开始顶点到最后一个顶点的最长路径称为关键路径。

不多解释，直接列表，事件最早发生时间 ve（和最大）, 事件最晚发生时间 vl（差最小）。

先写拓扑排序

正拓扑排序 v1 v2 v3 v5 v4 v6

逆拓扑排序 V6 V5 V4 V2 V3 V1

再算顶点 ve，vl

ve 使用正拓扑排序算路线和最大

vl 使用逆拓扑排序算路线差最小

事件 | v1 | v2 | v3 | v4 | v5 | v6
ve(i) | 0 | 3 | 2 | 6 | 6 | 8
vl(i) | 0 | 4 | 2 | 6 | 7 | 8

最后算边 (事件) 的弧尾顶点最早开始时间即活动最早开始时间 e ，边 (事件) 的弧头顶点活动最晚开始时间 - 权值即活动最晚开始时间（只要时最晚就是倒着求）， l 时间余量 l-e

活动 | a1 | a2 | a3 | a4 | a5 | a6 | a7 | a8
e(i) | 0 | 0 | 3 | 3 | 2 | 2 | 6 | 6
l(i) | 1 | 0 | 4 | 4 | 2 | 5 | 6 | 8-1
d(i) | 1 | 0 | 1 | 1 | 0 | 3 | 0 | 1

关键路径即为 v1 v3 v4 v6

时间余量为 0 代表关键活动 a2 a5 a7

知识点 31 图的应用 - 最短路径

Dijkstra (迪杰斯特拉) 算法是典型的单源最短路径算法，用于计算一个节点到其他所有节点的最短路径。主要特点是以起始点为中心向外层层扩展，直到扩展到终点为止。Dijkstra 算法是很有代表性的最短路径算法。

算法思想：

设 G=(V,E) 是一个带权有向图，把图中顶点集合 V 分成两组，第一组为已求出最短路径的顶点集合（用 S 表示，初始时 S 中只有一个源点，以后每求得一条最短路径，就将加入到集合 S 中，直到全部顶点都加入到 S 中，算法就结束了），第二组为其余未确定最短路径的顶点集合（用 U 表示），按最短路径长度的递增次序依次把第二组的顶点加入 S 中。在加入的过程中，总保持从源点 v 到 S 中各顶点的最短路径长度不大于从源点 v 到 U 中任何顶点的最短路径长度。此外，每个顶点对应一个距离，S 中的顶点的距离就是从 v 到此顶点的最短路径长度，U 中的顶点的距离，是从 v 到此顶点只包括 S 中的顶点为中间顶点的当前最短路径长度。

算法步骤：

a. 初始时，S 只包含源点，即 S＝{v}，v 的距离为 0。U 包含除 v 外的其他顶点，即={其余顶点}，若 v 与 U 中顶点 u 有边，则 < u,v > 正常有权值，若 u 不是 v 的出边邻接点，则 < u,v > 权值为∞。

b. 从 U 中选取一个距离 v 最小的顶点 k，把 k，加入 S 中（该选定的距离就是 v 到 k 的最短路径长度）。
c. 以 k 为新考虑的中间点，修改 U 中各顶点的距离；若从源点 v 到顶点 u 的距离（经过顶点 k）比原来距离（不经过顶点 k）短，则修改顶点 u 的距离值，修改后的距离值的顶点 k 的距离加上边上的权。
d. 重复步骤 b 和 c 直到所有顶点都包含在 S 中。

解题过程：

单源点带非负权值的最短路径求解算法 Dijkstra 迪杰斯特拉算法，看视频过程链接。

起点 | 终点 | 路径 | 距离
u | a | u | 2
| b | a | 5
| c | u | 1
| d | a | 3
| v | d | 5

根据上表 u-v 最短路径 u-a-d-v 最短距离为 5

超星习题选摘

栈和队列部分

1. 当栈的最大长度难以估计时，栈最好采用 ( 链式) 存储结构。

2. 栈结构通常采用的两种存储结构是顺序存储结构和链式存储结构。

3.栈可作为实现递归函数调用的一种数据结构。

4. 表达式 a*(b+c)-d 的后缀表达式是 (abc+*d-)

注：解决表达式转换为前后缀表达式的思路：

a+b-a*((c+d)/e-f)+g 为例

后缀：

括号法（推荐）：

①按照运算符的优先级，对所有的运算单位加括号。

于是变成：(((a+b)-(a*(((c+d)/e)-f)))+g)

②从最里面的括号开始，依次把运算符号移动到对应的括号的后面。

于是变成：(((ab)+(a (((cd)+e)/f)-)*)-g)+

③最后，把括号都去掉

于是变成：ab+acd+e/f-*-g+

基于堆栈的算法：

从左到右进行遍历。

1. 遇到的是运算数，直接输出。

2. 遇到的是左括号 '('，直接压入堆栈 (括号是最高优先级，无需比较；入栈后优先级降到最低，确保其他符号正常入栈)。

3. 遇到的是右括号 ')'，意味着括号已结束。不断弹出栈顶运算符并输出，直到遇到左括号，这个左括号弹出但是不输出。

4. 遇到的是运算符 ('+'、'-'、'*'、'/')，有三种情况

①如果栈为空，直接入栈。

②如果栈顶元素是左括号 '('，直接入栈。

③如果栈顶元素是运算符，则需要进行比较，

1 - 如果优先级大于栈顶运算符，则压入堆栈；

2 - 如果优先级小于等于栈顶运算符，则将栈顶运算符弹出并输出，然后比较新的栈顶运算符，直到优先级大于栈顶运算符或者栈空，再将该运算符入栈

5. 如果对象处理完毕，则按顺序弹出并输出栈中所有运算符

前缀：

括号法：（推荐）

①按照运算符的优先级，对所有的运算单位加括号。

于是变成：(((a+b)-(a*(((c+d)/e)-f)))+g)

②从最里面的括号开始，依次把运算符号移动到对应的括号的前面。

于是变成：+(-(+(ab)*(a-(/(+(cd) e) f))) g)

③最后，把括号都去掉

于是变成：+-+ab*a-/+cdefg

基于堆栈的算法

从右到左进行遍历。

1. 遇到的是运算数，直接输出。

2. 遇到的是右括号 ')'，直接压入堆栈 (括号是最高优先级，无需比较；入栈后优先级降到最低，确保其他符号正常入栈)。

3. 遇到的是左括号 '('，意味着括号已结束。不断弹出栈顶运算符并输出，直到遇到右括号，这个右括号弹出但是不输出。

4. 遇到的是运算符 ('+'、'-'、'*'、'/')，有三种情况

①如果栈为空，直接入栈。

②如果栈顶元素是右括号 ')'，直接入栈。

③如果栈顶元素是运算符，则需要进行比较，

1 - 如果优先级大于等于栈顶运算符，则压入堆栈；

2 - 如果优先级小于栈顶运算符，则将栈顶运算符弹出并输出，然后比较新的栈顶运算符，直到优先级大于等于栈顶运算符或者栈空，再将该运算符入栈

5. 如果对象处理完毕，则按顺序弹出并输出栈中所有运算符

5. 顺序存储的循环队列 sq 中，假定 front 和 rear 分别为队头指针和队尾指针，则入队操作为（ ）

A、sq.rear=sq.rear+1;sq.data[sq.rear]=x;

B、sq.data[sq.rear]=x;sq.rear=sq.rear+1;

C、sq.rear=(sq.rear+1)%maxsize; sq.data[sq.rear]=x;

D、sq.data[sq.rear]=x;sq.rear=(sq.rear+1)%maxsize;

6. 在具有 m 个单元的顺序存储的循环队列中，假定 front 和 rear 分别为队头指针和队尾指针，则判断队满的条件为（ ）

A、rear % m==front

B、(front+1) % m==rear

C、（rear－1）%m==front

D、(rear+1) % m==front

循环队列判断条件是 rear 和 front 相差一个元素 rear+1=front，这是为了避免因为循环队列的结构的导致判空判满条件相同 rear = front 导致歧义故意少用一个元素的空间使判满条件变更。

7. 在具有 m 个单元的顺序存储的循环队列中，假定 front 和 rear 分别为队头指针和队尾指针，则判断队空的条件为（ ）

A、rear ==front

B、(front+2) % m==rear

C、（rear－2）%m==front

D、(rear+2) % m==front

8. 链栈与顺序栈相比，有一个比较明显的优点，即（ ）

A、插入操作更方便

B、通常不会出现栈满的现象

C、不会出现栈空的情况

D、删除操作更加方便

9. 用链接方式存储的队列，在进行删除运算时（）

A、仅修改头指针

B、仅修改尾指针

C、头、尾指针都要修改

D、头、尾指针可能都要修改

当仅有一个元素时删除都要修改

10. 用单链表表示的链队列的队头在链表的（ ）位置。

A、链头

B、链尾

C、链中

D、任意位置

其他习题选摘

1. 栈 S最多可以容纳 4 个元素，现有六个元素 ABCDEF 按照顺序进栈，下列哪一序列可能时出栈序列（C）

A)EDCBAF B)BCEFAD C)CBEDAF D)ADFEBC

2. 高度为 h 的二叉树最多最少有多少个结点（2^h-1）（h）

3. 若一颗非空二叉树的先序和后序遍历相反，则（C）

A）二叉树任意结点无左孩子

B）二叉树任意结点无右孩子

C）二叉树只有一个叶子结点

D）二叉树为任意二叉树

4. 二维数组A[20][10]采用行序作为主序存储，每个元素占两个字节，A[10][5]的地址时 1000，则A[18][9]地址为 1168。

按照前文二位数组公式loc=1000+(8*10+9-5)*2=1168

5. 在一个单链表中 p 所指结点后输入 s 所指结点应该执行，（s->next=p->next） （p->next=s）

6. 一颗树的先序、中序序列分别为 ABCDEFGHIJ，BDCEAHGIJF，则该二叉树的后序遍历为（DECBHIJGFA）

直接奇技淫巧，我们使用列表或者画图法，将先序序列竖着排，中序序列横着排，两个序列相对重合的位置就是树中该结点的位置，最中心的结点就是根结点，然后层级越向上的越先与根节点连接，再将其余结点连接，实际二叉树如下图。

A | | | | | A | | | | |
B | B | | | | | | | | |
C | | | C | | | | | | |
D | | D | | | | | | | |
E | | | | E | | | | | |
F | | | | | | | | | | F
G | | | | | | | G | | |
H | | | | | | H | | | |
I | | | | | | | | I | |
J | | | | | | | | | J |
| B | D | C | E | A | H | G | I | J | F

对于后序中序遍历求先序，就需要将上表的左侧后序序列倒着写（确保根节点在最上方），然后同理即得。

7. 已知叶子结点值 2，3，5，6，9，11，构建哈夫曼树，并其带权路径长度。

解：采用逐个取值从小到大加边的方法构造哈夫曼树

带权路径和即wpl=(2+3)*4+5*3+(11+6+9)*2=87

注意：在构建哈夫曼树的过程中需要注意上一层级的权值一定要大于下层，然后注意用上全部的结点值。另外哈夫曼树不唯一但是不同哈夫曼树所得出的 wpl 是相等的。

8. 将算术表达式((a+b)+c*(d+e)+f)*(g+h)转化为二叉树，并写出该表达式的后缀表达式。

解：转换为二叉树就是把运算符当作根节点，然后数值作为叶子结点去组合，按照运算顺序去转化二叉树。

后缀表达式使用前文提到的括号法，后缀表达式为ab+cde+*+f+gh+*

9. 试画出下列邻接矩阵对应的图，并写出所对应的邻接表

CopyA={0100100400000600300850070}A=\begin{Bmatrix} 0&1 &0 & 0 & 1\\ 0 & 0& 4& 0 & 0\\ 0&0 & 0&6 &0 \\ 0 & 3& 0&0 & 8\\ 5& 0 & 0 &7 &0 \end{Bmatrix}A=⎩ ⎨ ⎧​00005​10030​04000​00607​10080​⎭ ⎬ ⎫​

10. 将下列森林转换为二叉树

按照前文所写加线去线旋转即可得出二叉树如下：

11. 用 Prim 算法会出下图的最小生成树（写步骤 U 表示已选顶点集，U-V 表示未选顶点集）

U | U-V | 1 | 2 | 3 | 4 | 5 | 6
{1} | {2，3，4，5，6} | / | (1,2) 16 | inf | (1,4) 21 | (1,5) 19 | inf
{1,2} | {3,4,5,6} | / | / | (2,3) 5 | (2,4) 11 | (1,5) 19 | (2,6) 6
{1,2,3} | {4,5,6} | / | / | / | (2,4) 11 | (1,5) 19 | (2,6) 6
{1,2,3,6} | {4,5} | / | / | / | (2,4) 11 | (5,6) 18 | /
{1,2,3,4,6} | {5} | / | / | / | / | (5,6) 18 | /

所得最小生成树如下

12. 数据的逻辑结构被分为集合结构，线性结构，树形结构，网状结构。

13. 一棵高度为 5 的完全二叉树最少有16个结点。

14. 循环队列元素个数 **(rear - front+MaxQSize)% MaxQSize**

15. 二维数组A[1…5,1…6], 每个元素占四个存储单元，若A[1,1]的地址为 1024，若以行序为主序顺序存储，则元素A[3,2]的存储地址为（1076），若以列序为主序 A [3,2] 的存储地址为 (1052).

16. 判断有向图是否有回路的两种方法（深度优先遍历算法）（拓扑排序）

17.n 个顶点的连通无向图至少有n-1条边，至多有n(n-1)/2条边，n 个顶点的连通有向图至少有 n 条边，至多有 **n (n-1)** 条边。

18. 从顶点 A 出发分别按广度优先搜索的顶点访问序列和按深度优先搜索的顶点访问序列。

深度优先遍历序列为 ABDHECFG

广度优先遍历序列为 ABCDEFGH

19. 专业课程的 AOV 网，请你来安排一下学生要依次学习的专业课程，也即写出该有向图的任意一种拓扑有序序列。

拓扑序列为 A,B,C,D,E, G,I,J,K, F,L,H（很多种答案不唯一）

附录

此部分为教材中各种数据结构的代码实现，均采用 C 语言指针实现，未使用 C++ 引用参数，都是我实验课所写并调试，仅供参考。

空间动态分配顺序表

Copy
#include <stdio.h>
#include <malloc.h>
#define LIST_INIT_SIZE 100
#define LISTINCREMENT 10
#define OVERFLOW -2
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
/*
动态线性表存储结构
*/
typedef struct{
int* elem;
int length;
int listsize;
}Sqlist;
/*
初始化函数
*/
int InitList_Sq(Sqlist *L){
L->elem=(int*) malloc(LIST_INIT_SIZE*sizeof(int));
if (!(*L).elem)
return (OVERFLOW);
L->length=0;
L->listsize=LIST_INIT_SIZE;
printf("L.length=%d\tL.listsize=%d\n",L->length,L->listsize);
return OK;
};
/*
删除函数
*/
int ListDElete_Sq(Sqlist *L,int i,int* e){
int* p;
int* q;
//检查i值是否合法，是否会越界
if(i<1||i>(L->length+1))
return ERROR;
p=&(L->elem[i-1]);
*e=*p;//崩溃点
q=L->elem+L->length-1;
for(++p;p<=q;++p)
*(p-1)=*p;
--L->length;
return OK;
};
/*
插值函数
i 插入到线性表的位置
e 插入的数值
*/
int ListInsert_Sq(Sqlist *L,int i,int e){
int* newbase;
int* q;
int* p;
//检查i值是否合法，是否会越界
if(i<1||i>(L->length+1))
return ERROR;
//长度超限时分配内存新空间用于存储
if(L->length>=L->listsize){
newbase=(int*)realloc(L->elem,(L->listsize+LISTINCREMENT)*sizeof(int));
//检查存储是否分配失败
if(!newbase)
return (OVERFLOW);
L->elem=newbase;
L->listsize+=LISTINCREMENT;
}
q=&(L->elem[i-1]);//q为插入位置
if(L->length>0){

for(p=&(L->elem[(L->length)-1]); p>=q; --p){
*(p+1)=*p;
}
}
*q=e;
++(L->length);
return OK;
};
/*
打印函数
*/
int PrintList(Sqlist *L){
int i;
printf("\nprint start \n");
for(i=0;i<L->length;i++)
printf("a%d=%d\n",i+1,L->elem[i]);
printf("print finish \n");
return OK;
};
/*
索引函数
*/
int GetElem(Sqlist *L,int i,int* e){
if(i<1||i>(L->length)){
printf("index out of bounds\n");
return ERROR;
}
*e=L->elem[i-1];
return OK;
};
/*
元素存在判
*/
int LocateElem(Sqlist *L,int e){
int i;
for(i=0;i<(L->length);i++){
if(e==(L->elem[i])){
return i+1;//返回位置
}
}
return FALSE;
};
/*
前索引
*/
int PriorElem(Sqlist *L,int cur_e,int *pre_e){
//判断cur_e存在及其位序
int i;
i=LocateElem(L,cur_e);
if(!i||i==1){
printf("error:cur_e null or index=1\n ");
return ERROR;
}
//返回前驱
*pre_e=L->elem[i-2];
return OK;
}/*
后索引
*/
int NextElem(Sqlist *L,int cur_e,int *next_e){
//判断cur_e存在及其位序
int i;
int l;
i=LocateElem(L,cur_e);
if(!i||i==L->length){
printf("error:cur_e null or index=len\n ");
return ERROR;
}
//返回后继
*next_e=L->elem[i];
return OK;
}
int ListLenght(Sqlist *L){
return L->length;
}

int main(){
Sqlist LA;
int i;
int e;
InitList_Sq(&LA);//初始化
//printf("初始化测试:LA.lenght=%d,LA.listsize=%d,LA.elem=%d\n",LA.length,LA.listsize,LA.elem);//初始化测试
/*
插值测试
*/
for(i=0;i<11;i++){
ListInsert_Sq(&LA,i,i+1);
}
PrintList(&LA);//打印测试
/*
删除测试

printf("开始删除，此时表长为%d",LA.length);
for(i=0;i<10;i++){
ListDElete_Sq(&LA,1,&e);//删除
}
PrintList(&LA);//打印测试
printf("结束删除，此时表长为%d\n",LA.length);
*/
/*
测试索引

int j;
for(i=0;i<12;i++){
j=GetElem(&LA,i,&e);
printf("\tj=%d\n",e);
}
*/
/*
测试locate

printf("%d",LocateElem(&LA,100));

*/
/*
测试前驱
PrintList(&LA);
printf("执行是否成功：%d,前驱为%d\n",PriorElem(&LA,4,&e),e);
printf("前驱为%d",e);
*/
/*
测试后继
*/
PrintList(&LA);
printf("执行是否成功：%d,",NextElem(&LA,0,&e));
printf("后继为%d",e);

}

链表

Copy
#include <stdio.h>
#include <malloc.h>
#define OVERFLOW -2
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
/*
typedef
*/
typedef int Elemtype;
typedef int Status;
typedef struct LNode
{
Elemtype data;
struct LNode *next;
}LNode,*LinkLIst;
/*
链表初始化
*/
Status InitList_L(LNode *L){
L->next=NULL;//头结点next域置空非常重要
return OK;
};
/*
链表插值
*/
Status ListInsert_L(LNode *L,int i,Elemtype e){
int j=0;
LNode *p;
p=L;
while(p&&j<i-1){
p=p->next;
j++;
}
if(!p||j>i-1){
return ERROR;//i值检验，负值或大于表长加1值
}
//开始插值
LNode *s;
s=(LinkLIst) malloc(sizeof(LNode));//空间分配！！！
s->data=e;
s->next=p->next;//中部插值时必须
p->next=s;
return OK;
};
/*
链表删除
*/
Status ListDelete_L(LNode *L,int i,Elemtype *e){
int j=0;
LNode *p;
p=L;
while(p&&j<i-1){
p=p->next;
j++;
}
if(!(p->next)||j>i-1){
return ERROR;//i值检验，负值或大于表长加1值
}
LNode *q;
q=(LinkLIst) malloc(sizeof(LNode));//空间分配！！！
q=p->next;
p->next=q->next;
*e=q->data;
free(q);
return OK;
};
/*
链表打印
*/
Status PrintList_L(LNode *L){

LNode *p;
int j=0;
p=L->next;
printf("开始打印链表:\n");
if(p==NULL){
printf("空表\n");
return OK;
}
while (p!=NULL)
{
printf("a[%d]=%d\n",j,p->data);
p = p->next;
j++;
}
return OK;
};
/*
链表查找
*/
Status LocateElem_L(LNode *L,Elemtype e){
LNode *p;
int i=1;
p=L->next;
while(p){
if(p->data==e){
printf("链表元素%d所在位序为%d\n",e,i);
return i;
}
i++;
p=p->next;
}
if(p==NULL){
printf("链表为空或元素不存在\n");
return ERROR;
}
};
/*
链表求后继
*/
Status NextElem_L(LNode *L,Elemtype e){
LNode *p;
p=L->next;
while(p){
if(p->data==e){
if(p->next==NULL){
printf("后继不存在\n");
return ERROR;
}
printf("链表元素%d后继为%d\n",e,p->next->data);
return p->next->data;
}
p=p->next;
}
if(p==NULL){
printf("链表为空\n");
return ERROR;
}
}
/*
链表求前驱
*/
Status PriorElem_L(LNode *L,Elemtype e){
LNode *p;
LNode *q;
int i=1;
int j=1;
p=L->next;
while(p){
if(p->data==e){
break;
}
i++;
p=p->next;
}
if(i==1){
printf("空表或前驱不存在\n");
return ERROR;
}
q=L->next;
while(q){
if(j==i-1){
printf("链表元素%d前驱为%d\n",e,q->data);
return q->data;
}
j++;
q=q->next;
}
};
/*
链表合并
*/
Status MergeList_L(LNode *LA,LNode *LB,LNode *LC){
LNode *p;
LNode *q;
LNode *r;
p=LA->next;
q=LB->next;
r=LC;
while(p||q){
//空表尾插
if(p==NULL){
r->next=q;
return OK;
}
if(q==NULL){
r->next=p;
return OK;
}
//非空插值
if(p->data>=q->data){
r->next=q;
q=q->next;
r=r->next;
}else{
r->next=p;
p=p->next;
r=r->next;
}
}
};
Status main(){
LNode LA;
LNode LB;
LNode LC;
int i;
Elemtype e;
InitList_L(&LA);//初始化
InitList_L(&LB);
InitList_L(&LC);
for(i=1;i<5;i++){
ListInsert_L(&LA,i,i);
}
for(i=1;i<5;i++){
ListInsert_L(&LB,i,2*i);
}
MergeList_L(&LA,&LB,&LC);
PrintList_L(&LC);
printf("stop");
return 1;
}

顺序栈

Copy
#include <stdio.h>
#include <malloc.h>
#define STACK_INIT_SIZE 100
#define STACKINCREMENT 10
typedef int SElemType;
typedef int Status;
typedef struct
{
SElemType *base;
SElemType *top;
int stacksize;
}SqStack;
/*
init
*/
Status InitStack(SqStack *S){
//构造空栈
S->base=(SElemType *)malloc(STACK_INIT_SIZE*sizeof(SElemType));
if(!S->base) return 0;
S->top=S->base;
S->stacksize=STACK_INIT_SIZE;
return 1;
}
//插值 Push
Status Push(SqStack *S,SElemType e){
if(S->top-S->base>S->stacksize){
S->base=(SElemType *)realloc(S->base,(S->stacksize+STACKINCREMENT)*sizeof(SElemType));//栈满追加分配空间
if(!S->base)return -2;//分配失败
S->top=S->base+S->stacksize;//栈顶指针移动
S->stacksize+=STACKINCREMENT;
}
*(S->top)++=e;//插值之后栈顶指针上移，这里实际为两步操作
}
//删除 Pop
Status Pop(SqStack *S,SElemType *e){
if(S->base==S->top){
printf("error: SqStack NULL");
return 0;//栈空
}
*e=*(S->top-1);
S->top--;
return 1;
}
//取栈顶 GetTop
Status GetTop(SqStack *S,SElemType *e){
if(S->base==S->top){
printf("error: SqStack NULL");
return 0;//栈空
}
*e=*(S->top-1);//不改变S->top栈顶指针
return 1;
}
//求栈长 StackLength
Status StackLength(SqStack *S){
printf("info: SqStack Length=%d\n",(S->top)-(S->base));
return (S->top)-(S->base);
}
//判空 StackEmpty
Status StackEmpty(SqStack *S){
if(S->base==S->top){
printf("info: SqStack Empty True\n");
return 1;
}else{
printf("info: SqStack Empty False\n");
return 0;
}

}
//置空 ClearStack
Status ClearStack(SqStack *S){
S->top=S->base;
return 1;
}
//销毁 Destory
Status DestoryStack(SqStack *S){
free(S->base);
S->base=NULL;
S->top=NULL;
S->stacksize=0;
return 1;
}
//10到8进制转换
int DecOctConv(int input){
int s,i,e;
SqStack SA;
InitStack(&SA);
for(i=input;i!=0;i=i/8){
Push(&SA,i%8);
}
i=StackLength(&SA);
printf("info:开始转换，转换前十进制值为%d，转换后的八进制为",input);
for(s=0;s<i;s++){
GetTop(&SA,&e);
printf("%d",e);
Pop(&SA,&e);
}
printf("(8)\n");
}
//10到16进制转换
int DecHexConv(int input){
int s,i,e;
SqStack SA;
InitStack(&SA);
for(i=input;i!=0;i=i/16){
Push(&SA,i%16);
}
i=StackLength(&SA);
printf("info:开始转换，转换前十进制值为%d，转换后的十六进制为",input);
for(s=0;s<i;s++){
GetTop(&SA,&e);
switch (e)
{
case 10:{
printf("A");
break;
}
case 11:{
printf("B");
break;
}
case 12:{
printf("C");
break;
}
case 13:{
printf("D");
break;
}
case 14:{
printf("E");
break;
}
case 15:{
printf("F");
break;
}
default:{
printf("%d",e);
break;
}
}
Pop(&SA,&e);
}
printf("(16)\n");
}
//栈打印 print
void print(SqStack *S){
int i=0;
printf("info: stack print elem start\n ");
while((S->base+i)!=(S->top)){
printf("%-4d",*(S->base));
i++;
}
printf("\ninfo: stack print elem end\n ");

}
void menu(){
printf("栈操作菜单:输入数字进行操作\n");
printf("[1]:init 初始化\n");
printf("[2]:push 栈顶插值\n");
printf("[3]:pop 栈顶删除\n");
printf("[4]:gettop 获取栈顶元素\n");
printf("[5]:stacklength 求栈长度\n");
printf("[6]:stackempty 判空\n");
printf("[7]:stackclear 置空\n");
printf("[8]:destroystack 销毁\n");
printf("[9]:DecOctConv 10到8进制转换\n");
printf("[10]:DecHexConv 10到16进制转换\n");
printf("[11]:print 打印栈元素\n");
printf("[0]:退出\n");
}
int main(){
SqStack SA;
int i=0,c;
menu();
while(1){
scanf("%d",&c);
if(c==0){
break;
}
switch(c){
case 1:{
InitStack(&SA);
printf("info: init success\n");
break;
}
case 2:{
printf("input e:\n");
scanf("%d",&i);
Push(&SA,i);
printf("info: pop success\n");
break;
}
case 3:{
Pop(&SA,&i);
printf("info: delete top %d success\n ",i);
break;
}
case 4:{
GetTop(&SA,&i);
printf("info: top= %d \n ",i);
break;
}
case 5:{
StackLength(&SA);
break;
}
case 6:{
StackEmpty(&SA);
break;
}
case 7:{
ClearStack(&SA);
printf("info: clear success\n");
break;
}
case 8:{
DestoryStack(&SA);
printf("info: destory success\n");
break;
}
case 9:{
int input;
printf("info: input data\n");
scanf("%d",&input);
DecOctConv(input);
break;
}
case 10:{
int input;
printf("info: input data\n");
scanf("%d",&input);
DecHexConv(input);
break;
}
case 11:{
print(&SA);
break;
}
default:{
printf("error: options Null,reselect\n");
break;
}
}
printf("\n\n");
menu();
}
return 1;
}

链队列

Copy
#include <stdio.h>
#include <malloc.h>
typedef int QElemType;
typedef int Status;
typedef struct QNode{
QElemType data;
struct QNode *next;

}QNode,*QueuePtr;
typedef struct
{
QueuePtr front;//队头指针
QueuePtr rear;//队尾指针
int len;
}LinkQueue;
Status InitQueue(LinkQueue *Q){
Q->rear=Q->front=NULL;
Q->len=0;
return 1;
}
Status EnQueue(LinkQueue *Q,QElemType e){
QNode *p=(QueuePtr)malloc (sizeof(QNode));
p->data=e;
p->next=NULL;
if(Q->front==NULL){
Q->front=Q->rear=p;
}else{
Q->rear->next=p;
Q->rear=p;
}
Q->len++;
return 1;
}
Status PrintQueue(LinkQueue *Q){
QNode *p=Q->front;
while(p!=Q->rear->next){
printf("%d\n",p->data);
p=p->next;
}
return 1;
}
Status QueueEmpty(LinkQueue *Q){
if(Q->front==Q->rear){
printf("info: Queue empty true\n");
return 1;
}else{
printf("info: Queue empty false\n");
return 0;
}
}
Status DeQueue(LinkQueue *Q,QElemType *e){
int isEmpty=QueueEmpty(Q);
if(!isEmpty){
QNode *p=Q->front;
Q->front=p->next;
printf("info: Queue delete head elem %d success\n",p->data);
free(p);
return 1;
}{
printf("error:Queue Empty \n");
return 0;
}
Q->len--;
}
Status DestoryQueue(LinkQueue *Q){
while(Q->front){
Q->rear = Q->front->next;
free(Q->front);
Q->front = Q->rear;
}
Q->len=0;
return 1;
}
Status ClearQueue(LinkQueue *Q){
InitQueue(Q);
return 1;
}
Status QueueLength(LinkQueue *Q){
printf("info:Queue Length = %d\n",Q->len);
return Q->len;
}
Status QueueHead(LinkQueue *Q,QElemType *e){
int isEmpty=QueueEmpty(Q);
if(!isEmpty){
QNode *p=(QueuePtr)malloc (sizeof(QNode));
p=Q->front;
*e=p->data;
printf("info:Queue Head = %d\n",*e);
return 1;
}else{
printf("error:Queue Empty \n");
return 0;
}
}
Status QueueLast(LinkQueue *Q,QElemType *e){
int isEmpty=QueueEmpty(Q);
if(!isEmpty){
QNode *p=(QueuePtr)malloc (sizeof(QNode));
p=Q->rear;
*e=p->data;
printf("info:Queue Last = %d\n",*e);
return 1;
}else{
printf("error:Queue Empty \n");
return 0;
}
}
int main(){
LinkQueue QA;
int e;
QueueEmpty(&QA);
InitQueue(&QA);
EnQueue(&QA,1);
EnQueue(&QA,2);
EnQueue(&QA,3);
QueueLength(&QA);
PrintQueue(&QA);
DeQueue(&QA,&e);
QueueLength(&QA);
QueueHead(&QA,&e);
QueueLast(&QA,&e);
DestoryQueue(&QA);
QueueEmpty(&QA);
QueueLength(&QA);
printf("stop");
}

循环队列

Copy
#include <stdio.h>
#include <malloc.h>
#define MAXQSIZE 100//最大队列长度
#define QISTINCREMENT 10//动态增量
typedef int QElemType;
typedef int Status;
typedef struct {
QElemType *base;//初始化动态分配空间
int front;//队头下标
int rear;//队尾下标
}SqQueue;
/*
init 初始化
*/
Status InitQueue(SqQueue *Q){
Q->base=(QElemType *)malloc(MAXQSIZE*sizeof(QElemType));
if(!Q->base) {
printf("error: malloc false \n");
return -2;
}
Q->front=Q->rear=0;
return 1;
}
/*
QueueLength 队列长度
*/
Status QueueLength(SqQueue *Q){
printf("info:Queue Length = %d\n",Q->rear-Q->front);
return Q->rear-Q->front;
}
/*
enqueue 入队
*/
Status EnQueue(SqQueue *Q,QElemType e){
int l=QueueLength(Q);
if(l>=MAXQSIZE){
QElemType *newbase;
printf("warning: Exceed the maximum length, reallocate space\n");
newbase=(QElemType*)realloc(Q->base,(MAXQSIZE+QISTINCREMENT)*sizeof(QElemType));
Q->base=newbase;
}
Q->base[l]=e;
Q->rear++;
printf("info:enque elem %d success \n",e);
return 1;
}
/*
DeQueue 删除
*/
Status DeQueue(SqQueue *Q,QElemType *e){
int l=QueueLength(Q);
if(l){
*e=Q->base[l];
Q->base[l]=NULL;
}else{
printf("error:Queue delete fail ,Queue NULL\n");
return 0;
}
printf("info:Queue elem %d sucess\n",*e);
Q->rear--;
return 1;
}
/*
PrintQueue 遍历打印
*/
Status PrintQueue(SqQueue *Q){
int l=QueueLength(Q);
int i;
if(l){
printf("info: print queue start:\n");
for(i=0;i<l;i++){
printf("Q[%d]=%d\n",i,Q->base[i]);
}
printf("info:print queue end\n");
return 1;
}else{
printf("error:queue null\n");
return 0;
}
}
/*
QueueHead 取队头
*/
Status QueueHead(SqQueue *Q){
int l=QueueLength(Q);
if(l){
printf("info:queue head elem = %d\n",Q->base[Q->front]);
return 1;
}else{
printf("error:queue null\n");
return 0;
}
}
/*
QueueLast 取队尾
*/
Status QueueLast(SqQueue *Q){
int l=QueueLength(Q);
if(l){
printf("info:queue last elem = %d\n",Q->base[Q->rear]);
return 1;
}else{
printf("error:queue null\n");
return 0;
}
}
/*
QueueEmpty 判空
*/
Status QueueEmpty(SqQueue *Q){
int l=QueueLength(Q);
if(l){
printf("info:queue empty true\n");
return 1;
}else{
printf("nfo:queue empty false \n");
return 0;
}
}
/*
Queueclear 置空
*/
Status QueueClear(SqQueue *Q){
Q->front=Q->rear=0;
printf("info:queue clear success \n");
return 1;
}
/*
QueueDestory 摧毁
*/
Status QueueDestory(SqQueue *Q){
free(Q->base);
Q->front=Q->rear=0;
printf("info:queue Destory success \n");
return 1;
}
int main(){
SqQueue QA;
InitQueue(&QA);
int i=0;
int e;
for(i=0;i<20;i++){
EnQueue(&QA,i);
}
for(i=0;i<20;i=i+5){
DeQueue(&QA,&e);
}
PrintQueue(&QA);
QueueLength(&QA);
QueueDestory(&QA);
QueueLength(&QA);

}

二叉树

Copy
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef char TElemType;
typedef int Status;
/*------二叉树的二叉链表存储结构------*/
typedef struct BiTNode
{
TElemType data;
struct BiTNode* left;
struct BiTNode* right;
}BTNode,*BiTree;
typedef BTNode *QDataType;
/*函数声明*/
BTNode* InitBTNode(TElemType x);//二叉树初始化
BTNode* BTNodeCreate(TElemType* a, int n, int* pi) ;// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
void BTNodeDestroy(BTNode** root);// 二叉树销毁
int BTNodeSize(BTNode* root);// 二叉树节点个数
int BTNodeLeafSize(BTNode* root);// 二叉树叶子节点个数
int BTNodeLevelKSize(BTNode* root, int k);// 二叉树第k层节点个数
BTNode* BTNodeFind(BTNode* root, TElemType x);// 二叉树查找值为x的节点
void BTNodePrevOrder(BTNode* root);// 二叉树前序遍历
void BTNodeInOrder(BTNode* root);// 二叉树中序遍历
void BTNodePostOrder(BTNode* root);// 二叉树后序遍历
void BTNodeLevelOrder(BTNode* root);// 层序遍历
int BTNodeComplete(BTNode* root);// 判断二叉树是否是完全二叉树
int BTNodeHeight(BTNode* root);//求二叉树的高度
BTNode * getParents(BTNode* root,BTNode* i);//求双亲
BTNode * getlc(BTNode* root,BTNode* i);//求左孩子
BTNode * getrc(BTNode* root,BTNode* i);//求右孩子
BTNode * getlb(BTNode* root,BTNode* i);//求左兄弟
BTNode * getrb(BTNode* root,BTNode* i);//求右兄弟
/*------队列的顺序存储结构------*/
typedef struct QueueNode
{
QDataType data;
struct QueueNode* next;
}QNode;
typedef struct Queue
{
QNode* head;
QNode* tail;
}Queue;
/*函数声明*/
void QueueInit(Queue* pq);
void QueueDestory(Queue* pq);
void QueuePush(Queue* pq, QDataType x);
void QueuePop(Queue* pq);
bool QueueEmpty(Queue* pq);
size_t QueueSize(Queue* pq);
QDataType QueueFront(Queue* pq);
QDataType QueueBack(Queue* pq);
BTNode* InitBTNode(TElemType x)
{
BTNode* node = (BTNode*)malloc(sizeof(BTNode));
assert(node);
node->data = x;
node->left = NULL;
node->right = NULL;
return node;
}
BTNode* BTNodeCreate(TElemType* a, int n, int* pi)
{
if(a[*pi] == '#' || *pi >= n)
{
printf("NULL ");
(*pi)++;
return NULL;
}
BTNode *dst= InitBTNode(a[*pi]);
printf("%c ",dst->data);
(*pi)++;
dst->left = BTNodeCreate(a,n,pi);
dst->right = BTNodeCreate(a,n,pi);
return dst;
}
//先序遍历
void BTNodePrevOrder(BTNode* root)
{
if(root == NULL)
{
return;
}
printf("%c ",root->data);
BTNodePrevOrder(root->left);
BTNodePrevOrder(root->right);
}
// 二叉树中序遍历
void BTNodeInOrder(BTNode* root)
{
BTNodePrevOrder(root->left);
if(root == NULL)
{
return;
}
printf("%c ",root->data);
BTNodePrevOrder(root->right);
}
// 二叉树后序遍历
void BTNodePostOrder(BTNode* root)
{
BTNodePrevOrder(root->left);
BTNodePrevOrder(root->right);
if(root == NULL)
{
return;
}
printf("%c ",root->data);
}
// 层序遍历
void BTNodeLevelOrder(BTNode* root)
{
Queue q;
QueueInit(&q);
assert(root);
QueuePush(&q, root);
while(!QueueEmpty(&q))
{
BTNode *temp= QueueFront(&q);
printf("%c ",temp->data);
if(temp->left)
{
QueuePush(&q,temp->left);
}
if(temp->right)
{
QueuePush(&q,temp->right);
}

QueuePop(&q);
}
QueueDestory(&q);
}
// 二叉树节点个数
int BTNodeSize(BTNode* root)
{
if(root==NULL)
{
return 0;
}
if(root->left==NULL&&root->right==NULL)
{
return 1;
}
return BTNodeSize(root->left)+BTNodeSize(root->right)+1;
}
// 二叉树查找值为x的节点
BTNode* BTNodeFind(BTNode* root, TElemType x)
{
if(root==NULL)
{
return NULL;
}
if(root->data==x)
{
return root;
}
BTNodeFind(root->left, x);
BTNodeFind(root->right,x);
}
// 二叉树第k层节点个数
int BTNodeLevelKSize(BTNode* root, int k)
{
if(root==NULL)
{
return 0;
}
if(k==1)
{
return 1;
}
return BTNodeLevelKSize(root->left, k-1)+BTNodeLevelKSize(root->right, k-1);
}
// 二叉树叶子节点个数
int BTNodeLeafSize(BTNode* root)
{
if(root==NULL)
{
return 0;
}
if(root->left==NULL&&root->right==NULL)
{
return 1;
}
return BTNodeLeafSize(root->left)+BTNodeLeafSize(root->right);
}
// 判断二叉树是否是完全二叉树
int BTNodeComplete(BTNode* root)
{
if(root==NULL)
{
return 1;
}
Queue q;
QueueInit(&q);
QueuePush(&q,root);
while(!QueueEmpty(&q))
{
BTNode *temp= QueueFront(&q);
QueuePop(&q);
if(temp==NULL)
{
break;
}
QueuePush(&q,temp->left);
QueuePush(&q,temp->right);

}
while(!QueueEmpty(&q))
{
BTNode *temp= QueueFront(&q);
QueuePop(&q);
if(temp!=NULL)
{
printf("info:该二叉树不是完全二叉树\n");
return 0;
}
}
printf("info:该二叉树是完全二叉树\n");
return 1;
}
// 二叉树销毁
void BTNodeDestroy(BTNode** root)
{
if(root==NULL)
{
return ;
}
if(*root==NULL)
{
return;
}
BTNodeDestroy(&((*root)->left));
BTNodeDestroy(&((*root)->right));
free(*root);
*root=NULL;
return;
}
//求二叉树的高度
int BTNodeHeight(BTNode*root)
{
if(root==NULL)
{
return 0;
}
if(root->right==NULL&&root->left==NULL)
{
return 1;
}
return (BTNodeHeight(root->left)>=BTNodeHeight(root->right) ? BTNodeHeight(root->left)+1 :BTNodeHeight(root->right)+1);
}
//队列的创建
void QueueInit(Queue* pq)
{
assert(pq);
pq->head = pq->tail = NULL;
}
void QueueDestory(Queue* pq)
{
assert(pq);
QNode* cur = pq->head;
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}

pq->head = pq->tail = NULL;
}
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
assert(newnode);

newnode->data = x;
newnode->next = NULL;

if (pq->tail == NULL)
{
assert(pq->head == NULL);
pq->head = pq->tail = newnode;
}
else
{
pq->tail->next = newnode;
pq->tail = newnode;
}
}
void QueuePop(Queue* pq)
{
assert(pq);
assert(pq->head && pq->tail);

if (pq->head->next == NULL)
{
free(pq->head);
pq->head = pq->tail = NULL;
}
else
{
QNode* next = pq->head->next;
free(pq->head);
pq->head = next;
}
}
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->head == NULL;
}
size_t QueueSize(Queue* pq)
{
assert(pq);
QNode* cur = pq->head;
size_t size = 0;
while (cur)
{
size++;
cur = cur->next;
}

return size;
}
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(pq->head);
return pq->head->data;
}
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(pq->tail);

return pq->tail->data;
}
//求双亲
BTNode * getParents(BTNode* root,BTNode* i){
if(!root||!i||root==i){
return NULL;
}
if(root->left==i||root->right==i){
return root;
}
BTNode *temp=getParents(root->left,i);
if(temp){
return temp;
}else{
return getParents(root->right,i);
}
}
//求左孩子
BTNode * getlc(BTNode* root,BTNode* i){
if(!root||!i||!i->left){
return NULL;
}
if(root->left==i||root->right==i||root==i){
return i->left;
}
getlc(root->left,i);
getlc(root->right,i);
}
//求右孩子
BTNode * getrc(BTNode* root,BTNode* i){
if(!root||!i||!i->right){
return NULL;
}
if(root->left==i||root->right==i||root==i){
return i->right;
}
getlc(root->left,i);
getlc(root->right,i);
}
//求左兄弟
BTNode * getlb(BTNode* root,BTNode* i){
if(!root||!i||i==root){
return NULL;
}
if(root->left==i||root->right==i){
return root->left;
}
BTNode *temp=getlb(root->left,i);
if(temp){
return temp;
}else{
return getlb(root->right,i);
}
}
//求右兄弟
BTNode * getrb(BTNode* root,BTNode* i){
if(!root||!i||i==root){
return NULL;
}
if(root->left==i||root->right==i){
return root->right;
}
BTNode *temp=getrb(root->left,i);
if(temp){
return temp;
}else{
return getrb(root->right,i);
}
}

int main() {
char a[17]={'A','B','D','#','#','E','#','H','#','#','C','F','#','#','G','#','#'};
int b=0;
int *pi=&b;
BTNode *Btree=BTNodeCreate(a, 16, pi);
printf("\n");
//前序遍历
printf("info:该二叉树前序遍历为");
BTNodePrevOrder(Btree);
printf("\n");
//中序遍历
printf("info:该二叉树中序遍历为");
BTNodeInOrder(Btree);
printf("\n");
//后序遍历
printf("info:该二叉树后序遍历为");
BTNodePostOrder(Btree);
printf("\n");
//层序遍历
printf("info:该二叉树层序遍历为");
BTNodeLevelOrder(Btree);
printf("\n");
//输出二叉树结点数
int number=BTNodeSize(Btree);
printf("info:该二叉树结点数为%d\n",number);
//查找为x的结点
BTNode *find=BTNodeFind(Btree, 'A');
printf("info:查找结果的根结点为%c\n",find->data);
//查询第k层的结点个数
int w=BTNodeLevelKSize(Btree, 3);
printf("info:该二叉树第3层结点数为%d\n",w);
//查询叶子结点的个数
int count=BTNodeLeafSize(Btree);
printf("info:该二叉树叶子结点个数为%d\n",count);
//判断当前是否为一棵完全二叉树
int r=BTNodeComplete(Btree);
//求二叉树的高度
int x=BTNodeHeight(Btree);
printf("info:该二叉树的高度是%d\n",x);
//求双亲
printf("info:该测试结点的双亲结点的前序遍历为");
BTNodePrevOrder(getParents(Btree,Btree->right->left));
printf("\n");
//求左孩子
printf("info:该测试结点的左孩子结点的前序遍历为");
BTNodePrevOrder(getlc(Btree,Btree->right));
printf("\n");
//求右孩子
printf("info:该测试结点的右孩子结点的前序遍历为");
BTNodePrevOrder(getrc(Btree,Btree));
printf("\n");
//求左兄弟
printf("info:该测试结点的左兄弟结点的前序遍历为");
BTNodePrevOrder(getlb(Btree,Btree->right->right));
printf("\n");
//求右兄弟
printf("info:该测试结点的右兄弟结点的前序遍历为");
BTNodePrevOrder(getrb(Btree,Btree->left));
printf("\n");
//销毁二叉树
BTNodeDestroy(&Btree);
printf("info:该二叉树已销毁\n");
return 0;
}

邻接表图

Copy
#include <stdio.h>
#include <stdlib.h>
typedef char datatype;
typedef int edgetype;
#define MAXVEX 20 //顶点最大数
#define INFINITY 65535 //代指无限大
typedef struct
{
datatype data[MAXVEX]; //顶点表
edgetype arc[MAXVEX][MAXVEX]; //存储边的权的邻接矩阵
int numVertexes,numEdges; //顶点数,边或弧的个数
}MGraph;//图结构邻接矩阵
int visited[MAXVEX]; //访问数组
void InitMGraph(MGraph *G);
void DFS(MGraph *G, int i);
void DFSTraverse(MGraph *G);
int main()
{
MGraph G;
InitMGraph(&G);
int i=1;
printf("info：以顶点%c为起点的DFS递归遍历结果为",G.data[i-1]);
DFS(&G,i);
printf("\ninfo：图G深度优先遍历结果为");
DFSTraverse(&G);
}
//使用图的邻接矩阵存储方式创建网图无向网图
void InitMGraph(MGraph *G)
{
int a,b,i,j;
int x,y;
datatype ch;
edgetype w;
printf("info:输入要创建图的顶点数和边数(a,b)\n");
scanf("%d,%d",&a,&b); //a:顶点个数 , b:边或弧的个数
G->numVertexes=a;
G->numEdges=b;
//初始化邻接矩阵
for(i=0;i<a;i++)
{
for(j=0;j<a;j++)
{
if(i==j)
G->arc[i][j]=0;
else
G->arc[i][j]=INFINITY;
}
}
//给每个顶点赋值
for(i=0;i<a;i++)
{
printf("info:输入每个顶点的信息(char)\n",i);
scanf(" %c",&ch);
G->data[i]=ch;
}
//给每条边或弧赋值
for(i=0;i<b;i++)
{
printf("info:输入邻接顶点的下标和权值(x,y,w)\n");
scanf(" %d,%d,%d",&x,&y,&w);
G->arc[x][y]=w;
G->arc[y][x]= G->arc[x][y]; // 若为无向图，矩阵对称。否则删除即可
}
}
void DFS(MGraph *G, int i) {
int j; //数组下标j，用来遍历图中顶点
visited[i] = 1; //标志下标为i的顶点已经被访问
printf("%c", G->data[i]); //打印该结点
for (j = 0; j < G->numVertexes; j++) {
if (G->arc[i][j] == 1 && !visited[j]) { //如果下标ij的顶点邻接且j没有被访问过
DFS(G, j);
}
}
}
//深度优先遍历
void DFSTraverse(MGraph *G) {
int i;
for (i = 0; i < G->numEdges; i++) {
visited[i] = 0; //初始化所有结点为未访问
}
for (i = 0; i < G->numEdges; i++) {
if (!visited[i]) {
DFS(G, i); //选用下标j对应的顶点作为新的起始点，递归直到图中所有顶点都被访问过为止
}
}
}

后记

很感谢你看到这里，希望你有所收获。   笔记版本:alpha

--2023年2月11日

itsbrqs