其实以前也写过很多遍图上的算法了,最近要准备各大公司面试,就把模板都贴在这吧。具体的算法看给的各个连接或者去网上搜一下就可以了。
图的表示
图的表示主要有邻接矩阵和邻接链表两者方法,前者随机访问容易但空间太大,后者空间小但随机访问较慢而且需要动态分配内存。
一般在实现一些常见算法代码时,还有一种方法来表示图,即链式前向星 。这种方法本质上还是邻接表方法,但不需要动态分配内存,只需要知道边的总数即可。
int e_num = 1; //边的数目
int head[maxN]; //每个顶点相关边的起始位置
struct E{
int v, u, cost, next;
}edge[maxM<<1];
//添加有向边a->b,如果是无向图则需要两次调用这个函数
inline void add(int a, int b, int d){
edge[e_num].v = a;
edge[e_num].u = b;
edge[e_num].cost = d;
edge[e_num].next = head[a];
head[a] = e_num++;
}
//遍历某个顶点v边的方式
for(int i = head[v]; i !=0; i = edge[i].next){
u = edge[i].u;
cost = edge[i].cost;
/**/
}
最短路径
该算法处理单源最短路径,早于下面的Dijkstra算法,可以处理负权图,但算法效率较慢。算法的本质是循环N-1次,每次循环内部对全部的边进行松弛,第i次循环完毕后保证源点s到v的距离dis 是当前所有边个数不超过i的路径中最小的。时间复杂度为 O(|V||E|)≈O(|V|3)
//代码采用上面的前向星结构
int dis[maxN]; //当前最小距离
int pre[maxN]; //前驱结点
bool Bellman_Ford(int s)
{
//初始化
for(int i = 0; i < N; ++i)
dis[i] = (i == s? 0 : INT_MAX);
int t = N-1, u, v, c;
//循环N-1次
while(t--){
//遍历每条边
for(int i = 1; i < e_num; ++i){
v = edge[i].v, u = edge[i].u, c = edge[i].cost;
//松弛操作
if(dis[v] + cost < dis[u]){
dis[u] = dis[v] + cost;
pre[u] = v;
}
}
}
//判断是否有负权回路
for(int i = 1; i < e_num; ++i){
v = edge[i].v, u = edge[i].u, c = edge[i].cost;
if(dis[v] + cost < dis[u])
return false;
}
return true;
}
Dijkstra算法
该算法是典型的贪心算法,可以求出非负权图中的单源最短路径。时间复杂度为
O(|V|2)
,但是可以用堆进行优化。
//代码采用邻接矩阵结构,也可以改成前向星
int adj[maxN][maxN]; //邻接矩阵
int dis[maxN]; //当前最小距离
int pre[maxN]; //前驱结点
bool vis[maxN]; //是否已经找到最短路径
void Dijkstra(int s){
//初始化
memset(vis, 0, maxN);
for(int i = 0; i < N; ++i)
dis[i] = (i == s? 0 : INT_MAX);
vis[s] = true;
for(int i = 1; i< N; i++){
int mindis = INT_MAX;
int u = s;
//在T中找到s最近的点
for(int j = 0; j < N; j++){
if(!vis[j] && dis[j] < mindis){
mindis = dis[j];
u = j;
}
}
//如果u仍为s说明剩下的点s都不能到达,直接退出查找
if(u == s)
break;
//把u加到S中,并更新T中到s的距离
vis[u] = true;
for(int j = 0; j < N; j++){
if(!vis[j] && adj[u][j] != INF){
int tmp = adj[u][j] + dis[u];
if(tmp < dis[j]){
dis[j] = tmp;
pre[j] = u;
}
}
}
}
SPFA算法
本质上是Bellman-Ford算法的一种队列实现。它可以处理负权图,比Dijkstra算法适用范围更广,平均效率也很低只有
O(|E|)
。
//代码采用前向星结构
int dis[maxN]; //当前最小距离
int pre[maxN]; //前驱结点
bool vis[maxN]; //是否已经找到最短路径
queue<int> q;
//入队
inline void push(int s){
if(vis[s]) return;
q.push(s);
vis[s] = true;
}
//出队
inline int pop(){
int t = q.front();
q.pop();
vis[t] = false;
return t;
}
void SPFA(int s){
while(!q.empty()) q.pop();
memset(vis, 0, N+1);
for(int i = 0; i < N; ++i)
dis[i] = (i == s? 0 : INT_MAX);
push(s);
while(!q.empty()){
int v = pop();
for(int i = head[v]; i != 0; i = edge[i].next ){
int u = edge[i].u;
int tmp = dis[v] + edge[i].cost;
if(tmp < dis[u]){
dis[u] = tmp;
pre[u] = v;
push(u);
}
}
}
}
Floyd算法
是和上面三个算法不同,是用来求解任意两点最短路径的算法,本质上是一个动态规划算法。时间复杂度为
O(|V|3)
//因为是任意两点距离,因此输出已经是O(n^2)的,故我们用邻接矩阵
void Floyd(){
for(int i = 0; i < N; i++){
for(int j = 0; j < N; j++)
dis[i][j] = adj[i][j];
}
//以顶点k为中继点计算i到j的距离
for(int k = 0; k< N; k++){
for(int i = 0; i < N; i++){
for(int j = 0; j < N; j++){
if(dis[i][k] != INT_MAX && dis[k][j] != INT_MAX){
int tmp = dis[i][k] + dis[k][j];
if(tmp < dis[i][j]){
dis[i][j] = tmp;
pre[i][j] = k;
}
}
}
}
}
}
最小生成树
Prim算法和Kruscal算法 ,前者类似于Dijkstra算法,复杂度为 O(|V|2) ,适用于稠密图;后者需要用到并差集,复杂度为 O(|E|log|E|) ,适用于稀疏图。
//采用前向星结构
int dis[maxN]; //到当前生成树T的最小距离
int pre[maxN]; //到生成树中上一个顶点编号
bool vis[maxN]; //判断是否已存入该点到生成树中
//求最小生成树
int Prim(){
//0作为生成树的第一个点
int s = 0;
memset(vis, 0, maxN);
for(int i = 0 ; i < N; ++i)
dis[i] = INT_MAX;
for(int i = head[s]; i!=0 ; i = edge[i].next)
dis[edge[i].u] = min(dis[edge[i].u], edge[i].cost);
int ans = 0;
dis[s] = 0;
vis[s] = true;
int t = N-1;
while(t--){
int mindis = INT_MAX;
int v = s;
//在剩下的点中找到生成树T最近的点
for(int i = 0; i < N; i++){
if(!vis[i] && dis[i] < mindis){
mindis = dis[i];
v = i;
}
}
//如果v仍为s说明剩下的点生成树都不能到达,直接退出查找
if(v == s)
return INT_MAX;
//把u加到生成树中,并更新剩下的点中到生成树的距离
ans += dis[v];
vis[v] = true;
for(int i = head[v]; i!=0; i = edge[i].next){
int u = edge[i].u;
if(dis[u] > edge[i].cost){
dis[u] = edge[i].cost;
pre[u] = v;
}
}
}
return ans;
}
bool CMP(E e1, E e2){
return e1.cost < e2.cost;
}
//并查集
class Union{
private:
int* fa;
int* R;
public:
Union(int N){
fa = new int[N+1];
R = new int[N+1];
for(int i = 1; i <= N; i++){
fa[i] = i;
R[i] = 1;
}
}
int find(int a){
int r = fa[a];
while(r != fa[r])
r = fa[r];
int u = a;
while(u != r){
a = fa[u];
fa[u] = r;
u = a;
}
return r;
}
int join(int a, int b){
int ra = find(a), rb = find(b);
if(ra != rb){
if(R[ra] >= R[rb]){
fa[rb] = ra;
R[ra] += R[rb];
return a;
}
else{
fa[ra] = rb;
R[rb] += R[ra];
return b;
}
}
return a;
}
bool same(int a, int b){
return find(a) == find(b);
}
~Union(){
delete fa;
delete R;
}
};
//求最小生成树
int Kruscal(){
memset(pre, 0, maxN);
sort(e, e+M, CMP);
int ans = 0;
Union U(N);
int k = 0, u, v, t;
for(int i = 1; i < N; i++){
while(k < M && U.same(e[k].u, e[k].v))
k++;
if(k == M)
return INF;
t = U.join(e[k].u, e[k].v);
ans += e[k].cost;
pre[t] = (t == e[k].u? e[k].v : e[k].u);
}
return ans;
}