数据结构随心复习笔记
作者:互联网
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 数组和广义表
数组
-
数组元素的地址关系:
- 数组在内存中主要采用两种存储方式:
- 以行序为主的存储方式;
- 以列序为主的存储方式
不同的存储方式有不同元素地址计算方法。
- 数组在内存中主要采用两种存储方式:
广义表
-
原子:如果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 的生成树。
标签:结点,遍历,复习,复杂度,随心,next,二叉树,front,数据结构 来源: https://blog.csdn.net/Cezzzzzz/article/details/110678187