图这里存的就是复杂数据了,算法普遍较难,并有很多细节需要经常看。
本章主要内容有,图的逻辑结构,存储结构,连通性,最小生成树,最短路径,AOV,AOE 网等问题。
一,图的逻辑结构
图的部分知识在离散数学已经有所介绍,如有向图顶点度(入度=出度=边数),注意有向图连通也是任意俩点之间连通,不是无向图有边串连所有点的形式。
稀疏图:称边数很少的图为稀疏图;
稠密图:称边数很多的图为稠密图
连通分量:非连通图的极大连通子图称为连通分量。
生成树:n个顶点的连通图G的生成树是包含G中全部顶点的一个极小连通子图。
eg:有7个顶点保证图在任何情况下都连通,最少要多少条边?
16条,6个点做完全图,一个点连上
图的遍历有深度优先搜索(栈辅助)、广度优先搜索(队辅助)两种主要方式。
二,图的存储结构
1,邻接矩阵(数组表达法)
关于顶点元素的集合一定用顺序存储。
注意无向图在构造邻接矩阵是对称的edge[i][j]=edge[j][i]=1;
DFS针对连通图的遍历
int visited[MaxSize];
template <class T>
void MGraph::DFSTraverse(int v){
cout<<vertex[v]; visited [v]=1;//记录是否被访问过
for (j=0; j<vertexNum; j++)
if (arc[v][j]==1 && visited[j]==0)
DFSTraverse( j );
}
///
BFS
int visited[MaxSize];
template <class T>
void MGraph::BFSTraverse(int v){
front=rear=-1;
cout<<vertex[v]; visited[v]=1; Q[++rear]=v;
while (front!=rear)
{
v=Q[++front];
for (j=0; j<vertexNum; j++)
if (arc[v][j]==1 && visited[j]==0 ) {
cout<<vertex[j];visited[j]=1;
Q[++rear]=j;
}
}
}
2,邻接表(出边表)
对于图的每个顶点vi,将所有邻接于vi的顶点链成一个单链表,称为顶点vi的边表(对于有向图则称为出边表)
所有边表的头指针和存储顶点信息的一维数组构成了顶点表。
struct ArcNode{
int adjvex;
ArcNode *next;
};
template <class T>
struct VertexNode{
T vertex;
ArcNode *firstedge;//初始化时赋空
};
链表的DFS
template <class T>
void ALGraph::DFSTraverse(int v){
cout<<adjlist[v].vertex; visited[v]=1;
p=adjlist[v].firstedge;
while (p!=NULL) {
j=p->adjvex;
if (visited[j]==0) DFSTraverse(j);
p=p->next;
}
}
链表的BFS
template <class T>
void ALGraph::BFSTraverse(int v){
front=rear=-1;
cout<<adjlist[v].vertex; visited[v]=1; Q[++rear]=v;
while (front!=rear) {
v=Q[++front]; p=adjlist[v].firstedge;
while (p!=NULL) {
j= p->adjvex;
if (visited[j]==0) {
cout<<adjlist[j].vertex; visited[j]=1; Q[++rear]=j;
}
p=p->next;
}
}
}
3,图的其余存储方法还有有向图的十字链表法、无向图的邻接多重表、边集数组。
边集数组:对边依次进行处理时用、最小代价生成树
利用两个一维数组
一个数组存储顶点信息,另外一个数组存储边及其权数组分量包含三个域:边所依附的两个顶点,权值
三,最小生成树,连通所有点,权值最小
1,prim算法(加点法,适用稠密图)
int minedge(int lowcost[],int n){
int m=100,p;
for(int i=0;i<n;i++)
if(lowcost[i]!=0&&m>lowcost[i]){
m=lowcost[i];
p=i;
}
return p;}
Void prime(MGraph G){
for(int i=1;i<G.vertexNu;i++){
lowcost[i]=G.arc[0][i]; adjvex[i]=0;//从v0开始生成树,low cost记录与v0邻接边的权
}
lowcost[0]=0;
for(i=1;i<G.vertexNum;i+++){
k=MinEdge(lowcost,G.vertexNum)//找最小权
cout<<K<<adjvex[k]<<lowcost[k];
lowcost[k]=0; //将找到的点列入树中
for(j=1;j<G.vertexNum;j++)
if((G.arc[k][j]<lowcost[j]){
lowcost[j]=G.arc[k][j];//更新关于树的结点权
arcvex[j]=k;
}
}
}
2,kruskal算法(加边法,稀疏图)
使用边集数组,并查集,parent数组判断是否俩点属于同一个连通分量,不属于后一树的parent变前一树的值。
#include<bits/stdc++.h>
using namespace std;
struct bian //边集数组
{
int from;
int to;
int weight;
};
bool cmp(bian a,bian b) //使sort按权值排序
{
return a.weight<b.weight;
}
int findd(int *parent,int o) //找根结点
{
int ff;
ff=o;
while(parent[ff]>-1)
ff=parent[ff];
return ff;
}
int main()
{
int m,n;
cin>>n>>m;
int parent[99];
int vis[99][99];
bian eg[99];
int f=0;
for(int i=0; i<n; i++)
parent[i]=-1; //初始都是单个点,n个树
for(int i=0; i<n; i++)
for(int j=0; j<n; j++)
vis[i][j]=0; //都没访问过
for(int i=0; i<n; i++)
for(int j=0; j<n; j++)
{
int p;
cin>>p;
if(p!=0&&p!=100&&vis[i][j]==0)
{
eg[f].from=i;
eg[f].to=j;
eg[f].weight=p;
vis[i][j]=1; //无向图
vis[j][i]=1;
f++;
}
}
sort(eg,eg+f,cmp);
int k=0,beginn,endd,countt=0;
for(k=0; k<f; k++)
{
beginn=eg[k].from;
endd=eg[k].to;
int x,y;
x=findd(parent,beginn);
y=findd(parent,endd);
if(x!=y)
{
cout<<beginn+1<<" "<<endd+1<<" ";//输出结点
parent[y]=x;
countt++;
if(countt==n-1)
break;
}
}
}
四,最短路径
在网图中,最短路径是指两顶点之间经历的边上权值之和最短的路径。
1,dijkstra算法(单元点到其他顶点的最短路径,不能解决负权问题)
路径长度递增,解n-1条路的数值
数组dist[n]:每个分量dist[i]表示当前所找到的从始点v到终点vi的最短路径的长度。初态为:
若从v到vi有弧,则dist[i]为弧上权值;否则置dist[i]为∞
数组path[n]:path[i]是一个字符串,表示当前所找到的从始点v到终点vi的最短路径。初态为:若从v到vi有弧,则path[i]为vvi;否则置path[i]空串。
数组s[n]:存放源点和已经找到最短路径的终点,其初态为只有一个源点v。
#include<iostream>
#include<string>
#include<cmath>
using namespace std;
const int MaxSize=10;
const int Max=1000;
class MGraph
{
public:
MGraph(int n,int e);
void Dijkstra(int v,int x);
private:
int vertexnum;
int arcnum;
int arc[MaxSize][MaxSize];
string vertex[MaxSize];
};
MGraph::MGraph(int n,int e){
vertexnum=n;
arcnum=e;
for(int i=0;i<vertexnum;i++){ 字符串与整形相结合存入vertex
vertex[i]="v";
vertex[i]+=(i+'0');
}
for(int i=0;i<vertexnum;i++)
for(int j=0;j<vertexnum;j++)
arc[i][j]=1000;
int i,j,num;
for(int k=0;k<arcnum;k++){
cin>>i>>j>>num;
arc[i][j]=num;
}
}
void MGraph::Dijkstra(int v,int x){
string s[Max];
string path[Max];
int dist[Max];
for(int i=0;i<vertexnum;i++){
s[i]="";
dist[i]=arc[v][i]; //存储相邻点的权值
if(dist[i]!=Max)
path[i]=vertex[v]+" "+vertex[i]; // 存储可能的路径
else
path[i]="";
}
s[v]=vertex[v]; //存储每条最短最短路径的终点可写成s[0]=vertex[v];
int num=1;
while(num<vertexnum){
int k=0;
for(int i=0;i<vertexnum;i++)
if(dist[i]<dist[k]&&s[i]=="") k=i;
s[k]=vertex[k]; //s[num++]=vetex[k];
num++;
for(int i=0;i<vertexnum;i++)
if(dist[k]+arc[k][i]<dist[i]){
dist[i]=dist[k]+arc[k][i];
path[i]=path[k]+" "+vertex[i];
}
}
if(path[x]!=""){
cout<<dist[x]<<endl;
cout<<path[x]<<endl;
}
else
cout<<"no answer"<<endl;
}
int main()
{
int n,e;
int v,x;
cin>>n>>e;
cin>>v>>x;
MGraph mg(n,e);
mg.Dijkstra(v,x);
}
2,floyd算法(任意一对节点之间的最短路径)
邻接矩阵存储,迭代进行
#include<iostream>
#include<vector>
using namespace std;
int path[99][99];
void judge(int x,int y)
{
int k=path[x][y];
if(k==-1) return;
judge(x,k);
cout<<"v"<<k<<" ";
judge(k,y);
}
int main(){
int n,m,v,e;
cin>>n>>m>>v>>e;
int dist[99][99];
for(int i=0;i<n;i++)
for(int j=0;j<n;j++){
dist[i][j]=10000;
path[i][j]=-1;}
while(m--){
int x,y,z;
cin>>x>>y>>z;
dist[x][y]=z;
}
for(int k=0;k<n;k++)
for(int j=0;j<n;j++)
for(int i=0;i<n;i++){
if(k==j || k==i || i==j) continue;
if(dist[i][k]+dist[k][j]<dist[i][j]){
dist[i][j]=dist[i][k]+dist[k][j];
path[i][j]=k;
}}
bool Flag=false;
if(dist[v][e]!=10000)
{
Flag=true;
cout<<dist[v][e]<<endl;
cout<<"v"<<v<<" ";
judge(v,e);
cout<<"v"<<e<<" "<<endl;
}
if(Flag==false)
cout<<"no answer"<<endl;
}
五,有向无环图及其应用。
1,AOV网
在一个表示工程的有向图中,用顶点表示活动,用弧表示活动之间的优先关系,称这样的有向图为顶点表示活动的网,简称AOV网。
拓扑序列(前驱后继关系都满足)
若从顶点vi到vj有一条路径,则在顶点的拓扑序列中顶点vi必在顶点vj之前。
拓扑排序:对一个有向图构造拓扑序列的过程称为拓扑排序 。
#include<bits/stdc++.h>
using namespace std;
struct bian{
int hao;
bian *e;
};
struct dian{
int ru;
int t;
bian *first;
};
int main(){
int v,a;
cin>>v>>a;
dian dd[v+1];
for(int i=1;i<=v;i++) //边集数组,单存一个入度
{
dd[i].ru=0;
dd[i].t=i;
dd[i].first=NULL;
}
for(int i=0;i<a;i++)
{
int x,y;
bian *s;
cin>>x>>y;
dd[y].ru++;
s=new bian;
s->hao=y;
s->e=dd[x].first;
dd[x].first=s;
}
stack<int>p; /用栈进行操作
for(int i=v;i>=1;i--)
if(dd[i].ru==0)
p.push(i);
while(!p.empty()){
int j=p.top();
p.pop();
cout<<"v"<<dd[j].t<<" ";
bian *q;
q=dd[j].first;
while(q!=NULL){
int k=q->hao;
dd[k].ru--;
if(dd[k].ru==0)
p.push(k);
q=q->e;
}
}
}
2,AOE网
在一个表示工程的带权有向图中,
用顶点表示事件,
用有向边表示活动,
边上的权值表示活动的持续时间,
称这样的有向图叫做边表示活动的网,简称AOE网。
AOE网中没有入边的顶点称为始点(或源点),没有出边的顶点称为终点(或汇点)。
AOE网的性质:
⑴ 只有在某顶点所代表的事件发生后,从该顶点出发的各活动才能开始;
⑵ 只有在进入某顶点的各活动都结束,该顶点所代表的事件才能发生。
关键路径:在AOE网中,从始点到终点具有最大路径长度(该路径上的各个活动所持续的时间之和)的路径称为关键路径。
关键活动:关键路径上的活动称为关键活动。
⑴ 事件的最早发生时间ve[k], 指从始点开始到顶点vk的最大路径长度。这个长度决定了所有从顶点vk发出的活动能够开工的最早时间。
⑵ 事件的最迟发生时间vl[k] ,指在不推迟整个工期的前提下,事件vk允许的最晚发生时间。
⑶ 活动的最早开始时间e[i]
⑷ 活动的最晚开始时间l[i]
#include<bits/stdc++.h>
using namespace std;
struct Edge{ //构建边的结构体,表式边的俩点,边上事件的最早最晚发生时间
int from;
int to;
int e;
int l;
};
int ve[99];
int vl[99];
int vertexnum;
int adjlist[99][99];
int start,end;
Edge edge[99];
int main(){
int v,a;
int visit[99];
cin>>v>>a;
vertexnum=v;
for(int i=1;i<=v;i++)
for(int j=1;j<=v;j++)
adjlist[i][j]=9999;
for(int i=1;i<=a;i++){
int x,y,z;
cin>>x>>y>>z;
adjlist[x][y]=z;
edge[i].from=x;
edge[i].to=y;
}
queue<int>q;
q.push(1);//源点事件入队
for(int j=1;j<=vertexnum;j++) {
ve[j]=0; visit[j]=0; }
visit[1]=1;
while(!q.empty()) {
int i=q.front();
q.pop();
for(int j=1;j<=vertexnum;j++){//计算i的邻接点的ve
if(adjlist[i][j]!=9999 && ve[i]+adjlist[i][j]>ve[j] ){
ve[j]=ve[i]+adjlist[i][j]; //算最早,找大的
if(!visit[j])
q.push(j);
visit[j]=1;
}
}
}
//for(int i=1;i<=v;i++)
// cout<<ve[i]<<" ";
//cout<<endl;
/
q.push(vertexnum);
for(int j=1;j<=vertexnum;j++) {
vl[j]=ve[vertexnum]; visit[j]=0; }
while(!q.empty()) {
int i=q.front();
q.pop();
for(int j=1;j<=vertexnum;j++) {
if(adjlist[j][i]!=9999 && vl[i]-adjlist[j][i]<vl[j] ){
vl[j]=vl[i]-adjlist[j][i];
if(!visit[j])
q.push(j);
visit[j]=1;
}
}
}
//for(int i=1;i<=v;i++)
// cout<<vl[i]<<" ";
//cout<<endl;
//
int f=0;
for(int i=1;i<=a;i++)
{
edge[i].e=ve[edge[i].from]; //根据节点算边,找相等的是关键路径
edge[i].l=vl[edge[i].to]-adjlist[edge[i].from][edge[i].to];
if(edge[i].e==edge[i].l){
if(f==0){
cout<<"v"<<edge[i].from<<" "<<"v"<<edge[i].to<<" ";
f=1;}
else
cout<<"v"<<edge[i].to<<" ";
}
}
cout<<endl;
/*for(int i=1;i<=a;i++)
cout<<edge[i].e<<" ";
cout<<endl;
for(int i=1;i<=a;i++)
cout<<edge[i].l<<" ";*/
}