图的邻接表的数组实现及其应用

来源:互联网 发布:淘宝舞蹈服 编辑:程序博客网 时间:2024/05/29 19:45

一、基础

对于有向图来说,我们把每条边的起始点(from)称为尾节点,终止点称为头结点。

我们可以把每条边按如下定义:

typedef struct{    int to;    int w;    int next;}Edge; Edge e[MAX];            //MAX为边的数量。e为结构体数组,用于存储边的信息int pre[MAX];           //pre数组记录的当前边的序号,pre[i]表示尾节点是 i 的边的序号是pre[i]//初始化memset(pre,-1,sizeof(pre));   //pre数组初始化为-1是正确的,原因后面阐述//输入,循环scanf("%d %d %d",&from,&to,&w1);e[i].to = to; e[i].w = w1; e[i].next = pre[from]; pre[from] = i;i++;

下面举例说明其正确性:


输入边的信息为(输入边的次序即为边的序号):

边序号          from             to

   0                  1                 2

   1                  1                 3

   2                  2                 4

   3                  3                 4

   4                  1                 4

则经过上面的程序之后,所得结果为(省略掉 to 和 w 的信息)

初值i=0

e[0].next= pre[1] = -1 , pre[1] = 0 , i = 1

e[1].next= pre[1] =  0 , pre[1] = 1 , i = 2

e[2].next= pre[2] = -1 , pre[2] = 2 , i = 3

e[3].next= pre[3] = -1 , pre[3] = 3 , i = 4

e[4].next= pre[1] =  1 , pre[1] = 4 , i = 5

我们可以看出 e[i].next 记录上一条从 i 出发的边的序号(想想它是如何做到的),所以如果只有一条从i 出发的边,那么e[i].next=pre[ i ] = -1,因为 i 没有上一条边,这就是pre数组初始化为-1的原因。
由此,我们可以如下操作一个顶点的所有邻接顶点:

/* now为尾结点序号,i为now所发出的边序号,adj为 头结点序号,w为now-->adj这条边的权值 */for(i = pre[now]; i != -1; i = e[i].next){     int adj = e[i].to ;     int w = e[i].w ;     //do something...}

参考资料:http://www.cnblogs.com/g0feng/archive/2012/09/18/2690913.html



二、应用   POJ 3249 Test for Job (http://poj.org/problem?id=3249)

#include <iostream>#include <cstdlib>#include <cstdio>#include <cstring>#include <string>#include <algorithm>#include <queue>using namespace std;typedef long long LL;const int MAXN = 100010;const int MAXM = 1000010;    //最多边const int INF = 0x3f3f3f3f;struct Edge{int v;LL w;int next;}e[MAXM];  int n, m;int count1, tot;int count2;LL d[MAXN], w[MAXN];int pre[MAXN];               //以顶点数为参考,而非边数int topo[MAXN];int ind[MAXN], outd[MAXN];   //入度和出度int save[MAXN];void init(){      tot = 0;count1 = 0;count2 = 0;memset(pre, -1, sizeof(pre));memset(ind, 0, sizeof(ind));memset(outd, 0, sizeof(outd));}void read_graph(int u, int v, LL w){      e[count1].v = v;      e[count1].w = w;      e[count1].next = pre[u];      pre[u] = count1++;}void toposort(){queue<int> q;for(int i = 0; i <= n; i++)         {    if(!ind[i])                  q.push(i);        }while(!q.empty()){int x = q.pop();topo[tot++] = x;               //出栈次序即为拓扑排序的次序for(int i = pre[x]; i!= -1; i = e[i].next)  //找出 x 所有的邻接顶点{int v = e[i].v;        //邻接顶点if(--ind[v] == 0)      //入度减1{    q.push(v);}}}}void DAGLongestPath(int src) //DAG有向无环图的最长路径{for(int i = 0; i <= n; i++)         //新增0节点,0~n 共 n+1 个节点             d[i] = ((i == src)? 0:-INF);   //源节点的d[0]=0,其他皆初始化为负无穷大for(int u = 0 ; u < tot; u++)     //对于每一个顶点{int x = topo[u];          //拓扑排序好的顶点次序for(int i = pre[x]; i!= -1; i = e[i].next){int v = e[i].v, w = e[i].w;if(d[v] < d[x] + w){d[v] = d[x] + w;}}}}void read_case(){init( );for(int i = 1; i <= n; i++){scanf("%lld", &w[i]);}while(m--){int u, v;scanf("%d%d", &u, &v);read_graph(u, v, w[v]);   //点权转换为边权(w[v])        outd[u]++;ind[v]++;}for(int i = 1; i <= n; i++){if(!outd[i])             //储存出度为0的点,即终点{save[count2++] = i;   //i为出度为0的点的编号}if(!ind[i]){read_graph(0, i, w[i]); //没有入边的点需要添加一个节点0,并连接一条有向边 (增加一个超级源点 0 )ind[i]++;}}}void solve(){read_case();toposort();DAGLongestPath(0);LL ans = -INF;for(int i = 0; i < count2; i++)      //枚举最大值(出度为0(出口)的顶点的个数),因为有可能不止一个出口{      ans = max(ans, d[save[i]]);   //save[i]表示出度为0的顶点的编号,这样的顶点共有count2 个!这里很重要,要想清楚(这里的i不同于save[count2++] = i;的i)!!}printf("%lld\n", ans);}int main(){while(~scanf("%d%d", &n, &m)){solve();}return 0;}

                              

        改变后的图                             

参考资料:http://blog.csdn.net/wall_f/article/details/8205246


0 0
原创粉丝点击