概念
拓扑算法是一个有向无线算法
是一个处理复杂关系的算法
应用场景
A = B+C
B = C+D
C = A+T
这里的值因为计算顺序不同,会导致计算结果不同,而拓扑排序就能解决这个问题,找出先计算那一位
环的概念
拓扑排序中的一个bug
如果
A =B+C
B = C+D
C = A+D
这里A的值会影响C,C会影响A,所以一旦出现这种情况即预示着出现了环,该逻辑有问题
代码示例
php
//假设数据结构是这样的
public function text(){
$accounts = [
['name' => 'A', 'value' => "C + B"],
['name' => 'B', 'value' => "C + D"],
['name' => 'C', 'value' => 100],
['name' => 'D', 'value' => 100],
['name' => 'E', 'value' => "A + B + C"],
];
$initialValues = [
'C' => 100,
'D'=> 100,
];
$analysister_config_data = $this->calculateAccountBalances($accounts,$initialValues);
return['data'=>$analysister_config_data ];
}
/**
* @param $accounts
* @param $initial_values
* @return array
* 用于计算所有所有账户最终余额
*/
function calculateAccountBalances($accounts, $initialValues) {
$graph = $this->buildDependencyGraph($accounts);
$sortedNodes = $this->topologicalSort($graph);
$balances = $initialValues;
foreach ($sortedNodes as $node) {
if (isset($balances[$node])) {
continue;
}
// 获取当前节点的表达式
$expression = $accounts[array_search($node, array_column($accounts, 'name'))]['value'];
// 替换表达式中的变量为已知值
foreach ($balances as $key => $value) {
$expression = str_replace($key, $value, $expression);
}
// 计算表达式的值
$balances[$node] = eval("return $expression;");
}
return $balances;
}
/**
* @param $value:当前账户的值,
* @param $accountValues:一个引用数组,存储已经计算的值避免重复计算
* @param $accounts:所有账户
* @return int*
*/
function parseValue($value, &$accountValues, $accounts, $initialValues) {
$parts = explode(' ', $value);
$result = 0;
$operator = '+';
foreach ($parts as $part) {
if ($part === '+' || $part === '-') {
$operator = $part;
continue;
}
if (ctype_alnum($part)) { // 只处理有效的账户名称
if (isset($accountValues[$part])) {
$value = $accountValues[$part];
} elseif (isset($initialValues[$part])) {
$value = $initialValues[$part];
$accountValues[$part] = $value; // 确保将初始值存储到 accountValues 中
} else {
if (isset($accounts[$part])) {
$value = $this->parseValue($accounts[$part]['value'], $accountValues, $accounts, $initialValues);
} else {
echo ("账户没找到 " . $part);
}
}
} else {
// 如果 part 是数字,直接使用
$value = (int)$part;
}
if ($operator === '+') {
$result += $value;
} else {
$result -= $value;
}
}
return $result;
}
/**
* @param $accounts
* @return array
* 将需要计算的数据生成为依赖关系图
*/
function buildDependencyGraph($accounts) {
$graph = [];
foreach ($accounts as $account) {
$name = $account['name'];
if (!isset($graph[$name])) {
$graph[$name] = [];
}
$parts = explode(' ', $account['value']);
foreach ($parts as $part) {
if (is_numeric($part)){
continue;
}
if (ctype_alnum($part) && !in_array($part, ['+', '-'])) {
$graph[$name][] = $part;
if (!isset($graph[$part])) {
$graph[$part] = [];
}
}
}
}
return $graph;
}
/**
* @param $graph 依赖图,是一个二维数组,表示依赖关系
* @return array
* 用于对数据排序,按照依赖关系排序
*/
function topologicalSort($graph) {
$inDegree = []; // 节点的入度
$zeroDegree = []; // 入度为0的节点
$sorted = []; // 拓扑排序结果
// 初始化所有节点的入度为0
foreach ($graph as $node => $dependencies) {
$inDegree[$node] = 0;
}
// 计算每个节点的入度
foreach ($graph as $node => $dependencies) {
foreach ($dependencies as $dependency) {
if (ctype_alnum($dependency)) { // 确保只处理有效的账户名称
if (!isset($inDegree[$dependency])) {
$inDegree[$dependency] = 0; // 确保所有节点都在 inDegree 数组中
}
$inDegree[$node]++; // 这里应该增加当前节点的入度
}
}
}
// 找出入度为0的节点
foreach ($inDegree as $node => $degree) {
if ($degree == 0) {
$zeroDegree[] = $node;
}
}
// 进行拓扑排序
while (!empty($zeroDegree)) {
$node = array_shift($zeroDegree);
$sorted[] = $node;
// 处理当前节点的依赖关系
foreach ($graph as $currentNode => $dependencies) {
if (in_array($node, $dependencies)) {
$inDegree[$currentNode]--;
if ($inDegree[$currentNode] == 0) {
$zeroDegree[] = $currentNode;
}
}
}
}
// 检测是否存在环
if (count($sorted) != count($graph)) {
echo '存在环,无法进行拓扑排序';
}
return $sorted;
}