[置顶] 链式前向星
前向星:
一种数据结构,以储存边的方式来存储图。
构造方法如下:读入每条边的信息,将边存放在数组中,把数组中的边按照起点顺序排序,前向星就构造完了。
用于:点的数目太多,或两点之间有多条弧的时候。一般在别的数据结构不能使用的时候才考虑用前向星。
优缺点:除了不能直接用起点终点定位以外,前向星几乎是完美的。
链式前向星:
是前向星的优化。在代码量减小很多的情况下效率得到了较大的提升。
/*摘录自https://www.cnblogs.com/zmin/p/7349100.html*/
我们首先来看一下什么是前向星.
前向星是一种特殊的边集数组,我们把边集数组中的每一条边按照起点从小到大排序,如果起点相同就按照终点从小到大排序,
并记录下以某个点为起点的所有边在数组中的起始位置和存储长度,那么前向星就构造好了.
用len[i]来记录所有以i为起点的边在数组中的存储长度.
用head[i]记录以i为边集在数组中的第一个存储位置.
那么对于下图:
我们输入边的顺序为:
1 2
2 3
3 4
1 3
4 1
1 5
4 5
那么排完序后就得到:
编号: 1 2 3 4 5 6 7
起点u: 1 1 1 2 3 4 4
终点v: 2 3 5 3 4 1 5
得到:
head[1] = 1 len[1] = 3
head[2] = 4 len[2] = 1
head[3] = 5 len[3] = 1
head[4] = 6 len[4] = 2
但是利用前向星会有排序操作,如果用快排时间至少为O(nlog(n))
如果用链式前向星,就可以避免排序.
我们建立边结构体为:
struct Edge
{
int next;
int to;
int w;
};
其中edge[i].to表示第i条边的终点,edge[i].next表示与第i条边同起点的下一条边的存储位置,edge[i].w为边权值.
另外还有一个数组head[],它是用来表示以i为起点的第一条边存储的位置,实际上你会发现这里的第一条边存储的位置其实
在以i为起点的所有边的最后输入的那个编号.
head[]数组一般初始化为-1,对于加边的add函数是这样的:
- void add(int u,int v,int w)
- {
- edge[cnt].w = w;
- edge[cnt].to = v;
- edge[cnt].next = head[u];
- head[u] = cnt++;
- }
初始化cnt = 0,这样,现在我们还是按照上面的图和输入来模拟一下:
edge[0].to = 2; edge[0].next = -1; head[1] = 0;
edge[1].to = 3; edge[1].next = -1; head[2] = 1;
edge[2].to = 4; edge[2],next = -1; head[3] = 2;
edge[3].to = 3; edge[3].next = 0; head[1] = 3;
edge[4].to = 1; edge[4].next = -1; head[4] = 4;
edge[5].to = 5; edge[5].next = 3; head[1] = 5;
edge[6].to = 5; edge[6].next = 4; head[4] = 6;
很明显,head[i]保存的是以i为起点的所有边中编号最大的那个,而把这个当作顶点i的第一条起始边的位置.
这样在遍历时是倒着遍历的,也就是说与输入顺序是相反的,不过这样不影响结果的正确性.
比如以上图为例,以节点1为起点的边有3条,它们的编号分别是0,3,5 而head[1] = 5
我们在遍历以u节点为起始位置的所有边的时候是这样的:
for(int i=head[u];~i;i=edge[i].next)
那么就是说先遍历编号为5的边,也就是head[1],然后就是edge[5].next,也就是编号3的边,然后继续edge[3].next,也
就是编号0的边,可以看出是逆序的.
迪杰斯特拉:前向星加优化:#include<iostream> #include<cstring> #include<cmath> #include<string> #include<vector> #include<algorithm> #include<cstdio> #include<queue> using namespace std; const int MAX_V = 200010; const int MAX_E = 2000010; const int INF = 0x3f3f3f3f; int V,E,cnt; int heap[MAX_V],dis[MAX_V]; struct Edge{ int to,next,cost; }rng[MAX_E]; void add(int u,int v,int cost){ rng[cnt].to = v; rng[cnt].next = heap[u]; rng[cnt].cost = cost; heap[u] = cnt++; } struct Rule{ bool operator()(int &a,int &b)const{ return dis[a] > dis[b]; } }; inline int read() { int X=0,w=1; char ch=0; while(ch<'0' || ch>'9') {if(ch=='-') w=-1;ch=getchar();} while(ch>='0' && ch<='9') X=(X<<3)+(X<<1)+ch-'0',ch=getchar(); return X*w; } void Dijkstra(int a_){ memset(dis,INF,sizeof(dis)); priority_queue<int,vector<int>,Rule > q; dis[a_] = 0;q.push(a_); while(!q.empty()){ int u = q.top();q.pop(); for(int k=heap[u];k != -1;k = rng[k].next){ int &v = rng[k].to; if(dis[v] > dis[u] + rng[k].cost){ dis[v] = dis[u] + rng[k].cost; q.push(v); } } } } int main(void){ cnt = 0; memset(heap,-1,sizeof(heap)); V = read(),E = read(); int x,y,z; for(int i=1;i<=E;i++){ x = read(),y = read(),z = read(); add(x,y,z); } Dijkstra(1); if(dis[V] == INF){ printf("-1\n"); } else printf("%d\n",dis[V]); return 0; }
spfa:前向星加优化:
/*摘录自https://www.cnblogs.com/crazyacking/p/3761686.html*/
#include<algorithm> #include<cstdio> #inlude<cstring> #include<cstdlib> #include<iostream> #include<vector> #include<queue> #include<stack> #include<iomanip> #include<string> #include<climits> #include<cmath> #define MAXV 10010 #define MAXE 50010 #define LL long long using namespace std; int T,n,m,u,v,w; int now,home,goal; bool vis[MAXV]; LL dis[MAXV]; namespace Adj { // edge数组是一个边集数组,存放一条边的信息 // to --- 该条边的终点 // next --- 下一条要访问的边(存的是edge数组的下标).即:访问完了edge[i],下一条要访问的就是edge[edge[i].next],如果next为0,表示now这个节点作为起点的边已经全部访问完.(下一步:Q.front()) // w --- 该条边的权值 struct Node { int to,next,w; }; Node edge[MAXE]; // idx --- edge数组的下标 // head[i] --- 表示以i节点为起点的所有出边在edge数组中的起始存储位置为head[i].(如果head[i]为0,表示结点i没有出边) int idx,head[MAXV]; // 初始化 void init() { idx=1; memset(head,0,sizeof(head)); } // 加边函数 void addEdge(int u,int v,int w) // 起点,终点,权值 { edge[idx].to=v; // 该边的终点 edge[idx].w=w; // 权值 edge[idx].next=head[u]; // (指向head[u]后,head[u]又指向了自己) head[u]=idx; // 以u结点为起点的边在edge数组中存储的下标 idx++; } } using namespace Adj; void visit(int sta) { for(int i=1;i<=n;i++) { vis[i]=0; dis[i]=LLONG_MAX; } // 起点进队 queue<int>Q; Q.push(sta); vis[sta]=1; dis[sta]=0; while(!Q.empty()) { int now=Q.front(); Q.pop(); vis[now]=0; // 在spfa中这儿需要改为0,因为每个节点需要重复进队 for(int i=head[now];i;i=edge[i].next) //取出now结点在edge中的起始存储下标(当i=0,即edge[i].next为0,说明以now节点为起始点的边全部访问完) { int w=edge[i].w; int son=edge[i].to; printf("%d --> %d , weight = %d\n",now,edge[i].to,edge[i].w); if(dis[now]+w<dis[son]) // 松弛操作 { dis[son]=dis[now]+w; if(!vis[son]) { Q.push(son); // 子节点未访问过 vis[son]=1; // 标记已访问 } } } } puts("/*************************************** END ******************************************/"); for(int i=1;i<=n;++i) { printf("%d --> %d shortest distance is %d\n",sta,i,dis[i]); } } int main() { while(1) { Adj::init(); scanf("%d",&n); scanf("%d",&m); for(int i=1;i<=m;++i) // 输入m条边 { int s,e,w; // 起点 终点 权值 scanf("%d %d %d",&s,&e,&w); addEdge(s,e,w); //若是无向图,反过来再加一次 } int start_point; //访问的起点 scanf("%d",&start_point); visit(start_point); } return 0; }