Evaluate Division
原文请访问:https://yanjia.me/zh/2018/12/…
Equations are given in the format A / B = k, where A and B are variables represented as strings, and k is a real number (floating point number). Given some queries, return the answers. If the answer does not exist, return -1.0.
Example:
Given a / b = 2.0, b / c = 3.0.
queries are: a / c = ?, b / a = ?, a / e = ?, a / a = ?, x / x = ? .
return [6.0, 0.5, -1.0, 1.0, -1.0 ].The input is:
vector<pair<string, string>> equations, vector<double>& values, vector<pair<string, string>> queries
, whereequations.size() == values.size()
, and the values are positive. This represents the equations. Returnvector<double>
.According to the example above:
equations = [ ["a", "b"], ["b", "c"] ],
values = [2.0, 3.0],
queries = [ ["a", "c"], ["b", "a"], ["a", "e"], ["a", "a"], ["x", "x"] ].
The input is always valid. You may assume that evaluating the queries will result in no division by zero and there is no contradiction.
深度优先搜索
思路
这道题抽象之后可以理解为,给定一系列图中的边,边的值表示其两端节点的比例,现在给定一系列图中节点对,求每对节点的比例。那无论如何,首先我们要先根据给定的equations和values把图先建出来。由于是无向图,不分前后,所以我们不仅要记下a到b的边,也要记下b到a的边,即倒数。图建好以后,如果给定的query并不是很多,那么最简单的就是从节点a开始,深度搜索寻找节点b,并同时记录下路径上的比例并乘起来,一旦找到节点b就返回这个乘积。这里代码使用了递归来实现DFS。
代码
func compute(graph map[string]map[string]float64, first, second string, visited map[string]bool, value float64) float64 {
nexts := graph[second]
for next, ratio := range nexts {
if _, ok := visited[next]; !ok {
// if the target is found, we return the final result
if next == first {
return value * ratio
} else {
// otherwise keep looking
// set visited to true to avoid circle
visited[next] = true
res := compute(graph, first, next, visited, value * ratio)
if res != -1 {
return res
}
delete(visited, next)
}
}
}
return -1
}
func buildGraph(equations [][]string, values []float64) map[string]map[string]float64 {
graph := map[string]map[string]float64{}
for i, equation := range equations {
first := equation[0]
second := equation[1]
value := values[i]
// initialize the map if not being created before
if _, ok := graph[first]; !ok {
graph[first] = map[string]float64{}
}
if _, ok := graph[second]; !ok {
graph[second] = map[string]float64{}
}
// record both direction for this edge
graph[first][second] = 1/value
graph[second][first] = value
}
return graph
}
func calcEquation(equations [][]string, values []float64, queries [][]string) []float64 {
graph := buildGraph(equations, values)
res := []float64{}
for _, query := range queries {
first := query[0]
second := query[1]
value := -1.0
visited := map[string]bool{}
// dfs and find the path from 'first' to 'second'
value = compute(graph, first, second, visited, 1)
res = append(res, value)
}
return res
}
Floyd算法
思路
但是如果query很多,甚至要求图中每个节点对两两之间的比例时,深度搜索就显得不是那么有效了。因为这时搜索会重复走很多次相同的路径。这种求两两之间最短路径有一个现成的算法:Floyd–Warshall 。基本上该算法就是通过三层循环,一层是中间节点,两层是开始和结束节点,穷举所有的可能性看是否开始和结束节点能否通过这个中间节点串联起来,如果可以的话就给图中加一条直接从开始节点到结束节点的边。这样虽然建图会花掉O(N^3)的时间,但是对每个query就成了一个O(1)的操作。
注意
使用该算法的时候要记得给每个节点到自身也加一条值为1的边(自己对自己的比例是1),这样当中间节点和开始节点是一个节点时,不会把0乘进去
代码
func buildGraph(equations [][]string, values []float64) map[string]map[string]float64 {
graph := map[string]map[string]float64{}
for i, equation := range equations {
first := equation[0]
second := equation[1]
value := values[i]
if _, ok := graph[first]; !ok {
graph[first] = map[string]float64{}
}
if _, ok := graph[second]; !ok {
graph[second] = map[string]float64{}
}
graph[first][second] = 1/value
graph[second][first] = value
}
return graph
}
func calcEquation2(equations [][]string, values []float64, queries [][]string) []float64 {
graph := buildGraph(equations, values)
for i := range graph {
graph[i][i] = 1.0
for j := range graph {
for k := range graph {
ratio1, ok1 := graph[j][i]
ratio2, ok2 := graph[i][k]
if ok1 && ok2 {
graph[j][k] = ratio2 * ratio1
}
}
}
}
res := []float64{}
for _, query := range queries {
first := query[0]
second := query[1]
value := -1.0
if _, ok := graph[second][first]; ok {
value = graph[second][first]
}
res = append(res, value)
}
return res
}