WD数据结构23复习笔记


本文内容源于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+en
邻接矩阵 n 2 n^2 n2n
  • BFS(n个顶点,e条边)
时间复杂度空间复杂度
邻接表n+en
邻接矩阵 n 2 n^2 n2n

(每个顶点进入队列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)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值