PS:算法并非原创,仅用于记录个人学习,侵删。
题目描述
算法分析
从人脑的角度出发,我想到的算法是这样的:
取一个基元字符,然后其他的字符都用这个字符和数字的组合来表示,之后求解问题的时候就只需要进行数字的除法运算即可。
但是查阅了一些算法博客,似乎图论法比较合适。
通过equation和value数组建立一个图,每个字符表示一个顶点,两者之间的有向线段表示两者的相除结果(a-2.0->b 表示a/b = 2.0)。之后的问题求解就是对于图的深度遍历,如果找到两点之间的可达路径,得出结果,否则,表示两者之间不可达,输出-1.
代码实现
【C】
/*
并查集思路:
定义一个数组s[i]来表示第i个元素属于哪个集团,因此初始化时s[i] = i;即每个元素都还是分散的。
对于可以合并的两个元素x与y,查找到他们两个所属的集团,将其中一个合并到另一个即可;
一般具备两个操作:
(1)查询元素a和元素b是否为同一组
(2)将元素a和b合并为同一组
*/
//自定义的结构体
typedef struct {
char *x;//顶点x
char *y;//顶点y
double z;//两者之间的路径权重,也就是value数组中的值
UT_hash_handle hh;//makes this structure hashable
/*
这里用到了hash表的C实现,uthash是用宏实现的,只用包含uthash.h即可。
每一个节点(用户自定义的)必须包含一个UT_hash_handle hh
*/
} Hash;
Hash *hash;//本题中所需要构建的数据结构
Hash* derive(Hash *p, double *t) {//查找p中两个顶点之间的路径
*t = 1;
while (strcmp(p->x, p->y)) {//如果两个顶点不是同一个字符
*t *= p->z;//t用来指向最终结果
char *s = p->y;//s表示分母
HASH_FIND(hh, hash, s, strlen(s), p);//查看hash表中是否存在key为s的元素,有就写入p
/*
查找操作HASH_FIND:
uthash存在三个简化的查找操作HASH_FIND_STR、HASH_FIND_INT和HASH_FIND_PTR,宏定义如下
#define HASH_FIND_STR(head,findstr,out) HASH_FIND(hh,head,findstr,strlen(findstr),out)
#define HASH_FIND_INT(head,findint,out) HASH_FIND(hh,head,findint,sizeof(int),out)
#define HASH_FIND_PTR(head,findptr,out) HASH_FIND(hh,head,findptr,sizeof(void *),out)
*/
}
return p;
}
void _add(char *x, char *y, double z) {//往hash总的结构中添加
Hash *p1, *p2;
HASH_FIND(hh, hash, x, strlen(x), p1);//查找x是否存在,存入p1中
HASH_FIND(hh, hash, y, strlen(y), p2);//查找y是否存在,存储p2中
if (p1 && p2) {//如果x和y都出现过并且无联系,那么将他们合并关系
double tx, ty;
p1 = derive(p1, &tx);//查找p1的父节点
p2 = derive(p2, &ty);//查找p2的父节点
if (strcmp(p1->x, p2->x)) {//如果p1和p2无联系
p1->y = p2->x;
p1->z = ty*z/tx;
}
} else if (p1) {//x出现过,y没有出现过
double tx;
p1 = derive(p1, &tx);//查找p1的父节点
Hash *p = malloc(sizeof(Hash));//分配一块存储空间
p->x = y;
p->y = p1->x;
p->z = tx/z;
HASH_ADD_STR(hash, x, p);//将p的父节点写入
/*
添加操作HASH_ADD:
uthash存在三个简化的添加操作HASH_ADD_STR、HASH_ADD_INT和HASH_ADD_PTR,宏定义如下
#define HASH_ADD_STR(head,strfield,add) HASH_ADD(hh,head,strfield,strlen(add->strfield),add)
#define HASH_ADD_INT(head,intfield,add) HASH_ADD(hh,head,intfield,sizeof(int),add)
#define HASH_ADD_PTR(head,ptrfield,add) HASH_ADD(hh,head,ptrfield,sizeof(void *),add)
*/
} else if (p2) {//y出现过,x没有出现过
double ty;
p2 = derive(p2, &ty);
Hash *p = malloc(sizeof(Hash));
p->x = x;
p->y = p2->x;
p->z = z/ty;
HASH_ADD_STR(hash, x, p);
} else {//x和y都没有出现过
p1 = malloc(sizeof(Hash));
p2 = malloc(sizeof(Hash));
p1->x = x;
p1->y = y;
p1->z = z;
HASH_ADD_STR(hash, x, p1);
p2->x = y;
p2->y = y;
p2->z = 1;
HASH_ADD_STR(hash, x, p2);
}
}
double _div(char *x, char *y) {
Hash *p1, *p2;
HASH_FIND(hh, hash, x, strlen(x), p1);
HASH_FIND(hh, hash, y, strlen(y), p2);
if (p1 && p2) {//如果x和y都出现过
double tx, ty;
p1 = derive(p1, &tx);
p2 = derive(p2, &ty);
if (strcmp(p1->x, p2->x) == 0) {//且两者没有联系
return tx/ty;
}
}
//如果两者中有任何一个没有出现过,返回-1.0
return -1.0;
}
double* calcEquation(char *** equations, int equationsSize, int* equationsColSize, double* values,
int valuesSize, char *** queries, int queriesSize, int* queriesColSize, int* returnSize){//主要实现的函数过程
hash = NULL;
for (int i=0; i<equationsSize; ++i) {//根据equations和values进行hash构建
_add(equations[i][0], equations[i][1], values[i]);
}
double *res = malloc(sizeof(double) * queriesSize);//res存储所有的最后结果,是一个浮点数数组,长度根据queriesSize决定
for (int i=0; i<queriesSize; ++i) {//对于每个问题进行查询
res[i] = _div(queries[i][0], queries[i][1]);
}
*returnSize = queriesSize;//确定返回的数组大小
return res;
}
【C++】
/*
结合并查集方法,将各组关联的字符放到一个集合中并确定传递关系,从而快速求解。
并查集中设计两个哈希表,分别记录各字符对应的父节点和权重值。
初始化时,各字符对应节点的父节点为自身,权重值为1。
根据方程式合并并查集,并进行路径压缩,将除数集合中除数以上的节点和被除数集合中被除数以上的节点压缩到被除数的父节点上。
最后根据问题方程式,在并查集中查找计算权重即可。
*/
class Solution {
private:
class UnionSet {//并查集的类定义
private:
unordered_map<string, string> str;//存储父节点
/*
unordered_map内部实现了一个哈希表(也称散列表)。因此,其元素的排列顺序是无序的
*/
unordered_map<string, double> val;//存储权重
bool exist(string a) {//在父节点中查看a是否存在
return str.find(a) != str.end();//如果存在,返回true
}
void init(string a) {//初始化
//如果并查集中存在a
if (exist(a))
return;
//如果并查集中不存在a,则添加a
str[a] = a;
val[a] = 1;
}
string findFather(string a) {//查找父节点并压缩路径
//如果a所对应的元素是自己,表示它没和其他元素产生联系,直接返回a
if (str[a] == a)
return a;
//如果a和其他元素产生联系,则
string father = findFather(str[a]);//找到它的父亲节点
val[a] = val[a] * val[str[a]];//更新a对应的结果值
str[a] = father; //更新a对应的父节点(表示的是 a/str[a] = val[a])
return father;
}
public://针对的是并查集这个类定义
void mergeNode(string a, string b, double value) {//合并集合并压缩路径
init(a);
init(b);
string father_a = findFather(a);
string father_b = findFather(b);
str[father_b] = father_a;
val[father_b] = val[a] * value / val[b];
}
double calc(string a, string b) {//根据问题方程式计算
if (!exist(a) || !exist(b))//如果a、b有至少一个不存在
return -1.0;
string father_a = findFather(a);
string father_b = findFather(b);
if (father_a != father_b)//如果两者没有联系
return -1.0;
return val[b] / val[a];//两者有联系
}
};
public:
vector<double> calcEquation(vector<vector<string>>& equations, vector<double>& values, vector<vector<string>>& queries) {
UnionSet unionSet;
for (int i = 0; i < equations.size(); i++) {//初始化并合并集合
unionSet.mergeNode(equations[i][0], equations[i][1], values[i]);
}
vector<double> ans;//结果数组
for (int i = 0; i < queries.size(); i++) {//计算各方程式结果
ans.push_back(unionSet.calc(queries[i][0], queries[i][1]));
}
return ans;
}
};
【JAVA】
/*图论中的深度优先遍历*/
public class Solution {
private Map<String, Integer> stringToInteger = new HashMap<>();
private int index = 0;
private double[][] graph;//对于图形的存储
private boolean[] visited;//标志该顶点有没有访问过
public double[] calcEquation(List<List<String>> equations, double[] values, List<List<String>> queries) {
for (List<String> list : equations) {
for (String string : list) {
if (!stringToInteger.containsKey(string)) {
stringToInteger.put(string, index++);
}
}
}
graph = new double[index][index];
for (int i = 0; i < equations.size(); i++) {//创建gragh
List<String> list = equations.get(i);
String string0 = list.get(0), string1 = list.get(1);
int index1 = stringToInteger.get(string0), index2 = stringToInteger.get(string1);
graph[index1][index2] = values[i];
graph[index2][index1] = 1.0 / values[i];
}
double[] result = new double[queries.size()];
Arrays.fill(result, -1.0);
for (int i = 0; i < result.length; i++) {
List<String> list = queries.get(i);
String string0 = list.get(0), string1 = list.get(1);
if (stringToInteger.containsKey(string0) && stringToInteger.containsKey(string1)) {
if (string0.equals(string1)) {
result[i] = 1.0;
} else {
int index1 = stringToInteger.get(string0), index2 = stringToInteger.get(string1);
visited = new boolean[index];
double len = 1.0;
dfs(index1, index2, len, result, i);
}
}
}
return result;
}
private void dfs(int begin, int end, double len, double[] result, int k) {//深度优先遍历
if (graph[begin][end] == 0) {
visited[begin] = true;
for (int i = 0; i < index; i++) {
if (!visited[i] && graph[begin][i] != 0) {
dfs(i, end, len * graph[begin][i], result, k);
}
}
} else {
result[k] = len * graph[begin][end];
}
}
}
【python】
#图论,深度优先遍历
class Solution(object):
def calcEquation(self, equations, values, queries):
"""
:type equations: List[List[str]]
:type values: List[float]
:type queries: List[List[str]]
:rtype: List[float]
"""
from collections import defaultdict
graph = defaultdict(set)
weight = defaultdict()
for i, equation in enumerate(equations):
start, end = equation[0], equation[1]
graph[start].add(end) #设置从start到end 的path
weight[(start, end)] = values[i] #给上一行设置的path分配weight
graph[end].add(start) #设置从end到start的path
weight[(end, start)] = 1.0 / values[i] #给上一行设置的path分配weight
# print weight
def dfs(start, end, visited):
if (start, end) in weight: #如果可以直接读出结果
return weight[(start, end)] #就直接返回
if start not in graph or end not in graph: #这两个点根本没出现过
return 0
if start in visited: #已经形成路径环了还没找到结果
return 0
visited.add(start) #标记一下start来过了
res = 0
for tmp in graph[start]:#遍历所有能从start出发的路径
res = weight[(start, tmp)] * dfs(tmp, end, visited)
if res != 0: #找到了第一条路
weight[(start, end)] = res #把这一条路的weight记录下来
break
visited.remove(start) #回溯
# print res
return res
res = []
for query in queries:
tmp = dfs(query[0], query[1], set())
if tmp == 0: #如果没找到
tmp = -1.0
res.append(tmp)
return res