本文内容源于23王道考研课程讲解中的学习心得、笔记整理以及杭电历年真题总结。若有错误欢迎转正 ♪(^ ∇ ^)。
一、绪论
一个算法必须具有的5个重要特性:有穷性、确定性、可行性、输入、输出
一个“好”的算法应考虑:正确性、可读性、健壮性、效率与低存储量需求
散列存储:结点关键字决定存储地址
数据的逻辑结构:指各数据元素之间的逻辑关系
-时间复杂度
例:斐波那契递归算法
时间复杂度看叶子节点 o(2^n) 空间复杂度看树的高度o(n)
-空间复杂度
二、线性表(算法题的核心章节)
-线性表的定义
常见的线性表结构:数组,链表、队列、栈等。
- 线性表的顺序表示(定义及分配)***
顺序表的静态分配
//顺序表的静态分配
#include<stdio.h>
#define MaxSize 10
typedef struct{
int data[MaxSize];
int length;
}SqList;
void InitList(Sqlist &L){
for(int i= 0;i< MaxSize;i++){
L.data[i]= 0;
}
L.length= 0;
}
int main(){
SqList L; //声明一个顺序表
InitList(L); //初始化一个顺序表
return 0;
}
顺序表的动态分配
//顺序表的动态分配
#include<stdio.h>
#include<stdlib.h> //malloc,free函数的头文件
#define InitSize 10 //默认最大长度
typedef struct{
int *data; //指示动态分配数组的指针
int MaxSize;
int length;
}SeqList;
void InitList(Seqlist &L){
L.data= (int*)malloc(InitSize*sizeof(int)); //申请一片连续的存储空间
L.MaxSize= InitSize;
L.length= 0;
}
void IncreaseSize(SeqList &L, int len){
int *p= L.data; //暂存数据
L.data=(int*)malloc((L.MaxSize+len)*sizeof(int));
for (int i=0;i< L.length;i++){
L.data[i]= p[i]; //将数据复制到新的空间
}
L.MaxSize= L.MaxSize+len;
free(p); //释放原来的内存空间
}
int main(void){
SeqList L; //声明一个顺序表
InitList(L); //初始化
IncreaseSize(L,5); //增加动态数组的长度
return 0;
}
插入操作 平均时间复杂度O(n)
//插入操作
bool ListInsert(SeqList &L, int i, int e){ //插入到第i个位子
if(i<1||i>L.length+1) //判断i的范围是否有效
return false;
if(L.length>= MaxSize) //判断存储空间是否满了
renturn false;
for(int j=L.length;j>=i;j--) //将第i个元素及之后的元素后移
L.data[j]=L.data[j-1];
L.data[i-1]=e;
L.length++; //线性表长度加1
return true;
}
删除操作 平均时间复杂度O(n)
//删除操作
bool ListInsert(SeqList &L, int i, int &e){
if(i<1||i>L.length) //判断i的范围是否有效
return false;
for(int j=i;j<L.length;j++)
L.data[j-1]=L.data[j];
L.length--; //线性表长度减1
return true;
}
按位查找 平均时间复杂度O(1)
按值查找 平均时间复杂度O(n)
\\按位查找
...
return L.data[i-1];
\\按值查找
...
for(int j=0;j<L.length;j++)
if(L.data[j]==e)
return j+1;
- 顺序表优点: 存储密度大
- 代码题
1.将两个有序顺序表合并为一个新的顺序表
//建立一个新的顺序表C,不断将A、B中较小的存入C中
bool Merge(SeqList A, SeqList B, SeqList &C){
if(A.length+B.length>C.MaxSize)
return false;
int i,j,k=0;
while(i<A.length&&j<B.length){
if(A.data[i]<=B.data[j])
C.data[k++]=A.data[i++];
else
C.data[k++]=B.data[j++];
}
while(i<A.length) //还剩一个没比较完的顺序表
C.data[k++]=A.data[i++];
while(j<B.length)
C.data[k++]=B.data[j++];
C.length=k;
return true;
}
- 链表、链式存储****
单链表、双链表、循环链表、静态链表、顺序表和链表的比较
typedef struct LNode{
ElementType data; //数据域
struct LNode *next; //指针域
}LNode,*LinkList;
struct LNode *p = (struct LNode*)malloc(sizeof(struct LNode))
-
如何表示空表?
有头结点时,当头结点的指针域为空时表示空表 -
在链表中设置头结点有什么好处
1.便于首元结点的处理 首元结点的地址保存在头结点的指针域中,所以在链表的第一个位置上的操作和其它位置一致,无须进行特殊处理;
⒉便于空表和非空表的统一处理
头插法 (链表的逆置)
//头插法
LinkList List_HeadInsert(LinkList &L){ //逆向建立单链表
LNode *s;
int x;
L=(LinkList)malloc(sizeof(LNode)); //创建头结点
L->next=NULL; //初始为空
scanf("%d",&x);
while(x!=9999){ //9999表示结束
s=(LNode*)malloc(sizeof(LNode)); //创建新结点
s->data=x;
s->next=L->next;
L->next=s;
scanf("%d",&x);
}
return L;
}
//空表判断
L->next == NULL
尾插法
按序号查找结点值 o(n)
//按序号查找结点值
LNode *GetElem(LinkList L,int i){
int j=1;
LNode *p=L->next; //第1个结点指针赋给p
if(i==0) return L; //返回头结点
if(i<1) return NULL;
while(p&&j<i){
p=p->next;
j++;
}
return p; //若i大于表长,则返回NULL
}
按值查找
//按值查找
LNode *LocateElem(LinkList L,int e){
LNode *p=L->next;
while(p!=NULL&&p->data!=e)
p=p->next;
return p;
}
插入删除 (带头、不带头都要会)
//单链表的插入 o(1)
1. p=GetElem(L,i-1); //查找插入位置的前驱结点
2. s->next=p->next;
3. p->next=s;
//单链表的删除
1. p=GetElem(L,i-1);
2. q=p->next; //q指向被删除结点
3. p->next=q->next;
4. free(q);
//双链表的插入
1. s->next=p->next; //结点*s插入到*p之后
2. p->next->prior=s;
3. s->prior=p;
4. p->next=s;
//双链表的删除
1. p->next=q->next;
2. q->next->prior=p;
3. free(q);
循环链表
静态链表
-广义表
- 概念:广义表,又称列表,也是一种线性存储结构,既可以存储不可再分的元素,也可以存储广义表,记作:LS = {a1,a2,…,an}
- 广义表的长度,指的是广义表中所包含的数据元素的个数
LS = {a1,a2,…,an} 的长度为 n;
广义表 {a,{b,c,d}} 的长度为 2;
广义表 {{a,b,c}} 的长度为 1;
空表 {} 的长度为 0。 - 广义表的深度,可以通过观察该表中所包含括号的层数间接得到,如下图所示,深度为2。
- 存储结构
其中tag域为标志字段:
tag为0时,该节点是原子节点,第二个域为data,存放原子元素的信息;
tag为1时,该节点是表节点,存放子表第一个元素对应节点的地址;
link域存放与本元素同一层的下一个元素所在节点的地址,当本元素是所在层的最后一个元素时,link域为NULL。
-真题考点
- 链表指针判断条件
-带头指针,头指针为L
循环双链表
初始化为空:L->prior==L ; L->next==L
判断是否为空: if(L->next==L)
判断P是否为表尾结点:if(p->next==L)
单链表
判断是否为空: if(L->next==NULL)
-不带头指针
单链表
判断是否为空:if(L==NULL)
- 链式存储与顺序存储优缺点
链式存储:频繁插入、删除
顺序存储:频繁查询 - 静态链表指针
静态链表中指针表示的是 下一元素在数组中的位置 - 邻接表表结点
- 广义表深度
- 广义表操作
对广义表L=((a,b),((c,d),(e,f)))执行head(tail(head(tail(L))))操作
先是取表尾tail(L)=( ((c,d),(e,f)) ),然后取表头head(tail(L))=( (c,d),(e,f) ),接着取表尾
tail(head(tail(L)))=( (e,f )),最后取表头head(tail(head(tail(L))))=(e,f )
head是去掉最外层括号,然后保留第一个,前的东西
tail除掉第一个元素,剩余元素组成子表 -括号不需要去掉
三、栈、队列和数组
- 栈在表达式求值中的应用
-队列
队列:一段插入另一端删除
队列应用在:层次遍历
//循环队列条件**
队满条件:(Q.rear+1)%MaxSize == Q.front
队空条件:Q.rear == Q.front
队列中元素个数: (Q.rear-Q.front+MaxSize)%MaxSize
入队:Q.rear=(Q.rear+1)%MaxSize
出队:Q.front=(Q.front+1)%MaxSize
-真题考点
- 循环队列的计算
- 循环队列通常用指针来实现队列的头尾相接。(×)-循环队列是解决假溢出问题
- 广义表
对广义表L=((a,b),((c,d),(e,f)))执行head(tail(head(tail(L))))操作
-从里向外写
先是取表尾tail(L)=( ((c,d),(e,f)) ),然后取表头head(tail(L))=( (c,d),(e,f) ),接着取表尾
tail(head(tail(L)))=( e,f ),最后取表头head(tail(head(tail(L))))=e
-head:去掉最外层括号,然后保留第一个逗号前的东西
-tail:去掉head所保留的那部分以及紧随其后的那个, 注意:这里括号就不需要去掉 - 栈和队列
相同的逻辑结构:线性表
-栈:只允许在一端进行插入或操作
----顺序栈:顺序存储。入栈操作受到数组上界的约束,当对栈的最大使用空间不足时,会出现上溢。
----共享栈:两个顺序栈共享一个一维数据空间。栈低在两段,两个栈顶向共享空间的中间延伸。当top1-top0=1时,判断栈满。
----链式栈:栈的链式存储 优点:便于多个栈共享存储空间,并且不存在栈满上溢情况,便于结点的插入删除
-队列:只允许在表的一段插入,表的一端删除。先进先出
----队列包括顺序存储(顺序栈、循环队列、双端队列)、链式存储
- 辅助储存空间
图的拓扑排序、深度优先、关键路径算法用的栈辅助
树的层次遍历、图的广度优先遍历用的队列辅助
-用两个栈实现一个队列的功能
用两个栈s1和s2模拟一个队列时,s1作输入栈,逐个元素压栈,以此模拟队列元素的入队。当需要出队时,将栈s1退栈并逐个压入栈s2中,s1中最先入栈的元素,在s2中处于栈顶。s2退栈,相当于队列的出队,实现了先进先出。显然,只有栈s2为空且s1也为空,才算是队列空。
四、串*****
-串的定义和存储结构
-朴素模式匹配算法
-KMP算法
-求next数组
五、树
-概念和基本术语
- 结点的层次/深度:自顶向下 ↓
- 结点的高度:从下到上 ↑
- 结点的度:有几个孩子(分支)
结点数 = 总度数 (总分支数)+ 1
总分支数 = 1 n 1 n_1 n1 + 2 n 2 n_2 n2 + 3 n 4 n_4 n4 + … + m n m n_m nm
结点数 = n 0 n_0 n0 + n 1 n_1 n1 + n 2 n_2 n2 + … + n m n_m nm = 1 n 1 n_1 n1 + 2 n 2 n_2 n2 + 3 n 4 n_4 n4 + … + m n m n_m nm + 1 - 树的度:max{ 结点的度 }
- 高度为 h 的 m叉树至少有 h 个结点
高度为 h,度为 m 的树至少有 h+m-1 个结点 - 树的路径长度指:树根到每个结点的路径长度总和
-二叉树的顺序存储
画出顺序结构:数组+层次遍历+编号对应
//二叉树的顺序存储
#define MaxSize 100
struct TreeNode{
ElemType value;
bool isEmpty;
};
void Tree(){
TreeNode t[MaxSize];
for (int i=0;i<MaxSize;i++){
t[i].isEmpty=true;
}
}
-二叉树的链式存储
//二叉树的链式存储
struct ElemType{
int value;
};
typedef struct BiTNode{
ElemType data;
struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;
//定义一棵空树
BiTree root = NULL;
//插入根结点
root = (BiTree)malloc(sizeof(BiTNode));
root->data = {1};
root->lchild = NULL;
root->rchild = NULL;
//插入新结点
BiTNode *p = (BiTNode *)malloc(sizeof(BiTNode));
p->data = {2};
p->lchild = NULL;
p->rchild = NULL;
root->lchild = p;//作为根结点的左孩子
-二叉树的遍历***
结构体
\\结构体
typedef struct BiNode {
int data;
struct BiNode *lchild, *rchild;
} BiNode, *BiTree;
递归方法
//先序遍历
void PreOrder(BiTree T){
if(T!=NULL){
visit(T);
PreOrder(T->lchild);
PreOrder(T->rchild);
}
}
//中序遍历
void InOrder(BiTree T){
if(T!=NULL){
InOrder(T->lchild);
visit(T);
InOrder(T->rchild);
}
}
//后序遍历
void PostOrder(BiTree T){
if(T!=NULL){
PostOrder(T->lchild);
PostOrder(T->rchild);
visit(T);
}
}
//层次遍历
void LevelOrder(BiTree T){
InitQueue(Q); //初始化辅助队列
BiTree p;
EnQueue(Q,T); //根结点入队
while(!IsEmpty(Q)){
DeQueue(Q,p); //队头结点出队
visit(p); //访问出队结点
if(p->lchild!=NULL)
EnQueue(Q,p->lchild);
if(p->rchild!=NULL)
EnQueue(Q,p->rchild);
}
}
非递归方法
//先序遍历
void PreOrder(BiTree T) {
int top = -1; //栈顶指针
BiNode* S[MAXSIZE]; //栈
//当栈为空且所指根结点为空,说明该二叉树遍历完成
while (T != NULL || top != -1) {
while (T != NULL) {//当T不为空,说明该根结点存在,则遍历
S[++top] = T;
printf("%d\t", T->data);
T = T->lchild;
}
//此时T == NULL退出循环,说明当前栈顶结点的左子树为空,开始遍历其右子树
if (top != -1) {
T = S[top--];
T = T->rchild;
}
}
}
//中序遍历
void MidOrder(BiTree T) {
int top = -1; //栈顶指针
BiNode *S[MAXSIZE]; //栈
//当栈为空且所指根结点为空,说明该二叉树遍历完成
while (T != NULL || top != -1) {
while (T != NULL) { //当T不为空,说明该根结点存在,则遍历
S[++top] = T;
T = T->lchild;
}
//此时T == NULL退出循环,说明当前栈顶结点的左子树为空,开始遍历其右子树
if (top != -1) {
T = S[top--];
printf("%d\t", T->data);
T = T->rchild;
}
}
}
-应用:求树的深度
//求树的深度
int treeDepth(BiTree T){
if(T==0) return 0;
else{
int l = treeDepth(T->lchild);
int r = treeDepth(T->rchild);
return l>r ? l+1 : r+1;
}
}
-二叉树的层次遍历
//二叉树的结点(链式存储)
typedef struct BiTNode{
char data;
struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;
//链式队列结点
typedef struct LinkNode{
BiTNode * data; //存指针
struct LinkNode *next;
}LinkNode;
typedef struct{
LinkNode *front,*rear; //队头队尾
};
void LevelOrder(BiTree T){
LinkQueue Q;
InitQueue(Q);
BiTree p;
EnQueue(Q,T); //根结点入队
while(!IsEmpty(Q)){
DeQueue(Q,p); //队头结点出队
visit(p); //访问出队结点
if(p->lchild!=NULL)
EnQueue(Q,p->lchild);
if(p->rchild!=NULL)
EnQueue(Q,p->rchild);
}
}
-线索二叉树
线索二叉树不能有效解决 先序线索二叉树找先序前驱 和 后序线索二叉树找后序后继。
-树的存储结构
-哈夫曼树
结点的带权路径长度:根到结点的路径长度×结点权值
树的带权路径长度WPL = 所有叶结点的带权路径长度
哈夫曼树:WPL最小的二叉树
-应用
前缀编码:没有一个编码是另一个编码的前缀(每个字都是叶子结点),各个字符出现频率作为结点权值
判断是否为前缀编码:{0,1,11} 1是11的前缀-不是
-计算
度为m的哈夫曼树只有度为0和m的结点
-并查集
-错题
- 度为2的有序树是二叉树 —错
如果有序树中的子树只有一个孩子时,这个孩子结点就无须区分其左右次序,而二叉树无论其孩子数是否为2,均需确定其左右次序
-真题考点
- 二叉树概念
- 哈夫曼结点个数
- 二叉树遍历
-唯一确定一颗二叉树:2种遍历方法,且必包含中序
由树节点的先根遍历和后根遍历(后根遍历与中序遍历同)可唯一确定一棵树。
-递归算法 - 森林->二叉树 :左孩子右兄弟
(树->二叉树:左孩子右兄弟,无右子树;二叉树->森林) - 二叉树顺序结构
- 线索二叉树
线索二叉树仍不能有效解决 先序线索二叉树找先序前驱 和 后序线索二叉树找后序后继。 - 树在计算机内的表示方式有
①双亲链表表示法 ②孩子链表表示法 ③孩子兄弟表示法 - 画顺序存储
六、图
- 简单图:不存在重复、顶点到自身的边
- 完全图:n(n-1)/2条边的无向图 n(n-1)条弧的有向图
- 连通图:无向图中任意2个顶点都是连通的(有路径) 至少n-1条边
连通分量==极大连通子图:连通且包含尽可能多的顶点和边 - 强连通图:有向图中任意2个顶点都强连通(v到w,w到v都有路径)至少n条边(回路)
- 生成树:包含连通图中全部顶点的极小连通子图(边尽可能少,但连通)(包含所有顶点)
- n个顶点 ,边大于n -1必有环
- 简单路径:顶点不重复
-图的存储
邻接矩阵
//邻接矩阵
#define MaxVertexNum 100 //顶点数目的最大值
#definr INFINITY //无穷 (带权图)
typedef struct{
char Vex[MaxVertexNum]; //顶点表
int Edge[MaxVertexNum][MaxVertexNum]; //邻接矩阵表
int vexnum,arcnum; //当前顶点数和边数
}MGraph;
邻接表(顺序+链式存储) 无向图空间复杂度 o(|V|+2|E|)
//邻接表(顺序+链式存储)
typedef struct{
AdjList vertices; //邻接表
int vexnum,arcnum;
}ALGraph;
//顶点
typedef struct VNode{
int data;
ArcNode *first;
}VNode,AdjList[MaxVertexNum];
//边
typedef struct ArcNode{
int adjvex; //指向哪个结点
struct ArcNode *next; //指向下一条边的指针
//int info; //边权值
}ArcNode;
十字链表 仅用于存储有向图,空间复杂度 o(|V|+|E|)
- 找指定顶点所有出边–沿绿色路线
- 找指定顶点所有入边–沿橙色路线
typedef struct ArcBox {
int headvex, tailvex; //起点和终点在顺序表中的索引
struct ArcBox *hlink, *tlink; //起点或终点相同的弧
InfoType data;
}ArcBox;
typedef struct VexNode {
ArcBox *firstin, *firstout; //出边和入边链表
VerTexType data;
}VexNode;
typedef struct {
VexNode xlist[MAX_VERTEX_NUM];
int vexnum, arcnum;
}OLGraph;
邻接多重表 仅用于存储无向图,空间复杂度 o(|V|+|E|)
删除边和节点很方便
//定义边结点的类型
typedef struct ArcNode {
int mark;
int ivex;
ArcNode* ilink;
int jvex;
ArcNode* jlink;
OtherInfo info;//权值
};
//定义表头结点类型
typedef struct VNode {
VerTexType data;
ArcNode* first;
}VNode,AdjList[MAXNum];
//定义图的类型
typedef struct ALGraph {
AdjList vertices;//定义邻接多重表
int vernum, arcnum;//总结点数和总边数
}ALGraph;
总结
-图的基本操作
广度优先遍历
- FirstNeighbor(G,x):求顶点 x的第一个邻接点
- NextNeighbor(G,x,y):返回除 y以外顶点 x的下一个邻接点顶点号
无向图BFS次数 = 连通分量
//广度优先遍历
bool visited[MAX_VERTEX_NUM]; //访问标记数组
void BFSTraverse(Graph G){
for(i=0;i<G.vexnum;++i) //访问标记数组初始化
visited[i]=FALSE;
InitQueue(Q); //初始化辅助队列 Q
for(i=0;i<G.vexnum;++i)
if(!visited[i])
BFS(G,i);
}
void BFS(Graph G,int v){
visit(v);
visited[v]=true;
Enqueue(Q,v); //顶点v入队列
while(!isEmpty(Q)){
DeQueue(Q,v);
for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w)) //检测 v所有邻接点
if(!visited[w]){
visit(w);
visited[w]=TRUE;
EnQueue(Q,w);
}
}
}
深度优先遍历
//深度优先遍历
bool visited[MAX_VERTEX_NUM]; //访问标记数组
void DFSTraverse(Graph G){
for(i=0;i<G.vexnum;++i) //访问标记数组初始化
visited[i]=FALSE;
for(i=0;i<G.vexnum;++i)
if(!visited[i])
DFS(G,i);
}
void DFS(Graph G,int v){
visit(v);
visited[v]=true;
for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w))
if(!visited[w]){
DFS(G,w);
}
}
邻接矩阵表示方法唯一,广/深度优先遍历唯一,生成树唯一
邻接表表示方法不唯一,广/深度优先遍历不唯一,生成树不唯一
-最小生成树
生成树: 一个连通图的生成树包含图的所有顶点和尽可能少的边。(n个顶点则有n-1条边)
最小生成树: 对于一个带权连通无向图,边的权重之和最小的生成树
- 最小生成树不唯一,但边的权重之和总是唯一且最小的。
- 边数 = 顶点数 - 1。砍掉一条则不连通,多一条则形成回路。
- 若连通图本身是树,则最小生成树就是它本身。
- 只有连通图才有生成树,非连通图只有生成森林。
时间复杂度:o (|V|^2) ,适用于边稠密图
时间复杂度:o( |E|log2|E| ),适用于边稀疏图
-最短路径
BFS算法 仅适用于无权图或权值均相等的图
Dijkstra算法 不适用于带负权值的图
Floyd算法
-有向无环图描述表达式**
有向无环图:若一个有向图中不存在环,则称为有向无环图(DAG图)
-拓扑结构
拓扑排序和逆拓扑排序的实现(课件12)
-关键路径
- AOE网:边上权值表示该活动的开销,边表示活动的网络,顶点表示事件
- 关键路径:最大路径长度,关键路径上的活动称为关键活动
- 最早发生时间Ve(k):事件vk的最早发生时间决定了所有从vk开始的活动能够开工的最早时间
最迟发生时间Vl(k):再不推迟整个工程完成的前提下,保证它的后继事件vj在其最迟发生时间vl(j)能够发生时,该事件最迟必须发生的时间
活动ai最早开始时间e(i):活动弧的起点所表示的事件的最早发生时间
活动ai最迟开始时间e(i):活动弧的起点所表示的事件的最迟发生时间与该活动所需时间之差
求关键路径算法:
1.求ve() ve(源点) = 0 ve(k) = max{…}
2.求vl() vl(汇点) = ve(源点) vl(k) = min{…}
3.e()=该弧的起点的顶点的ve()
4.l(i)=该弧的终点的顶点的vl()减去该弧持续的时间
5.根据l(i)-e(i)=0得关键活动,得关键路径
可以加快关键活动缩短工期,不能任意缩短,关键路径不唯一
-选择题概念合集
- 简单路径:如果路径上的各顶点均不互相重复,称这样的路径为简单路径。
回路:如果路径上的第一个顶点与最后一个顶点重合,这样的路径称为回路或环或圈。 - 一个有向图的邻接矩阵中对角线以下的元素都为0 => 不存在环 => 拓扑结构必存在(不一定唯一)
- 邻接多重表是 无向图 的存储结构
十字链表是 有向图 的存储结构 - 广度优先算法使用的数据结构是 队列
- DFS (n个顶点,e条边)
时间复杂度 | 空间复杂度 | |
---|---|---|
邻接表 | n+e | n |
邻接矩阵 | n 2 n^2 n2 | n |
- BFS(n个顶点,e条边)
时间复杂度 | 空间复杂度 | |
---|---|---|
邻接表 | n+e | n |
邻接矩阵 | n 2 n^2 n2 | n |
(每个顶点进入队列1次,因此空间复杂度 o(n))
- 连通分量:无向图的极大连通子图
极大连通子图:包含连通分量的全部边(可能存在回路)
极小连通子图:包含连通图的全部顶点(连通无向图的生成树)(P.S.连通图的生成树包含所有顶点和n-1条边)
(极大极小指边的数量) - 可判断一个有向图是否有环:深度优先遍历、拓扑排序、求关键路径
-真题考点
- 图判断是否存在长度为k的简单路径
- 无向图 生成树、连通分量
有两个无向图G=(V,E), G’=(V’,E),如果G’是G的生成树,则:G’是G的极小连通子图,且V’=V √
G’是G的连通分量 × 连通分量为极大连通子图 - 适用于求稀疏图最小生成树的方法:克鲁斯卡尔
- 邻接多重表存储结构示意图以及数据结构
- 最短路径描述填空
Dijkstra算法从源点到其余各顶点的最短路径的路径长度按( 递增 )次序依次产生,该算法在边上的权出现( 负值 )情况时,不能正确产生最短路径。
七、查找
- 折半查找(二分查找)
(注:二叉排序树的构造)
(注:折半ASL失败和散列ASL失败)
折半查找
//折半查找
typedef struct{
Elemtype *elem;
int TableLen;
}SeqList;
int Binary_Search(SeqList L, ElemType key){
int low=0,high=L.TableLen-1,mid;
while(low<=high){
mid=(low+high)/2;
if(L.elem[mid]==key)
return mid;
else if(L.elem[mid]>key)
high=mid-1;
else
low=mid+1;
}
return -1; //查找失败最终停在low>high
}
分块查找
- 二叉排序树
二叉排序树:左子树结点 < 根结点 < 右子树结点
查找效率取决于树的高度,最好 o(logn),最坏 o(n)
二叉排序树的非递归查找 最坏空间复杂度 o(1)
//二叉排序树的非递归查找
typedef struct BSTNode{
int key;
struct BSTNode *lchild,*rchild;
}BSTNode,*BSTree;
BSTNode *BST_Search(BSTree T, int key){
while(T!=NULL&&key!=T->key){
if (key<T->key) T=T->lchild;
else T=T->rchild;
}
return T;
}
二叉树的插入 最坏空间复杂度 o(n)
//二叉树的插入
int BST_Insert(BSTree &T, int k){
if(T==NULL){
T=(BSTree)malloc(sizeof(BSTNode));
T->key=k;
T->lchild=T->rchild=NULL;
return 1;
}
else if(k==T->key) //树中存在相同关键字的结点,插入失败
return 0;
else if(k<T->key)
return BST_Insert(T->lchild,k);
else
return BST_Insert(T->rchild,k);
}
二叉树的构造
//二叉树的构造
void Creat_BST(BSTree &T,int str[],int n){
T=NULL;
int i=0;
while(i<n){
BST_Insert(T,str[n]);
i++;
}
}
二叉排序树的删除
左、右子树均不空,在右子树上找中序第一个子女填补
- 平衡二叉树
平衡二叉树:任意结点的左右子树高度差的绝对值不超过1
结点的平衡因子=左子树高-右子树高
某结点的左子树与右子树的高度(深度)差即为该结点的平衡因子
-红黑树
- 定义-----考选择
-B树和B+树
- B树概念
- B树插入删除
(注意满足B树要求:关键字个数,所有叶节点出现在同一层次上)
插入 ----- 注:插入位置一定是最底层中的某个非叶节点
删除
非终端:用 直接前驱 或 直接后驱 代替 -> 变为删除终端结点问题
终端:1)直接删
2)兄弟够借,直接填补(哪边多,问哪边借)
3)兄弟不够借,左右兄弟及双亲关键字 进行合并
- B+树
-散列查找
二次探测==平方探测
冲突:两个不同的关键字,由于散列函数值相同,因而被映射到同一表位置上。 解决方法:拉链法
堆积:一个非同义词提前占用了发生冲突的同义词的位置
受堆积直接影响的是:平均查找长度
拉链法:
开放地址-线性探测法:一旦冲突,向后移动寻找新位置
开放地址-平方探测法:
再散列法:
带哨兵:在第0位置插入key
-真题考点
- 哈希查找
P.310、316 ASL计算:
成功=(…)/元素个数
失败=(…)/表长 - 折半插入算法
- B树叶结点个数
- B树删除插入过程
- 散列存储的基本思想:由 节点的关键码值 决定节点的存储地址
八、排序
-插入排序
-希尔排序
仅适用于顺序表
例:设用希尔排序对数组{98,36,-9,0,47,23,1,8,10,7}进行排序,给出的步长(也称增量序列)依次是4,2,1则排序需__3___趟,写出第一趟结束后,数组中数据的排列次序_ (10,7,-9,0,47,23,1,8,98,36)___。
3;(10,7,-9,0,47,23,1,8,98,36)
-冒泡排序
每一轮冒泡确定一个元素的位置:
把待排数列中最小元素冒到前面 / 最大元素冒到后面
//冒泡排序
void bubblesort(int a[],int n){
//将值大的元素往后面冒泡
for(int i=0;i<n-1;i++){
//每一轮冒泡确定一个元素的位置
for(int j=0;j<n-i-1;j++){
//如果当前值比后面的大,则交换两个数
if(a[j]>a[j+1]){
swap(a[j],a[j+1]);
}
}
}
}
-选择排序
//选择排序
void selectionsort(int a[],int n){
for(int i=0;i<n;i++){
//找后面还没完成排序中的最小的元素位置
int index=i;//最小元素的下标
for(int j=i+1;j<n;j++){
if(a[j]<a[index]){
index=j;//更新最小值的下标
}
}
swap(a[i],a[index]);//将最小值换到前面
}
}
-快速排序
每趟确定中间元素
//快速排序,时间复杂度是O(nlogn),空间复杂度是O(logn)
//快速排序
void quicksort(int a[],int left,int right){
if(left>=right){
//递归终止条件
return ;
}
int i=left,j=right;
int temp=a[left];//基准元素,这里不用考虑优化
while(i<j){
while(i<j&&a[j]>=temp){
//j从后面往前移,直到找到小于基准的
j--;
}
while(i<j&&a[i]<=temp){
//i从前面往后移,直到找到大于基准的
i++;
}
swap(a[i],a[j]);//交换i和j所指向的元素
}
swap(a[left],a[i]);//将基准元素放到中间
quicksort(a,left,i-1);//递归对左半段进行排序
quicksort(a,i+1,right);//递归对右半段进行排序
}
-简单排序
-堆排序***
适合关键字较多,如在10000个数中选出前10个最大值或最小值
step1.建立初始堆,按数组顺序逐层构造完全二叉树,然后调整为大根堆,根>=左、右,从n/2 -> 1依次检查,若根结点小,则直接与较大孩子结点交换;上层可能破坏下层,小元素不断下坠
step2.选择排序:将待排序中max与待排序序列的最后一个元素交换
step3.调整待排序序列为大根堆
step4.重复
-归并排序
代码思路
step1.A复制到辅助B
step2.i , j 指针分布在B中左右两个数组从左到右进行归并,较小者覆盖A[k]
step3.写递归函数
-基数排序
不需要进行关键字的比较
链式存储更优
-外部排序
- 真题考点
- 内部排序的时间、空间、复杂度
时间记忆:
直接插入、选择及冒泡时间为o(n2);基数o(d(n+r));其余都为o(nlog2n)
- 构造堆排序初始堆
- 快排非递归代码
杭电真题高频考点
时间复杂度 空间复杂度
算数表达式的前中后缀表达,有向无环图
哈夫曼编码
各种排序(排序过程、复杂度、稳定性、适用性)****
哈希
查找长度
算法题
非递归遍历二叉树(17)
堆排序(17)
邻接表存储结构-简单路径(17)