图算法

其实以前也写过很多遍图上的算法了,最近要准备各大公司面试,就把模板都贴在这吧。具体的算法看给的各个连接或者去网上搜一下就可以了。

图的表示

图的表示主要有邻接矩阵和邻接链表两者方法,前者随机访问容易但空间太大,后者空间小但随机访问较慢而且需要动态分配内存。

一般在实现一些常见算法代码时,还有一种方法来表示图,即链式前向星 。这种方法本质上还是邻接表方法,但不需要动态分配内存,只需要知道边的总数即可。

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;
    /**/
}

最短路径

Bellman-Ford 算法

该算法处理单源最短路径,早于下面的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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值