数据结构-图
数据结构-图(错题)
-
具有n个顶点的有向图最多有n(n-1)条边,(每个顶点可与其余n-1个顶点相连)
-
n个顶点的连通图用邻接矩阵表示时,该矩阵至少有2(n-1)个非零元素
-
有向完全图共有(n)(n-1)条边,无向完全图共有(n)(n-1)/2条边
-
强连通图:对于图中任意两个顶点,均有路径可以到达
邻接矩阵
- 邻接矩阵储存结构
javatypedef struct{ char vex[100];//顶点表 int arcs[100][100];//储存邻接矩阵 int vexnum,arcnum;//vex顶点,arc边 }AMGraph;
- 邻接矩阵创建无向网
javaint create(AMGraph &G){ cin>>G.vexnum>>G.arcnum;//输入顶点和边 for(int i=0;i<n;i++)cin>>G.vexs[i];//输入顶点信息 for(int i=0;i<n;i++){ for(int j=0;j<n;j++){ G.arcs[i][j]=1000000;//初始化 } } for(int k=0;k<G.arcnum;k++){ int v1,v2;cin>>v1>>v2; int i=Locate(G,v1),j=Locate(G,v2);//确定v1,v2在图中的下标 G.arcs[i][j]=G.arcs[j][i]=w;//w权值 } return 1; }
邻接表的创建
- 邻接表储存结构
javatypedef struct Arcnode{ //边节点 int adjvex;//该边所指向顶点的位置 struct ArcNode *nextarc;//指向下一条边的指针 int info;//和边相关的信息(权值) }ArcNode; typedef struct VNode{ //顶点信息 int data; ArcNode *first//第一条边 }Vnode,AdjList[100]; typedef struct{ AdjList vertices;//记录每个顶点信息 int vexnum,recnum;//定点数和边数 }ALGraph;
配上一张图方便大家理解
2. 用邻接表储存无向图javaint create(ALGraph &G){ cin>>G.vexnum>>G.arcnum; for(int i=0;i<G.vexnum;i++){ cin>>G.vertices[i].data; G.vertices[i].first=NULL;//输入每个顶点并且对其初始化 } for(int k=;k<G.arcnum;k++){ int v1,v2;cin>>v1>>v2; int i=Locate(G,v1),int j=Locate(G,v2); p1=new ArcNode;//邻接表的结点需要生成 p1->adjvex=j;//该顶点所指向下标 p1->nextarc=G.vertices[i].first;G.vertices[i].first=p1;//j顶点插入i的邻接表中 p2=ewn ArcNode; p2->adjvex=i; p2->nextarc=G.vertices[j].first;G.vertices[j].first=p2;//i顶点插入j的邻接表中 //无向图是这样的 } }
深度优先遍历
-
采用邻接矩阵表示的深度优先搜索
算法描述:
javabool visited[MVNum];//访问标志数组 void DFS_Am(Graph G,itn v){ cout<<v;Visited[v]=true; for(int w=0;w<G.vexnum;w++){ if(G.arcs[v][w]!=0&&(!visited[w])){ DFS_Am(G,w); } } }
- 采用邻接表表示图的深度优先搜索
java
void DFS_AL(ALGraph G,int v){
cout<<v;visit[v]=true;
p=G.vertices[v].first;//输出后指向第一个边节点
while(p!=NULL){
w=p->adjvex;
if(!visited[w])DFS_AL(G,w);
p=p->nextarc;//p指向下一个结点
}
}
广度优先遍历
算法描述:
java
void BFS(Graph G,int v){
cout<<v;visited[v]=true;
Initqueue(Q);//初始化辅助队列?
Enqueue(Q,v);//将顶点v入队
while(!QueueEmpty(Q)){
//如果队列不空
DeQueue(Q,u);//队头元素出队
for(w=FirstAdjvex(G,u);w>0;w=NextAdjvex(G,u,w));
//依次检查u的所有邻接点w,
if(!visited[w]){
cout<<w;visited[w]=true;//w没有被访问过,那么输出并入队
Enqueue(G,w);
//每次将该顶点所有相邻的结点入队,BFS没有用到递归,而是当队列为空时结束循环,函数结束
}
}
}
prim算法构建最小生成树
算法描述:
java
struct{
int adjvex;//最小边在u中那个顶点
int lowcost;//最小边上的权值
}closedge[100];
void prim(Graph G,int u){
//无向网以邻接矩阵存储,从顶点y出发构造g的最小生成树t,输出t的各条边
k=LocateVex(G,u);//找出顶点u的图中的下表
//对v------u的每个顶点进行初始化
for(int j=0;j<G.vexnum;j++){
if(j!=k)closedge[j]={u,arcs[k][j]};//没看太懂
}
closedge[k].lowcost=0;//最小边权值初始化为0
for(int i=1;i<G.vexnum;i++){
//选择其余n-1个结点,生成n-1条边
}
}
kruskal算法构建最小生成树
算法描述:
java
void kruskal(Graph G){
sort(Edge);//将数组edge中的元素按权值从小到大排序
for(int i=0;i<G.vexnum;i++){
Vexset[i]=i;//辅助数组vexset初始化,表示各个顶点自成一个联通分量
}
for(int i=0;i<G.arcnum;i++){
//依次查看数组edge的边
v1=Locatevex(G,Edge[i].head);//v1为边的初始点head的下标
v2=LocateVex(G,Edge[i].Tail);//v2为边的终点tail的下标
vs1=Vexset[v1];
vs2=Vexset[v2];//获取v1,v2的联通分量
if(vs1!=vs2){
//联通分量不相同,那说明没有相连
cout<<Edge[i].head<<Edge[i].tail;
for(int j=0;j<G.vexnum;j++){
if(vexset[i]==vs2)Vexset[j]=vs1;//将所有以前为vs1的联通分量都改为VS2,合并完成
}
}
}
}
kruskal算法是对边进行考虑,因此适用于稀疏图
如果对于流程不太理解的画可以参照这个视频王道最小生成树,
最短路
最短路问题一般有两种:一种是求从某个原点到其中各点的最短路,一种是求每一对顶点之间的最短路径
- 单源最短路问题:求v0到G中其余各个顶点的最短路径。迪杰斯特拉(Dijkstra),他是按照路径长度递增的次序产生最短路径的算法,称为迪杰斯特拉算法。
算法描述:
java
void Dijkstra(AMGraph G,int v0){
for(int v=0;v<G.vexnum;v++){
s[v]=false;//s数组确定最短路径长度
D[v]=G.arcs[v0][v];//D数组记录v0到v的权值
if(D[v]<100000)path[v]=v0;//如果有弧,就将v的前驱置为v0
else path[v]=-1;//没有,就置为-1
}
s[v0]=ture;
D[v0]=0;//原点到原点的距离为0,而且标记v0已经被访问过
//初始化结束,开始主循环,每次求得v0到某个顶点的最短路径
for(int i=1;i<G.vexnum;i++){//对其余n-1个顶点,依次进行考虑
min=MAXInt;
for(int w=0;w<G.vexnum;w++){
if(!s[w]&&D[w]<min){
v=w;
min=D[w];
}
S[v]=true;//这个节点的最短路找到了,更新一下
}
for(int w=0;w<G.vexnum;w++){
//看能不能以这个顶点为过渡,找到另一个顶点的最短路径
if(!s[w]&&D[v]+G.arcs[v][w]<D[w]){
D[w]=D[v]+G.arcs[v][w];//找到了,更新一下
Path[w]=v;//更新一下前驱结点
}
}
}
}
举个例子

- 初始化,初始化各个顶点到v1的距离,
- 找到一个距离v0最近的过渡结点,用这个节点遍历所有节点,看看通过这个过渡节点能不能求出比原先v0到这个节点更短的路径,并更新
- 为什么不能处理负权值问题呢,因为找到一个最短路,就会立即更新,比如1-->2(权值为5),2-->3(权值为-4),3-->4(权值为3)、那么根据dijkstra算法的流程,他就会先找到3-->4这条边,然后用4这个顶点去更新其他值,就导致了判断错误。
floyd算法求最短路
floyd算法用于求每一对顶点之间的最短路方法
floyd算法可以处理负权边,但是不能处理负权回路
算法描述:
java
void Floyd(Graph G){
for(int i=0;i<G.vexnum;i++){
for(int j=0;j<G.vexnum;j++){
D[i][j]=G.arcs[i][j];//记录一下初值
if(D[i][j]<MAXint&&i!=j)path[i][j]=i;
else path[i][j]=-1;//有通路就赋值,没有通路就赋值为-1
}
}
//初始化完毕,接下来开始寻找最短路并变更路径
for(int k=0;k<G.vexnum;k++){
for(int i=0;i<G.vexnum;i++){
for(int j=0;j<G.vexnum;j++){
//k作为中转点
if(D[i][k]+D[k][j]<D[i][j]){
D[i][j]=D[i][k]+D[k][j];
path[i][j]=path[k][j];//更改j的前驱为j
}
}
}
}
}
拓扑排序
- 拓扑排序的过程:
1):有向图中选一个无前驱的顶点并且输出它
2):从图中删除该顶点和所有以它为尾的弧
3):重复以上步骤,直到不存在无前驱的顶点
4):若经历了以上步骤,顶点并没有输出完毕,那么说明图中存在环
算法描述:
java
int TopoSort(Graph G,int topo[]){
//有向图G采用邻接表作为储存结构
FindIndegree(G,indegree);//该函数求出各点入度并储存在该数组中
InitStack(s);//初始化栈空间
for(int i=0;i<G.vexnum;i++){
//对于入度为零的点先入队
if(!indegree[i])push(S,i);
}
int m=0;//对于要输出的顶点计数
while(!StackEmpty(S)){
//栈不空继续循环
pop(S,i);//使栈顶顶点出栈
topo[m++]=i;
p=G.vertices[i].first;//p等于关于顶点i的邻接表的第一个邻接点
}
while(p!=NULL){
k=p->adjvex;//k为顶点下标
--indegree[k];
if(indegree[k]==0)push(S,k);//入度为零的点继续入栈
p=p->nextarc;//p指向下一个邻接点,类似于广度优先遍历,先把单个顶点的所有邻接点入栈
}
if(m<G.vexnum)return -1;//说明存在负环
else return 1;
}