数据结构与算法——图

1. 图的定义

  • 图(Graph): 由一组顶点(Vertex)和连接这些顶点的边(Edge)组成。图可以是有向的(Directed Graph)或无向的(Undirected Graph)。
  • 有向图: 边有方向,从一个顶点指向另一个顶点。
  • 无向图: 边没有方向,顶点间的连接是双向的。

2. 图的表示

  (1)邻接矩阵(Adjacency Matrix)

  • 定义: 邻接矩阵是一个二维数组,其中的元素表示图中顶点间是否存在边。对于无向图,矩阵是对称的。

  • 空间复杂度: 对于有V个顶点的图,邻接矩阵总是需要O(V^2)的空间,不论图中有多少边。

  • 优点:

    • 简单、直观。

    • 检查两个顶点之间是否存在边的操作非常快(O(1)时间复杂度)。

    • 更适合表示密集图(边的数量接近V^2)。

    • 有一些比较好用的性质,比如n次幂矩阵https://blog.csdn.net/bairui6666/article/details/134595589

  • 缺点:

    • 对于稀疏图(边相对较少),空间效率低。

    • 添加或删除边的操作需要O(1)时间,但初始化很慢(需要初始化V^2个元素)。

    • 遍历一个顶点的所有邻接点需要O(V)时间,即使该顶点的度(邻接点数量)很小。

//在给出各条边的关系的情况下初始化一个邻接矩阵
graph = [[0, 1], [0, 2], [1, 2], [1, 2]]
//邻接矩阵的初始化
        vector>adjMatrix(n,vector(n,0));
        for(const auto&edge:graph){
            adjMatrix[edge[0]][edge[1]]=1;//有向图中表示两个顶点之间有通路
            //adjMatrix[edge[1]][edge[0]]=1;无向图中两个顶点之间有通路
        }

(2)邻接表(Adjacency List)

  1. 定义: 邻接表是一个数组的列表,数组中的每个元素是一个列表,表示与该顶点相邻的所有顶点。

  2. 空间复杂度: 对于有V个顶点和E条边的图,邻接表需要O(V + E)的空间。

  3. 优点:

    • 空间效率更高,特别是对于稀疏图。
    • 遍历一个顶点的所有邻接点的时间与该顶点的度成正比。
    • 较容易实现图的动态增减操作。
  4. 缺点:

    • 检查两个顶点之间是否存在边的操作较慢(最坏情况下为O(V))。
    • 实现稍微复杂,需要额外的数据结构(如链表、动态数组)。
//给出边的关系
graph = [[0, 1], [0, 2], [1, 2], [1, 2]]

//邻接表的初始化
         vector>adjList(n);
        for (const auto& edge : graph) {
            adjList[edge[0]].push_back(edge[1]);
        }

 

3. 图的类型

  • 简单图: 不含有自环(顶点到自身的边)和平行边(两个顶点之间的多条边)。
  • 多重图: 可以有平行边。
  • 加权图: 边赋有权值。

4. 特殊类型的图

  • 树(Tree): 无环的连通图。
  • 有向无环图(DAG, Directed Acyclic Graph): 有向图,没有方向性的循环。
  • 完全图: 每一对不同的顶点间都恰好有一条边。

5. 图的遍历

  • 深度优先搜索(DFS, Depth-First Search): 沿着树的深度遍历树的节点。
void DFSUtil(int v, vector& visited, const vector>& adj) {
    visited[v] = true;
    // 访问当前节点v
    for (int u : adj[v]) {
        if (!visited[u]) {
            DFSUtil(u, visited, adj);
        }
    }
}

void DFS(int V, const vector>& adj, int start) {
    vector visited(V, false);
    DFSUtil(start, visited, adj);
}
  • 广度优先搜索(BFS, Breadth-First Search): 沿着树的宽度遍历树的节点。
void BFS(int V, const vector>& adj, int start) {
    vector visited(V, false);
    queue queue;

    visited[start] = true;
    queue.push(start);

    while (!queue.empty()) {
        int v = queue.front();
        queue.pop();
        // 访问节点v
        for (int u : adj[v]) {
            if (!visited[u]) {
                visited[u] = true;
                queue.push(u);
            }
        }
    }
}

 一道很好的模板题

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

class Solution {
public:
    void dfs(vector&vis,vector>&graph,int curr){
        vis[curr]=true;
        for(int now:graph[curr]){
            if(!vis[now]){
                dfs(vis,graph,now);
            }
        }
    }
    bool bfs(vector>&graph,int target,int start){
        queuetovis;
        vectorvis(graph.size(),false);
        vis[start]=true;
        tovis.push(start);
        while(!tovis.empty()){
            int to=tovis.front();
            tovis.pop();
            vis[to]=true;
            for(int next:graph[to]){
                if(!vis[next]){
                    tovis.push(next);
                }
            }
        }
        return  vis[target];
    }
    bool findWhetherExistsPath(int n, vector>& graph, int start, int target) {
        vectorvis(n,false);
       
        //邻接表的初始化
         vector>adjList(n);
        for (const auto& edge : graph) {
            adjList[edge[0]].push_back(edge[1]);
        }
        //邻接矩阵的初始化
        vector>adjMatrix(n,vector(n,0));
        for(const auto&edge:graph){
            adjMatrix[edge[0]][edge[1]]=1;//有向图中表示两个顶点之间有通路
            //adjMatrix[edge[1]][edge[0]]=1;无向图中两个顶点之间有通路
        }
        //dfs(vis,adjList,0); return vis[target];
        return bfs(adjList,target,start);
    }
};

6. 图的算法 

  • 最短路径: 如Dijkstra算法,Bellman-Ford算法。
  • 最小生成树: 如Kruskal算法,Prim算法。
  • 网络流: 如Ford-Fulkerson算法。

7. 图的应用

  • 社交网络: 分析社交网络中的人际关系。
  • 网络路由: 寻找数据包在网络中的最优路径。
  • 资源分配: 如在项目计划中分配资源。

你可能感兴趣的:(数据结构与算法,数据结构,c++,图论)