写在前面
为迎接期末,总结了下知识点,供个人复习使用,仅供参考。
本文用到的复习资料:点我跳转,提取码:6q5q
若需要本文markdown文件下方评论留言看到即回
绪论
知识点
1.逻辑结构:数据之间的相互关系。(与计算机无关)
- 集合 结构中的数据元素除了同属于一种类型外,别无其它关系。
- 线性结构 数据元素之间一对一的关系
- 树形结构 数据元素之间一对多的关系
- 图状结构或网状结构 结构中的数据元素之间存在多对多的关系
也可分为线性结构(可理解成一条直线能串起来)和非线性结构
2.存储结构分为顺序存储结构和链式存储结构(散列、索引) (与计算机有关)
3.算法五个特性: 有穷性、确定性、可行性、输入、输出
4.算法设计要求:正确性、可读性、健壮性、高效性。 (好的算法)
5.typedef可以理解成给现有数据类型起个别名
例如:typedef struct{…}SqList,即给struct{…}起了个名字叫SqList
也用于类似于typedef int ElemType; 给int 起个别名叫ElemType即ElemType a;等价于int a;
这样做的好处是代码中用ElemType定义变量,如果想修改变量类型只需修改typedef ** ElemType即可,而不用一一修改。
我们注意到有时候会有typedef struct LNode{…}LNode,即struct后有个LNode,这是因为如果结构体内部有指向结构体的指针则必须在struct后面加上LNode(单链表里有next指针struct LNode *next)
6.时间复杂度:基本操作的执行次数(可以理解成就看执行了多少次)
7.研究数据结构就是研究数据的逻辑结构、存储结构及其基本操作
8.抽象数据类型的三个组成部分为数据对象、数据关系、基本操作。
9.数据:描述客观事物的符号
数据元素:是数据的基本单位(元素、结点)
数据项:组成数据元素的最小单位 (如学生信息表中的学号、姓名等)
数据对象:相同性质的数据元素的集合(如大写字母)
大小关系为:数据=数据对象 > 数据元素 > 数据项
10.数据结构:相互之间存在一种或多种特定关系的数据元素的集合
11.数据的运算包含:插入、删除、修改、查找、排序
12.算法:解决某类问题而规定的一个有限长的操作序列
13.算法的空间复杂度:算法在运行时所需存储空间的度量
习题
1.通常要求同一逻辑结构中的所有数据元素具有相同的特性, 这意味着( B )。
A. 数据具有同一特点
B. 不仅数据元素所包含的数据项的个数要相同, 而且对应数据项的类型要一致
C. 每个数据元素都一样
D. 数据元素所包含的数据项的个数要相等
2.以下说法正确的是( D )。
A. 数据元素是数据的最小单位
B. 数据项是数据的基本单位
C. 数据结构是带有结构的各数据项的集合
D. 一些表面上很不相同的数据可以有相同的逻辑结构
答:数据元素是数据的基本单位,数据项是数据的最小单位,数据结构是带有结构的各数据元素的集合
3.算法的时间复杂度取决于( D )。
A.问题的规模 B.待处理数据的初态 C.计算机的配置 D. A 和 B
答:肯定与问题规模(难和简单的问题)有关,不过也与初态有关,比如某些排序算法,若初始已经排好序可能时间复杂度就会降低。
4.下列算法时间复杂度为
count=0;
for(k=1;k<=n;k*=2)
for(j=1;j<=n;j+=1)
count++;
答:最外层循环数值为20,21,22…所以假设执行m次即2m=n所以外层执行了log2n次
内层执行了n次,所以时间复杂度为nlog2n(可理解为log2n个n相加)
int fact(int n){
if(n<=1) return 1;
return n*fact(n-1);
}
答:第一次是n*fact(n-1),然后是n*(n-1)*fact(n-2)…一直到n(n-1)(n-2)…2*1
但是我们要看执行了多少次,也就是函数fact调用了多少次,从n到1也就是n次,所以时间复杂度为O(n)
线性表
知识点
1.线性结构:第一个无前驱,最后一个无后继,其他都有前驱和后继
2.顺序表插入一个元素平均移动n/2个元素,删除平均移(n-1)/2个
插入的那一位置需要向后移,删除的位置那一位不用移(直接覆盖)所以删除少1
3.首元结点:存储第一个有效数据元素的结点
头结点:首元结点之前指向首元结点的结点,为处理方便而设
头指针:指向第一个结点(有头结点指头结点没有指首元结点)的指针
单链表通常用头指针命名
4.随机存取:可以像数组一样根据下标直接取元素
顺序存取:只能顺藤摸瓜从前往后一个一个来
5.单链表加一个前驱指针prior就变成了双向链表
6.单链表最后一个元素的next指针指向第一个结点即为循环链表 (属于线性表!)
7.线性表和有序表合并的时间复杂度
线性表的合并时间复杂度为O(m*n)
A=(7,5,3,11),B=(2,6,3),结果为A=(7,5,3,11,2,6)
算法需要循环遍历B(O(n))且LocateElem(A)(判断是否与B重复为O(m))所以为O(m*n)
有序表的合并时间复杂度为O(m+n)
A=(3,5,8,11),B=(2,6,8),结果为A=(2,3,5,6,8,11)
算法只需同时遍历A和B,然后将还没遍历完的那个直接插到最后就行,所以是相加
8.顺序表和单链表的比较
9.单链表也是线性表(一对一的关系,用绳子可以穿起来)的一种
10.顺序表存储密度(数据占比/结点占比)等于1,单链表的小于1(因为要存指针)
习题
1.线性表只能用顺序存储结构实现 (X)也可用链式如单链表
2.在双向循环链表中,在 p指针所指的结点后插入 q所指向的新结点,其修改指针的操作是( C )。
A. p->next = q; q->prior = p; p->next->prior = q; q->next = q;
B. p->next = q; p->next->prior = q; q->prior=p; q->next = p->next;
C. q->prior = p; q->next = p->next; p->next->prior = q; p->next = q;
D. q->prior = p; q->next = p->next; p->next = q; p->next->prior = q;
答:这样的题只能画图看看对不,但是我们可以看到在p的后面插入,那么p->next就不能非常早的更改否则就会出现找不到的情况,所以排除A,B。C和D画个图试下
3.在一个有127个元素的顺序表中插入一个新元素并保持原来顺序不变,平均要移动的元素个数为( B)。
A. 8 B. 63.5 C. 63 D. 7
答:插入平均移动n/2即63.5,注意不用取整
栈和队列
知识点
1.栈和队列是操作受限的线性表(1对1)
2.栈后进先出,只能在栈顶(表尾)插入删除
3.队列先进先出,队头删除,队尾插入(和平常排队一样排后面)
4.顺序栈栈空时:S.top=S.base 栈顶指针等于栈底指针
栈满时:S.top-S.base=S.stacksize 栈顶-栈底等于最大空间
5.链栈在栈顶操作,用链表头部作为栈顶即可,不需要头结点
栈空:S=NULL (指向第一个结点的指针为空)
6.栈的应用:括号匹配,表达式求值(中缀式求值),递归转非递归、函数调用
7.中缀表达式:符号在中间,如a+b,前缀就是+ab(前缀中缀指的是符号的位置)
8.循环队列队空:Q.front=Q.rear
队满:(Q.rear+1)%MAXSIZE==Q.front
队列元素个数:(Q.rear-Q.front+MAXSIZE)%MAXSIZE
入队:Q.rear=(Q.rear+1)%MAXSIZE
出队:Q.front=(Q.front+1)%MAXSIZE
习题
1.若一个栈以向量V[1…n]存储,初始栈顶指针 top设为n+1, 则元素x进栈的正确操
作 是( C )。
A. top++; V[top]=x; B. V[top]=x; top++; C. top–; V[top]= x; D. V[top]=x; top–;
答:注意初始top为n+1,而存储下标为v[1]~v[n],所以就不存在ABD中的v[n+2]或者v[n+1]。应该先让top减一使得指向最后一个地址v[n],可以把它看成是倒过来的栈,然后存v[n-1],v[n-2]…
2.用链接方式存储的队列,在进行删除运算时( D )。
A. 仅修改头指针 B. 仅修改 尾指针 C. 头、尾指针都要修改 D. 头、尾指针可能都要修改
答:由于只能在队头删除,一般只需修改头指针(head=head->next)即可。但当删最后一个元素时(此时head=rear)删除后(delete p)尾指针就丢失了也得修改
3.一个递归算法必须包括( B )。
A. 递归部分 C. 迭代部分 B. 终止条件和递归部分 D. 终止条件和迭代
答:算法有穷形所以都得有终止条件,递归算法那肯定得有递归部分
4.最不适合用作队列的链表是( A )。
A.只带队首指针的非循环双链表 B.只带队首指针的循环双链表
C.只带队尾指针的循环双链表 D.只带队尾指针的循环单链表
答:就看找头尾指针好不好找,A只有头指针还非循环只能从头到尾遍历找到尾指针
5.表达式a*(b+c)-d的后缀表达式是( B )。
A. abcd*± B. abc+*d- C. abc*+d- D. -+*abcd
答:前缀后缀指的是运算符号位置,先看原运算顺序,先算(b+c)后缀表达式是bc+
原式然后算*,a*(bc+)后缀表达式是abc+*,然后是abc+*d-
6.已知循环队列存储在一维数组A[0…n-1]中,且队列非空时front和rear分别指向队头元素和队尾元素。若初始时队列为空,且要求第1个进入队列的元素存储在A[0]处,则初始时front和rear的值分别是( B )。
A.0,0 B.0,n-1 C.n-1,0 D.n-1,n-1
答:平常入队时先在rear位置赋值,再把rear+1,即rear指向的是队尾元素的下一位置,所以入队时先赋值再加一。但是此题说的是rear指向队尾。也即第一个入队后队尾指向的是第一个元素的位置也即0,所以入队前rear那就是0前面的n-1而front默认都为0
串、数组和广义表
知识点
1.求next数组和nextval数组
当j=1(即第一个字符)时为特殊情况next和nextval均为0
1️⃣ next数组:其值为当前字母前方的最大前后缀+1
例如:j=3(A),前面有A,B。没有前后缀即为0,0+1=1
j=4(B),前面有ABA,有前缀和后缀A,即前后缀为1,1+1=2
j=5(A),前面有ABAB,前后缀为AB,2+1=3 //ABA和BAB不等,所以AB为最大前后缀
next[j]=k,它的意思是,当模式串的第j位与主串的第i位失配时,这时主串的位置不回退,而是将模式串退到第k位,再次与主串的第i位进行匹配。
比如主串为ABAA,不匹配时next[4]=2,将模式串中的2位置即B与主串的最后A比较也就达到了不匹配时直接根据前后缀移动的目的

2️⃣ nextval数组:两种情况
若是不匹配就看next[j]数值,若当前字母和next[j]字母不等时,nextval等于上面落下来的next[j]
若是不匹配就看next[j]数值,若当前字母和next[j]字母相等时,nextval值为前面的那个nextval[]
不等就用自家的,相等直接拿过来
例如:j=2,next[2]为1表不匹配时退到下标为1的位置,1的位置是A和当前2对应的B不等用自家的所以next[2]落下来成为nextval[2]
j=3,next[3]=1表不匹配时模式串回退到下标为1的位置,1的位置是A和当前3对应的A相等,所以把前面的nextval数值拿过来即为nextval[3]
2.行优先和列优先
其实就是行优先就是从上到下先一行一行的存,列优先就是从左到右一列一列的存
无论是哪个其元素如a[2][3]位置不变(但顺序变了),行优先就是先存上面2行再到它,列优先就是先存左面3列再存它
3.广义表是线性表的推广,也称列表(暂时理解成python里的列表)
4.广义表元素可为原子或子表
广义表长度:即元素个数(最外层括号里的小括号算一个元素)
广义表深度:就看有多少对括号就行(注意要将里面的子表全部展开)
5.表头(Head)和表尾(Tail):当表非空时,第一个元素为表头其余均为表尾
注意表头是第一个元素所以不带最外层的那个括号,表尾带最外层的括号
例如A=((a,b),c),表头为(a,b)而表尾为(c)
6.串的子串个数为n(n+1)/2+1(1+1+2+…+n,空串也算所以加1)
7.主串长度为n,模式串长度为m,KMP算法时间复杂度为O(m+n)
习题
1.求子串数目
2.串 “ababaabab” 的 nextval 为(A)
A. 010104101 B. 010102101 C. 010100011 D0101010
3.设有数组 A[i,j], 数组的每个元素长度为 3 字节, i 的值为 1~8 , j的值为 1~10 ,
数组从内存首地址 BA 开始顺序存放, 当用以列为主存放时, 元素 A[5,8]的存储首地址为(B)
A. BA+ 141 B. BA+ 180 C. BA+222 D. BA+225
答:以列为主那就是一列一列的存,[5,8]表示这是第8列,前面有7列是存满的,所以这是第(7*8)+5=61个元素,而其地址为BA+(61-1)*3=BA+180
注意要不要减1的问题,可先试下,假如是第二个元素只需要加一倍的3即BA+3所以要减1
4.二维数组 A 的每个元素是由 10 个字符组成的串,其行下标 i=0,1, …,8,列下标j=1,2, , ,10 。若 A 按行先存储,元素 A[8,5] 的起始地址与当 A 按列先存储时的元素(B)的起始地址相同。设每个字符占一个字节。
A. A[8,5] B . A[3,10] C. A[5,8] D . A[0,9]
答:一定要注意下标是否从0开始,这里共有9行
行优先,[8,5]前面有8行(0,1,2,3,4,5,6,7共8行)所以是第8*10+5=85个元素
列优先,[3,10]前面有9列,所以是第9*9+4=85个元素 (注意行标从0开始)
计算总数记住行乘列,列乘行
5.广义表 ((a,b,c,d)) 的表头是( C ),表尾是( B )
A. a B . ( ) C. (a,b,c,d) D. (b,c,d)
答:第一个元素为表头其余均为表尾,所以表尾要带个外层的括号
6.设广义表 L=((a,b,c)) ,则 L 的长度和深度分别为( 1和2 )。
答:长度就看有多长(元素个数),深度就看有多深(括号层数)
7.以行序为主序方式,将n阶对称矩阵A的下三角形的元素(包括主对角线上所有元素)依次存放于一维数组B[1…(n(n+1))/2-1]中,则在B中确定aij (i<j) 的位置k的关系为( B ) 。
A.i*(i-1)/2+j B.j*(j-1)/2+i C.i*(i+1)/2+j D.j*(j+1)/2+i
答:注意题目说的是确定aij (i<j) ,i要小于j,但存的是下三角元素,假如a13=5,确定a13的位置就是确定5的位置,而a13=a31也就是根据i,j (i=1,j=3) 确定a31的位置,B中代入即3*1+1=4,而a31位置正是4(前面是1+2)
树和二叉树
知识点
1.满二叉树(最完美最满的状态) 完全二叉树(编号是连续的即最右面缺而且是最后一层缺)
完全二叉树度为1的结点个数为0或1
当前结点编号为i,它的左孩子编号为2i,右孩子为2i+1(从1开始时)
2.二叉树常用性质
- n0 = n2+1 即叶子节点个数为度为2的结点个数加1
- 有 n 个结点的完全二叉树的深度为⎣log2 n⎦+1 (没记住可以一个一个试)
- 深度为k的二叉树最多有2k-1个结点(满二叉树)
3.二叉树遍历
- 先序遍历NLR:根节点->左子树->右子树。
- 中序遍历LNR:左子树->根节点->右子树。必须要有中序遍历才能画出相应二叉树
- 后续遍历LRN:左子树->右子树->根节点。
- 助记:先后中遍历指的是根结点在先还是中还是右,且时间复杂度均为O(n)
- 层次遍历:一层一层从上到下,从左到右
4.二叉树线索化目的是加快查找结点的前驱或后继的速度。实质上就是遍历一次二叉树,检查当前结点左,右指针域是否为空,若为空,将它们改为指向前驱结点或后继结点
5.哈夫曼树即带权路径最短树,也称最优树。
树的带权路径长度=树根到各叶子结点的路径(树根到该结点要走几步)乘对应权值;通常记作 WPL=∑wi×li
6.哈夫曼编码是最优前缀编码(任一个编码都不是其他编码的前缀,便于通信减少数据传输)

哈夫曼树没有度为1的结点,且不一定是完全二叉树
7.树的存储结构有三种:双亲表示法、孩子表示法、孩子兄弟表示法,其中孩子兄弟表示法是最常用的表示法,任意一棵树都能通过孩子兄弟表示法转换为二叉树进行存储。
8.含有n个节点的二叉树共有(2n)!/(n!*(n+1)!) (常考3个节点共5种)
9.二叉树的高度是最大层次数(根节点为第一层)
10.树和二叉树均可以为空(注意树可为空是在严蔚敏教材中可空,有的地方规定不能为空)
11.树的先序对应二叉树的先序,树的后序对应二叉树的中序(这里的二叉树一般指经孩子兄弟法转换的树)
12.哈弗曼树属于二叉树有左右子树之分
习题
1.
答:n0= n2+1 n1=0或n1=1 n0+n1+n2=1001
**2.**注意题目说的是存储树,而树的存储结构中,孩子兄弟表示法又称二叉链表表示法
3.在一颗度为4的树T中,若有20个度为4的结点,10个度为3的结点,1个度为2的结点,10个度为1的结点,则树T的叶结点个数是______82_。
答:任何树中,分支数(度数之和)比节点数少1
题目中,分支数为20*4+10*3+1*2+10*1=122,所以有123个节点
度为0的节点为123-20-10-1-10=82
也可用公式n0=1*n2+2*n3+3*n4+1=1+2*10+3*20+1=82
**4.**设哈夫曼树中有199 个结点,则该哈夫曼树中有_100__个叶子结点
答:哈弗曼树没有度为1的结点,n0=n2+1,n0+n2=199,所以n0=100
**5.**一棵高度为4的完全二叉树至少有______8_个结点
答:前三层是满二叉树,最后一层只有一个即1+2+4+1=8
6.
后是左右根,所以C是根,根据中序(左根右)得到DEBA均是C左子根
根据后序的DABE得到E是DABE的根,再由中序的DEBA得到D是E的左字根,BA是E的右子根
后序是左右根是AB,而中序是左根右是BA,正好相反则当没有左时正好是右根和根右,即B是根A是右

7.一颗高度为h的完全二叉树至少有_____2h-1__个结点
答:最少的情况就是前h-1层是满的,第h层只有一个。
即2h-1-1(前h-1层)+1(第h层)
8.有n个结点,高度为n的二叉树的数目为_____2n-1__
答:结点数和高度相同,那么每层都只有一个结点。对于除根节点以外的结点都可能是左子树或右子树,即有两种可能,n-1个2相乘即为2n-1
9.二叉树遍历

注意中序先访问C的左而不是先访问W的左
10.树与森林之间的转换(左孩子右兄弟)
11.只要LTag为1表明线索为真即它肯定没左子树,为0表示一定有左子树
图
知识点
1.完全图:任意两个顶点都有边,都可达,有向完全图的边数(n*(n-1))是无向完全图的2倍
2.子图:一个图的一部分称为其子图
3.回路或环:简单来说就是转个圈
4.简单回路:转圈的过程不能有重复的点
5.连通图:图的每两个顶点都有一个到另一个的路径,若都互相可达就是强连通(不一定是完全图)
6.生成树:含图的全部顶点但只有n-1条边而且是连通图(就是用线串起来所有顶点)
7.邻接矩阵存储图,若没权值1代表有边,0代表没边。若有权值,有边存权值,没边存无穷大
8.图中度数之和为边数之和的2倍(一条边被两个顶点共用所以是2倍)
9.完全图要求每两个顶点都有一条边(无向时),连通图只要求两个顶点之间存在路径就行(可能是多条边)
10.深度优先(DFS)即越深越好,直到不能再深了这时退到上一层继续深度优先。类似先序借助于栈(递归)
广度优先(BFS)就是越广越好类似层次遍历,而且先被访问的节点其子节点也先被访问。借助于队列(存放被访问的结点)
广度和深度若用邻接矩阵实现时间复杂度为O(n2),邻接表是O(n+e)即O(顶点+边)
层次遍历就是一层一层从左到右遍历
树的先序,中序,后序遍历用栈,层次遍历用队列。
11.最小生成树:加权连通图的最小权值生成树,常用于修一条路使得可到所有顶点且花费最小
普里姆(Prim)算法:加点不构成回(选可达的最小的点)适合稠密图
克鲁斯卡尔(Kruskal)算法:加边不构成回(选现有的最小的边)适合稀疏图
12.v(vertex)是顶点,e(edge)是边
13.求某个点到其余各点的最短路径:迪杰斯特拉(Dijkstra)算法O(n2)(必考)
求每对顶点的最短路径:弗洛伊德(Floyd)算法O(n3)(不常考)
Floyd:比如求v0到其他顶点,在邻接矩阵中,v0这一行这一列这一主对角线划掉,剩下的中间经过v0看是否比原来路径短,若短则更新
14.拓扑排序:对有向无环图的顶点的一种排序
15.AOV网:在有向图中,用顶点表示活动,弧表示活动间的优先关系,则称此有向图为用顶点表示活动的网络(Activity On Vertex Network翻译即在顶点上的活动)
16.拓扑排序可以解决先决条件问题,比如学院有的课是其他课的基础,怎样排课的问题
找到入度为0的点输出,删除该点的所有出边,找到剩余点中入度为0的点输出,删除所有出边,重复操作(借用队列实现,若入度为0则入队,当前没有入度为0的点则出队,也可用栈二者结果不同)
17.AOE网:用顶点表示事件,弧表示活动(注意和AOV网相反),弧上的权值表示活动持续时间(Activity On Edge Network)。其用于研究 1.完成工程最短时间 2.哪些活动是影响工程的关键
18.关键路径:即从源点(起始点)到汇点(最终点)最长的路径,路径上的活动称为关键活动
19.事件的最早发生时间:从前往后找前驱节点到当前节点的最大时间 前面的都完成就发生就是最早
事件的最迟发生时间:从后往前,后继节点的最迟-边的权值(找其中最小的)超过最迟后面就无法完成
源点和汇点的最早(都为0)和最晚(路径最大值)相同
20.有向图的极大强连通子图,称为强连通分量
习题
1.
答:若要让顶点最少,就是顶点之间的边尽可能的多,最好每两个点都有边,又说是非连通,那么可以一个连通图加一个点。8个顶点有(8*7)/2=28条边加一个点就是非连通,所以是9个点
2.一个有n个结点的图,最少有(1 )个连通分量,最多有(n )个连通分量
答:最少就是整体是连通图时,最多就是每个顶点都是孤立的点,那么每个点都是连通分量,注意不可能有0个连通分量,只要有点(哪怕一个)就得是连通分量
3.N个顶点的无向连通图用邻接矩阵表示时,该矩阵 至少有 2(n-1) 个非零元素。
答:邻接矩阵非零元素的个数即图的边数之和的2倍(因为无向一条边会被存两次),图最少有n-1条边,那么矩阵最少有2(n-1)个非零元素
4.深度优先和广度优先遍历结果均不唯一


5.最小生成树问题

若是Kruskal算法即加边,第一次选取最小的一条边即(v1,v4)第二次最小的边是8即图中所示三个边
若是Prim算法即加点法,从V4开始,v4可到达的点中到达v1最小,然后v1和v4所能到达的其他点中(v1,v3)和(v4,v3)最小,所以答案为(v2,v3)
6.下图共有3种拓扑排序P

7.判断一个图是否有回路除了用拓扑排序还可以用深度优先遍历(若遍历转一圈回到自身即存在回路)
8.有向图可拓扑排序的判别条件是____不存在环____(拓扑排序的定义就是对有向无环图定义的)
9.邻接表示例 ,注意存的是顶点的数组下标,即使有权值也是存下标

10最小生成树计算过程(加边不构成回)

11.最短路径问题


12.AOE网问题

13.由邻接矩阵写成深度优先和广度优先遍历结果

深度优先:要求越深越好。第一行1和7有边,然后由7出发,7和3有边,然后由3出发,3和4有边…
广度优先:要求越广越好。第一行1和7,1和9有边(所以7和9是1的左右孩子),然后7和9同时出发…
14.由邻接表写成深度优先和广度优先遍历结果

广度优先:0出发,0后面有1,2,3。所以遍历结果为0 1 2 3
深度优先:0出发,0后面第一个为1,由1出发,1后面第一个0访问过了,所以访问2,由2出发。2后面0和1都被访问过了,所以访问3也是 0 1 2 3
注意深度优先给出邻接表不能画图求,画图比如0后面的1 2 3是没有次序的,先访问哪个都行。但是若给出邻接表那么一定先访问1,所以邻接表求深度优先遍历是唯一的
虽然这题二者结果相同,但思想不同(越深越好和越广越好)
15.用DFS遍历一个无环有向图,并在DFS算法退栈返回时打印相应的顶点,则输出的顶点序列是____逆拓扑有序___
答:比如有A->B->C。A先入栈,然后A可到B所以B入栈,B可到C所以C入栈,C没有可达的所以C出栈,然后是BA出栈。而拓扑排序先是A,删除A的出边,B入度为0所以是B,以此类推得到ABC
这题说的退栈返回打印顶点不是按照深度优先搜索的顺序输出,最先访问的在栈底最后才能弹出
16.假设一个有n个顶点和e条弧的有向图用邻接表表示,则删除与某个顶点V1相关的所有弧的时间复杂度是(C)
A.O(n) B.O(e) C.O(n+e) D.O(n*e)
答:要找到所有指向这个顶点的边,必须得遍历邻接表所有顶点然后遍历每个顶点的边看是否和V1相连,相当于对邻接表遍历,而邻接表遍历深度优先和广度优先都是O(n+e),注意不是O(n*e)
查找
知识点
1.线性表的查找(静态查找表)
- 顺序查找 (就是最简单的按顺序一个一个比较)
- 算法简单对表结构无要求
- 折半查找(二分查找) (要求是顺序存储有序表)
- data[mid] == k 找到元素,返回下标mid
- data[mid] > k high=mid-1 (k比中间值小,要向中间值左边继续找)
- data[mid] < k low=mid+1 (k比中间值大,要向中间值右边继续找)
- 助记:就是找到中间值比较待查元素和中间值,再换个中间值再比较
- 优点:比较次数少查找效率高,但不适于经常变动
- 分块查找 块之间有序(左块最大小于右块最小),块内任意,另建索引表放每块最大关键字
- 适用于既要快速查找又经常动态变化
2.折半查找的判定树:把中间位置的值作为树根,左边和右边的记录作为根的左子树和右子树
判定树的中序遍历(左根右)得到的是有序的序列(判定树左子树比根节点小,右子树比根节点大)
3.加入监视哨(存待查元素) 免去每一步查找都要判断是否查找完的情况,只要读到监视哨就说明没查到
4.树表的查找(动态(可插入删除)查找表)
- 二叉排序树(判定树就是二叉排序树,左比根小右比根大)
- 时间复杂度最好为O(log2n),最差退化成O(n)的顺序查找(如都只有1个分支)
- 平衡二叉树(AVL) 左右子树高度差绝对值不超过1
- 平衡因子:左子树的高度减去右子树的高度只能为0、-1、+1
- 由于后人发现树越矮查找效率越高因此发明了AVL,时间复杂度为O(log2n)
- B-树 适合外存文件系统索引
- B+树 适合做文件系统的索引
5.二叉排序树的删除:缺右补左,缺左补右,不缺左(左子树)中(中序)后(最后一个)
6.平衡调整:当插入一个结点破坏了平衡性就要调整
- LL型调整
- LR型调整
- RR型调整
- LR型调整
LL、LR等是对不平衡状态的描述
若是LL和RR型就把画圈的中间的那个掂起来(想想有重量,另外俩即自己落下去了)
若是LR和RL型就把画圈的最下面那个掂起来(另外俩也落到它两边)
若新插入结点在最小不平衡根节点的左(L)孩子的左(L)子树上即为LL型调整

若新插入结点在最小不平衡根节点的右®孩子的右左(L)子树上即为RL型调整

7.B-树(B树) m阶B-树,阶数其实就是树的度数 适合外存文件系统索引
- 根结点最少有两个分支 (叶子节点除外)
- 非终端结点最少有(m/2)上限个分支(根节点除外)
- 有n个分支的结点有n-1个关键字递增从左到右排列
- 叶子结点(失败结点)在同一层可用空指针表示,是查找失败到达的位置
8.B-树的查找 (类似于二叉树的查找,但是可以有三个或多个方向)
如查找关键字42。首先在根结点查找,因为42>30,则沿着根结点中指针p[1](下标从0开始)往右下走;因为39<42<45,则沿着子树根结点中指针p[1]往下走,在下层结点中查找关键字42成功,查找结束。
9.B+树是B-树的变型树,更适合做文件系统的索引。
- 叶子结点包含所有关键字从左到右递增且顺序连接
- 可从根节点随机查找也可从叶子结点顺序查找 (严格来讲,不算是树了)
10.散列表:根据给定的关键字计算关键字在表中的地址
负载(装载因子):表中结点/表的空间,所以表越满越容易发生冲突
冲突:不相等的关键字计算出了相同的地址
同义词:发生冲突的两个关键字
11.散列表的构造方法
- 数字分析法 取关键字的若干位或其组合做哈希地址
- 适用于事先知道关键字集合且关键字位数比散列地址位数多
- 平方取中法 关键字平方后取中间若干位
- 适用于不了解关键字或难从关键字找到取值较分散的几位
- 折叠法 分割关键字后将这几部分叠加(舍去进位)
- 适用于散列地址位数少,关键字位数多
- 除留取余法 取模运算(最常用)
12.处理冲突的方法
- 开放地址法
- 线性探测法 看下一个元素是否为空,当成一个循环表来看 (可能二次聚集)
- 二次探测法 原基础加12、-12、22、-22、32… (可避免二次聚集)
- 伪随机探测法 原基础加个伪随机数 (可避免二次聚集)
- 链地址法 相同地址的记录放到同一个单链表中 (可避免二次聚集)
习题
1.折半查找求判定树
答:先找中间的值,(1+20)/2=10,所以1-9为10的左子树(比根小),11-20为10的右子树。
比较时先和10比较,若比10小,则比较1-9,那先和谁比较呢,1-9中的中间值为(1+9)/2=5,所以先和5比较(即5和10相连)。如果还比5小,那就要和1-4比了,同样1-4先和谁比呢,1-4的中间值(1+4)/2=2,所以先和2比较(即2和5相连比5小在左边),其他依次类推
查找为4的有1、3、6、8、11、13、16、19(依次和10,15,18,19比较所以4次)
2.用顺序表和单链表表示的有序表均可使用折半查找方法来提高查找速度。 (错)
答:单链表无法使用折半查找必须是顺序存储,因为要取中间值
3.二叉排序树序列判定
答:二叉排序树序列可理解为一个元素与二叉排序树比较的记录构成的序列。A中91后面是24说明待查元素X比91小所以后面是24,而24后面是94,说明X比24大,但是24前面已经比较过91了(说明已经肯定比91小了),现在后面又来了个94显然是错的
4.
答:装填因子越大就越满越可能发生冲突。冲突少减少不必要的查找。
不能完全避免聚集(不是同义词却抢相同地址)只能减少但可避免二次聚集
5.n个元素的表做顺序查找时,若查找每个元素的概率相同,则平均查找长度为_______(n+1)/2______
答:总查找次数为1+2+3+…+n=n(n+1)/2,则平均查找长度为N/n=(n+1)/2
6.如果要求一个线性表既能较快的查找,又能适应动态变化的要求,最好采用 (C)
A.顺序查找 B.折半查找 C.分块查找 D.哈希查找
答:分块查找的优点是:在表中插入和删除数据元素时,只要找到该元素对应的块, 就可以在该块内进行插入和删除运算。由于块内是无序的,故插入和删除比较容易,无需进行大量移动。如果线性表既要快速查找又经常动态变化,则可采用分块查找。严版P198
7.对22个记录的有序表作折半查找,当查找失败时,至少需要比较 ( 4 ) 次关键字。
答:4层的满二叉树有24-1=15个结点,5层的有31。题目是22个结点,所以是前4层是满二叉树,第五层不是满的,因此最少4次,最多5次。
8.下面关于 B- 和 B+ 树的叙述中,不正确的是( C)。
A. B- 树和 B+ 树都是平衡的多叉树 B. B- 树和 B+ 树都可用于文件的索引结构
C. B- 树和 B+ 树都能有效地支持顺序检索 D. B- 树和 B+ 树都能有效地支持随机检索
答:B+树支持顺序(从最小的关键字叶子起从左到右),而B-树因为其叶子结点互相没连接只支持从根节点起随机检索
9.假定对有序表: (3, 4,5,7,24,30,42,54,63,72,87,95) 进行折半查找,
①画出描述折半查找过程的判定树;
②若查找元素90,需依次与哪些元素比较?
③假定每个元素的查找概率相等,求查找成功时的平均查找长度。
答:1️⃣ 画判定树一般先画出坐标的判定树,再根据坐标填值即可,注意取下界及low和high的变化
2️⃣ 需要与30、63、87、95比较
3️⃣ 前3层:1+2*2+4*3=17 第四层:5*4=20
ASL=(17+20)/ 12 = 3.08 即总查找次数除总个数
10.设哈希函数 H(K) =3 K mod 11 ,哈希地址空间为 0~ 10 ,对关键字序列( 32, 13 ,49, 24 , 38, 21 , 4, 12),按下述两种解决冲突的方法构造哈希表,并分别求出等概率下查找成功时和查找失败时的平均查找长度 ASLsucc 和 ASLunsucc 。
① 线性探测法;
② 链地址法。
答:1️⃣ 散列地址就是若算的关键字为空就放里面,不为空就往后找
ASLsucc = ( 1+1+1+2+1+2+1+2 ) /8=11/8
ASLunsucc =( 1+2+1+8+7+6+5+4+3+2+1 ) /11=40/11
因为最多成功查8个元素,所以查找成功时分母为8,分子就是每个元素查找的次数之和
而查找失败时可能计算得到的地址有11种,即分母为11,而关键字为空的查一次就知道失败了(要是有也不会为空),若不为空要往后找直到找到第一个空元素(说明确实没有这个元素不然该放到这个空着的位置了)
2️⃣ 链地址就是要是地址被占了放后面挂着就行

ASLsucc = ( 1*5+2*3 ) /8=11/8 第一列查一次就知道了第二列要查两次
ASLunsucc =( 1+2+1+2+3+1+3+1+3+1+1 ) /11=19/11
失败的情况:查一次若为空说明肯定不存在,若不为空继续向下查直到为空说明到底了查找失败(比如第二行需要查两次,第一次查到为4,第二次查到了空,记住不是查一次就行)
总结:查找成功看位置,查找失败就找空
11.适宜于对动态查找表进行高效率查找的组织结构是 (C)
A.有序表 B. 分块有序表 C. 三叉排序表 D. 线性
答:如果线性表既要快速查找又要经常动态变化,则可采用分块查找。而这里说的是动态查找表,分块查找属于静态查找表。动态即要进行修改。有序表和线性不适合修改操作
排序
知识点
1.稳定性:排序前和排序后相同关键字的相对位置不发生变化就是稳定的
若关键字都不重复稳定与否无关紧要,若重复就得具体分析
2.排序算法的分类 (以下均是非递减排序的情况)
- 插入排序:一个有序的序列,新来的一个插入后仍然有序
- 直接插入排序:第n趟将第n个待排序关键字插入到前面(前n个有序)
- 折半插入排序:与直接插入不同的是查找插入位置时用的是折半查找
- 希尔排序:对间隔为n的元素排序,缩小间隔直至为1后简单插入排序
- 交换排序:核心在于交换,比如排高低,每倆换一下后,可能还得换
- 冒泡排序:1号与2号比较然后2号与3号比较…,可确定最大的元素放在最后
- 快速排序:选一枢轴,两边指针往中间移(先移右)使得比枢轴小的移到其左边
- 选择排序:核心是选择,每趟选最大或最小与第一个或最后一个元素交换
- 简单选择排序:第n趟选择最小和第n个位置元素交换
- 树形选择(锦标赛)排序:对树如8个选4个最小,4个选倆,2选1,置最小无穷大
- 堆排序:调整成堆,根和末尾编号交换输出(每趟得到一个最大值),重复操作
- 归并排序:比如每两个归并成一组有序序列,再每两组归并成一大组有序序列
- 基数排序:选个位放到对应桶中再选十位放到对应桶中,依次类推
3.各方法时间空间复杂度和稳定性比较
排序方法 | 时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|
直接插入排序 | O(n2) n比较×n移动 | O(1) 插入排序都为1 | 稳定 |
折半插入排序 | O(n2) n比较×n移动 | O(1) | 稳定 |
希尔排序 | O(n1.3) 研究证明 | O(1) | 不稳定 |
冒泡排序 | O(n2) | O(1) | 稳定 |
简单选择排序 | O(n2) | O(1) | 不稳定 |
锦标赛排序 | O(nlog2n) | O(n) | 稳定 |
快速排序 | O(nlog2n) | O(log2n) | 不稳定 |
堆排序 | O(nlog2n) | O(1) | 不稳定 |
归并排序 | O(nlog2n) | O(n) | 稳定 |
基数排序 | O(d(n+rd)) 每个记录d个关键字 | O(n+rd) n个记录 | 稳定 |
稳定性:希尔快速选择堆不稳,其他都稳
时间:
-
除特例外插入和交换类时间都是O(n2),剩下的时间都是O(nlog2n)
-
可记忆为因为简单所以时间长,快速是最快的不可能是O(n2),希尔是最怪的,基数是最长的
空间:
- 树形(锦标赛)分叉多或赛道多而归并要一级一级选占空间最多,快速去掉n,基数去掉d
- 其他都是O(1)
4.关键字较少 ,选取简单的:
- 直接插入排序 (最简单,性能最佳)
- 冒泡排序
关键字较多,就用先进的:
- 关键字较乱,不要求稳定性:快速排序
- 关键字基本有序,就用堆排序或归并排序
- 不要求稳定性:堆排序
- 要求稳定性:归并排序
关键字多但都较小:基数排序
习题
1.设待排序的关键字序列为{12,2, 16, 30, 28, 10, 16*, 20, 6, 18}, 试分别写出使用以下排序方法, 每趟排序结束后关键字序列的状态
①直接插入排序 (第n趟将第n个待排序关键字插入到前面已排序的序列)
原序列为{12,2, 16, 30, 28, 10, 16*, 20, 6, 18}
[2] 12 16 30 28 10 16* 20 6 18 第一趟第一个有序
[2 12] 16 30 28 10 16* 20 6 18 第2个即12与前面的排序
[2 12 16] 30 28 10 16* 20 6 18 第3个即16与前面的排序
[2 12 16 30] 28 10 16* 20 6 18 前4个有序
[2 12 16 28 30] 10 16* 20 6 18 前5个有序
[2 10 12 16 28 30] 16* 20 6 18
[2 10 12 16 16* 28 30] 20 6 18
[2 10 12 16 16* 20 28 30] 6 18
[2 6 10 12 16 16* 20 28 30] 18
[2 6 10 12 16 16* 18 20 28 30] 最后一个与前面的排序 (查找插入位置是依次比)
②折半插入排序
排序过程同①,只不过查找插入位置用的是折半查询
③希尔排序 (增量选取5,3,1)
原序列为{12,2, 16, 30, 28, 10, 16*, 20, 6, 18}
10 2 16 6 18 12 16* 20 30 28 (增量选取 5)第1个和第6个排序,第2和第7…
6 2 12 10 18 16 16* 20 30 28 (增量选取 3)第1和第4,第2和第5…
2 6 10 12 16 16* 18 20 28 30 (增量选取 1) 就是直接插入排序
④冒泡排序 (1号与2号比较然后2号与3号比较…,可确定最大的元素放在最后)
原序列为{12,2, 16, 30, 28, 10, 16*, 20, 6, 18}
2 12 16 28 10 16* 20 6 18 [30] 12与2比较交换,12和16比较,16和30比较…
2 12 16 10 16* 20 6 18 [28 30] 每一趟确定一个最大的放最后
2 12 10 16 16* 6 18 [20 28 30] 第3趟确定3个最大
2 10 12 16 6 16* [18 20 28 30] 确定4个最大
2 10 12 6 16 [16* 18 20 28 30]
2 10 6 12 [16 16* 18 20 28 30]
2 6 10 [12 16 16* 18 20 28 30]
2 6 10 12 16 16* 18 20 28 30]
⑤快速排序 (选一枢轴,两边指针往中间移使得比枢轴小的移到其左边,先移右指针)
原序列为{12,2,16, 30, 28, 10, 16*, 20, 6, 18}
12 [6 2 10] 12 [28 30 16* 20 16 18] 先让右指针往左移
6 [2] 6 [10] 12 [28 30 16* 20 16 18 ] 一般选第一个为枢轴
28 2 6 10 12 [18 16 16* 20 ] 28 [30 ]
18 2 6 10 12 [16* 16] 18 [20] 28 30
16* 2 6 10 12 16* [16] 18 20 28 30
⑥简单选择排序 (第n趟选择最小和第n个位置元素交换)
原序列为{12,2,16, 30, 28, 10, 16*, 20, 6, 18}
[2] 12 16 30 28 10 16* 20 6 18 最小的2和第一个12交换
[2 6 ]16 30 28 10 16* 20 12 18 最小的6和第二个12交换
[2 6 10 ]30 28 16 16* 20 12 18 最小的10和第三个16交换
[2 6 10 12] 28 16 16* 20 30 18 最小的12和第四个30交换
[2 6 10 12 16] 28 16* 20 30 18
[2 6 10 12 16 16* ]28 20 30 18
[2 6 10 12 16 16* 18 ]20 30 28
[2 6 10 12 16 16* 18 20 ]28 30
[2 6 10 12 16 16* 18 20 28] 30
⑦堆排序
原序列为{12,2, 16, 30, 28, 10, 16*, 20, 6, 18}
18 12 16 20 28 10 16* 2 6 [30] 得到最大值30,继续调整交换
6 20 16 18 12 10 16* 2 [28 30] 得到两个最大值,继续调整交换
… 由于此题没有答案,下面类似
建堆(按编号即层次遍历)然后调整堆(从最后面的非叶子结点向前选择最大的放到根,可能不止调整一趟)。
然后交换根和最后一个编号(注意不是最小),再重新调整交换重复操作
⑧二路归并排序 (每两个归并成一组有序序列,再每两组归并成一组有序序列)
原序列为{12,2, 16, 30, 28, 10, 16*, 20, 6, 18}
[2 12] [16 30] [10 28] [16 * 20] [6 18] 每两个合为一组
[2 12 16 30] [10 16* 20 28] [6 18] 每两组即四个合为一组
[2 10 12 16 16* 20 28 30] [6 18] 每两组即八个合为一组
[2 6 10 12 16 16* 18 20 28 30 ]
2.树形选择(锦标赛)排序
原序列为{49,38, 65, 97, 76, 13, 27, 49*}
对树8个选4个最小,4个选倆,2选1,选中13为最小输出,置最下面13为无穷大,重复操作

3.基数排序
原序列为{278,109,063,930,589,184,505,269,008,083}
准备10个桶,第一趟收集按个位放到对应桶中
即结果为:930 063 083 184 505 278 008 109 589 269 (个位已经有序)
第二趟收集按十位放到对应桶中
即结果为:505 008 109 930 063 269 278 083 184 589
我们可以看到最低2位已经有序了,只需再来一趟收集即可,就不写了
4.根据结果写排序方法
5.对 n 个不同的排序码进行冒泡排序, 在元素无序的情况下比较的次数最多为( )
答:比较次数最多时,第一趟比较 n-1 次,第二趟比较 n-2 次, 最后一趟比较 1
次,即 (n-1)+(n-2)+…+1= n(n-1)/2
6.若一组记录的排序码为( 46, 79 , 56,38 , 40,84),则利用快速排序的方法,以第一个记录为基准得到的一次划分结果为()
答:左右设两指针,右指针先移,84比46大不移动,40比46小所以40覆盖46的位置,然后该左边的指针移动了,79比46大,所以移到空着的原40的位置。然后该右指针移了,38比46小所以38覆盖空着的原79位置,左边的56比46大移到空着的原38,然后将46放到空着的原56即可。结果为:40,38,46,56,79,84
7.数据表中有 10000 个元素,如果仅要求求出其中最大的 10 个元素,则采用 ( D )
算法最节省时间
A.冒泡排序 B .快速排序 C.简单选择排序 D.堆
答:堆用大根堆一趟选取一个最大的最快
冒泡每两个比较,有10000个肯定慢。快速是选枢轴,再左右移动也慢
简单选择每一趟都几乎快遍历一遍也肯定慢
8.下列排序算法中, ( A )不能保证每趟排序至少能将一个元素放到其最终的位置上
A.希尔排序 B .快速排序 C. 冒泡排序 D.堆
答:快速排序的每趟排序能将作为枢轴的元素放到最终位置;冒泡排序的每趟排序能将最大或最小的元素放到最终位置;堆排序的每趟排序能将最大或最小的元素放到最终位置。而希尔排序只是对间隔为n的元素排序所以不确定。
这种让选择哪个排序的需要知道每个排序大致是咋排的就很好选择
各类型存储结构
顺序表

#define MAXSIZE 100 //顺序表可能达到的最大长度
typedef struct
{
ElemType *elem; //存储空间的基地址(例如用L.elem[0]取元素)
int length; //当前长度
}SqList;
L.elem[i]取值 //L.length-1=>i>=0,如元素为1,2,3,L.length=3,i=0,1,2
单链表

typedef struct LNode
{
ElemType data; //结点的数据域
struct LNode *next; //结点的指针域,指向下一结点
}LNode,*LinkList; //LinkList为指向结构体LNode的指针类型(相当于LNode *)
若带头结点,空表条件为L->next==NULL(L为头指针指向头结点永不为空)
若不带头结点,空表条件为L==NULL
双向链表

typedef struct DuLNode
{
ElemType data; //结点的数据域
struct DuLNode *prior; //指向直接前驱
struct DuLNode *next; //指向直接后继
}DuLNode,*DuLinkList;
顺序栈

#define MAXSIZE 100 //顺序栈存储空间的初始分配量
typedef struct
{
SElemType *base; //栈底指针
SElemType *top; //栈顶指针
int stacksize; //栈可用的最大容量
}SqStack;
栈空:S.top==S.base //首尾指针相同
栈满:S.top-S.base==S.stacksize //尾-首等于最大容量即为满
链栈

//定义类似,类似操作受限的单链表
typedef struct StackNode
{
ElemType data; //数据域
struct StackNode *next; //指向下一结点
}StackNode,*LinkStack;
链栈一定是没有头结点,所以栈空的条件为:S==NULL
循环队列

#define MAXSIZE 100 //队列可能达到的最大长度
typedef struct
{
QElemType *base; //存储空间的基地址
int front; //头指针(只是有指针的作用,例如用Q.base[Q.front]取元素)
int rear; //尾指针
}SqQueue;
队空:Q.front==Q.rear //首尾指针相同
//尾指针指向的为最后一个元素的下一个地址(永远为空),所以+1
队满:(Q.rear+1)%MAXSIZE==Q.front
链队

//只看第一个定义和单链表类似,不同的是第二个设了队头和队尾指针
typedef struct QNode
{
QElemType data; //数据域
struct QNode *next; //指向下一结点
}QNode,*QueuePtr;
typedef struct
{
QueuePtr front; //队头指针(相等于QNode *front)
QueuePtr rear; //队尾指针
}LinkQueue;
队空:Q.front=Q.rear
由于串、数组、广义表的存储结构不是重点在这里就不再列出其存储结构
小结
栈和队列除了链栈都有头尾指针
顺序二叉树(不常用)

#define MAXSIZE 100 //二叉树的最大结点数
typedef TElemType SqBiTree[MAXSIZE] //0号存储根结点
SqBiTree bt;
二叉链表(常用)

typedef struct BiTNode
{
TElemType data; //结点数据域
struct BiTNode *lchild,*rchild; //左右孩子指针
}BiTNode,*BiTree;
线索二叉树

typedef struct BiThrNode
{
TElemType data;
struct BiThrNode *lchild,*rchild; //左右孩子指针
int LTag,RTag; //左右标志
}BiThrNode,*BiThrTree;
孩子兄弟二叉树

tyrpedef struct CSNode //又称二叉链表表示,本质存的是树用类似存二叉树的方法存
{
ElemType data;
struct CSNode *firstchild,*nextsibling; //即左是孩子右是兄弟
}CSNode,*CSTree;
邻接矩阵

#define MaxInt 32767 //表示极大值,即∞
#define MVNum 100 //最大顶点数
typedef char VertexType; //假设顶点的数据类型为字符型
typedef int ArcType; //假设边的权值类型为整型
typedef struct{
VertexType vexs[MVNum]; //顶点表
ArcType arcs[MVNum][MVNum]; //邻接矩阵
int vexnum,arcnum; //图的顶点数和边数
}AMGraph;
邻接表

//注意存的是顶点数组下标不是存的顶点本身
typedef struct ArcNode { //边结构
int adjvex; //该边所指向的顶点位置
struct ArcNode *nextarc; //指向下一条边的指针
OtherInfo info; //和边相关的信息
} ArcNode;
#define MVNum 100
typedef struct VNode{ //顶点结构
VertexType data; //顶点信息
ArcNode * firstarc; //指向依附该顶点的第一条弧的指针
} VNode, AdjList[MVNum];
typedef struct { //图结构
AdjList vertics ; //邻接表
int vexnum, arcnum; //顶点数和弧数
int kind; //图的种类
} ALGraph;