路径搜索问题

之前碰到的很多问题都可以归结为路径搜索问题,就是求两点之间的路经

1)是否存在路径

2)求任意一条路径

3) 是否存在环

4)求所有路径

求是否有路径和任意一条路径以及判断是否存在环的时候,和正常遍历一样,一个点被mark之后不再访问,因为如果这个结点到终点有路径,之前就应该结束了,没结束说明没路径,再访问也无益。是 O(n)的,对于求所有路径,usedInPath 在进入结点时候mark,退出结点时候unmark, 每个结点可能被多次访问,总体是O(n!)的。可以有一些剪枝,比如当一个结点退出时候如果是可以到达终点的,就可以把这个点的marked重置为false,意思是这个点到终点有路径,可以供其他结点扩展再次进入。如果一个结点返回没有到达过终点,其marked项保持true, 以后再也不用进入。

def allPaths(G, start, end):
    marked, path, ans  = [False] * len(G), [], []
    def dfs(v):
        marked[v] = True
        path.append(v)
        num = len(ans)
        if v == end: ans.append(path[:])
        else:
            for w in G[v]:
                if not marked[w]: dfs(w)
        path.pop()
        if len(ans) > num:  marked[v] = False # if current node is connected to end, reopen it
    dfs(start)
    return ans

补充,这里的marked和经典dfs里用来防止重复遍历的marked不同,这里是用来防止进入环从而带来无限状态的,而不是防重。路径搜索本身的状态是个 n!的DAG,路径一直在增长,不可能回到之前的路径,不需要去重,只是有环的话会无限多。上面reopen 又用marked做剪枝,是业务语义层面的判断,既不是去重,也不是去环

无向图判断是否有环

和原始的图遍历dfs算法几乎一样,只是要带上上游节点信息,a->b, b->a 不算环。

class Solution {
    char[][] grid;
    boolean[][] marked;
    public boolean containsCycle(char[][] grid) {
        this.grid = grid;
        this.marked = new boolean[grid.length][grid[0].length];
        for (int i = 0; i < grid.length; ++i) {
            for (int j = 0; j < grid[i].length; ++j) {
                if (!marked[i][j] && dfs(i, j, -1, -1)) return true;
            }
        }
        return false;
    }
    
    boolean dfs(int i, int j, int p, int q) {
        marked[i][j] = true;
        for (int[] d : new int[][] {{1, 0}, {-1, 0}, {0, 1}, {0, -1}}) {
            int x = i + d[0], y = j + d[1];
            if (x >= 0 && x < grid.length && y >= 0 && y < grid[0].length && (x != p || y != q) && grid[x][y] == grid[i][j]) {
                if (marked[x][y] || dfs(x, y, i, j)) return true;
            } 
        }
        return false;
    }
}

bfs 求路径问题

1)维护前驱列表,最后还原

2)结点保存路径,存储要求比较高。

3)最短路径必须用bfs,如果是求所有最短路径,扩展到一个结点时候不是立即mark, 而是这一层之后统一mark,因为允许同层的其他结点也指向这个结点。

DAG上的路径搜索(有点类似二叉树上的问题 f(root) = g(f(left),  f(right)),当前节点的解依赖于左右孩子上的解。

DAG上还有一种常见搜索,求DAG中最长的路径,例题是POJ最长雪道,以及leetcode 数字三角最长到底边路径。即没有起点和/或终点的路径搜索

一般用 记忆化搜索,递推关系:d[v] = max{ d[w] } + 1 where (v, w)属于边集E。注意是DAG,DAG就是一个结点沿着有向边走下去不会回到自己。雪道问题就是,沿着坡度降低的方向走,高度一直下降,不可能回到路径上任何一点,典型的DAG。

背包问题用路径搜索建模:(隐式图建模)

都是DAG,不可逆,一直在减少,不可能回到之前的状态。

1)用最少(最多)硬币凑给定面值V问题

顶点:需要凑的面值。

边:可以进行的状态转移,(可以进行的操作,aka, 选择一个面值的硬币)

问题:从顶点V 到 顶点0的最短(最长)路径问题问题。f[v] = min{ f[v - w[i]] } + 1

2)完全背包问题

顶点:剩余容量C

边:可以进行的状态转移(选择一个物品),边的权值为物品的价值

问题:从顶点C出发的权值和最大的路径,(无终点路径搜索)。f[c] = max{ f[i - V[i]] + W[i]},假如已知图是DAG,那么最短带权路径可以用这个dp,而不只是djisktra

如果是必须刚好装满,则是从顶点C出发,到顶点0的最大路径问题(不带权的就是1,带权的就是权值)。

DAG下的最短路径径,当然可以用记忆化搜索,然而有一种最针对的方法

按拓扑序遍历节点,relax节点的每条边,最终d[i]和 prev[i]就是最短路径

最长路径求法:每条边的权值取反,然后按上述方法求最短路径,然后将结果d[i]取反

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值