读书笔记《算法》第四版——4.1无向图

1、无向图的API

public class Graph
Graph(int V) 创建一个含有V个顶点但不含有边的无向图
Graph(In in) 从标准输入流in读入一幅图
int V() 顶点数
int E() 边数
void addEdge(int v, int w) 向图中添加一条边v-w
Iterable<Integer> adj(int v) 和v相邻的所有顶点
String toString() 对象的字符串表示

2、Graph的数据结构

/**  
* 类说明   
*  无向图数据类型
* @author Zoye  
* @date 2018年7月5日  新建  
*/
public class Graph {
    private final int V; // 顶点数目
    private int E; // 边的数目
    private Bag<Integer>[] adj;
    public Graph(int V) {
        this.V  = V; this.E = 0;
        adj = (Bag<Integer>[]) new Bag[V];
        for(int v = 0; v < V; v++) 
            adj[v] = new Bag<Integer>();
    }
    public Graph(In in) {
        this(in.readInt());
        this.E = in.readInt();
        for(int i = 0; i < E; i++) {
            int v = in.readInt();
            int w = in.readInt();
            addEdge(v, w);
        }
    }
    public int V() { return V; }
    public int E() { return E; }
    public void addEdge(int v, int w) {
        adj[v].add(w);
        adj[w].add(v);
        E++;
    }
    public Iterable<Integer> adj(int v) {
        return adj[v];
    }
}

3、深度优先搜索

在访问一个顶点时:

  • 将它标记为已访问;
  • 递归地访问它的所有没有被标记过的邻居顶点。

命题A。深度优先搜索标记与起点连通的所有顶点所需的时间和顶点的度数之和成正比。

4、寻找路径

4.1 路径的API

public class Paths
Paths(Graph G, int s) 在G中找出所有起点为s的路径
boolean hasPathTo(int v) 是否存在从s到v的路径
Iterable<Integer> pathTo(int v) s到v的路径,如果不存在则返回null

4.2 实现

算法4.1 使用深度优先搜索查找图中的路径

/**  
* 类说明   
*  使用深度优先搜索的应用:查找从s点出发的所有路径
* @author Zoye  
* @date 2018年7月5日  新建  
*/
public class DepthFirstPaths {
    private boolean[] marked;
    private int[] edgeTo;
    private final int s;
    public DepthFirstPaths(Graph G, int s) {
        marked = new boolean[G.V()];
        edgeTo = new int[G.V()];
        this.s = s;
        dfs(G, s);
    }
    private void dfs(Graph G, int v) {
        marked[v] = true;
        for(int w : G.adj(v))
            if(!marked[w]) {
                edgeTo[w] = v;
                dfs(G, w);
            }
    }
    public boolean hasPathTo(int w) {
        return marked[w];
    }
    public Iterable<Integer> pathTo(int v) {
        if(!hasPathTo(v)) return null;
        Stack<Integer> path = new Stack<Integer>();
        for(int x = v; x != s; x = edgeTo[x])
            path.push(x);
        path.push(s);
        return path;
    }
}

5、广度优先搜索

5.1 实现

它使用了一个队列来保存所有已经被标记过但邻接表还未被检查过得顶点。先将起点加入队列,然后重复以下步骤直到队列为空:

  • 取队列中的下一个顶点v并标记它;
  • 将与v相邻的所有未被标记过的顶点加入队列。

算法4.2 使用广度优先搜索查找图中的路径

/**  
* 类说明   
*  使用宽度优先搜索查找最短路劲
* @author Zoye  
* @date 2018年7月5日  新建  
*/
public class BreadthFirstPath {
    private boolean[] marked;
    private int[] edgeTo;
    private final int s;
    public BreadthFirstPath(Graph G, int s) {
        marked = new boolean[G.V()];
        edgeTo = new int[G.V()];
        this.s = s;
        bfs(G, s);
    }
    private void bfs(Graph G, int s) {
        marked[s] = true;
        Queue<Integer> queue = new Queue<Integer>();
        queue.enqueue(s);
        while(!queue.isEmpty()) {
            int v = queue.dequeue();
            for(int w : G.adj(v)) {
                if(!marked[w]) {
                    edgeTo[w] = v;
                    marked[w] = true;
                    queue.enqueue(w);
                }
            }
        }
    }
    public boolean hasPathTo(int v) {
        return marked[v];
    }
    public Iterable<Integer> pathTo(int v) {
        if(!hasPathTo(v)) return null;
        Stack<Integer> path = new Stack<Integer>();
        for(int x = v; x != s; x = edgeTo[x])
            path.push(x);
        path.push(s);
        return path;
    }
}

命题B。对于s可达的任意顶点v,广度优先搜索都能找到一条从s到v的最短路径(没有其他从s到v的路径所含的边比这条路径更少)。
命题B(续)。广度优先搜索所需的时间在最坏情况下和V+E成正比。

6、连通分量

深度优先搜索的直接应用:找出一幅图的连通分量。

连通分量的API

public class CC
CC(Graph G) 预处理构造函数
boolean connected(int v, int w) v和w连通吗
int count() 连通分量数
int id(int v) v所在的连通分量的标识符(0——count()-1)

6.1 实现

使用marked[]数组来寻找一个顶点作为每个连通分量中深度优先搜索的起点。
使用id[]数组将同一个连通分量中的顶点和连通分量的标识符关联起来(int值)。

算法4.3 使用深度优先搜索找出图中的所有连通分量

import edu.princeton.cs.algs4.Bag;
import edu.princeton.cs.algs4.In;
import edu.princeton.cs.introcs.StdOut;

/**
 * 类说明
 *  使用深度优先搜索的应用:查找连通分量
 * @author Zoye
 * @date 2018年7月5日  新建
 */
public class CC {
    private boolean[] marked;
    private int[] id; // v所在连通分量的标识符
    private int count; // 连通分量数
    public CC(Graph G) {
        marked = new boolean[G.V()];
        id = new int[G.V()];
        for(int s = 0; s < G.V(); s++) {
            if(!marked[s]) {
                dfs(G, s); // 使用深度优先搜索查找该顶点的路径,并将其标记
                count++; // 更新标记
            }
        }
    }
    private void dfs(Graph G, int v) {
        marked[v] = true;
        id[v] = count;
        for(int w : G.adj(v))
            if(!marked[w])
                dfs(G, w);
    }
    public boolean connected(int v, int w) {
        return id[v] == id[w];
    }
    public int id(int v) {
        return id[v];
    }
    public int count() {
        return count;
    }

    public static void main(String[] args) {
        Graph G = new Graph(new In(args[0]));
        CC cc = new CC(G);

        int M = cc.count();
        StdOut.println(M + " components");

        Bag<Integer>[] components;
        components = (Bag<Integer>[]) new Bag[M];
        for (int i = 0; i < M; i++) {
            components[i] = new Bag<Integer>();
        }
        for (int v = 0; v < G.V(); v++) {
            components[cc.id(v)].add(v);
        }
        for (int i = 0; i < M; i++) {
            for (int v : components[i]) {
                StdOut.print(v + " ");
            }
            StdOut.println();
        }
    }
}

命题C。深度优先搜索的预处理使用的时间和空间与V+E成正比而且可以在常数时间内处理关于图的连通性查询。

7、符号图

用符号作为顶点名的图的API

public class SymbolGraph
SymbolGraph(String filename, String delim) 根据filename制定的文件构造图,使用delim来分隔顶点名
boolean contains(String key) key是一个顶点吗
int index(String key) key的索引
String name(int v) 索引v的顶点名
Graph G() 隐藏的Graph对象

7.1 实现

使用以下3种数据结构:

  • 一个符号表st,键的类型为String(顶点名),值的类型为int(索引);
  • 一个数组keys[],用作反向索引,保存每个顶点索引所对应的顶点名;
  • 一个Graph对象G,它使用索引来引用图中的顶点。

SymbolGraph会遍历两遍数据来构造以上数据结构,这主要是因为构造Graph对象需要顶点总数V。

符号图的数据类型

import edu.princeton.cs.algs4.ST;
import edu.princeton.cs.algs4.StdOut;
import edu.princeton.cs.introcs.In;
import edu.princeton.cs.introcs.StdIn;

/**  
* 类说明   
*  用符号作为顶点名的图
* @author Zoye  
* @date 2018年7月5日  新建  
*/
public class SymbolGraph {
    private ST<String, Integer> st; // 符号名->索引
    private String[] keys; // 索引->符号名
    private Graph G; // 图
    public SymbolGraph(String stream, String sp) {
        st = new ST<String, Integer>();// 第一遍读取文件,建立符号表
        In in = new In(stream);
        while(in.hasNextLine()) {
            String[] a = in.readLine().split(sp);
            for(int i = 0; i < a.length; i++)
                if(!st.contains(a[i])) st.put(a[i], st.size());
        }
        keys = new String[st.size()]; // 建立反向索引
        for(String name : st.keys())
            keys[st.get(name)] = name;
        G = new Graph(st.size()); // 第二遍读取文件,建立图
        in = new In(stream);
        while(in.hasNextLine()) {
            String[] a = in.readLine().split(sp);
            int v = st.get(a[0]);
            for(int i = 1; i < a.length; i++)
                G.addEdge(v, st.get(a[i]));
        }
    }
    public boolean contains(String key) { return st.contains(key); }
    public int index(String key) { return st.get(key); }
    public String name(int v) { return keys[v]; }
    public Graph G() { return G; }
    public static void main(String[] args) {
        String filename = args[0];
        String delim = args[1];
        SymbolGraph sg = new SymbolGraph(filename, delim);
        Graph G = sg.G();
        while(StdIn.hasNextLine()) {
            String source = StdIn.readLine();
            for(int w : G.adj(sg.index(source)))
                StdOut.println("   " + sg.name(w));
        }
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,463评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,868评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,213评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,666评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,759评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,725评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,716评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,484评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,928评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,233评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,393评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,073评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,718评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,308评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,538评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,338评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,260评论 2 352

推荐阅读更多精彩内容

  • 图的典型应用 图的种类: 无向图(简单连接),有向图(连接有方向),加权图(连接带有权值),加权有向图(连接有方向...
    浩林Leon阅读 760评论 0 1
  • https://zh.visualgo.net/graphds 浅谈图形结构https://zh.visualgo...
    狼之独步阅读 4,146评论 0 0
  • 图是一种比线性表和树更复杂的数据结构,在图中,结点之间的关系是任意的,任意两个数据元素之间都可能相关。图是一种多对...
    Alent阅读 2,303评论 1 22
  • 现实生活中有很大一类问题可以用简洁明了的图论语言来描述,可以转化为图论问题。 相关定义 图可以表示为G=(V, E...
    芥丶未央阅读 1,698评论 0 7
  • 4.2.1介绍 在有向图中,边是单向的.实际上组合性的结构对算法有深远影响,使得有向图和无向图之间的处理大有不同....
    浩林Leon阅读 2,500评论 0 1