图论算法以及迪杰斯特拉算法(最短路径问题)C++

图的应用背景:

1、网络爬虫
2、地图应用:最近距离推荐,最短时间推荐
3、社交网络分析:好友推荐、垃圾用户分析、社交关系分析
4、推荐、精准营销
5、舆情控制、信息传播
6、防欺诈(网络欺诈和电信欺诈)
7、计算生物学:模拟分子运动

图的分类

1、有向图
在这里插入图片描述

2、无向图
在这里插入图片描述

3、权重图
在这里插入图片描述
在这里插入图片描述

图的基本概念

顶点集合(vex-set):如上图S(vex) = {‘A’,‘B’,‘D’,‘E’,‘F’}
边集合(arc-set):如上图S(arc) = {<‘A’,‘B’>,<‘A’,‘C’>,<‘A’,‘D’>,<‘B’,‘F’>,<‘B’,‘C’>,<‘C’,‘E’>,<‘D’,‘E’>,<‘E’,‘F’>}
度(degree):无向图中从一个点延伸出去的边数就是该点的度;有向图中包含出度和入度
出度(out-degree):有多少条边指向某点就是该点的出度;
入度(in-degree):从某点出发向外指向延伸的边数就是该点的入度;

图的存储

存储的关键是带点集合和边集合

邻接矩阵

顶点信息存储在一维数组中,边信息存储在二维数组中
在这里插入图片描述
优点:很容易算出边邻接关系;以及节点的度(不管出度还是入度)
缺点:边集合存储空间复杂度比较大,图中大量0,空间利用率不高(尤其是在点多边少的情况下);对于无向图,邻接矩阵是对承德,可以只存下半部分。
在这里插入图片描述

连接表

顶点集合依然存储在一维数组当中,边集合存储在连接表中。
有向图
在这里插入图片描述
无向图
在这里插入图片描述
权重无向图
在这里插入图片描述
优点:很容易算出邻接关系;以及节点的出度
缺点:很难算出入度,需要遍历整张表;可以建立一张逆连接表(相当于记录入度的表)

图的遍历

从图中某一个顶点出发,访问图中其余顶点,使每个顶点被访问一次且只被访问一次
可以从图中任意一个顶点出发进行遍历
需要解决的问题
1、确定一条搜索路径
2、确保每个顶点被访问到
3、确保每个顶点只能被访问一次
设置辅助数组visited,数组元素的初始值均为false,一旦遍历过就置为true

深度优先搜索

在这里插入图片描述
应用:
检测连通分量(独立的图,互相之间没有边或者顶点连接)的个数
两个点是否在一个连通分量中
检测是否构成环;从一个点出发能否回到出发点

广度优先遍历

在这里插入图片描述
关键数据结构:队列
应用:游戏中找寻路径问题

迪杰斯特拉算法(dijkstra)

该算法主要解决最短路径问题,采用的是贪心思想
对象:权重图
核心思想:每次从路径最短的点出发遍历相临边,检测修改路径值(确保相邻点也是最短的),从未被确认路径最短的顶点集合中选择最短路径的点,将该点加入确认路径最短的顶点集合,并将该点作为下次遍历相临边的出发点。
代码实现:


#define INF 0x7F7F7F7F

// 16  前8位存储权重信息 | 后8位存储索引
#define WEIGHT(i) ((i&0xff00)>>8)
#define INDEX(i) (i&0xff)
#define MAKE(w,i) ((((w)&0xff)<<8) | i)

typedef vector<list<int>> GraphArc;
typedef string VexType;
typedef struct Vex {
    int idx;        // 顶点索引
    VexType data;   // 顶点信息
} GVex;

typedef pair<VexType, VexType> GArc;

typedef bool (*VisitFunc)(GVex &vex);

typedef vector< GVex > GraphVex;

// 应该提供哪些接口? 有向图 无向图 权重图 
// 构造图
// 插入边 删除边  顶点之间关
// 插入点 删除点  顶点相关的边信息也要删掉
// 修改权重信息
// 获取节点信息
// 获取最短路径
// 遍历
// 获取边信息
// 有向图 度 信息
class CGraph {
public:
    CGraph(bool direct = true) {}
    void CreateGraph(vector<VexType> &vex, vector< pair<VexType, VexType> > &arc,
        const vector<int> &weights = vector<int>())
    {
        int i = 0;
        for (auto iter = vex.begin(); iter != vex.end(); iter++,i++) {
            _graph_vex.push_back({i, *iter});
            _vex_idx.insert(make_pair(*iter, i));
        }
        for (int j = 0; j < i; j++) {
            _graph_arc.push_back(list<int>());
        }
        bool has = weights.size() > 0;
        for (size_t i = 0; i < arc.size(); i++) {
            InsertArc(arc[i], has ? weights[i] : 0);
        }
    }
    inline size_t VexSize() {return _graph_vex.size();}
    // 根据顶点信息查找位置
    int LocateVex(VexType &v);
    // 根据位置查找顶点信息
    VexType GetVex(int idx); 
    /*
        用于遍历某顶点的邻接点,
        此时用 vector<list<int>> 存储的邻接信息,
        所以不需要实现该接口
        VexBegin();
        VexNext();
    **/
    // 修改idx的顶点的值
    bool PutVex(int idx, int value);

    // 往图中添加顶点 
    bool InsertVex(VexType &v) {
        if (-1 != _get_idx(v))
            return false;
        int i = (int)_graph_vex.size();
        _graph_vex.push_back({i, v});
        _vex_idx.insert(make_pair(v, i));
        return true;
    }

    // 往图中删除顶点 除了删除顶点还需要删除相关的边信息
    bool DeleteVex(VexType &v) {
        int idx = _get_idx(v);
        if (-1 == idx)
            return false;
        auto &vexlist = _graph_arc[idx];
        for (auto iter = vexlist.begin(); iter != vexlist.end(); iter++) {
            _graph_arc[*iter].remove(idx);
        }
        _graph_arc[idx].clear();
        _graph_vex[idx].data.clear();
        _vex_idx.erase(v);
        return true;
    }

    // 往图中添加一条边 <'A', 'B'>
    bool InsertArc(const GArc &arc, int weight = 0) {
        int iFirst = _get_idx(arc.first);
        if (-1 == iFirst)
            return false;
        int iSecond = _get_idx(arc.second);
        if (-1 == iSecond)
            return false;
        int wFirst = MAKE(weight, iFirst);
        int wSecond = MAKE(weight, iSecond);
        auto &vexlist = _graph_arc[iFirst];
        if (find(vexlist.begin(), vexlist.end(), wSecond) != vexlist.end())
            return false;
        vexlist.push_back(wSecond);
        if (!_direct) { // 无向图 <'B', 'A'>
            auto &revlist = _graph_arc[iSecond];
            if (find(revlist.begin(), revlist.end(), wFirst) != revlist.end())
                return false;    
            revlist.push_back(wFirst);
        }
        return true;
    }

    // 往图中删除一条边
    bool DeleteArc(GArc &arc) {
        int iFirst = _get_idx(arc.first);
        if (-1 == iFirst)
            return false;
        int iSecond = _get_idx(arc.second);
        if (-1 == iSecond)
            return false;

        auto &vexlist = _graph_arc[iFirst];
        
        bool has = false;
        auto iter = vexlist.begin();
        for(; iter != vexlist.end(); iter++) {
            if (INDEX(*iter) == iSecond) {
                has = true;
                break;
            }
        }
        if (!has)
            return false;
        vexlist.erase(iter);
        if (!_direct) {
            has = false;
            auto &revlist = _graph_arc[iSecond];
            iter = revlist.begin();
            for(; iter != revlist.end(); iter++) {
                if (INDEX(*iter) == iFirst) {
                    has = true;
                    break;
                }
            }
            if (!has)
                return false;    
            revlist.erase(iter);
        }
        return true;
    }
// A B C D E F
// 0 1 2 3 4 5
    // 对图进行深度优先遍历 (不能遍历删除)
    void DFSTraverse(vector<bool> &visited, VisitFunc func) {
        for (size_t i = 0; i < _graph_vex.size(); i++) { // 有多个
            if (!visited[i]) {
                _dfs_traverse(i, visited, func);
            }
        }
    }

    // 对图进行广度优先遍历 (不能遍历删除)
    void BFSTraverse(vector<bool> &visited, VisitFunc func) {
        for (size_t i = 0; i < _graph_vex.size(); i++) { // 可能出现多个连通图
            if (!visited[i]) {
                _bfs_traverse(i, visited, func);
            }
        }
    }

    //迪杰斯特拉算法
	//param:
	//@s: 顶点数据
	//@visited:根据索引记录某一顶点是否确认最短路径
	//@dis:根据索引记录某一顶点当前最短路径
	//@parent:根据索引记录某一顶点的上一顶点,返回后可以用它来判断最短路径的节点是如何组成的
    void Dijkstra(VexType s, vector<bool> &visited, vector<int> &dis, vector<int> &parent) {
        int iStart = _get_idx(s);
        if (-1 == iStart) {
            cout << "start not exist" << endl;
            return;
        }
        visited[iStart] = true;
        dis[iStart] = 0;
        parent[iStart] = iStart;

        int i = iStart;
        while (true) {
            // 更新  相邻节点的距离信息
            auto &arclist = _graph_arc[i];
            int min = INF, midx = -1;
            for (auto iter = arclist.begin(); iter != arclist.end(); iter++) {
                int idx = INDEX(*iter);
                int wgt = WEIGHT(*iter);
                if (dis[idx] > wgt+dis[i]) {
                    dis[idx] = wgt+dis[i];
                    parent[idx] = i;
                }
            }
            // 扫描 
            for (size_t j = 0; j < dis.size(); j++) {
                if (!visited[j] && min > dis[j]) {
                    min = dis[j];
                    midx = j;
                }
            }
            if (midx == -1)
                break;
            // 重新选取点
            visited[midx] = true;
            i = midx;
        }
    }

private: // 回溯 函数堆栈来实现 实际开发过程 图纵深  尾递归
// 所有的递归 都能够转化为 递推
    void _dfs_traverse(int idx, vector<bool> &visited, VisitFunc func) {
        if (visited[idx]) return;
        visited[idx] = true;
        func(_graph_vex[idx]);
        auto &arclist = _graph_arc[idx];
        for (auto iter = arclist.begin(); iter != arclist.end(); iter++) {
            int next = INDEX(*iter);
            if (!visited[next]) {
                _dfs_traverse(next, visited, func);
            }
        }
    }

    void _bfs_traverse(int idx, vector<bool> &visited, VisitFunc func) {
        deque<int> queue;
        queue.push_back(idx);
        visited[idx] = true;
        func(_graph_vex[idx]);
        while (0 != queue.size()) {
            int i = queue.front(); queue.pop_front();
            auto &arclist = _graph_arc[idx];
            for (auto iter = arclist.begin(); iter != arclist.end(); iter++) {
                int next = INDEX(*iter);
                if (!visited[next]) {
                    queue.push_back(next);
                    visited[next] = true;
                    func(_graph_vex[next]);
                }
            }
        }
    }

    int _get_idx(const VexType &v) {
        auto iter = _vex_idx.find(v);
        if (iter == _vex_idx.end()) return -1;
        return iter->second;
    }

private:
    GraphArc    _graph_arc; // 存储边信息
    GraphVex    _graph_vex; // 存储顶点信息

    map<VexType, int> _vex_idx; // 数据对应 idx
    bool        _direct;    // 是否为有向图
};

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
斯特算法是一种用于求解最路径的经典算法。它可以计算一个节点到其他所有节点的最路径。下面是一个使用C++实现的斯特算法求最路径的示例[^1]: ```cpp #include <iostream> #include <limits.h> #define V 9 int minDistance(int dist[], bool sptSet[]) { int min = INT_MAX, min_index; for (int v = 0; v < V; v++) { if (sptSet[v] == false && dist[v] <= min) { min = dist[v]; min_index = v; } } return min_index; } void printSolution(int dist[]) { std::cout << "Vertex \t\t Distance from Source" << std::endl; for (int i = 0; i < V; i++) { std::cout << i << " \t\t " << dist[i] << std::endl; } } void dijkstra(int graph[V][V], int src) { int dist[V]; bool sptSet[V]; for (int i = 0; i < V; i++) { dist[i] = INT_MAX; sptSet[i] = false; } dist[src] = 0; for (int count = 0; count < V - 1; count++) { int u = minDistance(dist, sptSet); sptSet[u] = true; for (int v = 0; v < V; v++) { if (!sptSet[v] && graph[u][v] && dist[u] != INT_MAX && dist[u] + graph[u][v] < dist[v]) { dist[v] = dist[u] + graph[u][v]; } } } printSolution(dist); } int main() { int graph[V][V] = {{0, 4, 0, 0, 0, 0, 0, 8, 0}, {4, 0, 8, 0, 0, 0, 0, 11, 0}, {0, 8, 0, 7, 0, 4, 0, 0, 2}, {0, 0, 7, 0, 9, 14, 0, 0, 0}, {0, 0, 0, 9, 0, 10, 0, 0, 0}, {0, 0, 4, 14, 10, 0, 2, 0, 0}, {0, 0, 0, 0, 0, 2, 0, 1, 6}, {8, 11, 0, 0, 0, 0, 1, 0, 7}, {0, 0, 2, 0, 0, 0, 6, 7, 0}}; dijkstra(graph, 0); return 0; } ``` 这段使用邻接矩阵表示图,其中`V`表示节点的数量。通过调用`dijkstra`函数,可以计算从源节点到其他所有节点的最路径,并将结果打印出来。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值