数据结构随心复习笔记
数据结构随心复习
文章目录
递归
想在开头先提一嘴递归,虽然在工程应用中使用的可能不太多,但是在数据结构中很多地方用到了这个东西
看了不少文章,最重要的三点
1.明确函数的作用
2.寻找递归结束的条件
3.寻找递归的关系式
递归要有大局观,即在思路没有问题的情况下,我们就宏观上认为这个函数一定能解决子问题,然后就假定问题的规模缩小,此时问题转化为什么情形,据此写出代码。
Chapter1 绪论
时间复杂度
-
若干层嵌套循环的时间复杂度等于各层循环次数的乘积再乘以循环体代码的复杂度
如下面2层嵌套循环的时间复杂度为 O ( n 2 ) O(n^2) O(n2)
for( i=0; i<N; i++ ){ for( j=0; j<N; j++ ){ x = y*x + z; k++; } }
-
if-else结构的复杂度取决于if的条件判断复杂度和两个分枝部分的复杂度,总体复杂度取三者中最大
如下面代码的复杂度为 m a x ( O ( f 1 ) , O ( f 2 ) , O ( f 3 ) ) max(O(f1),O(f2),O(f3)) max(O(f1),O(f2),O(f3))
if (P1) /* P1的复杂度为O(f1)*/ P2;/* P2的复杂度为O(f2) */ else P3; /* P3的复杂度为O(f3) */
算法
-
算法的五个特性:输入、输出、有穷性、可行性、确定性
-
算法的时间复杂度取决于问题的规模
Chapter2 线性表
- 线性结构的基本特征
- 集合中必存在唯一的一个“第一元素”;
- 集合中必存在唯一的一个“最后元素”;
- 除最后元素之外,均有唯一的后继;(所有结点均有0或1个后继)
- 除第一元素之外,均有唯一的前驱。(所有结点均有0或1个前驱)
顺序表
-
顺序表插入算法的时间复杂度
- 若假定在任何一个位置上插入元素的概率相同,则平均移动次数为: E I S ( n ) = ∑ i = 1 n + 1 n − i + 1 n + 1 E_{IS}(n)={\displaystyle \sum^{n+1}_{i=1}{\frac{n-i+1}{n+1}}} EIS(n)=i=1∑n+1n+1n−i+1 , O ( n ) O(n) O(n)
-
顺序表删除算法的时间复杂度
- 若假定在线性表中任何一个位置上进行删除的概率都是相等的,则移动元素的期望值为: E I S ( n ) = ∑ i = 1 n n − i n = n − 1 2 E_{IS}(n)={\displaystyle \sum^{n}_{i=1}{\frac{n-i}{n}}}=\frac{n-1}{2} EIS(n)=i=1∑nnn−i=2n−1, O ( n ) O(n) O(n)
-
顺序表的优点:
- 节省存储空间;
- 对线性表中第i个结点的操作易于实现;
- 容易查找一个结点的前驱和后继。
-
顺序表的缺点:
- 插入和删除操作需要移动数据;
- 建立空表时,较难确定所需的存储空间。
链表
-
结构特点:
- 逻辑次序和物理次序不一定相同;
- 元素之间的逻辑关系用指针表示;需要额外空间存储元素之间的关系。
-
头结点的作用:
-
使对第一个元素的操作与对其它元素的操作保持一致。
-
单链表的插入
s->next=p->next;p->next=s
(在p所指结点后插入)时间复杂度为 O ( L i s t L e n g t h ( L ) ) O(ListLength(L)) O(ListLength(L))
-
单链表的删除
q=p->next; p->next=q->next; e=q->data;//保存删除的结点的数据 free(q);
(删除结点为q)
时间复杂度为 O ( L i s t L e n g t h ( L ) ) O(ListLength(L)) O(ListLength(L))
-
头插法:
p=(LNode *)malloc (sizeof (LNode)); P->data=e; p->next=L->next; L->next=p;
-
链表的优缺点:
- 插入和删除不需要移动数据(时间复杂度低);
- 不需预先分配空间。
-
缺点:
- 指针占用存储空间,增加了内存负担;
- 只能顺序查找(操作)
-
循环链表
- 单链表最后一个结点的指针域没有利用,如果使其指向头指针(头结点),则首尾构成一个循环,称作循环链表
- 指向表尾的指针称为尾指针
- 采用头指针时,查找第一个结点容易,查找尾结点不容易,如果采用尾指针,则查找第一个结点和最后一个结点都容易。
-
双向链表
- 在双向链表中:p->prior指向p的前驱,p->next指向p的后继
- 查询和单链表相同。插入和删除时需要同时修改两个方向上的指针。
Chapter3 栈和队列
栈
-
FILO
-
栈顶(top)是栈中允许插入和删除的一端。
栈底(bottom)是栈顶的另一端
-
顺序栈
typedef struct{ SElemType*base;//基地址 SElemType*top;//栈顶指针 int stacksize;//栈容量 } SqStack; SqStackS;
栈空:
S.top==S.base
S.top指向的是栈顶元素的下一个位置,比如入栈操作为:
*S.top=e; S.top++;
-
然而在阅读CSDN很多大佬写栈时,通常用静态数组,不局限于栈的本身,而是简洁为美,聚焦于解决问题本身。
SElemType Stack[MAX_STACK_SIZE]; int top=-1; //入栈操作 Stack[++top]=e; //出栈操作 top--;
-
链栈
typedef struct SNode{ SElemType data;//数据域 struct Snode *next;//链域 }SNode, *LinkStack;
- 入栈操作与单链表头插法一致
- 出栈操作相当于删除单链表的第一个元素
队列
-
FIFO,队头删除,队尾插入
-
链队列
typedef struct QNode{// 结点类型 QElemType data; struct QNode *next; }QNode, *QueuePtr; typedef struct {// 链队列类型 QueuePtr front;// 队头指针 QueuePtr rear;// 队尾指针 }LinkQueue;
-
带头结点的链队列Q为空的条件:
Q.front==Q.rear
-
出队
if(Q.front == Q.rear) return ERROR; p = Q.front->next; e = p->data; Q.front->next = p->next; if (Q.rear == p) //如果队列只有一个结点,尾指针你就别乱指了,老老实实回来跟着头指针 Q.rear = Q.front; free (p);
-
-
循环队列
- 有n个单元的循环队列中,队满时只有n-1个元素
typedef struct{ QElemType*base; // 动态分配存储空间 int front;// 头指针,若队列不空指向队列头元素 int rear;// 尾指针,若队列不空,指向队列尾元素的下一个位置 }SqQueue; SqQueue Sq;
队列长度:
Sq.rear-Sq.front;
队头元素:
Sq.base[Sq.front];
队尾元素:
Sq.base[Sq.rear-1];
入队:
Sq.rear=(rear+1)%maxsize
出队:
Sq.front=(front+1)%maxsize
队满的条件:
(sq.rear+1) mod maxsize==sq.front
队空的条件:
sq.front==sq.rear
队列的长度:
(Q.rear-Q.front+MaxSize)%MaxSize
-
可以用带尾指针的循环单链表来模拟队列
Chapter4 串
请允许我以最高的敬意膜拜Knuth,Morris和Pratt三位大神
- 串的朴素匹配算法最坏情况的时间复杂度: O ( m ∗ n ) O(m*n) O(m∗n)
- KMP算法时间复杂度可以达到 O ( m + n ) O(m+n) O(m+n)
KMP算法详解请移步:KMP (我是不会在考试前一天详细写KMP详解的,等等,喂,自己都不会KMP你详解个屁啊混蛋!)
Chapter5 数组和广义表
数组
-
数组元素的地址关系:
- 数组在内存中主要采用两种存储方式:
- 以行序为主的存储方式;
- 以列序为主的存储方式
不同的存储方式有不同元素地址计算方法。
- 数组在内存中主要采用两种存储方式:
-
稀疏矩阵
typedef struct{ int i, j; //该非零元的行下标和列下标 ElemType e; // 该非零元的值 } Triple; // 三元组类型 typedef struct{ Triple data[MAXSIZE + 1]; //data[0]未用 int mu, nu, tu; //行数、列数、总数 } TSMatrix; // 稀疏矩阵类型
广义表
-
原子:如果ai是单个元素,称为原子,一般用小写字母表示;
-
子表:如果ai是广义表,称为子表,一般用大写字母表示。
-
表头(Head):非空广义表的第一个元素a1;
-
表尾(Tail):除了表头的其余元素组成的表;
-
深度:广义表中括号嵌套的最大层数。
Chapter6 树和二叉树
-
基本
- 结点的层次:假设根结点的层次为1,第l层的结点的子树根结点的层次为l+1
- 二叉树
- 对任何一棵二叉树,若它含有 n 0 n_0 n0 个叶子结点、 n 2 n_2 n2 个度为2的结点,则必存在关系式: n 0 = n 2 + 1 n_0= n_2+1 n0=n2+1
- 二叉树的第i层上至多有 2 i − 1 2^{i-1} 2i−1个结点。
- 深度为k 的二叉树上至多含 2 k − 1 2^k-1 2k−1 个结点(k≥1)
- 具有n个结点的完全二叉树的深度为 ⌊ l o g 2 n ⌋ + 1 \lfloor{log_2n}\rfloor+1 ⌊log2n⌋+1
-
二叉树的遍历
- 先序、中序、后序遍历
- 递归算法人人都会
- 非递归算法用栈实现,例如非递归中序遍历:
void inOrderByStack(BiTree T){ /* 从当前节点开始遍历 (当入栈时访问节点内容,则为前序遍历;出栈时访问,则为中序遍历) 1. 若当前节点存在,就存入栈中,并访问左子树; 2. 直到当前节点不存在,就出栈,并通过栈顶节点访问右子树; 3. 不断重复12,直到当前节点不存在且栈空。 */ BiTree Stack[100]; int top = -1; BiTree p = T; while(p!=NULL||top!=-1){ if(p!=NULL){ //将左子树中的所有结点都入栈 Stack[++ top] = p; p = p->lchild; }else{ //p为空(到达最左下时),且栈不空,检验右子树 p = Stack[top --]; visit(p); //出栈时,访问输出 p = p->rchild; //如果p没有右子树则将会一直else,则打印栈顶的元素 } } }
-
层次遍历
队列
- 先序、中序、后序遍历
-
统计二叉树中叶子结点、复制二叉树、求二叉树的高度
-
递归
-
先缀表达式建树
如-*+abc/de
- 操作数为叶子结点
- 运算符为分支节点
-
由先序和中序序列建二叉树
根据先序找到中序序列中根节点,然后划分出左右子树,再递归求解即可
-
线索二叉树
- n个结点有n+1个空链域
- 若
p->lchild==NULL
,则在线索二叉树中p->lchild
保存其前驱 - 若
p->rchild==NULL
,则在线索二叉树中p->rchild
保存其后继 - 线索二叉树中,可以找到中序前驱和后继,但是找不到前序前驱和后序后继
-
树的二叉链表
- 左孩子,右兄弟
-
森林转换为二叉树
- 左孩子,右兄弟
- 森林的先序遍历:若森林不空,则访问森林中第一棵树的根结点;先序遍历森林中第一棵树的子树森林;先序遍历森林中(除第一棵树之外)其余树构成的森林。即:依次从左至右对森林中的每一棵树进行先根遍历
- 森林的中序遍历:若森林不空,则中序遍历森林中第一棵树的子树森林;访问森林中第一棵树的根结点;中序遍历森林中(除第一棵树之外)其余树构成的森林。即:依次从左至右对森林中的每一棵树进行后根遍历。
-
HuffmanTree
- n个叶子结点总共进行n-1次合并
- 画图即可
Chapter7 图
图的定义
- 无向完全图(Undirected Complete Graph):无向图且边数为 n ( n − 1 ) 2 \frac{n(n-1)}{2} 2n(n−1),则称其为无向完全图;
- 有向完全图(Directed Complete Graph):有向图且边数为 n ( n − 1 ) n(n-1) n(n−1) ,则称其为有向完全图
- 顶点的度:一个顶点v的度是与它相关联的边的条数,记作D(v)。
- 顶点v的入度:是以v为终点的有向边的条数, 记作ID(v);
- 顶点v的出度:是以v为始点的有向边的条数,记作OD(v)
- 边数 e = ∑ i = 1 n D ( V i ) 2 e={\displaystyle \sum^{n}_{i=1}{\frac{D(V_i)}{2}}} e=i=1∑n2D(Vi) (很显然,一条边对应两度)
- 连通图: 在无向图G=< V, E >中,若对任何两个顶点v、u 都存在从v 到u 的路径,则称G是连通图。
- 强连通图: 在有向图G=< V, E >中,若对任何两个顶点v、u 都存在从v 到u 的路径,则称G是强连通图。
- 连通分量: 无向图中的极大连通子图。
- 强连通分量: 有向图中的极大强连通子图。
- 网络(边带权图): 某些图的边具有与它相关的数, 称之为权。这种带权图叫做网络。
- 生成树:连通图的一个子图如果是一棵包含G的所有顶点的树,则该子图称为G 的生成树。
图的存储结构
-
邻接矩阵
#define INFINITY INT_MAX //最大值 ∞ #define MAX_VERTEX_NUM 20//最大顶点个数 typedef char InfoType; typedef int VRType;//顶点关系类型,即矩阵的元素类型 typedef char VertexType;//顶点类型 typedef enum{DG,DN,UDG,UDN}GraphKind;//{有向图,有向网,无向图,无向网} typedef struct ArcCell { // 弧的定义 VRType adj; // VRType是顶点关系类型。 // 对无权图,用1或0表示相邻否; // 对带权图,则为权值类型。 InfoType *info; // 该弧相关信息的指针 } ArcCell,AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM]; typedef struct { // 图的定义 VertexType vexs[MAX_VERTEX_NUM]; // 顶点信息 AdjMatrix arcs; // 弧的信息 int vexnum, arcnum; // 顶点数,弧数 GraphKind kind; // 图的种类标志 } MGraph;
- 特点:
- 时间复杂度为 O ( n 2 ) O(n^2) O(n2)
- 边或弧的删除和插入操作容易。顶点的插入删除操作不容易。
- 特点:
-
邻接表(若为有向图,则为出边表)
typedef struct ArcNode { //弧的结点结构 int adjvex; // 该弧所指向的顶点的位置 struct ArcNode *nextarc; // 指向下一条弧的指针 VRType adj; // 弧<v1,v2>的权值 InfoType *info; // 该弧相关信息的指针 }ArcNode; typedef struct VNode { //顶点的结点结构 VertexType data; // 顶点信息 ArcNode *firstarc; // 指向第一条依附该顶点的弧 }VNode, AdjList[MAX_VERTEX_NUM]; typedef struct { //图的结构定义 AdjList vertices; int vexnum, arcnum; int kind; // 图的种类标志 }
- 对于顶点多边少的图采用邻接表存储节省空间;空间复杂度 O ( n + e ) O(n+e) O(n+e)。
- 容易找到任一顶点的第一个邻接点。
-
逆邻接表即出边表
-
十字链表
图的遍历
深度优先遍历(DFS)
-
过程
输入:图G,初始访问节点v; 输出:图G的深度优先搜索序列; (1)访问v; (2)改变v的访问标志; (3)任选一个与v相邻又没被访问的顶点w; (4)从w开始继续进行深度优先搜索。 访问顶点V :for (W1、W2、W3、...)//Wi为V的邻接点,若该邻接点W未被访问,则从它出发进行深度优先搜索遍历。
void DFS(Graph G, int v) {// 从顶点v出发,深度优先搜索遍历连通图G visited[v] = TRUE; VisitFunc(v); //访问v for(w=FirstAdjVex(G, v);w!=0; w=NextAdjvex(G,v,w)) if (!visited[w]) // 对v的尚未访问的邻接顶点w DFS(G, w); // 递归调用DFS }// DFS
-
时间复杂度分析:
假设图中有n 个顶点,e 条边。
如果用邻接矩阵表示图,则查找每一个顶点的所有的边,所需时间为 O ( n ) O(n) O(n),则遍历图中所有的顶点所需的时间为 O ( n 2 ) O(n^2) O(n2)。
如果用邻接表表示图,沿每个链可以找到某个顶点v 的所有邻接顶点w。由于总共有 2 e 2e 2e 个边结点,所以扫描边的时间为 O ( e ) O(e) O(e)。而且对所有顶点递归访问1次,所以遍历图的时间复杂性为 O ( n + e ) O(n+e) O(n+e)。
深度优先遍历(BFS)
-
过程
输入:图G,初始访问节点v; 输出:图G的广度优先搜索序列; (1)设置辅助队列Q,访问节点v,修改v的标志,v入队列; (2)while(!Isempty(Q)){ 出队列节点u; 访问与u临接的所有节点; 修改与u邻接的所有结点的标志; 与u邻接的所有节点入队列; }
void BFSTraverse(MGraph G,Status(*Visit)(VertexType))
{ /* 初始条件: 图G存在,Visit是顶点的应用函数。算法7.6 */
/* 操作结果: 从第1个顶点起,按广度优先非递归遍历图G,并对每个顶点调用函数 */
/* Visit一次且仅一次。一旦Visit()失败,则操作失败。 */
/* 使用辅助队列Q和访问标志数组visited */
int v,u,w;
VertexType u1;
LinkQueue Q;
for(v=0;v<G.vexnum;v++)
visited[v]=FALSE; /* 置初值 */
InitQueue(Q); /* 置空的辅助队列Q */
for(v=0;v<G.vexnum;v++)
if(!visited[v]) /* v尚未访问 */
{
visited[v]=TRUE; /* 设置访问标志为TRUE(已访问) */
Visit(G.vexs[v]);
EnQueue(Q,v); /* v入队列 */
while(!QueueEmpty(Q)) /* 队列不空 */
{
DeQueue(Q,u); /* 队头元素出队并置为u */
//strcpy(u1,*GetVex(G,u));
u1=GetVex(G,u);
for(w=FirstAdjVex(G,u1);w>=0;w=NextAdjVex(G,u1,GetVex(G,w)))
if(!visited[w]) /* w为u的尚未访问的邻接顶点的序号 */
{
visited[w]=TRUE;
Visit(G.vexs[w]);
EnQueue(Q,w);
}
}
}
printf("\n");
}
-
时间复杂度分析:
如果使用邻接表表示图,则循环的总时间代价为 d 0 + d 1 + . . . + d n − 1 = O ( n + e ) d_0+d_1+ ... + d_{n-1}= O(n+e) d0+d1+...+dn−1=O(n+e),其中的 d i d_i di 是顶点i的度。
如果使用邻接矩阵,则对于每一个被访问过的顶点,循环要检测矩阵中的n 个元素,总的时间代价为 O ( n 2 ) O(n^2) O(n2)
图的连通性问题
连通分量
- 深度优先搜索和广度优先搜索在基于一点的搜索过程中,能访问到该顶点所在的最大连通子图的所有顶点,即一个连通分量。通过变换搜索顶点,可求得无向图的所有连通分量
void DFSTraVErse(Graph G, Status (*Visit)(int v)) {
int i=1;// 对图G 作深度优先遍历。
VisitFunc= Visit;
for (v=0; v<G.VExnum; ++v)
visited[v] = FALSE; // 访问标志数组初始化
for (v=0; v<G.VExnum; ++v)
if (!visited[v]) {
printf(“第%d个连通分量”,i++);
DFS(G, v);
}
}
最小生成树
-
定义:对于边带权图(网)来说:在所有的生成树中,各边的权值(边长)总和最小的生成树称作最小生成树。
-
Prim算法
- 以顶点为主体,每次选出两顶点间权值最小的边归入生成树
- 时间复杂性主要体现在两层循环上,复杂性是 O ( n 2 ) O(n^2) O(n2)
-
Kruskal算法:
-
(1)从G中取最短边e,如果边e所关联的两个顶点不在T的同一个连通分量中,则将该边加入T;
(2)从G中删除边e;
(3)重复(1)和(2)两个步骤,直到T中有n-1条边
-
拓扑排序
-
拓扑序列:把AOV网中的所有顶点排成一个线性序列,该序列满足如下条件:若AOV网中存在从 v i v_i vi到 v j v_j vj的路径,则在该序列中, v i v_i vi必位于 v j v_j vj之前
-
过程
(1)在有向图中选一个入度为0的顶点输出; (2)从图中删除该顶点及所有它的出边; (3)重复执行(1)和(2),直到全部顶点均已输出,或图中剩余顶点的入度均不为0(说明图中存在回路,无法继续拓扑排序)。 增加一个存放各顶点入度的数组indegree[] (1)扫描indegree[],将入度为零的顶点入栈; (2)while(栈非空){ 弹出栈顶元素vi并输出; 检查vi的出边表,将每条出边<vi,vj>的终点vj的入度减1,若vj的入度减至0,则vj入栈; } (3)若输出的顶点数小于n,则“有回路”;否则正常结束。
-
时间复杂度分析:
设AOV网有n个顶点,e条边。初始建立入度为0 的顶点栈,要检查所有顶点一次,执行时间为 O ( n ) O(n) O(n);排序中,若AOV网无回路,则每个顶点入、出栈各一次,每个边表结点被检查一次,执行时间为 O ( n + e ) O(n+e) O(n+e)拓扑排序算法的时间复杂度为 O ( n + e ) O(n+e) O(n+e)。
如果采用邻接矩阵存储结构,则时间复杂度为 O ( n 2 ) O(n^2) O(n2)
关键路径
-
长度最长的路径称为关键路径。
-
关键路径上的活动都是关键活动。
-
关键活动延长必然影响工期,关键活动缩短不一定能缩短工期
-
事件 v i v_i vi的最早发生时间是从源点 v 1 v_1 v1到 v i v_i vi的最长路径长度,记作 v e ( i ) v_e(i) ve(i);
-
事件 v k v_k vk的最迟发生时间是 v n v_n vn的最早发生时间 v e ( n ) v_e(n) ve(n)减去 v k v_k vk到 v n v_n vn的最长路径长度,记作 v l ( k ) v_l(k) vl(k)
-
活动<vi,vj>的最早开始时间是vi的最早开始时间,记作 e e ( i ) e_e(i) ee(i)。
-
活动<vi,vj>的最迟开始时间是vj的最迟开始时间减去<vi,vj>的持续时间,记作 e l ( i ) e_l(i) el(i)
-
最早发生时间与最迟发生时间相等的活动为关键活动
-
时间复杂度:
若采用邻接表: O ( n + e ) O(n+e) O(n+e)
若采用邻接矩阵,时间复杂性是 O ( n 2 ) O(n^2) O(n2)。
最短路径
Dijkstra算法求单源最短路径
- Dijkstra算法其实和算贪心算法比较类似,每次都选取一个可到达的最短路径结点
- Dijkstra算法不能处理带负边权的图,因为按照Dijkstra算法,每次选取完的结点路径不会再进行更新了
- 时间复杂度:
- Dijkstra算法的时间复杂性主要体现在求每个顶点的最短路径时,需要修改距离值和求最小值,时间复杂性 O ( n 2 ) O(n^2) O(n2)
Floyd算法求各定点间最短路径
经典五行:
for(u=0;u<G.vexnum;++u)//经过的顶点
for(v=0;v<G.vexnum;++v)
for(w=0;w<G.vexnum;++w)
if(D[v][u]+D[u][w]<D[v][w])//从v经u到w的一条路径更短
D[v][w]=D[v][u]+D[u][w];
-
Floyd算法可以处理带负边权的图,因为按照Floyd算法,每次遍历都会更新所有结点的路径
-
时间复杂度: O ( n 3 ) O(n^3) O(n3)
Chapter8 查找
顺序查找
- 设置a[0]为监视哨,防止下标越界,提高效率
for (i=ST.length; ST.elem[i].key!=key; --i);
- 顺序查找表的平均查找长度为: n + 1 2 \frac{n+1}{2} 2n+1
折半查找
前提要求:顺序存储且有序
-
平均查找长度: O ( l o g 2 n ) O(log_2n) O(log2n)
-
n个结点的用于折半查找的判定树,表示查找失败的外部结点有n+1个
(n个结点将序列分为n+1段)
索引顺序表
-
查找表分成n块,当i>j时,第i块中的最小元素的关键字大于第j块中的最大元素的关键字
-
索引表的要求:
索引表是顺序存储并且索引表里存储了各个块最大值和开始地址
-
首先确定所要查找关键字在哪一块中,然后在所确定的块中用顺序查找查找关键字
-
如果索引表长度为b,每块平均长度为L,则索引查找的平均查找长度是 b + 1 2 + L + 1 2 \frac{b+1}{2}+\frac{L+1}{2} 2b+1+2L+1(两次顺序查找)
-
长度为n的线性表,平均分成 n \sqrt{n} n 块平均查找次数最少
二叉排序树
- 时间复杂度: O ( l o g 2 n ) O(log_2n) O(log2n)
AVL树
强推B站up林语石的讲解(下方链接)
Hash
哈希表的创建
解决冲突的方法:开放定址法
拉链法
Chapter9 内部排序
利益无关,只能说懂的都懂,匿了匿了
上一篇: 输入一颗树,打印“双樱桃”的个数。
下一篇: win10开机卡在logo界面怎么解决