图论 应用篇

上次写了篇图的基本构造方法,运用图这种强大的数据结构结构,还能解决实际应用中的许多问题,今天这篇就主要整理一些常见的应用

一、路径问题

路径问题在图的处理领域是非常重要的。如我们最常见的走迷宫,就是典型的寻路问题。这里主要运用深度优先和广度优先算法两种方式来进行路径寻找,这2种搜索算法在很多数据结构中都有重要的运用,之前写的一篇二叉查找树中的层序遍历就用到了广度优先算法,这里就详细的介绍一下。

1.深度优先寻找路径

首先是深度优先,为了更加形象,直接看下图
![][1]

这里以顶点1为出发点,4为终点。假设一个人要走到终点,从1出发有三条路径,首先沿着往5的路径进行遍历,依次1 -> 5 -> 9 -> 8,然后发现没路了,就返回上一顶点,到顶点5这里发现还有一条路就继续沿着这条路走,5->3->6,结果又没路了,就继续返回到起点,沿着另一条路行走......1->2->4,一看,这下就直接到终点了,转了几条路终于找到了终点,那心里真是无比的兴奋啊!回到正题,看看这个具体实现,可以用一个boolean类型的变量来标记是否遍历过该顶点,用一个int型的变量表示从起点到一个顶点的已知路径上的最后一个顶点。至于图的基本构造可以参考我之前写的[图论基础篇][2]

/**
 *  图的深度优先查找路径
 * @author Legend
 */
public class DepthFirstPaths {

    private boolean[] marked; // 该顶点是否调用过dfs
    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;
        // 遍历与顶点v相连的边
        for (int w : G.adj(v)) {  
            if (!marked[w]) {
                edgeTo[w] = v;
                dfs(G,w);  // 继续递归的进行遍历
            }
        }
    }
   // 是否存在s到v的路径
    public boolean hasPathTo(int v) {
        return marked[v];
    }
   // s到v的路径
    public Iterable<Integer> PathTo(int v) {
        if (!hasPathTo(v)) {
            return null;
        }
        Stack<Integer> path = new Stack<>();
        for (int i = v;i != s;i = edgeTo[i]) {
            path.push(i);
        }
        path.push(s);
        return path;
    }
}

因为我们要准确的知道每一条路径,所以这里创建了一个edgeTo变量用于记录从起点到一个顶点的已知路径上的最后一个顶点,而edgeTo[w] = v表示的就是从w到v的路径。再看PathTo方法,首先判断是否有从s到v的路径,没有就返回null。然后实例化一个Stack类型对象path,依次遍历,把路径上每一个顶点都push进去,最后在push顶点,并返回path。

2.广度优先寻找路径

广度优先正如其名,优先进行广度的遍历,整个过程呈扩散状。这里还是用上面那张图,为了方便查看,还是把图片放到这里。
![][3]
[1]: http://img.mukewang.com/59fb24c20001780108340606.png
[2]: http://blog.cspojie.cn/2017/10/20/%E5%9B%BE%E8%AE%BA-%E5%9F%BA%E7%A1%80%E7%AF%87/#more,这里直接用Graph来表示,然后具体实现看看下面的代码
[3]: http://img.mukewang.com/59fd753d0001780108340606.png
还是用之前的情景,从顶点1出发,先遍历和1相邻的顶点 2,5,3,然后从顶点2开始,右继续遍历和2相邻的顶点,因为已经遍历过顶点1,所以这里就只需要遍历顶点7和顶点4,发然后现就直接到终点了,这是在特殊的情况下,如果先遍历的是另外的顶点,那么几乎要走完每条路才能找到终点。这里就不多说了,重点讲下具体实现,这里用一个抽象数据结构---队列来实现,首先创建一个队列,然后把起点标记后插入队列中去,如果队列不为空就把当前的顶点弹出队列,然后依次遍历和这个顶点相邻的顶点,并把这些顶点标记后也加入队列,接下来用同样的方式将下一顶点弹出队列,遍历和其相邻的顶点,直到队列为空就停止遍历。好了,具体看下面的代码

/**
 *
 * 广度优先寻找路径
 * @author Legend
 **/

public class BreadthFirstPaths {

    private boolean[] marked;
    private int[] edgeTo;
    private final int s;
    public BreadthFirstPaths(Graph G,int s) {
        marked = new boolean[G.V()];
        edgeTo = new int[G.V()];
        this.s = s;
        try {
            bfs(G,s);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    private void bfs(Graph G,int s) throws InterruptedException {
        Queue<Integer> queue = new Queue<Integer>();
        marked[s] = true;
        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<>();
        for (int i = 0;i != s;i =edgeTo[i] ){
            path.push(i);
        }
        path.push(s);
        return path;
    }
}

这里同样运用了pathTo方法,顶点与路径的标记过程和深度优先寻找路径是一样的,这里就不多说了。

连通分量问题

1.介绍

连通分量也是一个比较常见的问题,主要用于判断任意两个结点的连接状态,特别是用于检测网络连接与电路连接的问题中,运用比较广。

2.基本实现

也没什么好说的,这里还是运用深度优先的方法,比较简单,就直接上代码

/**
 * 使用深度优先找出图中的连通分量
 *
 * @author Legend
 * @create 2017-11-01 8:23
 **/

public class CC {
    private int count;
    private boolean marked[];
    private int[] id;

    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 isConnected(int v,int w) {
        return id[v] == id[w];
    }
    // v所在连通分量的标识符(0~count-1)
    public int id(int v) {
        return id[v];
    }
    // 连通分量的数量
    public int count() {
        return count;
    }
}

双色问题

1.介绍

能否用2种颜色将图的所有顶点着色,使得任意一条边的两个端点的颜色都不相同,这个问题也就等价于当前的图是不是二分图。因为二叉树其实就是一种比较特殊的图,所以才有这个问题。

2.基本实现

具体实现可以用一个bool类型的变量来表示2种颜色,直接在构造方法里面进行循环,这里也是运用深度优先的方式。首先判断当前结点是否被遍历,没被遍历过就进行遍历,也就是用bool型变量将其标记为true,然后遍历和这个结点相连的结点,并且把这个结点和相邻的结点涂上不同的颜色。然后进行判断
先来看下代码

/**
 * 双色问题
 *
 * @author Legend
 * @create 2017-11-01 9:17
 **/

public class TwoColor {
    private boolean[] marked; //当前结点是否被遍历过
    private boolean[] color; // 表示不同颜色
    private boolean isTwoColorable = true; // 是否能用2种颜色表示
    public TwoColor(Graph G) {
        marked = new boolean[G.V()];
        color = new boolean[G.V()];
        for (int s = 0;s < G.V();s++) {
            if (!marked[s]) {
                dfs(G,s);
            }
        }
    }
    private void dfs(Graph G,int v) {
        marked[v] = true;
        for (int w : G.adj(v)) {
            if (!marked[w]) {
                color[w] = !color[v];
                dfs(G,w);
            } else if (color[w] == color[v]) {
                isTwoColorable = false;
            }
        }
    }
    public boolean isBipartite() {
        return isTwoColorable;
    }
}

如果这2个顶点颜色相同,则不能使得任意一条边的2个端点的颜色都不相同,则这个图不是二分图,试想一下,如果是二分图,任意一条的两个端点肯定颜色是不相同的。因为每次遍历时都将当前顶点与连接顶点标记了2种不同的颜色,如果这个顶点有多个相邻顶点,并且这些相邻顶点又有边相连,这必然会造成2个颜色相同,这样的图自然也不可能是二分图了。

因为这篇博客主要是整理图论的一些应用的问题,所以对于这些问题的优化这里不是重点,有兴趣的可以自己去查查资料,那图论应用篇就暂时到这里了。
ps:该blog首发lenged's blog

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容

  • 第一章 绪论 什么是数据结构? 数据结构的定义:数据结构是相互之间存在一种或多种特定关系的数据元素的集合。 第二章...
    SeanCheney阅读 5,657评论 0 19
  • 本文涉及更多的是概念,代码部分请参考之前写过的 2 篇博客 基于Javascript的排序算法基本数据结构和查找算...
    faremax阅读 1,152评论 0 2
  • B树的定义 一棵m阶的B树满足下列条件: 树中每个结点至多有m个孩子。 除根结点和叶子结点外,其它每个结点至少有m...
    文档随手记阅读 13,005评论 0 25
  • 图是一种比线性表和树更复杂的数据结构,在图中,结点之间的关系是任意的,任意两个数据元素之间都可能相关。图是一种多对...
    Alent阅读 2,236评论 1 22
  • 在不同人眼里,人生有不同的状态。乐观的人把人生活成一场喜剧,悲观的人把人生活成一场悲剧。你很坚强,那么你就可以支配...
    怪咖习心阅读 352评论 0 0