• leetcode 200.Number of Islands 求岛屿数量,复习图论基础


    一、求岛屿数量

    前段时间面试某科技公司,在线做了两个笔试题,第一题是基础编程题,很简单,我也很快做出来了,暂且不提。第二题是什么求岛屿数量,当时我有反应过来这是考图的遍历算法,但是奈何好久没复习过这部分知识,一下子思路没起得来,最终没能在给定时间做出来。

    笔试结束后,我当即到百度上搜这道题,还真搜出来了,尼玛,居然是leetcode原题!!!

    Given an m*n 2D binary grid grid which represents a map of  '1's  (land) and '0's(water), return the number of islands. An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically. You may assume all four edges of the grid are all surrounded by water.

    比如

    输入:grid = [
      ["1","1","1","1","0"],
      ["1","1","0","1","0"],
      ["1","1","0","0","0"],
      ["0","0","0","0","0"]
    ]
    输出:1
    输入:grid = [
      ["1","1","0","0","0"],
      ["1","1","0","0","0"],
      ["0","0","1","0","0"],
      ["0","0","0","1","1"]
    ]
    输出:3

     这道题如果你之前接触过图论,或者之前看过这道题的解法,那思路将非常清晰,否则就算绞尽脑汁也很难想出来。题中要求从一个1出发,分别往上下左右四个方向推进,遇到0则暂停,以此得到的一整块1为一座岛屿。用图论里的术语来说,就是求以1构成的无向图的所有连通分量

    解法:

    class Solution {
    public:
         int numIslands(vector<vector<char>>& grid) {
            //行空 或 列空
            if(grid.empty() || grid[0].empty()){
                return 0;
            }
            int N = grid.size();//grid网格的列长
            int M = grid[0].size();//grid网格的行长
            int res = 0;
            //深度优先搜索
            for(int i = 0; i < N; ++i){
                for(int j = 0; j < M; ++j){
                    if(grid[i][j] == '1'){
                        ++res;//只计数作为起始感染点的1的个数,即为岛屿个数
                        infect(grid,i,j,N,M);
                    }
                }
            }
            return res;
        }
        
        void infect(vector<vector<char>>& grid,int i,int j,int N,int M){
            if(i<0 || i>=N || j<0 || j>= M || grid[i][j] != '1'){
                return;
            }
            grid[i][j] = '0';
            infect(grid,i+1,j,N,M);
            infect(grid,i-1,j,N,M);
            infect(grid,i,j-1,N,M);
            infect(grid,i,j+1,N,M);
        }
    
    };

    二、图的基本概念

    1. 与图有关的若干概念

    图是由顶点集合(vertex)及顶点间的关系集合组成的一种数据结构:Graph = (V, E)。图被分为有向图和无向图。在有向图中,顶点对<x, y>是有序的,称为从顶点x到y的一条有向边,所以这里<x, y>和<y, x>是两条不同的边。而在无向图中,顶点对(x, y)是无序的,是联接与顶点x和顶点y的一条边。这条边没有特定的方向,(x, y)和(y, x)是同一条边。注意无向边和有向边各自的记法。

    在讨论图时有两个限制:

    • 不考虑顶点有直接与自身相连的边(自环)
    • 在无向图中,任意两个顶点之间不能有多条边直接相连(多重图)

    完全图(complete graph)  在由n个顶点组成的无向图中,若有n(n-1)/2条边,则称之为无向完全图。在由n个顶点组成的有向图中,若有n(n-1)条边,则称之为有向完全图。完全图中,边数达到最大。

    权(weight)在某些图中,边具有与之相关的数值,称为权重。带权图也叫做网络。

    邻接顶点(adjacent vertex)如果(u, v)是 E(G) 中的一条边,则 u 与 v 互为邻接顶点,且边(u, v)依附于顶点 u 和 v,顶点 u 和 v 依附于边(u, v)。

    子图(subgraph)设图G=(V, E)和G'=(V', E')。若V'属于V且E'属于E,则称图G'是图G的子图。

    度(degree)与顶点v关联的边数,称作v的度,记作deg(v)。在有向图中,顶点的度等于其入度与出度之和。其中,顶点 v 的入度是以 v 为终点的有向边的条数,记作indeg(v);顶点v的出度是以v为始点的有向边的条数,记作outdeg(v)。顶点v的度deg(v) = outdeg(v) + indeg(v)。一般地,若图G中有n个顶点、e条边,则 e = 1/2 {v1+v2+...+vn}

    路径(path)  在图G(V, E)中,若从顶点v(i)出发,沿一些边经过若干顶点v(p1),v(p2),......,v(pm)到达顶点v(j),则称顶点序列(v(i),v(p1),...,v(pm),v(j))为从顶点v(i)到顶点v(j)的一条路径。它经过的边都来自于E的边。

    路径长度(path length)对于不带权的图,路径长度是指此路径上边的条数。对于带权图,路径长度是指路径上各条边上权值的和。

    简单路径与回路(cycle)若路径上各顶点v1,v2,...,vm均不互相重复,则称这样的路径为简单路径。若路径上第一个顶点v1与最后一个顶点vm重合,则称这样的路径为回路或环。

    连通图与连通分量(connected graph & connected component)在无向图中,若从顶点v1到顶点v2有路径,则称顶点v1与v2是连通的。如果图中任意一对顶点都是连通的,则称此图是连通图。非连通图的极大连通子图叫做连通分量。

    强连通图与强连通分量(strongly connected diagraph)  在有向图中,若在每一对顶点vi和vj之间都存在一条从vi到vj的路径,也存在一条从vj到vi的路径,则称此图是强连通图。非强连通图的极大强连通子图叫做强连通分量。

    2. 图的抽象结构

    class Graph {
    public:
        Graph();
    
        void insertVertex(const T& vertex);
    
        void insertEdge(int v1, int v2, int weight);
    
        void removeVertex(int v);
    
        void removeEdge(int v1, int v2);
    
        bool IsEmpty();
    
        T getWeight(int v1, int v2);
    
        int getFirstNeighbor(int v);
    
        int getNextNeighbor(int v1, int v2);
    };

    三、图的存储结构

    1. 图的邻接矩阵表示

    2. 图的邻接表表示

    3. 图的邻接多重表表示

    四、图的遍历

    1. 深度优先搜索

    类似树的先根遍历,是树的先根遍历的推广。看代码(此处图的存储方式为邻接矩阵(数组))

    /*
     * 深度优先遍历
     */
    void DFSTraverse(MGraph G, Status(Visit)(VertexType)) {
        int v;
        
        // 使用全局变量VisitFunc,使得DFS不必设置函数指针参数
        VisitFunc = Visit;
        
        // 访问标志数组初始化
        for(v = 0; v < G.vexnum; v++) {
            visited[v] = FALSE;
        }
        
        for(v = 0; v < G.vexnum; v++) {
            if(!visited[v]) {
                DFS(G, v);  // 对尚未访问的顶点调用DFS
            }
        }
    }
    
    /*
     * 深度优先遍历核心函数
     */
    static void DFS(MGraph G, int v) {
        int w;
        
        // 从第v个顶点出发递归地深度优先遍历图G
        visited[v] = TRUE;
        
        // 访问第v个顶点
        VisitFunc(G.vexs[v]);
        
        for(w = FirstAdjVex(G, G.vexs[v]);
            w >= 0;
            w = NextAdjVex(G, G.vexs[v], G.vexs[w])) {
            if(!visited[w]) {
                DFS(G, w);  // 对尚未访问的顶点调用DFS
            }
        }
    }

    2. 广度优先搜索

    /*
     *
     * 广度优先遍历(借助队列)
     */
    void BFSTraverse(MGraph G, Status(Visit)(VertexType)) {
        int v, w;
        LinkQueue Q;
        QElemType u;
        
        // 初始化为未访问
        for(v = 0; v < G.vexnum; v++) {
            visited[v] = FALSE;
        }
        
        // 置空辅助队列
        InitQueue(&Q);
        
        for(v = 0; v < G.vexnum; v++) {
            // 如果该顶点已访问过,则直接忽略
            if(visited[v]) {
                continue;
            }
            
            // 标记该顶点已访问
            visited[v] = TRUE;
            
            // 访问顶点
            Visit(G.vexs[v]);
            
            EnQueue(&Q, v);
            
            while(!QueueEmpty(Q)) {
                DeQueue(&Q, &u);
                
                // 先集中访问顶点v的邻接顶点,随后再访问邻接顶点的邻接顶点
                for(w = FirstAdjVex(G, G.vexs[u]);
                    w >= 0;
                    w = NextAdjVex(G, G.vexs[u], G.vexs[w])) {
                    if(!visited[w]) {
                        visited[w] = TRUE;
                        Visit(G.vexs[w]);
                        EnQueue(&Q, w);
                    }
                }
            }
        }
    }

    五、最小生成树

    1. Kruskal算法

    2. Prim算法

    六、最短路径

    1. 从某个源点到其余各顶点的最短路径

    Dijkstra算法

    2. 每一对顶点之间的最短路径

    每次以一个顶点为源点,重复执行Dijkstra算法n次。时间复杂度O(n^3)。

    或者,Floyd算法。

  • 相关阅读:
    Mysql高手系列
    Mysql高手系列
    Mysql高手系列
    Mysql高手系列
    Mysql高手系列
    Mysql高手系列
    Mysql高手系列
    Mysql高手系列
    Mysql高手系列
    Mysql高手系列
  • 原文地址:https://www.cnblogs.com/jdbc2nju/p/16094874.html
Copyright © 2020-2023  润新知