超越线性,探寻复杂性:深入理解图结构

上期填坑

leetcode, 888. 公平的糖果交换.

爱丽丝和鲍勃拥有不同总数量的糖果。给你两个数组 aliceSizes 和 bobSizes ,aliceSizes[i] 是爱丽丝拥有的第 i 盒糖果中的糖果数量,bobSizes[j] 是鲍勃拥有的第 j 盒糖果中的糖果数量。
两人想要互相交换一盒糖果,这样在交换之后,他们就可以拥有相同总数量的糖果。一个人拥有的糖果总数量是他们每盒糖果数量的总和。
返回一个整数数组 answer,其中 answer[0] 是爱丽丝必须交换的糖果盒中的糖果的数目,answer[1] 是鲍勃必须交换的糖果盒中的糖果的数目。如果存在多个答案,你可以返回其中 任何一个 。题目测试用例保证存在与输入对应的答案。

public int[] fairCandySwap(int[] aliceSizes, int[] bobSizes) {
  int sumA = Arrays.stream(aliceSizes).sum();
  int sumB = Arrays.stream(bobSizes).sum();
  int delta = (sumA - sumB) / 2;
  Set<Integer> rec = new HashSet<Integer>();
  for (int num : aliceSizes) { //将alice的糖果数量存入hashSet便于快速查找 
      rec.add(num);
  }
  int[] ans = new int[2];
  for (int y : bobSizes) {// sumA - x + y  = sumB - y + x
    for (int y : bobSizes) {// sumA - x + y  = sumB - y + x
      int x = y + delta;    // x = (sumA - sumB)/2 + y
      if (rec.contains(x)) {// x = delta + y
        ans[0] = x;
        ans[1] = y;
        break;
    }
  }
  return ans;
}
图.jpg

图的概念

也许你听说过六度分隔理论,即你和任何一个陌生人之间所间隔的人不会超过六个,也就是说,最多通过六个人你就能够认识任何一个陌生人。
通过六度分隔理论,可以知道我们生活在一个巨大的网络且复杂的网络中,我们只是这个网络中的一个节点。
这就引出了,今天我们要学的新的数据结构——图。
图由顶点组成,顶点用有穷非空集合V(G)={v1,v2,...,vn}表示,顶点之间的边用集合E(G)={(u,v)|u∈V,v∈V}表示,图可以表示为:G=(V,E)。其中G表示图,V表示顶点,E表示边。|V|表示顶点的个数,也称图的阶,|E|表示边的条数。这就是一张图的定义,如下图所示:

图.png

从上图中可以看出,图相比之前所学的数据结构在逻辑结构上更为复杂:线形-->树形-->图形,如下图,复杂的逻辑结构为更为复杂的场景应用带来解决之道。

线数图.png

图的分类

  1. 无向图与有向图:顾名思义,根据图中边是否有方向来划分。
  2. 带权图:权即权重,在实际应用中,一般会给边附加上权重属性,表示两个顶点间关系的程度、大小等。
  3. 完全图:在无向图中任意两个顶点之间都有一条边相连,在有向图总任意两个顶点之间都有两条相反的边相连。
  4. 稀疏图与稠密图:相对于图的边的稀疏与稠密而言,没有绝对的界限。一般越接近完全图,越稠密。
    图比较.png

图的术语

路径:指顶点v到顶点u所经过的顶点序列。
路径长度:顶点序列中的顶点个数。
:起点和终点相同的路径,也叫回路。

连通图:无向图中,如果图中任意两个节点之间都存在路径,则图是连通的。
连通分量:无向图中的极大连通子图称为连通分量。

连通图与连通分量.png

强连通图:有向图,如果图中任意两个节点之间都存在路径,则图是强连通的。
强连通分量:有向图中的极大连通子图称为强连通分量。 这里需要注意的是:连通图/强连通图的连通分量/强连通分量,是其本身,且唯一。

强连通图与强连通分量.png

入度出度:有向图中节点的入度表示指向该节点的边的数量,出度表示从该节点出发的边的数量。
子图:若存在图G1能够在图G中找到,即G1是G的子集,那么G1是G的子图。

图的实现

图的实现通常使用邻接矩阵邻接表来表示,一般情况下,稠密图多采用邻接矩阵存储,稀疏图多采用邻接表存储

邻接矩阵:

利用二维矩阵表示图中各顶点之间的关系,对于有n个顶点的图来说,用n阶方阵来表示该图,矩阵中的元素的值为1或者权重值,表示对应的顶点相连,值为0或者无穷,这表示不相连。如图:

邻接矩阵.png

邻接矩阵的Java代码示例:

public class Graph {
    private int V; // 节点数量
    private int[][] adjMatrix; // 邻接矩阵
    public Graph(int v) {
        V = v;
        adjMatrix = new int[V][V];
    }
    public void addEdge(int i, int j) {
        adjMatrix[i][j] = 1;
        adjMatrix[j][i] = 1; // 无向图需将对称位置也设为 1
    }
    // 其他方法:如遍历、查找、删除边等
}

邻接表

邻接表是一种链表数组,数组中的每个元素是一个链表,用于存储与对应节点相连的其他节点。

邻接表.png

邻接表的Java代码示例:

public class Graph {
    private int V; // 节点数量
    private LinkedList<Integer>[] adjList; // 邻接表
    public Graph(int v) {
        V = v;
        adjList = new LinkedList[V];
        for (int i = 0; i < V; ++i) {
            adjList[i] = new LinkedList<>();
        }
    }
    public void addEdge(int i, int j) {
        adjList[i].add(j);
        adjList[j].add(i); // 无向图需在两个节点的链表中都添加对方
    }
    // 其他方法:如遍历、查找、删除边等
}

图的搜索

图的搜索是指在图中查找特定节点或路径的过程。常见的图搜索算法包括深度优先搜索(DFS)和广度优先搜索(BFS)。

BFSDFS.png

深度优先搜索(DFS)

深度优先搜索是一种通过沿着图的深度方向遍历节点的搜索算法。它从图的某一起始节点开始,沿着一条路径尽可能深地访问节点,直到该路径上的所有节点都被访问过,然后回溯到前一节点继续搜索。
使用递归来实现深度优先搜索。
从起始节点开始,递归地访问其邻居节点,直到没有未访问的邻居节点为止。 代码示例:

// 无向图的表示
class Graph {
    private int V; // 顶点数
    private LinkedList<Integer> adj[]; // 邻接表
    // 构造函数
    Graph(int v) {
        V = v;
        adj = new LinkedList[v];
        for (int i=0; i<v; ++i)
            adj[i] = new LinkedList();
    }
    // 添加边到图中
    void addEdge(int v, int w) {
        adj[v].add(w);
    }

    // 从顶点v开始DFS遍历
    void DFSUtil(int v, boolean visited[]) {
        // 当前顶点标记为已访问
        visited[v] = true;
        System.out.print(v + " ");

        // 遍历邻接节点
        Iterator<Integer> it = adj[v].listIterator();
        while (it.hasNext()) {
            int n = it.next();
            // 如果邻接节点未被访问,则递归调用DFSUtil
            if (!visited[n])
                DFSUtil(n, visited);
        }
    }

    // 以顶点v为起点进行DFS
    void DFS(int v) {
        // 标记所有顶点为未访问
        boolean visited[] = new boolean[V];

        // 调用DFSUtil开始遍历
        DFSUtil(v, visited);
    }

    // 对整个图进行DFS
    void DFS() {
        // 标记所有顶点为未访问
        boolean visited[] = new boolean[V];

        // 对每个未访问的顶点调用DFSUtil
        for (int i=0; i<V; ++i) {
            if (!visited[i])
                DFSUtil(i, visited);
        }
    }

    public static void main(String args[]) {
        Graph g = new Graph(4);

        g.addEdge(0, 1);
        g.addEdge(0, 2);
        g.addEdge(1, 2);
        g.addEdge(2, 0);
        g.addEdge(2, 3);
        g.addEdge(3, 3);

        System.out.println("从顶点2开始的DFS:");
        g.DFS(2);

        System.out.println("\n整个图的DFS:");
        g.DFS();
    }
}

广度优先搜索(BFS)

广度优先搜索是一种通过按照图的广度方向遍历节点的搜索算法。
它从图的某一起始节点开始,首先访问其所有的邻居节点,然后逐层地向下访问其他邻居节点,直到找到目标节点或所有节点都被访问过。
使用队列来实现广度优先搜索。
从起始节点开始,依次将其邻居节点加入队列,并标记为已访问,然后继续处理队列中的下一个节点。 代码示例:

// 无向图的表示
class Graph {
    private int V; // 顶点数
    private LinkedList<Integer> adj[]; // 邻接表

    // 构造函数
    Graph(int v) {
        V = v;
        adj = new LinkedList[v];
        for (int i=0; i<v; ++i)
            adj[i] = new LinkedList();
    }

    // 添加边到图中
    void addEdge(int v, int w) {
        adj[v].add(w);
    }

    // 以顶点v开始BFS
    void BFS(int v) {
        // 创建一个标记数组来跟踪访问状态
        boolean visited[] = new boolean[V];

        // 创建一个队列用于BFS
        LinkedList<Integer> queue = new LinkedList<>();

        // 标记当前顶点为已访问,并将其加入队列
        visited[v] = true;
        queue.add(v);

        // 迭代BFS过程直到队列为空
        while (queue.size() != 0) {
            // 出队并打印
            v = queue.poll();
            System.out.print(v + " ");

            // 获取所有未访问的邻接节点,并标记它们为已访问,并将其加入队列
            Iterator<Integer> it = adj[v].listIterator();
            while (it.hasNext()) {
                int n = it.next();
                if (!visited[n]) {
                    visited[n] = true;
                    queue.add(n);
                }
            }
        }
    }

    public static void main(String args[]) {
        Graph g = new Graph(4);

        g.addEdge(0, 1);
        g.addEdge(0, 2);
        g.addEdge(1, 2);
        g.addEdge(2, 0);
        g.addEdge(2, 3);
        g.addEdge(3, 3);

        System.out.println("从顶点2开始的BFS:");
        g.BFS(2);
    }
}

图的应用

我们生活中各种场景应用都可以抽象成一种图,因此图,这个强大的数据结构,在实际中有着广泛的用途:

1.社交网络:社交网络中的好友关系可以用图来表示,节点表示用户,边表示好友关系。

  1. 地图导航:地图导航系统中的路网可以表示为图,节点表示交叉路口,边表示道路。
  2. 网络路由:网络路由中的路由器连接关系可以用图来表示,节点表示路由器,边表示连接。
    4.组织结构:组织结构可以用图来表示,节点表示员工或部门,边表示管理或从属关系。
  3. 棋类游戏:棋类游戏中的棋盘布局可以用图来表示,节点表示棋子的位置,边表示合法移动。

小试牛刀

leetcode, 1791. 找出星型图的中心节点 有一个无向的 星型 图,由 n 个编号从 1 到 n 的节点组成。星型图有一个 中心 节点,并且恰有 n - 1 条边将中心节点与其他每个节点连接起来。 给你一个二维整数数组 edges ,其中 edges[i] = [ui, vi] 表示在节点 ui 和 vi 之间存在一条边。请你找出并返回 edges 所表示星型图的中心节点

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容