从源代码甚至三地址 (TAC) 代码的角度来看,您可以在此页面上非常轻松地可视化问题...
http://cgm.cs.mcgill.ca/~hagha/topic30/topic30.html#Exptree
如果你转到表达式树部分,然后向下翻页,它会显示树的“拓扑排序”,以及如何评估表达式的算法。
因此,在这种情况下,您可以使用 DAG 来评估表达式,这很方便,因为通常会解释评估,并且使用这样的 DAG 评估器原则上会使简单的解释器更快,因为它不会压入和弹出堆栈,而且还因为它正在消除常见的子表达式。
在非古埃及语(即英语)中计算 DAG 的基本算法是这样的:
1) 像这样制作你的 DAG 对象
您需要一个活动列表,该列表包含所有当前活动的 DAG 节点和 DAG 子表达式。 DAG 子表达式是一个 DAG 节点,或者您也可以将其称为内部节点。我所说的实时 DAG 节点的意思是,如果您分配给变量 X,那么它就会变成实时的。然后使用 X 的公共子表达式使用该实例。如果再次分配 X,则创建一个 NEW DAG NODE 并将其添加到活动列表中,并删除旧的 X,因此使用 X 的下一个子表达式将引用新实例,因此不会与只需使用相同的变量名。
一旦你给变量 X 赋值,那么巧合的是,所有在赋值点处于活动状态的 DAG 子表达式节点都变为不活动状态,因为新赋值使使用旧值的子表达式的含义无效。
class Dag {
TList LiveList;
DagNode Root;
}
// In your DagNode you need a way to refer to the original things that
// the DAG is computed from. In this case I just assume an integer index
// into the list of variables and also an integer index for the opertor for
// Nodes that refer to operators. Obviously you can create sub-classes for
// different kinds of Dag Nodes.
class DagNode {
int Variable;
int Operator;// You can also use a class
DagNode Left;
DagNode Right;
DagNodeList Parents;
}
所以你要做的就是在你自己的代码中遍历你的树,例如源代码中的表达式树。以现有节点 XNodes 为例。
所以对于每个 XNode 你需要决定如何将它添加到 DAG 中,并且它有可能已经在 DAG 中。
这是非常简单的伪代码。不用于编译。
DagNode XNode::GetDagNode(Dag dag) {
if (XNode.IsAssignment) {
// The assignment is a special case. A common sub expression is not
// formed by the assignment since it creates a new value.
// Evaluate the right hand side like normal
XNode.RightXNode.GetDagNode();
// And now take the variable being assigned to out of the current live list
dag.RemoveDagNodeForVariable(XNode.VariableBeingAssigned);
// Also remove all DAG sub expressions using the variable - since the new value
// makes them redundant
dag.RemoveDagExpressionsUsingVariable(XNode.VariableBeingAssigned);
// Then make a new variable in the live list in the dag, so that references to
// the variable later on will see the new dag node instead.
dag.AddDagNodeForVariable(XNode.VariableBeingAssigned);
}
else if (XNode.IsVariable) {
// A variable node has no child nodes, so you can just proces it directly
DagNode n = dag.GetDagNodeForVariable(XNode.Variable));
if (n) XNode.DagNode = n;
else {
XNode.DagNode = dag.CreateDagNodeForVariable(XNode.Variable);
}
return XNode.DagNode;
}
else if (XNode.IsOperator) {
DagNode leftDagNode = XNode.LeftXNode.GetDagNode(dag);
DagNode rightDagNode = XNode.RightXNode.GetDagNode(dag);
// Here you can observe how supplying the operator id and both operands that it
// looks in the Dags live list to check if this expression is already there. If
// it is then it returns it and that is how a common sub-expression is formed.
// This is called an internal node.
XNode.DagNode =
dag.GetOrCreateDagNodeForOperator(XNode.Operator,leftDagNode,RightDagNode) );
return XNode.DagNode;
}
}
所以这是看待它的一种方式。树的基本遍历,只是添加并引用 Dag 节点。例如,dag 的根是树的根返回的任何 DagNode。
显然,示例过程可以分解成更小的部分,或者作为具有虚函数的子类。
至于对 Dag 进行排序,您从左到右遍历每个 DagNode。换句话说,跟随 DagNodes 的左手边,然后是右手边。数字是反向分配的。换句话说,当您到达没有子节点的 DagNode 时,为该节点分配当前的排序号并增加排序号,以便递归展开,以递增的顺序分配数字。
此示例仅处理具有零个或两个子节点的树。显然有些树的节点有两个以上的孩子,所以逻辑还是一样的。不是左右计算,而是从左到右计算等等......
// Most basic DAG topological ordering example.
void DagNode::OrderDAG(int* counter) {
if (this->AlreadyCounted) return;
// Count from left to right
for x = 0 to this->Children.Count-1
this->Children[x].OrderDag(counter)
// And finally number the DAG Node here after all
// the children have been numbered
this->DAGOrder = *counter;
// Increment the counter so the caller gets a higher number
*counter = *counter + 1;
// Mark as processed so will count again
this->AlreadyCounted = TRUE;
}