数据结构C语言描述2(专插本/专升本)

图:G=(V,E)
V:顶点(数据元素)的有穷非空集合
E:边的又穷集合
图里可以只有顶点没有边,但如果只有边没有顶点则是一个空图

无向图:每条边都是无方向的
右向图:每条边都是有方向的
完全图:任意两个点都有一条边相连
有向完全图:如果有n个顶点,则有n(n-1)条边
无向完全图:如果有n个顶点,则有n(n-1)/2条边

在这里插入图片描述
在这里插入图片描述

稀疏图:有很少边或弧的图(e<nlogn ),其中n为顶点数
稠密图:有较多边或弧的图
网:边/弧带权的图
邻接:有边/弧相连得两个顶点之间的关系。
存在(Vi,Vj),则称Vi和Vj互为邻接点;
存在<Vi,Vj>,则称Vi邻接到Vj,Vj邻接于Vi
关联(依附):边/弧与顶点之间的关系。
存在(Vi,Vj)或<Vi,Vj>,则称该边或弧关联于Vi和Vj
顶点的度:与该顶点相关联的边的数目,记为TD(V)

在有向图中,顶点的度等于该顶点的入度和出度之和。
顶点V的入度是以V为终点的有向边的条数,记作ID(V).
顶点V的出度是以V为起点的有向边的条数,记作OD(V)

在这里插入图片描述
路径:接续的边构成的顶点序列
路径长度:路径上边/弧的数目/权值之和
回路(环):第一个顶点和最后一个顶点相同的路径
简单回路:除路径起点和终点可以相同外,其余顶点均不相同的路径
简单回路(简单环):除路径起点和终点相同外,其余顶点均不相同的路径
在这里插入图片描述
连通图(有向的称为强连通图)
在无(有)向图G=(V,{E})中,若对任何两个顶点v、u都存在从v到u的路径,则称G是连通图(强连通图)
在这里插入图片描述

权和网
图中边/弧所具有的相关数称为权,表明从一个顶点到另一个顶点的距离或消耗。
带权的图称为网

子图:设有两个图G=(V,{E})、G1=(V1,{E1}),若V1∈V,E1∈E,则称G1是G的子图
在这里插入图片描述
连通分量(强连通分量)
无向图G的极大连通子图称为G的连通分量
极大连通子图意思是:该子图是G连通子图,将G的任何不在该子图的顶点加入,子图不再连通。
在这里插入图片描述
在这里插入图片描述
极小连通子图:该子图是G的连通子图,在该子图中删除任何一条边,子图不再连通。
生成树:包含无向图G所有顶点的极小连通子图
生成森林:对非连通图,由各个连通分量的生成树的集合。
在这里插入图片描述

图的存储结构

图的逻辑结构:多对多
数组表示法(邻接矩阵):图没有顺序存储结构,但可以借助二位数组来表示元素间的关系
练市存储结构:1、邻接矩阵(数组)表示法
2、邻接表(链式)表示法

1、邻接矩阵(数组表示法):
建立一个顶点表(记录各个顶点信息)和一个邻接矩阵(表示各个顶点之间的关系)
设图A=(V,E)有n个顶点,则

在这里插入图片描述
注:<>代表有方向的弧,()代表没方向的边
如果有n个顶点则创建一个n*n的矩阵

无向图邻接矩阵


特点:
1、对角线为该顶点自身,所以为0。
2、无向图的邻接矩阵是对称的
3、第i个顶点的度=第i行中1的个数
3、完全图的邻接矩阵中,对角元素为0,其余为1

有向图的邻接矩阵表示法

在这里插入图片描述
**注:**在有向图的邻接矩阵中,
1、第i行含义:以结点Vi为尾的弧(即出度边)
2、第i列含义:以结点Vi为头的弧(即入度边)
3、顶点的出度=第i行元素之和
4、顶点的入度=第i列元素之和
5、顶点的度=入度+出度

定义:网(即有权图),带权的图
网的邻接矩阵表示法:
在这里插入图片描述
有边记录权值,没边记录无穷大

//邻接矩阵的存储表示:用两个数组分别存储顶点表和邻接矩阵
#define MaxInt 32767//表示极大值,即∞
#define MVNum 100 //最大顶点数
typedef char VerTexType;//设顶点的数据类型为字符型
typedef int ArcType;	//假设边的权值类型为整型

typedef struct{
	VerTexType vexs[MVNum];//顶点表
	ArcType arcs[MVNum][MVNum];//邻接矩阵
	int vexnum,arcnum;//图的当前点数和边数
}AMGraph;
//在图中查找顶点
int LocateVex(AMGraph G,VertexType u){
	//图G中查找顶点u,存在则返回顶点表中的下标;否则返回-1
	int i;
	for(int i=0;i<G.vexnum;++i){
		if(u==G.vexs[i]){
			return i;
		}
	}
	return -1;
}

采用邻接矩阵表示法创建无向网
【算法思想】
1、输入总顶点数和总边数
2、一次输入点的信息存入顶点表中
3、初始化邻接矩阵,使每个权值初始化为最大值
3、构造邻接矩阵

Status CreateUDN(AMGraph G){//采用邻接矩阵表示法,创建无向网G
	scanf("%d,%d",&G.vexnum,&Garcnum);//输入总顶点数,总边数
	for(int i = 0;i<G.vexnum;++i){
		scanf("%d",Gvexs[i]);//一次输入点的信息
	}
	for(int i=0;i<G.vexnum;++i){//初始化邻接矩阵
		for(int j=0;j<G.vexnum;++i){
			G.arcs[i][j]=MaxInt;//边的权值均置为最大值
		}
	}
	for(int k = 0;k<G.arcnum;++k){//构造邻接矩阵
		char v1,v2;//一条边所依附的两个顶点
		scanf("%c,%c,%d",&v1,&v2,&w);//输入一条边所依附的顶点及边的权值
		i=LocateVex(G,v1);
		j=LocateVex(G,v2);//确定边所依附的两个顶点在顶点表所在的位置
		G.arcs[i][j]=w;//边<v1,v2>的权值为w
		G.arcs[j][i]=G.arcs[i][j];//置<v1,v2>的对称边<v2,v1>的权值为w
	}
	return OK;
}

无向图和无向网的区别就是有没有带权值,如果是无向图,则将网边上的权值∞用0代替,其他用1代替
**有向网:**仅为G.arcs[i][j]赋值,不需要为G.arcs[j][i]赋值。因为有向网和有向图都是非对称矩阵

缺点:矩阵是一个固定结构,不便增加或者减少一个结点

邻接表表示法(链式存储)

在这里插入图片描述
注:firstarc表示顶点与存在关系的另外一个顶点的地址;adjvex表示顶点下标;nextarc表示下一个与该头结点顶点存在关系的顶点下标
1、头结点存放每个顶点的信息:按编号顺序将顶点数据存储在以为数组中
2、表结点存放与头结点相连的结点信息

无向图
在这里插入图片描述
特点:邻接表不唯一
若无向图中有n个顶点、e条边,则其邻接表需n个头结点和2e个表结点。适宜存储稀疏图
邻接表的度等于头结点后面的结点个数

有向邻接表在这里插入图片描述

入度邻接表特点:1、顶点Vi的出度为第i个单链表中的结点个数
2、顶点Vi的入度为整个单链表中邻接点域值是i-1的结点个数

出度邻接表(逆邻接表)特点:1、顶点Vi的入度为第i个单链表中的结点个数
2、顶点Vi的出度为整个单链表中邻结点域值是i-1的结点个数(需要遍历找到邻结点为当前结点的个数)

在这里插入图片描述

图的邻接表存储实现

在这里插入图片描述

typedefstruct VNode{
	VerTexType data;	//顶点信息
	ArcNode *firstarc;	//指向第一条依附该顶点的边的指针
}VNode,AdjList[MVNum];	//AdjList表示邻接表类型

在这里插入图片描述

邻接表的特点:1、方便找任意顶点的所有“邻接点”
2、节约稀疏图的空间:需要N个头指针+2E个结点(每个结点至少2个域)
3、方便计算任意无向图顶点的度
4、不方便检查任意一对顶点间是否存在边

邻接矩阵和邻接表表示法的关系
在这里插入图片描述
1、联系:邻接表中每个链表对应于邻接矩阵中的一行,链表中结点个数等于一行中非零元素的个数(如果是网,非零元素则是权值)
2、区别:
a、对于任一确定的无向图,邻接矩阵是唯一的(行列号与顶点编号一致),但邻接表不唯一(链接次序与顶点编号无关)
b、邻接矩阵的空间复杂度为O(n²),而邻接表的空间复杂度为O(n+e)顶点数+边数
3、用途:邻接矩阵多用于稠密图,邻接表多用于稀疏图

邻接表+逆邻接表=十字链表:是有向图的一种链式存储结构。
顶点结点:在邻接表的表头结点结构基础上加一个指针域,存放第一条入度边
弧结点:在邻接表的弧结点结构基础上添加一个与该结点构成出度边指针域,和一个与该结点构成入度边的指针域
在这里插入图片描述
变化后的十字链表
在这里插入图片描述
图的遍历
定义:从已给定的连通图中某一顶点出发,沿着一些边访问图中所有的顶点,且每个顶点仅访问一次,这就叫图的遍历。他是图的基本运算
**遍历实质:**找每个顶点的邻接点的过程

图的特点:图中可能存在回路,且图的任一顶点都可能与其他顶点相通,在访问完某个顶点之后可能会沿着某些边又回到曾经访问过的顶点。
在这里插入图片描述

如何避免重复访问?
解决思路:设置辅助数组visited[n],用来标记每个被访问过得顶点。
- 初始状态visited[i]=0
- 顶点i被访问,改visited[i]=1,防止被多次访问

图常用的遍历方法:
- 深度优先搜索
- 广度优先搜索

深度优先搜索
在这里插入图片描述
1、从入口处开始,访问第一个灯。
2、与第一个灯相连接的右三个灯,随意选择一个。点亮第二个灯时,查看是否与第二个灯有相连的灯未点亮,如果是单路径则继续往前点
3、当到第四个灯的时,按照1的方式随机选择一条路径
4、当发现与当前灯连接的灯都已经点亮时,后退到前一个灯。按此道理递归执行
5、若遇到有未点亮的灯与当前灯相连则将未点亮灯点亮,直到后退至出发点。遍历结束

当图用邻接矩阵表示时的遍历
在这里插入图片描述
这个遍历算法我还是有点懵逼,插本好像考不到这里。就让他随风去吧

void DFS(AMGraph G,int v){//图G为邻接矩阵类型
	scanf("%d",&v);
	visited[v]=true;//访问第V个顶点
	for(int w=0;w<G.vexnum;w++){//依次检查邻接矩阵v所在的行
		if((G.arcs[v][w]!=0)&&(!visited[w])){
			DFS(G,w);//w是v的邻接点,如果w未访问,则递归调用DFS
		}
	}
}

非连通图的遍历

在这里插入图片描述
按照连通图的方法遍历某一个,当回退到入口顶点时,结束该图的遍历,遍历第二个图

广度优先搜索遍历
类比树的层次遍历

----------------------------------------------------------**总结-**--------------------------------------------------------------
生成树:所有顶点均由边连接在一起,但不存在回路的图
			一个图可以有多棵不同的生成树
			所有生成树具有相同特点:生成树的顶点个数与图的顶点个数相同
														生成树是图的极小连通子图,去掉一条边则非连通
														一个有n个顶点的连通图的生成树有n-1条边
														在生成树中在加一条边必然形成回路
														生成树中任意两个顶点间的路径是唯一的

在这里插入图片描述

-------------------------------------------------------------------------------------------**应用**-----------------------------------------------------------------------------------------------

最小生成树
给定一个无向网,在该网的所有生成树中,使得各边权值之和最小的那棵生成树称为该网的最小生成树,也叫最小代价生成树
在这里插入图片描述

构造最小生成树的方法一:普里姆算法
在这里插入图片描述
定义两个集合V:{}表示网的所有顶点,U:{}表示最小生成树元素
1、从V1作为U中的第一个元素,找到与其相连且权值最小的。加入到U中。U:{V1,V3}
2、从与V1、V3相连接的顶点路径中找到权值最小的路劲,加入到U中。一次类推,直到全部顶点都加入到U中

构造最小生成树方法二:克鲁斯卡尔算法
直接将路径权值进行排序,将小路径从小到大一次加入。如果遇到会导致最小生成树构成回路的路径直接舍弃,直至所有顶点都连通 在这里插入图片描述
在这里插入图片描述

最短路径问题
问题抽象:在有向网中A点到B点的多条路径中,寻找一条各边权值之和最小的路径。即最短路径

最短路径与最小生成树不同,路劲不一定包含n个顶点,也不一定包含n-1条边

在这里插入图片描述
在这里插入图片描述

有向无环图
无环的有向图,简称DAG图
在这里插入图片描述
应用:常用来描述一个工程或系统的进行过程,将工程分成若干子工程,只要完成了所有子工程,就完成了整个工程

AOV网:拓扑排序
用一个有向图表示一个工程的各子工程及其相互制约的关系,其中以顶点表示活动,弧表示活动之间的优先制约关系,称这种有向图为顶点表示活动的网

检测AOV网中是否存在环的方法:
		对有向图构造其顶点的拓扑有序序列,若网中所有顶点都在它的拓扑有序序列中,则该AOV网必定不存在环

	拓扑排序:在AOV网没有回路的前提下,我们将全部活动排列成一个线性序列,使得若AOV网中有弧<i,j>存在,则在这个序列中,i一定排在j前面,具有这种性质的线性序列称为拓扑有序序列,相应的拓扑有序排序的算法称为拓扑排序。

方法
1、在有向图中选一个序号比较小且没有前驱的顶点输出
2、从图中删除该顶点和所有以它为尾的弧
3、重复上面两步,直至全部顶点均已输出。或者当图中不存在无前驱的顶点为止

AOE网:关键路径
用一个有向图表示一个工程的各子工程及其相互制约的关系,以弧表示活动,以顶点表示活动的开始或结束时间,称这种有向图为边表示活动的网
把工程计划表示为边表示活动的网络,即AOE网,用顶点表示事件,弧表示活动,弧的权表示活动持续时间。
事件表示它之前的活动已经完成,在它之后的活动开始进行
**关键路径:**路径长度最长的路径
**路径长度:**路径上各活动持续事件之和
在这里插入图片描述

在这里插入图片描述

Ve(i):最早发生时间
Vl(j):最晚发生时间
在这里插入图片描述
在这里插入图片描述
关键路径:l-e=0的路径

-------------------------------------------------------------------------------------------**查找算法**-----------------------------------------------------------------------------------------------

查找

查找表:由同一类型的数据元素(或记录)构成的集合。由于“集合”中的数据元素之间存在着松散的关系,因此查找表示一种应用灵便的结构。

查找:根据给定的某个值,在查找表中确定一个其关键字等于给定值的数据元素或记录
关键字:用来标识一个数据元素(或记录)的某个数据项的值
主关键字:可唯一地标识一个记录的关键字是主关键字
次关键字:反之,用以识别若干记录的关键字是次关键字

查找表的分类:
1、静态查找表:仅作查询(检索)操作的查找表
2、动态查找表:作“插入”和“删除”操作的查找表
a、在查询之后,还需要将“查询”结果为不在查找表中的数据元素插入到查找表中;
b、从查找表中删除查询到的数据

查找算法的评价指标:关键字的平均比较次数,也称平均查找长度ASL

查找方法取决于查找表的结构,即表中数据元素是什么关系组织在一起的

顺序查找
应用范围: 1、顺序表或线性链表表示的静态查找表
2、表内元素之间无序
方法:按顺序逐个比较查找

int Search_Seq(SSTable ST,KeyType key){
	//若成功,返回其位置信息,否则返回0
	for(int i = ST.Length;i>=1;--i){
		if(ST.R[i].key==key){
			return i;
		}
	}
	return 0;
}

上述算法每循环一次都需要进行两次判断i>=1;和ST.R[i].key==key
优化方式:将待查关键字key存入表头(“哨兵”、“监视哨”),从后往前逐个比较,可免去查找过程中每一步都要检查是否查找完毕。避免下表越界问题

int Search_Seq(SSTable ST,KeyType key){
	ST.R[0].key=key;//设置监视哨兵
	for(int i = ST.length;ST.R[i].key!=key;--i);
	return i;
}

在这里插入图片描述
当ST.length较大时,此改进能使进行一次查找所需的平均时间几乎减少一般。

比较次数与key的位置有关:
- 查找第i个元素,需要比较n-i+1次
- 查找失败,需要比较n+1次
- 时间复杂度:O(n)
- 空间复杂度:O(1),只需要一个辅助空间

优化效率:
1、记录的查找概率不相等时如何提高查找效率?
查找表存储记录原则——按查找概率高低存储:
- 查找概率越高,比较次数越少
- 查找概率越低,比较次数较多
即查找概率越高的数据越靠后存储

2、记录的查找概率无法测定时,如何提高查找效率?
	方法——按查找概率动态调整记录顺序:
		- 在每个记录中设一个访问频度域
		- 始终保持记录按非递增有序的次序排列
		- 每次查找后均将刚查到的记录直接移至表头

折半查找法
适用于有序表,且仅限于顺序存储结构(链式失效)

在这里插入图片描述

该算法就是不断缩小范围,且目标如果存在,一定在折半后的某个的中间位置
如果目标元素比中间位置的值小,则在将右指针指向中间位置-1,并在中间位置的左半区进行折半查找
如果目标元素比中间位置的值大,则在将左指针指向中间位置+1,并在中间位置的右半区进行折半查找
如果右指针下标值小于左指针,则目标元素不存在

int Search_Bin(SSTable ST,KeyType key){
	int low =1,high=ST.length,mid;//置区间初始值
	while(low<=high){
		mid=(low+high)/2;
		if(ST.R[mid].key==key){
			return mid;
		}else if(ST.R[mid].key>key){//在前半区进行查找
			high = mid-1;
		}else if(ST.R[mid]<key){//在后半区进行查找
			low = mid+1;	
		}
	}
	return 0;
}

在这里插入图片描述

树表的查找

当表插入、删除操作频繁时,为维护表的有序,需要移动表中大量记录。

改用动态查找表——几种特殊的树

二叉排序树、平衡二叉树、红黑树、B-树、B+树、键树

表结构在查找过程中动态生成
对于给定的key,若表中存在,则成功返回;否则插入关键字等于key的记录

二叉排序树

二叉排序树:又称二叉搜索树、二叉查找树
定义:二叉排序树或是空树,或是满足如下性质的树:
	- 若其左子树非空,则左子树上所有结点的值均小于根结点的值
	- 若其右子树非空,则右子树上所有结点的均值大于等于根结点的值
	- 其左右子树本身又各是一棵二叉排序树
	- 中序遍历结果是一个有序递增的序列

在这里插入图片描述
二叉排序树的性质:
中序遍历非空的二叉排序树所得到的数据元素序列是一个按关键字排列的递增有序序列

二叉排序树的操作——查找
	- 若查找的关键字等于根结点,成功
	- 否则,若小于根结点,查其左子树;若大于根结点,查其右子树
	- 在左右子树上的操作类似

在这里插入图片描述
二叉排序树的存储结构

typedef struct{
	KeyType key;//关键字
	InfoType otherinfo;//数据域
}ElemType;

typedef struct BSTNode{
	ElemType data;//数据域
	struct BSTNode *lchild,*rchild;//左右孩子指针
}BSTNode,*BSTree;//定义二叉排序树

【查找算法思想】
1、若二叉排序树为空,则查找失败,返回空指针
2、若二叉排序树非空,将给定值key与根结点的关键字T->data.key进行比较:
a、若key等于T->data.key,则查找成功,返回根结点地址
b、若key小于T->data.key,则进一步查找左子树
c、若key大于T->data.key,则进一步查找右子树

BSTree SearchBST(BSTree T,KeyType key){
	if((!T)||key==T->data.key){
		return T;
	}else if(key<T->data.key){
		return SearchBST(T->lchild,key);//如果key小于结点值,则在结点的左边查找
	}else if(key>T->data.key){
		return SearchBST(T->rchild,key);//如果key大于结点值,则在结点的右边查找
	}
}

二叉排序树上查找关键字就是走了一条从根结点到该结点的路径
含有n个结点的二叉排序树的平均查找长度和树的形态有关

在这里插入图片描述
在这里插入图片描述

将不平衡的二叉排序树进行平衡话处理,可以提高查找效率。这种结点平衡的二叉树叫做平衡二叉树

【插入算法思想】
- 若二叉排序树为空,则插入结点作为根结点插入到空树中
- 否则,继续在其左、右子树上查找
- 若树中已存在该数值,则不再插入
- 树中没有:
- 查找直至某个叶子结点的左子树或右子树为空的位置,该位置即是要插入的位置,插入结点应为该叶子结点的左孩子或右孩子

插入结点一定在叶子节点上

二叉排序树的操作——生成
从空树出发,经过一系列的查找、插入操作之后,可生成一棵二叉排序树
在这里插入图片描述
特点:
1、一个无序序列可通过构造二叉排序树二变成一个有序序列,构造树的过程即是对无序序列进行排序的过程。
2、插入的结点均为叶子结点,故无需移动其他结点。相当于在有序序列上插入记录而无需移动其他记录
3、关键字的输入顺序不同,建立的二叉排序树形态也不同
在这里插入图片描述
二叉排序树的操作——删除
从二叉排序树中删除一个结点,不能把以该结点为根的子树都删除,只能删掉该结点,并且还应保证删除后所得的二叉树仍然满足二叉排序树的性质不变。
由于中序遍历二叉排序树可以得到一个递增有序的序列,那么在二叉排序树中删除一个结点应该当保持具有同样的性质:
- 将因删除结点而断开的二叉链表重新连接起来
- 防止重新连接后树的高度增加
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

平衡二叉树(AVL树)

一棵平衡二叉树或者是空树,或者是具有下列性质的二叉排序树
	- 左子树与右子树的高度差的绝对值小于等于1
	- 左子树和右子树也是平衡二叉树 

平衡因子:为了方便起见,给每个结点附加一个数字,给出该结点左子树与右子树的高度差
	平衡因子 = 结点左子树的高度-结点右子树的高度
平衡二叉树的平衡因子只能是1、-1、0

在这里插入图片描述
对于一棵有n个结点的AVL树,其高度保持在O(log2n)数量级,ASL也保持在O(log2n)数量级

在这里插入图片描述
平衡调整的四种类型
在这里插入图片描述
调整原则:
- 1、降低高度
- 2、保持二叉排序树的性质
- 3、将小的结点放在左,最大放在右,中间作为父节点

插入一个结点导致多个结点失衡时,需要找到最小的失衡子树(结点最少的平衡子树)进行调整。

在这里插入图片描述

散列表

基本思想

	记录的存储位置与关键字之间存在对于关系,对应关系——hash函数(Loc(i)=H(keyi))

根据散列函数H(key)=k
查找key=num,则访问H(key)=num的地址,若内容为num则成功
若查不到,则返回一个特殊值

散列方法(杂凑法)
选取某个函数,依该函数按关键字计算元素的存储位置。并按此存放;
查找时,由同一个函数对给定值k计算地址,将k与地址单元中元素关键码进行对比,确定查找是否成功
散列函数(杂凑函数):散列方法使用的转换函数
散列表(杂凑表):按上述思想构造的表
冲突:不同的关键码映射到同一个散列地址
key1≠key2,但H(key1)=H(key2)
同义词:具有相同函数值的多个关键字
在散列查找方法中,冲突是不可能避免的,只能尽可能减少

散列表的存储

使用散列表要解决好两个问题:
	1、构造好的散列函数
		a、所选函数尽可能简单
		b、所选函数对关键码计算出的地址,应在散列地址集中均匀分布,以减少空间浪费
	2、制定一个好的解决冲突的方案
		查找时,如果从散列函数计算出的地址中查不到关键码,则应当依据解决冲突的规则,有规律地查询其他相关单元。 

构造散列函数考虑因素
1、执行速度(计算散列函数所需时间)
2、关键的长度
3、散列表的大小
4、关键字的分布情况
5、查找频率

根据元素集合的特性:
要求一:n个数据原仅占用n个地址,虽然散列查找是以空间换时间,但仍希望散列的地址空间尽量小
要求二:无论用什么方法存储,目的都是尽量均匀地存放元素,以避免冲突

解决冲突的方法

1、开放定址法:有冲突时就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将数据元素存入。
例如:除留余数法Hi=(Hashi(key)+Di)%m

在这里插入图片描述
2、链地址法(拉链法)
基本思想:相同散列地址的记录链成一个单链表
m个散列地址就设m个单链表,然后用一个数组将m个单链表的表头指针存储起来,形成一个动态的结构。

例如:一组关键字为{19,14,23,1,68,20,84,27,55,11,10,79}
散列函数为:hash(key)=key%13

在这里插入图片描述

排序(这个很重要)

排序:将一组杂乱无章的数据按一定规律顺次排列起来。即,将无序序列排成一个有序序列(由小到大或由大到小)的运算
插入前用什么方法进行查找,则称为该查找方法的排序法。例如:顺序查找法找到对应要插入的位置,则叫插入排序法

排序的方法分类:
- 按数据存储介质:内部排序和外部排序
- 内部排序:数据量不大、数据在内存,无序内外存交换数据
- 外部排序:数据量较大,数据在外存(文件排序)
外部排序时,要将数据分批调入内存来排序,中间结果还要即是放入外存,显然外部排序要复杂一些
- 按比较器个数:串行排序和并行排序
- 串行排序:单处理器(同一时刻比较一对元素)
- 并行排序:多处理器(同一时刻比较多对元素)
- 按主要操作:比较排序和基数排序
- 比较排序:用比较的方法(插入排序、交换排序、选择排序、归并排序)
- 基数排序:不比较元素的大小,仅仅根据元素本身的取值确定其有序位置。
- 按辅助空间:原地排序和非原地排序
- 原地排序:辅助空间用量为O(1)的排序方法(所占的辅助存储空间与参加排序的数据量大小无关)
- 非原地排序:辅助空间用量超过O(1)的排序方法
- 按稳定性:稳定排序和非稳定排序
- 稳定排序:能够使用任何数值相等的元素,排序后相对次序不变
- 非稳定排序:不是稳定排序的方法
- 排序的稳定性只对结构类型数据排序有意义
在这里插入图片描述
- 按自然性:自然排序和非自然排序
- 自然排序:输入数据越有序,排序的速度越快的排序方法
- 非自然排序:不是自然的排序方法
在这里插入图片描述
在这里插入图片描述
**定义顺序表的结构 **

#define MAXSIZE 20//设记录不超过20个
typedef int KeyType;//设关键字为整型量
Typedef struct{//定义每个记录(数据元素)的结构
	KeyType key;//关键字
	InfoType otherinfo;//其他数据项
}RedType;
Typedef struct{//定义顺序表的结构
	RedType r[MAXSIZE+1];//存储顺序表的向量
	//r[0]一般作哨兵或者缓冲区
	int length;//顺序表的长度
}SqList;

插入排序

在这里插入图片描述
基本思想:
每一步将一个待排序的对象,按其关键码大小,插入到前面已经排好序的一组对象的适当位置上,知道对象全部插入位置。
即边插入边排序,保证子序列中随时都是排好序的

基本操作:有序插入
在有序序列中插入一个元素,保持序列有序,有序长度不断增加

在这里插入图片描述
插入排序的种类
在这里插入图片描述
直接插入排序

采用顺序查找法查找插入位置

在这里插入图片描述
步骤:1、将要插入的数值存储起来,必将移动元素时被覆盖
2、将要插入的数值与前面一个元素对比,如果比前面元素小则将前面元素向后移动一位,然后与前面元素的前一个元素对比。直到遇见比前面元素大的情况,则将目标元素插入到比目标元素小的元素后面
3、如果j到0依然没有遇见比目标元素小的元素,则将目标元素插入到数组下标0号位置(即a[0])

插入算法优化——添加哨兵
减少for循环中的比较判断次数,当所有元素都大于目标元素时,在0号哨兵位置就可以找到本身,不满足for循环中的r.[0].key<L.r[j].key条件,则跳出循环,直接在1号位置插入
在这里插入图片描述

void InsertSort(SqList &L){
	int i,j;
	for(i=2,i<=L.length;i++){//从第二个元素开始向前比较
		if(L.r[i-1].key>L.r[i].key){//如果后一个元素比前一个元素小,则将后面元素放到哨兵位子
			L.r[0].key = L.r[i].key;
			for(j=i-1;L.r[0].key<L.r[j].key;j--){
			//将目标元素的前面有序元素与目标元素比较,如果前面元素大于目标元素,则将元素往后移动一位
				L.r[j+1]=L.r[j];
			}
			L.r[j+1]=L.r[0];//如果在大于0号的位置位置找比目标元素小的元素,则直接将目标元素插入到该元素后面
		}
	}
}

折半排序算法

在这里插入图片描述

void BInsertSort(SqList &L){
	int i,j;
	for(i = 2;i<=L.length;i++){
		L.r[0]=L.r[i];//将目标元素放到哨兵位置
		int low = 1,high = i-1;
		while(low<=high){//采用二分查找法查找插入位置
			mid = (low+high)/2;
			if(L.r[0].key<L.r[mid]){
				high = mid-1;
			}else{
				low = mid + 1;
			}//循环结束,high+1则为插入位置
			for(j=i-1;j>=high+1;j--){//移动元素
				L.r[j+1]=L.r[j];
			}
			L.r[high+1]=L.r[0];//插入到正确位置
		}
	}
}

性能分析
折半插入排序的对象移动次数与直接插入排序相同,依赖于对象的初始排列
- 减少了比较次数,但没有减少移动次数
- 平均性能优于直接插入排序
在这里插入图片描述

希尔排序

基本思想:
	先将整个待排记录序列分割成若干子序列,分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行一次直接插入排序。

希尔算法特点:
	1、缩小增量
	2、多遍插入排序

在这里插入图片描述
【算法思路】
1、定义增量序列Dk:Dm>Dm-1>…>D1=1
2、对每个Dk进行“Dk-间隔”插入排序(k=M,M-1,M-2,…1)

特点:
- 一次移动,移动位置跨度比较大,跳跃式地接近排序后的最终位置
- 最后一次只需要少量移动
- 增量序列必须是递减的,最后一个必须是1
- 增量序列应该是互质的

	void ShellInsert(SqList &L,int dk){
	//对顺序表L进行一趟增量为dk的shell排序,dk为增量因子
		for(int i = dk+1;i<=L.length;i++){
			r[0]=r[i];
			for(int j = i-dk;j>0&&(r[0].key<r[j].key);j=j-dk){
				r[j+dk]=r[j];
			}
			r[j+dk]=r[0]
		}
	}


	void ShellSort(SqList &L,int dlta[],int t){
		//按增量序列dlta[t-1,....1,0]对顺序表L作希尔排序
		for(int k = 0;k<t;k++){
			ShellInsert(L,dlta[k]);//一次增量为dlta[k]的插入排序
		}
	}

希尔排序算法效率与增量序列的取值有关
在这里插入图片描述
希尔排序算法是一种不稳定的排序算法
在这里插入图片描述
在这里插入图片描述
特点:
- 最后一个增量值必须是1,无除了1之外的公因子
- 不宜在链式存储结构上实现

交换排序

基本思想:两两比较,如果发生逆序则交换,直到所有记录都排好序为止。
常见交换排序方法:冒泡排序法O(n²)
								快速排序法O(nlog2n)

冒泡排序法——基于简单交换思想
基本思想:每趟不断将记录两两比较,并按“前小后大”规则交换。
N个记录总需要比较N-1趟
第M趟需要比较N-M次

void bubble_sort(SqList &L){//冒泡排序算法
	int m,i,j;
	RedType x;//交换时临时变量
	for(m = 1;m<=n-1;m++){//总共需m趟
		for(j=1;j<=n-m;j++){
			if(L.r[j].key>L.r[j+1].key){//发生逆序,则进行交换
				x = L.r[j];
				L.r[j]=L.r[j+1];
				L.r[j+1]= x;
			}
		} 
	}
}

在这里插入图片描述

快速排序
基本思想:
- 任取一个元素(如:第一个)为中心
- 所有比它小的元素一律放前,比它大的元素一律放后,形成两个字表
- 对各字表重新选择中心元素并以此规则调整
- 直到每个字表的元素只剩下一个
在这里插入图片描述
1、每一趟的子表的形成是采用两头向中间交替式逼近法
2、由于每趟中对各子表的操作都相似,可采用递归算法

void main(){
	QSort(L,1,L.length);
}
//排序算法
void QSort(SqList &L,int low,int high){//对顺序表L快速排序
	if(low<high){//子表长度大于1
		int pivotloc = Partition(L,low,high);//将L一分为二,pivotloc为中心枢轴元素排好序的位置
	QSort(L,low,pivotloc-1);//对低子表递归排序
	QSort(L,privotloc+1,high);//对高子表递归排序
	}
}

//查找中心轴
int Partition(SqList &L,int low,int high){
	L.r[0]=L.r[low];
	int pivotkey = L.r[low].key;//将第一个元素取出
	while(low<high){
		while(low<high&&L.r[high].key>=pivotkey){//由于前面第一个位置已经取出,所以从后面开始找比中心轴小的元素;如果两个指针重合,则结束
			--high;
		}
		L.r[low]=L.r[high];//找到比中心轴小的元素,将该元素放到前面取出的空位中
		while(low<high && L.r[low].key<=pivotkey){
		//在从前往后找到比pivotkey大的元素,并填到后面空着的位置,如果没有就直接下一位
			++low;
		}
		L.r[high]=L.r[low];
	}
	L.r[low]=L.r[0];//将pivotkey填到该有的位置
	return low;//方位pivotkey所在下标值
}

快速排序的空间复杂度:
快速排序不是原地排序

由于程序中使用了递归,需要递归调用栈的支持,而栈的长度取决于递归的调用深度。

在这里插入图片描述

快速排序是一种不稳定的排序方法
在这里插入图片描述

快速排序不适于对原本有序或基本有序的记录序列进行排序(不是自然排序)

选择排序

简单选择排序

基本思想:在待排序的数据中选出最大(小)的元素放在最终的位置
基本操作:
	1、首先通过n-1次关键字比较,从n个记录中找出关键字最小的记录,将它与第一个记录交换
	2、再通过n-2次比较,从剩下的n-1个记录找到关键字次小的记录,将它与第二个记录交换
	3、重复上述操作,共进行n-1趟排序后,排序结束

在这里插入图片描述

void SelectSort(SqList &L){
	for(int i=1;i<L.length;i++){
		int k = i;//默认k是最小值
		for(int j = i+1;j<=L.length;j++){
			if(L.r[j].key<L.r[k]){
				k=j;
			}
		}
		if(k!=i){
			int x=L.r[i].key;
			L.r[i].key=L.r[k].key;
			L.r[k].key=x;
		}
	}
}

时间复杂度
记录移动次数:
- 最好情况:0
- 最坏情况:3(n-1)
比较次数:无论待排序列处于什么状态,选择排序所需进行比较的次数都是相同的(都需要从头到尾的比较)
在这里插入图片描述
简单选择排序算法是不稳定排序

堆排序

在这里插入图片描述
堆实质是满足如下性质的完全二叉树:
二叉树中任意非叶子结点均小于(大于)它的孩子结点;大于孩子结点的叫大根堆,小于孩子结点的叫小根堆。
在这里插入图片描述
如何在输出堆顶元素后,调整剩余元素为一个新的堆?
小根堆:
1、输出堆顶元素之后,以堆中最后一个元素替代之
2、然后将根结点值与左、右子树的根结点值进行比较,并与其中小者进行交换
3、重复上述操作,直至叶子结点,将得到新的堆。称这个从堆顶至叶子的调整过程为“筛选”
在这里插入图片描述
输出13后,找到最后一个元素代替他的位置
在这里插入图片描述
调整后的二叉树不满足堆的定义,需要将堆顶元素与左右孩子比较小的元素交换位置
在这里插入图片描述
**重复与左右孩子比较小的交换位置,直到在叶子结点 **
在这里插入图片描述

//堆调整算法
void HeapAdjust(elem R[],int s,int m){
	//一支R[s..m]中记录的关键字除了R[s]之外均满足堆的定义,本函数调整R[s]的关键字,使R[s...m]称为一个大根堆
	elem rc = R[s];
	int j;
	for(j=2*s;j<=m;j*=2){//沿key比较大的孩子结点向下筛选
		if(j<m&&R[j]<R[j+1]){
			++j;//j为key比较大的记录下标
		}
		if(rc>=R[j]){
			break;
		}
		R[s]=R[j];
		s=j;//rc应插入再位置s上
	}
	R[s]=rs;//插入
}

如何由一个无序序列建成一个堆?
单结点的二叉树是堆;
在完全二叉树中所有以叶子结点(需要i>n/2)为根的子树是堆。
这样,我们只需要依次将以序号为n/2,(n/2)-1,…1的结点为根的子树均调整为堆即可
即:对应由n个元素组成的无序序列,“筛选”只需从第n/2个元素开始

堆排序就是利用完全二叉树中父子结点之间的内在关系来排序

//堆排序
void HeapSort(elem R[]){//对R[1]到R[n]进行堆排序
	int i;
	int n = R.length;
	for(i = n/2;i>=1;i--){
		HeapAdjust(R,i,n);//建初始堆
	}
	for(i = n;i>1;i--){//进行n-1趟排序
		Swap(R[1],R[i]);//根与最后一个元素交换
		HeapAdjust(R,1,i-1);//对R[1]到R[i-1]重新建堆
	}
}

在这里插入图片描述

归并排序

2路归并排序:就是将为排序的元素看成有一个元素的小集合,然后将两个小集合合并为一个有两个元素的集合并排序。再将相邻两个有两个元素的集合合并且排好序列。以此类推,直到所有元素都在同一个集合中
在这里插入图片描述
在这里插入图片描述
关键操作
如何将两个有序序列合并成一个有序序列?
将两个序列的第一个元素相比较,小的放到空序列中,然后被拿出一个元素的序列指针向后移动一位。依次类推
在这里插入图片描述

基数排序

基本思想:分配+收集
也叫桶排序或箱排序:设置若干个箱子,将关键字为k的记录放入第k个箱子,然后再按序号将非空的连接

**基数排序:**数字是有范围的,均由0-9这十个数字组成,则只需要设置十个箱子,相继按个、十、百…进行排序
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
各算法时间性能比较
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
专插本C语言数据结构的比例会根据具体的学校和课程设置有所不同。C语言数据结构是计算机专业中非常重要的基础课程,对于学习计算机编程和算法有着至关重要的作用。 一般来说,C语言数据结构是计算机专业专插本课程中的重点内容之一,占据一定比例。在专插本的课程设置中,C语言通常作为第一门编程语言的学习内容,学生将会通过学习C语言的基本语法和编程技巧来掌握计算机编程的基本概念和常用操作。 数据结构则是专插本课程体系中的另一门重要课程,主要涉及各种常用数据结构的概念、特点和实现方法,以及相关算法和数据的存储与管理。数据结构课程旨在培养学生对于数据的组织和管理能力,以及解决实际问题的算法设计思维。 至于具体的比例,可能会根据学校的课程设置和教学方案而有所不同。一般来说,C语言所占比例可能会比较大,因为它是计算机编程的基石,学生通过学习C语言能够更好地理解和应用其他编程语言。数据结构的比例可能相对较小,但同样重要,因为数据结构是编写高效程序和解决复杂问题的关键。 总之,专插本C语言数据结构的比例会根据学校的教学要求和课程设置而有所不同,但它们都是计算机专业中非常重要的基础课程,对于学生的专业素养和职业发展都具有重要意义。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值