数据结构之图

图的基础概念:完全图,简单图,连通图,生成树,简单路径等。
图的存储及基本操作:
邻接矩阵法,存在即表现权值,自身则表示为0,不能连接则表示为无穷大。
邻接表法:邻接图对图中的每个顶点V建立一个单链表,包括顶点表和边表。
十字链表法:针对有向图的一种链式存储结构,包括弧结点和顶点结点。
邻接多重表:针对无向图的一种链式存储结构,包括顶点表和弧结点表。

图的操作有:判断是否存在边,插入顶点,删除顶点,获取权值等。

图的遍历:
广度优先搜索:
类似于二叉树的层序算法。首先访问起始顶点v,然后依次访问v的各个未访问的邻接点。
伪代码如下:
bool visited[MAX_VERTEX_NUM];//访问标记数组
void BFSTraverse(Graph G){//初始化辅助队列Q
for(i=0;G.vernum>i;i++)
//从0号开始遍历
visited[i]=FALSE;//访问标记数组初始化
InitQueue(Q);//初始化辅助队列Q
for(i=0;G.vexnum>i;i++)//对每个连通分量调用一次BFS
if(!visited[i])//v未访问,从v开始BFS
BFS(G,I);
}
void BFS(Graph G,int v){
visit(v);//访问初始顶点v
visited[v]=TRUE;//对v做已访问标记
Enqueue(Q,v);//顶点v入队列
while(!isEmpty(Q)){
DeQueue(Q,v);//顶点v出队
for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w))//检测v所有邻接点
if(!visited[w]){//w为v尚未访问的邻接顶点
visite(w);//访问顶点w
visited[w]=TRUE;//对w做已访问标记
EnQueue(Q,w);//顶点w入队
}
}
}

深度优先搜索:
搜索思想和广度优先搜索类似,只是搜索方式不同。

图的应用:
最小生成树:最小生成树不唯一,边数是顶点数减1。
常用生成树的方法有prim算法和kruskal算法
Prim算法:思想就是任选一个点作为顶点,选择其相邻最小权值的结点,不断生成。
Prim算法的步骤包括:
1. 将一个图分为两部分,一部分归为点集U,一部分归为点集V,U的初始集合为{V1},V的初始集合为{ALL-V1}。
2. 针对U开始找U中各节点的所有关联的边的权值最小的那个,然后将关联的节点Vi加入到U中,并且从V中删除(注意不能形成环)。
3. 递归执行步骤2,直到V中的集合为空。
4. U中所有节点构成的树就是最小生成树。
简单实现代码:
include “iostream”
include “vector”
using namespace std;

//Prim算法实现
void prim_test()
{
int n;
cin >> n;

for(int i = 0; i < n ; ++i) {
    for(int j = 0; j < n; ++j) {
        cin >> A[i][j];
    }
}

int pos, minimum;
int min_tree = 0;
//lowcost数组记录每2个点间最小权值,visited数组标记某点是否已访问
vector visited, lowcost;
for (int i = 0; i < n; ++i) {
    visited.push_back(0);    //初始化为0,表示都没加入
}
visited[0] = 1;   //最小生成树从第一个顶点开始
for (int i = 0; i < n; ++i) {
    lowcost.push_back(A[0][i]);    //权值初始化为0
}

for (int i = 0; i < n; ++i) {    //枚举n个顶点
    minimum = max_int;
    for (int j = 0; j < n; ++j) {    //找到最小权边对应顶点
        if(!visited[j] && minimum > lowcost[j]) {
            minimum = lowcost[j];
            pos = j;
        }
    }

if (minimum == max_int) //如果min = max_int表示已经不再有点可以加入最小生成树中
break;
min_tree += minimum;
visited[pos] = 1; //加入最小生成树中
for (int j = 0; j < n; ++j) {
if(!visited[j] && lowcost[j] > A[pos][j]) lowcost[j] = A[pos][j]; //更新可更新边的权值
}
}

cout << min_tree << endl;

}

int main(void)
{
prim_test();
return 0;
}
Kruskal算法:算法思想:将权值排序,选取满足条件下的最小权值边,不断生成树。
代码如下:
include “iostream”
include “vector”
include “algorithm”
using namespace std;

//并查集实现最小生成树
vector u, v, weights, w_r, father;
int mycmp(int i, int j)
{
return weights[i] < weights[j];
}
int find(int x)
{
return father[x] == x ? x : father[x] = find(father[x]);
}
void kruskal_test()
{
int n;
cin >> n;
for(int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
cin >> A[i][j];
}
}

int edges = 0;
// 共计n*(n - 1)/2条边
for (int i = 0; i < n - 1; ++i) {
    for (int j = i + 1; j < n; ++j) {
        u.push_back(i);
        v.push_back(j);
        weights.push_back(A[i][j]);
        w_r.push_back(edges++);
    }
}
for (int i = 0; i < n; ++i) {
    father.push_back(i);    // 记录n个节点的根节点,初始化为各自本身
}

sort(w_r.begin(), w_r.end(), mycmp); //以weight的大小来对索引值进行排序

int min_tree = 0, cnt = 0;
for (int i = 0; i < edges; ++i) {
    int e = w_r[i];    //e代表排序后的权值的索引
    int x = find(u[e]), y = find(v[e]);
    //x不等于y表示u[e]和v[e]两个节点没有公共根节点,可以合并
    if (x != y) {
        min_tree += weights[e];
        father[x] = y;
        ++cnt;
    }
}
if (cnt < n - 1) min_tree = 0;
cout << min_tree << endl;

}

int main(void)
{

kruskal_test();

return 0;

}
最短路径:
Dijkstra算法求单源最短路径问题:
1,从任意一条单边路径开始。所有两点之间的距离是边的权,如果两点之间没有边相连,则权为无穷大。
2,对于每一对顶点 u 和 v,看看是否存在一个顶点 w 使得从 u 到 w 再到 v 比已知的路径更短。如果是更新它。
算法如下:
include “iostream”
using namespace std;

int edgs[100][100]; //边
int dist[MAXNUM]; //v0到各边的值
int prev[MAXNUM]; //前驱数组

void Dijkstra(int v0,int n)
{
bool S[MAXNUM]; //确定顶点是否在S集合中

 for(int i=0;i

}
int main()
{
int n; //图的顶点数
cin>>n;
for(int i=0;n>i;i++)
for(int j=0;n>j;j++)
cin>>edgs[i][j]; //边的权值

return 0;

}
Floyd算法求各顶点之间的最短路径问题
算法思想:
最开始只允许经过1号顶点进行中转,接下来只允许经过1和2号顶点进行中转……允许经过1~n号所有顶点进行中转,求任意两点之间的最短路程。用一句话概括就是:从i号顶点到j号顶点只经过前k号点的最短路程。
采用划十字方法进行更新,其代码如下:
include “stdio.h”
int main()
{
int e[10][10],k,i,j,n,m,t1,t2,t3;
int inf=99999999; //用inf(infinity的缩写)存储一个我们认为的正无穷值
//读入n和m,n表示顶点个数,m表示边的条数
scanf(“%d %d”,&n,&m);

//初始化
for(i=1;i<=n;i++)
    for(j=1;j<=n;j++)
        if(i==j) e[i][j]=0;  
          else e[i][j]=inf;

//读入边
for(i=1;i<=m;i++)
{
    scanf("%d %d %d",&t1,&t2,&t3);
    e[t1][t2]=t3;
}

//Floyd-Warshall算法核心语句
for(k=1;k<=n;k++)
    for(i=1;i<=n;i++)
        for(j=1;j<=n;j++)
            if(e[i][j]>e[i][k]+e[k][j] ) 
                e[i][j]=e[i][k]+e[k][j];

//输出最终的结果
for(i=1;i<=n;i++)
{
 for(j=1;j<=n;j++)
    {
        printf("%10d",e[i][j]);
    }
    printf("\n");
}

return 0;

}
拓扑排序:
对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边(u,v)∈E(G),则u在线性序列中出现在v之前。
拓扑排序对应施工的流程图具有特别重要的作用,它可以决定哪些子工程必须要先执行,哪些子工程要在某些工程执行后才可以执行。为了形象地反映出整个工程中各个子工程(活动)之间的先后关系,可用一个有向图来表示,图中的顶点代表活动(子工程),图中的有向边代表活动的先后关系,即有向边的起点的活动是终点活动的前序活动,只有当起点活动完成之后,其终点活动才能进行。通常,我们把这种顶点表示活动、边表示活动间先后关系的有向图称做顶点活动网(Activity On Vertex network),简称AOV网。
一个AOV网应该是一个有向无环图,即不应该带有回路,因为若带有回路,则回路上的所有活动都无法进行(对于数据流来说就是死循环)。在AOV网中,若不存在回路,则所有活动可排列成一个线性序列,使得每个活动的所有前驱活动都排在该活动的前面,我们把此序列叫做拓扑序列(Topological order),由AOV网构造拓扑序列的过程叫做拓扑排序(Topological sort)。AOV网的拓扑序列不是唯一的,满足上述定义的任一线性序列都称作它的拓扑序列。
实现步骤:

1.在有向图中选一个没有前驱的顶点并且输出
2.从图中删除该顶点和所有以它为尾的弧(白话就是:删除所有和它有关的边)
3.重复上述两步,直至所有顶点输出,或者当前图中不存在无前驱的顶点为止,后者代表我们的有向图是有环的,因此,也可以通过拓扑排序来判断一个图是否有环。
算法代码:
bool Graph_DG::topological_sort_by_dfs() {
stack result;
int i;
bool * visit = new bool[this->vexnum];
//初始化我们的visit数组
memset(visit, 0, this->vexnum);
cout << “基于DFS的拓扑排序为:” << endl;
//开始执行DFS算法
for (i = 0; i < this->vexnum; i++) {
if (!visit[i]) {
dfs(i, visit, result);
}
}
//输出拓扑序列,因为我们每次都是找到了出度为0的顶点加入栈中,
//所以输出时其实就要逆序输出,这样就是每次都是输出入度为0的顶点
for (i = 0; i < this->vexnum; i++) {
cout << result.top() << ” “;
result.pop();
}
cout << endl;
return true;
}
void Graph_DG::dfs(int n, bool * & visit, stack & result) {

    visit[n] = true;
    ArcNode * temp = this->arc[n].firstarc;
    while (temp) {
        if (!visit[temp->adjvex]) {
            dfs(temp->adjvex, visit,result);
        }
        temp = temp->next;
    }
    //由于加入顶点到集合中的时机是在dfs方法即将退出之时,
    //而dfs方法本身是个递归方法,
    //仅仅要当前顶点还存在边指向其他不论什么顶点,
    //它就会递归调用dfs方法,而不会退出。
    //因此,退出dfs方法,意味着当前顶点没有指向其他顶点的边了
    //,即当前顶点是一条路径上的最后一个顶点。
    //换句话说其实就是此时该顶点出度为0了
    result.push(this->arc[n].data);

}
关键路径:路径各个活动所持续的时间之和称为路径长度,从源点到汇点具有最大路径长度的路径叫做关键路径,在关键路径上的活动叫做关键活动。
AOV网(Activity On Vertex NetWork)用顶点表示活动,边表示活动(顶点)发生的先后关系。AOE网的边不设权值,若存在边[a,b]则表示活动a必须发生在活动b之前。
AOE网(Activity On Edge Network)是边表示活动的网,AOE网是带权有向无环图。边代表活动,顶点代表 所有指向它的边所代表的活动 均已完成 这一事件。由于整个工程只有一个起点和一个终点,网中只有一个入度为0的点(源点)和一个出度为0的点(汇点)。
区别:AOV网是顶点表示活动的网,它只描述了活动之间的约束关系,而AOE网是用有向边表示活动,边上的权值表示活动持续的时间。AOE网是建立在AOV网基础之上(活动之间约束关系没有矛盾),再来分析完成整个工程至少需要多少时间,或者为缩短完成工程所需时间,应当加快那些活动等问题。

你可能感兴趣的:(图,prim,kruskal,dijkstra,floyd,算法学习)