复杂度的计算
抽象数据类型: 描述数据类型的方法不依赖具体方法实现。只描述数据对象集和相关操作集“是什么”,不涉及“如何做到”。
数据结构:数据对象在计算机中的组织方式
注意:这部分(指第一个老师的部分)作业答案全是用的老师给的,但是有些代码很离谱,斟酌阅读。。没给答案我就没放,此部分知识点也是随便整理的,请自行复习。。
线性表
线性表(简称为表):是零个或多个元素的有穷序列。
1、顺序表示
顺序表示的线性表也称顺序表
struct SeqList {
int MAXNUM; /*顺序表中可以存放元素的个数*/
int n; /* 实际存放线性表中元素的个数,n≤MAXNUM */
DataType *element; /*element[0], element[1],…,element[n-1]存放线性表中的元素*/
};
typedef struct SeqList *PSeqList; //创建一个 SeqList数组,即 PSeqList[]
2、链接表示
struct Node {/*单链表结点结构*/
DataType info;
Node link; //老师写的pnode link , 离谱+1
};
typedef struct Node * LinkList ; /*单链表类型*/
LinkList llist; /*llist是一个链表的头指针*/
作业:
//在palist所指的顺序表中下标为 p的元素之后插入一个值为 x的元素,并返回插入成功与否标志。
int insertAfter_seq(PSeqList palist, int p, DataType x) {
int q;
if (palist->n = = palist-> MAXNUM) {
prinft("Overflow!\n");
return 0;
}
if (p < -1 || p> = palist ->n ) {
printf("Not exist! \n");
return 0;
}
for (q=palist->n-1; q > p; q--)
palist -> element[q+1] = palist -> element[q] ;
palist->element[p+1] = x ;
palist->n = palist->n + 1;
return 1;
}
物理位置相邻、指针域
//顺序表
void reverse(PSeqlist palist) { //答案函数是 int reverse 离谱+1
int p;
DataType temp;
if ((palist ==NULL) && (palist->n==0)) return 0;
for (p=0; q < palist->n/2; p++) {
temp = palist -> element[p];
palist -> element[p] = palist -> element[palist->n-1-p];
palist -> element[palist->n-1-p]=x;
}
}
//无头结点的单链表
LinkList reverse(LinkList L) {
PNode p,q;
p=L;
L=NULL;
q->link=NULL;
while(p!=NULL) {
q=p;
p=p->link;
q->link=L;
L=q;
}
return q; //自己加的,答案没有,离谱+1
}
//采用带表头单元的单链表
LinkList delete_same(LinkList llist) {
PNode p,q;
LinkList r;
if (llist==NULL) return NULL;
p = llist;
while(p->next!=NULL) {
q=p->next;
while(q->next!=NULL))
if(p->next->info == q->next->info) {
r = q ->next;
q ->next = q->next->next;
free(r);
} else {
q = q->next;
}
p = p->next;
}
return 1; //???返回1没看懂,有时间再琢磨,离谱+1
}
编写一算法,删除单链表中所有值为x的无素
//采用带表头单元的单链表
int delete_x(LinkList llist, DataType x) {
PNode p,q;
if((llist==NULL) || (llist->next==NULL)) return 0;
p = llist;
while(p ->next !=NULL) {
if(p ->next ->info== x ) {
q =p ->next;
p ->next= p ->next ->next;
free(q);
}
}
else {
p = p ->next;
}
return 1;
}
void insertPost_link( LinkList llist, PNode p, DataType x) {
PNode q = (Pnode)malloc(sizeof(struct Node));
if (q == NULL) {
printf("Out of space!! \n");
} else {
q->info=x;
if ( p == NULL) {
q->link = llist;
llist = q;
} else {
q -> link = p -> link ;
p->link = q;
}
}
}
以下是循环链表作业:
1:简述以下算法的功能
void BB(Lnode *s, Lnode *q){
p=s;
while (p->next!=q) p=p->next;
p->next =s;
}
void AA(Lnode *pa, Lnode *pb){
BB (pa, pb);
BB (pb, pa);
}
其中, pa, pb两个指针指向某个单循环链表中的某两个单元。
答案:
如果L长度不小于2,将L的首节点变成尾结点。将单循环链表拆成两个单循环链表。
3、栈,队列
栈:后进先出
队列:先进先出
字符串
顺序表示:
structSeqString {/* 顺序串的类型*/
int MAXNUM;/* 串允许的最大字符个数*/
int n; /* 串的长度,n≤MAXNUM */
char *c;
};
typedef structSeqString *PSeqString;
链接表示:
struct StrNode; /* 链串的结点 */
typedef struct StrNode *PStrNode; /* 结点指针类型 */
struct StrNode {/* 链串的结点结构 */
char c;
PStrNode link;
};
typedef struct StrNode *LinkString; /* 链串的类型 */
1、kmp算法
详见 KMP算法
树
1、结点个数计算
例子:
PS:树的度:树内各结点的度的最大值
这里求叶子结点个数(即度为0,没有子结点),如果换成问度为n的结点个数,一样的做法。
- 等式左边:所有结点个数和 -1,即为边的个数
(除了根结点,其他结点都有往父亲连的边,相当于自底向上计算) - 等式右边:每个结点×度数 之和,还是边的个数
(相当于自上而下计算) - 解等式就可以算出任何结点的个数
2、树的三种表示方法
这部分需要知道三种表示方法是什么样的
- 父亲数组表示法:
struct ParTreeNode{
DataType info; //结点数据信息
int parent; //父亲的下标
}
按照某种遍历顺序,依次存放各结点,例如按照前序顺序:
- 子表表示法:
一棵树 = 结点表 + 子表- 子表:按从左至右的顺序组成1个单链表;
- 结点表:包含n个元素,每个元素结构为:
- 结点信息和该结点的子表头指针;
struct EdgeNode { //子表中元素的类型
int nodePosition;
struct EdgeNode * link;
}
struct ChiTreeNode { //结点表中元素的类型
DataType info;
struct EdgeNode * children;
}
struct ChiTree { //树的类型(顺序表结构)
int MaxNum;
int root; //树根在数组中的下标
int n;
struct ChiTreeNode * nodelist; //数组, 结点表
}
typedef struct ChiTree * PChiTree; //树指针类型
- 长子-兄弟表示法:
靠 左孩子结点(lchild) 和 右兄弟结点(rsibling) 确定结构
struct CSNode; //长子--兄弟表示法,结点结构
typedef struct CSNode * PCSNode;
struct CSNode {
Datatype info;
PCSNode lchild; //长子指针
PCSNode rsibling; //右兄弟指针
}
3、树的遍历
知道即可,主要考察二叉树遍历
-
深度优先遍历
- 先根(先序)遍历(根-左-右)
- 中根(中序)遍历(左-根-右)
- 后根(后序)遍历(左-右-根)
-
广度优先遍历
字典
1、顺序检索,二分检索
顺序检索不用说了,按顺序一个个查。
二分检索:有序的顺序表
- 将key与待查找表中间位置的关键码比较
- 若相等,则找到
否则,中间位置的某一侧作为新待查找表
例子:
注意:最后一次相等的比较也算一次
- 法1:
第1次比较:512, 第2次比较:703
第3次比较:653, 第4次比较:612 - 法2:
第1次比较:509, 第2次比较:677,
第3次比较:612
2、顺序检索,二分检索的查找长度
ASL(Average Search Length),即平均查找长度,在查找运算中,由于所费时间在关键字的比较上,所以把平均需要和待查找值比较的关键字次数称为平均查找长度。
-
顺序查找:
-
二分查找:
最大检索长度 j和元素数目n之间关系:- 2j-1-1< n ≤2 j-1, 即 log2(n+1) ≤ j < log2(n+1)+1
所以 j = log2(n+1)
- 2j-1-1< n ≤2 j-1, 即 log2(n+1) ≤ j < log2(n+1)+1
3、散列,碰撞,除余法
散列:
碰撞:
散列函数:
除余法:
例子:
m=16*2=32
p=31
散列函数 h(key)=key%p
4、线性勘察法(开地址法处理碰撞)
例子:
已知n个关键码具有相同的散列值d,若采用线性探查法解决碰撞,则在散列这n个关键码的过程中,共将要发生 n(n-1)/2 次碰撞
5、拉链法解决碰撞
例子:
平均查找长度ASL:
二叉排序树
重点:
- 二叉排序树概念
- 重要性质(中序递增)
- 构造二叉排序树(插入新节点过程),检索,删除
- 二叉排序树检索长度,平均检索长度
具体查看 二叉搜索树
作业题:
算法思想:在二叉排序树中,查找一个元素,总共比较n次,该结点的层数就是n-1层 (约定根为第0层)
(因为当前层不匹配就直接跳到下一层子结点比较)
int search_layer(PBinSearchTree ptree, PBinSearchNode pnode) { //查找pnode结点
PBinSearchNode p = *ptree;
int layer=0; //设根为第0层
while ( p != NULL) {
if(p->key == pnode->key) //相等,则找到
return layer;
if(p->key > pnode->key) {
p = p->llink; //已比较1次
layer++;
} else {
p = p->rlink; //已比较1次
layer++;
}
}
return -1; //未找到或者是空树
}
算法思想:
从根出发,沿着p, q的公共祖先前进:
- 当p, q小于当前结点,沿着当前结点的左指针前进;
- 当p, q大于当前结点,沿着当前结点的右指针前进;
- 当第1次遇到1个结点X的值介于p, q的值之间、或者等于其中1个,则X就是p, q的最近公共祖先。
PBinSearchNode FindLowestAncestor
(PBinSearchTree ptree, PBinSearchNode p,
PBinSearchNode q) { //查找p, q的最近公共祖先
PBinSearchNode X = *ptree;
while ( X != NULL) {
if(X->key > p->key && X->key > q->key)
X = X->llink; //X的值比p,q都大
else if(X->key < p->key && X->key < q->key)
X = X->rlink; //X的值比p,q都小
else
return X;
}
return -1; //未找到
}
平衡二叉排序树
重点:
- 平衡因子
- 四种失衡模式
- 找出最小不平衡子树
- 失衡调整(例题
具体参考 平衡二叉树
AVL树:每个结点的左、右子树高度之差的绝对值不超过1.
结点的平衡因子 = 右子树高度 – 左子树高度
例子:
(1)
(2)
PBinSearchNode p= t->rlink;
t->rlink = p->llink;
p->llink = t;
t->bf=0;
p->bf=0;
t = p;
图
1、基本概念,度和边的关系
-
简单图:无重边和自环
-
完全图:任意两个顶点之间,都有1条边
- 若G是有向图,则e=n(n-1)
- 若G是无向图,则e=n(n-1)/2
-
(顶点间)邻接、(顶点与边)关联
-
顶点的度D(V),(有向图顶点的)入度和出度:
- 有向图:
- V的入度:以V为终点的边数
- V的出度:以V为起点的边数
- V的度:入度 + 出度 - 无向图:
- V的度:V的关联边的个数
- 边数 e = 0.5 × ∑(所有顶点的度)
- 有向图:
-
回路:起点与终点相同的一条路径;
简单路径:只有起点和终点可以相同,即内部无回路;
简单回路:起点和终点相同的简单路径; -
(有向图中的)有根图:
有向图G=(V,E)中,
- 若存在一个顶点V,可以达到所有顶点,则称V是图的根;
- 1个有向图的根,可不唯一; -
(无向图中的)连通、连通图
- Vi与Vk连通:Vi与Vk之间至少存在1条路径;
- 连通图:图中任意两个顶点都连通;
-
(无向图的)连通分量:极大(最大)连通子图G*;
- 极大:向G*中增加原图G中的任意顶点或边,新子图不连通;
- 若G不连通,则其连通分量> 1个; -
(有向图中的)强连通图、强连通分量
- 强连通图:任意两个顶点Vi与Vk, 从Vi到Vk 、从Vk到Vi都有路径;
- 强连通分量:有向图G的最大强连通子图;
-
带权图:每1条边都被赋予一定权值;
带权路径长度:路径上,所有边的权值之和;
网络:带权的连通图;
2、邻接矩阵(带权不带权),计算度(出入度),邻接关系
-
不带权:
-
无向图:对称矩阵
- 顶点Vi的度 = 下标为 i 的行 or 列中,元素之和
-
有向图:不一定为对称矩阵
- Vi 的出度 = 下标为i的行, 元素之和
- Vi 的入度 = 下标为i的列, 元素之和
-
-
带权:ω为权值
- 无向带权图:对称矩阵
- 顶点Vi的度 = 下标为 i 的行 or 列中,非0非∞的个数
- 有向带权图:不一定为对称矩阵
- 顶点Vi出度 = i 行中非0非∞的个数;
- 顶点Vi入度 = i 列中非0非∞的个数;
- 无向带权图:对称矩阵
typedef char VexType; //设置顶点类型为字符型
typedef float AdjType; //设置关系矩阵元素为float型
typedef struct {
int vexNum, arcNum; //顶点个数,边数
VexType vexs[vn]; //一维数组:顶点表vexs
AdjType arcs[vn][vn]; //二维数组:邻接矩阵arcs
} GraphMatrix; //GraphMatrix:图的类型
3、邻接表(两个结点是否连接),计算度,删除边,边反向
邻接(链)表表示:
-
无向图 = 顶点表 + 边表
-
有向图 = 顶点表 + 出边表 (出边表计算入度)
-
有向图 = 顶点表 + 入边表 (入边表计算出度)
删除边:单链表删除一个结点,如果为第一个结点,还要修改顺序数组中的数值
边反向:在原来的单链表删除一条边,在反向的单链表增加一条边(加在尾部)
struct EdgeNode; //边表中的结点类型
typedef struct EdgeNode * PEdgeNode;
typedef struct EdgeNode * EdgeList;
struct EdgeNode { // 边表中的结点结构
int ajdVex; //邻接顶点的下标
AdjType weight;
PEdgeNode nextEdge; //指向下1条(出、入)边
}; // 边表中,1个结点代表1条(出、入)边
typedef struct {
VexType vertex; //顶点信息
EdgeList edgelist; //边表头指针
} VexNode; //VexNode:顶点表中元素类型
typedef struct {
VexNode vexs[VN]; //顶点表(1维数组)
int vexNum, arcNum; //顶点数, 边数
} GraphList; //GraphList:顶点表(图)的结构类型
4、图的遍历(dfs,bfs)序列,两种生成树,最小生成树两种方法
图的遍历(dfs和bfs)就不说了,基础。
具体查看 最小生成树
作业题:
1、已知一个无向图G=(V, E),其中V={a, b, c, d, e},E={(a,b), (a, c), (a, d), (c, d), (b, e)},从a开始,深度优先遍历序列、广度优先遍历序列分别是 ?
深度优先遍历序列:a b e c d
广度优先遍历序列:a b c d e
2.、P327,对于图9.23,请给出其深度优先、广度优先遍历序列。
深度优先遍历序列:v0 v1 v4 v6 v8 v7 v2 v3 v5
广度优先遍历序列:v0 v1 v2 v3 v4 v5 v6 v7 v8
3.、P327,对于图9.26, 请给出其深度优先、广度优先遍历序列。
深度优先遍历序列:v0 v1 v3 v2 v4 v5 v6 v7
广度优先遍历序列:v0 v1 v2 v3 v4 v5 v6 v7
(1) 邻接矩阵arcs中,非0元素个数的1/2.
(2) 若arcs[i][j]或arcs[j][i]不等于0,则vi与vj有边相连;
(3) vi的度:arcs中下标为i的行或列中1的个数。
出边表表示:
Struct EdgeNode;
typedef struct EdgeNode *PEdgeNode;
typedef struct EdgeNode *EdgeList;
Struct EdgeNode { //边表结点结构
int endVex;
int weight;
PEdgeNode nextEdge;
};
typedef struct { //顶点表元素结构
char vertex;
EdgeList edgelist;
} VexNode;
typedef struct { //图结构
VexNode vexs[VN];
int vexNum, arcNum;
} GraphList;
int is_end(GraphList g, int k) {
EdgeList p;
int i;
for(i=0; i<g.vexNum; i++) {
p=g.vexs[i].edgelist; //p取得边表头指针
while(p!=Null) {
if(p->endvex == k) return1;
p = p->nextedge;
}
}
return 0;
}
邻接矩阵表示:
typedef struct {
int vexNum, arcNum;
char vexs[vn];
float arcs[vn][vn];
} GraphMatrix; //图结构
int is_end(GraphMatrix g, char x) {
int i, k;
for(i=0; i<g.vexNum; i++) //寻找字符x的下标k
if( g.vexs[i]==x ) {
k=i;
break;
}
for(i=0; i<g.vexNum; i++) //判断arcs中第k列是否有1
if(g.arcs[i][k] ==1) return 1;
return 0
}
5、最短路径(distance数组),给出数组,求出最短路径
具体参考 最短路径
不过考试只要求Dijkstra算法:
基于贪心策略的思想,算法是按路径长度从小到大的顺序一步一步并入来求取,用来解决单源点到其余顶点的最短路径问题。
时间复杂度O(N^2),堆优化后时间复杂度O(nlogn)。
即:从剩余路径中找最短的路径,然后更新最短路径。
d[ i ] = min { d[ j ] + (j -> i 边权值) | e=(j,i)∈E}
下面直接看作业题理解
作业题:
注意:写出完整的dist数组变化过程;并列出:最短路径(即顶点序列)、各最短路径长度
6、拓扑排序概念和序列
具体参考 拓扑排序
作业题:
采用栈作为辅助空间,计算1个拓扑序列。
老师给的答案:
7、关键路径概念,计算过程
具体参考 关键路径
作业题:
排序
重点内容
- 插入排序,选择排序,冒泡排序,交换排序重点
- 知道希尔操作过程
- 快排第一趟快排结果()
- 堆排序要会应用(完全二叉树从左到右,整个过程
- 知道基数排序和归并
具体参考 排序汇总