抽象数据类型(Abstract Data Type, ADT)
抽象数据类型的特征是 实现 与 操作 分离,从而实现 封装 。
抽象数据类型体现了程序设计中 问题分解 和 信息隐藏 的特征。它把问题分解为多个规模较小且容易处理的问题,然后把每个功能模块的实现为一个独立单元,通过一次或多次调用来实现整个问题。
- 可视化网站
算法分析
递归简论
- 基准情况:在某一情况下无须递归就会return。
- 不断推进:每一次递归都往基准推进。
- 设计法则:所有递归都能调用。
- 合成效益法则:在同一个问题中,勿在不同递归调用中做同一个工作
时间复杂度
(大概是看看就好了吧)
- 定义:如果存在正常数$𝑐$和$𝑛_0$, 使当$𝑁≥𝑛_0$时$𝑇(𝑁)≤𝑐𝑓(𝑁)$, 则记为$𝑇(𝑁)=\mathcal{O}(𝑓(𝑁))$
- 定义:如果存在正常数$𝑐$和$𝑛_0$, 使当$𝑁≥𝑛_0$时$𝑇(𝑁)≥𝑐𝑔(𝑁)$, 则记为$𝑇(𝑁)=Ω(𝑔(𝑁))$
- 定义:$𝑇(𝑁)=Θ(ℎ(𝑁)$当且仅当$𝑇(𝑁)=\mathcal{O}(ℎ(𝑁))$且$𝑇(𝑁)=Ω(ℎ(𝑁))$
- 定义:如果$𝑇(𝑁)=\mathcal{O}(𝑝(𝑁))$且$𝑇(𝑁)≠Ω(𝑝(𝑁))$, $𝑇(𝑁)=\mathcal{O}(𝑝(𝑁))$
| 名称 | 运行时间T(N) | 算法举例 |
|---|---|---|
| 常数时间 | $\mathcal{O}(1)$ | 判断一个二进制数的奇偶 |
| 对数时间 | $\mathcal{O}(logN)$ | 二分搜索 |
| (小于1次)幂时间 | $\mathcal{O}(N^c)$其中$0<c<1$ | K-d树的搜索操作 |
| 线性时间 | $\mathcal{O}(N)$ | 无序数组的搜索 |
| 线性对数时间 | $\mathcal{O}(N\log{N})$ | 最快的比较排序 |
| 二次时间 | $\mathcal{O}(N^2)$ | 冒泡排序、插入排序 |
| 三次时间 | $\mathcal{O}(N^3)$ | 矩阵乘法的基本实现,计算部分相关性 |
| 指数时间 | $\mathcal{O}(2^N)$ | / |

- 计算时间复杂度
- 一层循环的时间复杂度为$𝑂(𝑁)$
- 多层嵌套循环的时间复杂度是每层循环的时间复杂度之积$𝑂(𝑁^n)$
if…else…/case…switch…语句中取各个分支中最长的时间作为时间开销
表、栈、队列
表ADT(链表) List
习惯上会留出一个 标志节点 , 有时称之为 表头(header) 或 哑节点(dummy node)
- 动画
- Head File
1 | struct Node; |
2 | typedef struct Node *PtrToNode; |
3 | typedef PtrToNode List; |
4 | typedef PtrToNode Position; |
5 | typedef int ElementType; |
6 | // series of functions |
7 | // ...... |
- 结构体定义
1 | struct Node{ |
2 | ElementType Element; |
3 | Position Next; |
4 | // 可以加一个前置节点, 则成为双链表 |
5 | }; |
Find 查询
其实就是从头遍历1Position Find( ElementType X, List L){2Position P;3P = L->Next;4while( P != NULL && P->Element != X){5P = P->Next;6}7return P;8}FindPrevious 查询前一个节点
和Find类似, 区别仅在line2,31Position FindPrevious( ElementType X, List L){2Position P;3P = L;4while(P->Next != NULL && P->Next->Element != X){5P = P->Next;6}7return P;8}Delete 删除
1void Delete( ElementType X, List L){2Position P, TmpCell;3P = FindPrevious(X, L);4if ( !IsLast( P, L) ){5TmpCell = P->Next;6P->Next = TmpCell->Next;7free(TmpCell); // 及时释放内存空间8}9}Insert 插入
1void Insert( ElementType X, List L, Position P){2Position TmpCell;3TmpCell = malloc( sizeof (struct Node));4if (TmpCell == NULL){5// FatalError("Out of space!!!");6printf("Out of space!!!");7}8TmpCell->Element = X;9TmpCell->Next = P->Next;10P->Next = TmpCell;11}- 其他类型
- 双链表
- 循环链表
- 多重表
- 基数排序(radix sort)
- 低位排序LSD
- 高位排序MSD
- 游标(cursor)实现法
需要链表又不能使用指针(如 BASIC 和 FORTRAN 等)
栈ADT Stack
又名 LIFO(先进后出)表
基本上就Pop(出栈)和Push(进栈)操作
实现
- 链表实现
主要时间花费在于分配新空间和删除弹出的结点 - 数组实现
唯一潜在危害: 需要提前声明一个数组大小
- 链表实现
应用
- 后缀表达式
- 中缀到后缀的转换
- 函数调用
- 尾递归(TODO:未懂)
执行的任何递归调用是在这种情况下的最后操作,而且通过封闭递归,递归调用的返回值(若有)立即返回.
必须是 线性递归 (递归内只调用一个新的递归)
- 尾递归(TODO:未懂)
- 平衡符号

队列 Queue
- 基本操作
Enqueue入队 : 在表的末端(队尾rear)插入一个元素Dequeue出队 : 删除 / 返回 表的开头(队头front)
- 实现
- 链表实现
- 数组实现(循环数组circular array实现)
- 应用
- 排队论(queueing theory)
树 Tree
预备知识
- 基本术语
| 术语 | 定义 |
|---|---|
| 根(root) | / |
| 边(edge) | 连接节点的有向线 |
| - - - | - - - |
| 子节点(child) | 一个节点含有的子树的根节点称为该节点的子节点 |
| 父节点(parent) | 若一个节点含有子节点,则这个节点称为其子节点的父节点 |
| 兄弟节点(sibling) | 具有相同父节点的节点互称为兄弟节点 |
| 堂兄弟节点 | 双亲在同一层的节点互为堂兄弟 |
| 祖先(grandparent) | 从根到该节点所经分支上的所有节点 |
| 子孙(grandchild) | 以某节点为根的子树中任一节点都称为该节点的子孙 |
| - - - | - - - |
| 树叶(leaf) | 没有子节点的节点 |
| 从节点$n_1$到$n_k$的路径(path) | 节点$n_1, n_2, \cdots,n_k$的一个序列 |
| 路径的长(length) | 路径上边的条数 |
| 深度(depth) | 从根到$n_i$的位移路径的长 |
| 高度(height) | 从$n_i$到一片树叶的最长路径的长 |
| 树的高度或深度 | 树中节点的最大层次 |
| - - - | - - - |
| 非终端节点或分支节点 | 度不为0的节点 |
| 节点的层次 | 从根开始定义起,根为第1层,根的子节点为第2层,以此类推 |
| 节点的度 | 一个节点含有的子树的个数称为该节点的度 |
| 树的度 | 一棵树中,最大的节点的度称为树的度 |
| 森林 | 由m(m>=0)棵互不相交的树的集合称为森林 |
- 种类
- 无序树:树中任意节点的子结点之间没有顺序关系,这种树称为无序树,也称为自由树;
- 有序树:树中任意节点的子结点之间有顺序关系,这种树称为有序树;
- 二叉树:每个节点最多含有两个子树的树称为二叉树;
- 完全二叉树
- 满二叉树
- 霍夫曼树:带权路径最短的二叉树称为哈夫曼树或最优二叉树;
遍历
二叉树
- 种类: 表达式树, 查找树ADT——二叉查找树
Head File
1struct TreeNode;2typedef struct TreeNode *Position;3typedef struct TreeNode *SearchTree;4typedef int ElementType;5// series of functions6// ......结构体定义
1struct TreeNode{2ElementType Element;3SearchTree Left;4SearchTree Right;5};Find 查询
1Position Find( ElementType X, SearchTree T){2if (T==NULL){3return NULL;4}5if(X<T->Element){// 元素大了6return Find(X,T->Left);// 去找左边小一点的7}else if(X>T->Element){8return Find(X,T->Right);9}else{10return T;// 返回后类型变为Position了11}12}Insert 插入
1SearchTree Insert( ElementType X, SearchTree T){2if (T==NULL){3//创建并返回一个一节点树4T = malloc(sizeof(struct TreeNode));5if (T==NULL){6printf("out of space!!!");7}else{8T->Element = X;9T->Left = T->Right = NULL;10}11}else if(X < T->Element){12T->Left = Insert(X,T->Left);13}else if(X > T->Element){14T->Right = Insert(X,T->Right);15}else{16printf("%d is already in tree...\n",X);17}18return T;19}Delete 删除节点
1//TODO:PrintTree 打印树
1void PrintTree( SearchTree T,int space,int LR,int *N){2// LR: 标志左右节点的参数,用以打印`/`或`\`3// *N: 打印前N个节点4int len = 10; // 每一层的打印间距(空格)5if (T!=NULL && (*N)>0){6(*N)--;7PrintTree(T->Right,space+len,1,N);8for (int loop = 0;loop<space; loop++){9printf(" ");10}11if(LR==1){12printf("/");13}else if(LR==0){14printf("\\");15}16printf("%-6d\n",T->Element);17PrintTree(T->Left,space+len,0,N);18}19}
AVL树 (Adelson-Velsky 和 Landis)
- 定义: 每个节点的左子树和有字数的高度最大相差1的二叉查找树
- 深度: $\mathcal{O}(\log{N})$
- 旋转
- 单旋转
相邻子树高度相差大于等于2 - 双旋转
相邻子树高度相差为1
- 单旋转
伸展树 splay tree
- 基本想法: 当一个节点被访问后,它就要经过一系列AVL树的旋转被放到根上
- 两种情况
- 之字形(zig-zag): AVL双旋转
- 一字形(zig-zig): AVL单旋转两次
B-树 (B-tree)
不是二叉树的查找树
- 称呼
- 4阶B-树 == 2-3-4树
- 3阶B-树 == 2-3树
分析树
这个好像也不考
哈希 Hash
又名散列
哈希函数
- 简单
1Index HashNormal(ElementType Key, int TableSize){2return Key%TableSize;3} - 优秀
1Index HashBeautifully(ElementType Key, int TableSize){2ElementType * ptrKey = &Key;3unsigned int HashVal = 0;4while (*ptrKey !='\0'){5HashVal = (HashVal << 5) + *ptrKey++;6}7return HashVal % TableSize;8}分离链表法(拉链法)
头文件
1struct ListNode;2typedef struct ListNode *Position;3struct HashTbl;4typedef struct HashTbl *HashTable;5typedef int ElementType;6typedef int Index;初始化
1HashTable2InitializeTable(int TableSize){3HashTable H;4int i;5if (TableSize < MinTableSize){6printf("Table size too small");7return NULL;8}9// allocate table10H = malloc(sizeof(struct HashTbl));11if(H == NULL){12printf("out of sapce !!!");13}14// allocate list headers15for (i=0; i<H->TableSize; i++){16H->TheLists[i] = malloc(sizeof(struct ListNode));17if (H->TheLists[i]==NULL){18printf("out of space !!!");19}else{20H->TheLists[i]->Next = NULL;21}22}23return H;24}结构体定义
1struct ListNode{2ElementType Element;3Position Next;4};56typedef Position List;78struct HashTbl{9int TableSize;10List *TheLists;11};Find
1Position2Find( ElementType Key, HashTable H, Index HashNum){3Position P;4List L;56// L = H->TheLists[Hash(Key, H->TableSize)];7L = H->TheLists[HashNum];8P = L->Next;9while(P!=NULL && P->Element!=Key){10P=P->Next;11}12return P;13}Insert
1void2Insert( ElementType Key, HashTable H, Index HashNum){3Position Pos, NewCell;4List L;5Pos = Find(Key,H,HashNum);6if(Pos == NULL){7NewCell = malloc(sizeof(struct ListNode));8if (NewCell == NULL){9printf("out of space!!!");10}else{11// L = H->TheLists[Hash(Key,H->TableSize)];12L = H->TheLists[HashNum];13NewCell->Next = L->Next;14NewCell->Element = Key;15L->Next = NewCell;16}17}else{18printf("already have it!\n");19}20}装填因子(load factor) $\lambda$
尽量让表的大小与预料的元素个数差不多,即 $\lambda\approx 1$ .
开放定址法
- 头文件
1typedef unsigned int Index;2typedef Index Position;34struct HashTbl;5typedef struct HashTbl *HashTable;6typedef int ElementType;7// series of functions8// ...... 基础声明
12extern int Collision_Square,Collision_Liner,Collision_Double;3enum KindOfEntry{Legitimate,Empty,Deleted};45struct HashEntry{6ElementType Element;7enum KindOfEntry Info;8};910typedef struct HashEntry Cell;1112// 哈希表13struct HashTbl{14int TableSize;15Cell *TheCells;16};初始化
1HashTable2InitializeTable(int TableSize){3HashTable H;4int i;56if(TableSize<MinTableSize){7printf("Table Size too small");8return NULL;9}1011// allocate table12H = malloc(sizeof(struct HashTbl));13if (H == NULL){14printf("out of space!!!\n");15}16H->TableSize = NextPrime(TableSize);1718// allocate array of cells19H->TheCells = malloc(sizeof(Cell) * H->TableSize);20if (H->TheCells == NULL){21printf("out of space!!!\n");22}2324for (i = 0;i<H->TableSize;i++){25H->TheCells[i].Info = Empty;26}27return H;28}
线性探测法
- Find
1Position2FindByLiner(ElementType Key, HashTable H){3Position CurrentPos;4int CollisionNum;5CollisionNum = 0;6CurrentPos = HashNormal(Key,H->TableSize);7while(H->TheCells[CurrentPos].Info != Empty && // 非空8H->TheCells[CurrentPos].Element != Key){ // 不等于查询值9CurrentPos++;10CollisionNum++;11if(CurrentPos >= H->TableSize){12CurrentPos -= H->TableSize; // 循环查找13}14}// 一旦找到就跳出循环15Collision_Liner += CollisionNum;16return CurrentPos;17} Insert
1void InsertByLiner(ElementType Key, HashTable H){2Position Pos;3Pos = FindByLiner(Key,H);4if(H->TheCells[Pos].Info != Legitimate){// 该位置没有数据5H->TheCells[Pos].Info = Legitimate;6H->TheCells[Pos].Element = Key;7}8}一次聚集(primary clustering)
占据的单元形成一些区块预期探测次数
- 插入 / 不成功的查找
- 成功的查找
平方散列法
- 快速公式
CurrentPos += 2 * ++CollisionNum - 1 - Find
1Position2FindBySquare(ElementType Key, HashTable H){3Position CurrentPos;4int CollisionNum;5CollisionNum = 0;6CurrentPos = HashNormal(Key,H->TableSize);7while(H->TheCells[CurrentPos].Info != Empty && // 非空8H->TheCells[CurrentPos].Element != Key){ // 不等于查询值9CurrentPos += 2 * ++CollisionNum - 1; // 平方探测 快速方法10if(CurrentPos >= H->TableSize){11CurrentPos -= H->TableSize; // 循环查找12}13}// 一旦找到就跳出循环14Collision_Square += CollisionNum;15return CurrentPos;16} - Insert
1void InsertBySquare(ElementType Key, HashTable H){2Position Pos;3Pos = FindBySquare(Key,H);4if(H->TheCells[Pos].Info != Legitimate){ // 该位置没有数据5H->TheCells[Pos].Info = Legitimate;6H->TheCells[Pos].Element = Key;7}8}
双(倍)散列
Find
1Position2FindByDouble(ElementType Key, HashTable H){3Position CurrentPos;4int CollisionNum = 0;5int addDouble = HashDoubel(Key,7);6CurrentPos = HashNormal(Key,H->TableSize);7while(H->TheCells[CurrentPos].Info != Empty && // 非空8H->TheCells[CurrentPos].Element != Key){ // 不等于查询值9CurrentPos += addDouble;10CollisionNum++;11if(CurrentPos >= H->TableSize){12CurrentPos -= H->TableSize; // 循环查找13}14}// 一旦找到就跳出循环15Collision_Double += CollisionNum;16return CurrentPos;17}Insert
1void InsertByDouble(ElementType Key, HashTable H){2Position Pos;3Pos = FindByDouble(Key,H);4if(H->TheCells[Pos].Info != Legitimate){ // 该位置没有数据5H->TheCells[Pos].Info = Legitimate;6H->TheCells[Pos].Element = Key;7}8}
再散列
当表有超过 70% 的单元是满的, 建立新表
新表大小是原表大小两倍后的第一个素数
- 实现
- 表满一半就
再散列 - 当插入失败是才
再散列 - 途中(middle-of-the-road)策略 : 当表达到某个装填因子时进行
再散列
- 表满一半就
可扩散列
略
优先队列(堆) Heap
二叉堆 binary heap
动画
堆是一刻完全二叉树(complete binary tree)
- 堆序性 heap order
- 最小元在根上
- 任意节点小于其所有后裔
基本堆操作
Insert
- 上滤
加在队尾然后向上排序 - 标记 sentinel
通过添加一条 哑信息(dummy piece of information) , 避免在插入新的最小值时每个循环都执行一次的测试
- 上滤
DeleteMin
- 下滤
删除根, 队尾换到队头, 向下排序
- 下滤
应用
- 选择问题
- 时间模拟
d-堆
略TODO:
左式堆
略TODO:
斜堆
略TODO:
二项队列
略TODO:
图论 Graph
基本概念
| 术语 | 定义 |
|---|---|
| 阶(Order) | 图中顶点的个数 |
| 尺寸(Size) | 图中边的个数 |
| 子图(Sub-graph) | 一个图的顶点与边的子集。即,当$𝑉(𝐺’)∈𝑉(𝐺)$并且$𝐸(𝐺’ )∈𝐸(𝐺)$时,$𝐺’$是$𝐺$的一个子图 |
| 生成子图(Sub-graph) | 当$𝐺’$是$𝐺$的子集,并且$𝑉(𝐺’ )=𝑉(𝐺)$时, $𝐺’$是$𝐺$的一个生成子图 |
| 度(Degree) | 一个顶点𝑣的度是与它相连的边的数量,记作𝑑(𝑣)。$\sum_{\nu \in V}d(\nu)=2 \mid E \mid$ |
| 出度(In-degree) | 出度为从该顶点出发的边的个数 |
| 入度(Out-degree) | 入度为终点时该节点的边的个数 |
| 路径(Path) | 路径经过的边数为𝑘,即路径长度为$𝑘$ |
| 简单路径(simple-path) | 路径的所有顶点都不相同或只有起点$𝑣_0$和终点$𝑣_𝑘$两个顶点相同 |
| 圈(cycle) | 在有向图中路径起点$𝑣_0$和终点$𝑣_𝑘$相同。若满足简单路径的条件则是一个简单圈。 |
| 距离 | 两个顶点之间的最短路径长度就是两点之间的距离 |
图的分类
- 按边
| 类型 | 定义 |
|---|---|
| 简单图(simple graph) | 既没有平行边,也没有自环 |
| 多重图(multi-graph) | 有平行边的图 |
| 伪图(Pseudograph) | 既有平行边的图,也有自环的图 |
| 完全图 | 每对顶点之间都有边的图是完全图 |
有的作者也允许多重图有自环

- 按路径
| 类型 | 定义 |
|---|---|
| 连通图 | 一个图中的每个顶点到其他顶点都有路径 |
| 强连通图 | 有向图满足该条件 |
| 弱连通图 | 有向图不连通,但是它的基础图连通 |
基础图: 边去掉方向的图
- 按方向

- 按权重

存储显示方式
邻接矩阵 adjacency matrices
邻接矩阵使用$|V|∗|V|$的二维数组来表示图。$g[i][j]$表示的是顶点$i$和顶点$j$的关系。
- 无向图
只需要知道顶点$i$和顶点$j$是否是相连的,因此我们只需要将$g[i][j]$和$g[j][j]$设置为1或是0表示相连或不相连即可。
- 有向图
只需要知道是否有从顶点$i$到顶点$j$的边,因此如果顶点$i$有一条指向顶点$j$的边,那么$g[i][j]$就设为1,否则设为0。有向图与无向图不同,并不需要满足$g[i][j]=g[j][i]$
有权图
在带权值的图中,g[i][j]表示的是顶点i到顶点j的边的权值。由于在边不存在的情况下,如果将g[i][j]设为0,就无法和权值为0的情况区分开来,因此选取适当的较大的常数INF(只要能和普通的权值区别开来就可以了),然后令g[i][j]=INF就好了。当然,在无向图中还是要保持g[i][j]=g[j][i]。在一条边上有多种不带权值的情况下,定义多个同样的|V|∗|V|数组,或者是使用结构体或类作为数组的元素,就可以和原来一样对图进行处理了。
优点
很方便地判断任意两个顶点之间是否有边以及确定顶点的度- 缺点
在这种表示法中扫描所有边至少需要O(n2)时间,因为必须检查矩阵中的n2−n个元素才能确定图中边的条数(邻接矩阵对角线上的n个元素都是0,因此不用检查。又因为无向图的邻接矩阵是对称的,实际只需检查邻接矩阵的一半元素)。
通常把边很少的图成为稀疏图(sparse graphs)。如果用邻接矩阵表示稀疏图就会浪费大量内存空间
邻接表
- 优点: 只需$\mathcal{O}(|V|+|E|)$的内存空间

12typedef struct node *node-pointer;3typedef struct node {4int vertex;5struct node *link;6};7node_pointer graph[MAX_VERTICES];8int n=0;
链接多重表
在无向图的邻接表存储表示中,每一条边$(v_i,v_j)$
都表示为两项:一项在顶点vi 的邻接表中,而另一项在顶点 $v_j$ 的邻接表中。在多重表中,各链表中的结点可以被几个链表共享,此时图中的每一条边只对应于一个结点,而这个结点出现在该边所关联的两个顶点的每个邻接链表中。1typedef struct edge *edge-pointer 2typedef struct edge { 3 short int marked; 4 int vertex1; 5 int vertex2; 6 edge_pointer path1; 7 edge_pointer path2; 8};
操作
拓扑排序
最短路径
略……
参考资料:



