首先我们需要知道到底怎么给一个树排序的
基本规则
优先级高的要放在最下面的位置,新节点优先级低于当前比较的节点向上调整直到达到优先级合适的位置,优先级等于或大于当前节点的优先级则向下调整更新。
尽量将所有的带有operator的节点放在左边
举个例子
4 * 1 / 2 + 3 - 2
首先我们解析到第一个符号
第二个符号是÷,因为除法和惩罚是同等优先级的,所以在经过判断之后,优先级一致向下调整
这里要说明的是一个运算当中因为运算符优先级是不一样的所以一个数字应该与哪个符号结合也是问题
那么1 * 1 / 2中 中间的1应该与前面的*结合还是与后面的 / 结合呢?灵魂拷问
当然不可能在一开始就考虑那么多了,你怎能知道下一个操作符的优先级是什么?所以我们这里要拥抱变化
换一个思路,我们要如何安排一个操作符两边的数据呢?答案是,将操作符前面的数据放入右子树,将操作符后面的数据放入左子树。所以上面的图一是不对的哦,小朋友不要学习
因为我们希望将所有的操作符都放在左边一侧,所以左子树肯定要经常拥抱变化,而对于一个操作符来说,它的第二个数据可能并不属于它(没错它可能只是个备胎,优先级小于或者等于后面的操作符,哦悲哀啊~~)。
每当后面出现一个变量的优先级等于或者大于它的话,他就是个备胎,准备放手,把自己的左子节点送给对方当正宫(右子树),没错我们的右子树都是正宫地位妥妥的,就是已经确定不离不弃的那个数字。
第三个符号是+号依旧乘号和除号,包括后面的减号也是,所以最后得到的结果就是
我们测试一下程序main.go修改一下
得到结果
我们看看代码就很好懂了(一遍不懂就多看几遍:)
首先就是把主要的CreateTree代码贴一下
func (this *AstNode) CreateAstTree(bracketsPos map[int]int , tokens []Token, begin , end int) (*AstNode , error) {
tokenSize := len(tokens)
if(tokenSize < 0){
return nil , errors.New("no token")
}
var err error
var nodes []*AstNode = make([]*AstNode, tokenSize)
var lastOperator int = -1
var lastIndex int = 0
var root *AstNode = nil
for index := begin ; index < end && index < tokenSize; index++ {
tokenPtr := &tokens[index]
tokenType , ok := tokenPtr.GetType()
if(ok == nil) {
//1.初始化节点
nodes[index] = &AstNode{nil,LeftPart,nil , nil , 0, 0}
//2.节点类型如果为运算符
if(tokenType == OperatorType) {
//获取操作符的类型
nodes[index].Op , err = tokenPtr.GetOperator()
if(err != nil) {
return nil , err
}
/*保持左侧是运算符项不是数据项,先更新右边的数据内容
这里为何是先更新右边的数据大家可以看我上图的解释很清晰,主要是为了保证左侧都是运算项好遍历
*/
if(nodes[index].RNode == nil) {
nodes[index].RNode = nodes[index - 1]
//一般来说index-1的位置就是数据,如果不是也应该在输入的部分被检测,所以这里没有检测
nodes[index - 1].Father = nodes[index]
//这个Part项主要的意思就是告诉我们它是属于父节点的左子树还是右子树
nodes[index - 1].Part = RightPart
}
//当前是第一个操作符,初始化root
if(lastOperator == -1) {
root = nodes[index]
lastIndex = index
lastOperator = nodes[index].Op
} else {
//插入新的数据块到AST内,我们只将带操作符的数据块插入树,
//你可以理解为带操作符可以进行排序,其他的数据项只是附属,有点索引的意思
root = this.getPostion(root , nodes[lastIndex] , nodes[index])
lastOperator = nodes[index].Op
lastIndex = index
}
} else if(tokenType == VarType) {
} else if(tokenType == NumberType) { //第一个token基本都是数字类型的
nodes[index].Val , err = strconv.Atoi(tokenPtr.GetText()) //之后Token带了val就不用在这里转换了,太麻烦
if(err != nil) {
return nil , err
}
//将数字复制到前一个运算符的左节点,因为它不一定属于它前面的运算符,是渣女无疑了
if(index > 0 && nodes[index - 1].LNode == nil) {
nodes[index - 1].LNode = nodes[index]
nodes[index].Father = nodes[index - 1]
nodes[index].Part = LeftPart
} else if(index > 0 && nodes[index - 1].LNode.Op > 0) { //很有可能它的左子树已经有了其他的操作符,这说明他已经稳定了左边,那就直接当正宫了,这可是绝世好男人没有忘不掉的白月光!!
nodes[index - 1].RNode = nodes[index]
nodes[index].Father = nodes[index - 1]
nodes[index].Part = RightPart
}
} else if(tokenType == SymbolType) { //写了还没敢测试,你可知道当二叉树和递归碰撞是多么的可怕一件事,窒息
pos ,_ := bracketsPos[index + begin]
//遇到括号的情况,括号的内部应该是执行内容的
if(nodes[lastIndex].LNode == nil ) {
nodes[lastIndex].LNode , err = this.CreateAstTree(bracketsPos, tokens, begin + index + 1, pos - 1)
} else {
nodes[lastIndex].RNode , err = this.CreateAstTree(bracketsPos, tokens, begin + index + 1, pos - 1)
}
if(err != nil) {
return nil , err
}
}
}
}
return root , nil
}
还有确定一个节点在整个的树中的位置的getPosition函数
func (this *AstNode) getPostion(head , root , node *AstNode)*AstNode{
//就node都没有还想getPostion 想peach呢?
if(node == nil) {
return head
}
last := root
for root != nil {
//如果当前的优先级一直大于之前的优先级那就一直上调
if(isPriorityHigher(root.Op, node.Op)) {
last = root
root = root.Father
}else {
break;
}
}
/*root为空,为什么root为空要判断last?
因为last初始值是root,如果root不为空且循环进行的话last永远不会为空,
而单纯判断root是不是空,有可能是上调导致的,也有可能是一开始就是空的,就很难判断。*/
if(last == nil) {
root = node
head = node
return head
}
//root等于last说明,当前的位置正正好,不需要上调,可以直接在下面接一下就好
if(root == last) {
if(root.LNode != nil){
//如果出现root的左节点不为空而且有值,node需要从他手里接过正宫,完成交接
if(root.LNode.Op == 0) { //数据点 op = 0 操作符 val = 0
node.RNode = root.LNode //单纯是数据的话就是接过正宫
node.RNode.Father = node
node.RNode.Part = RightPart
root.LNode = node
node.Father = root
node.Part = LeftPart
} else { //如果啥也没有那就不用交接了
node.Father = root
node.Part = LeftPart
node.LNode = root.LNode
node.LNode.Father = node
node.LNode.Part = LeftPart
root.LNode = node
}
}
} else {
//需要直接添加在顶部,root已经到达树顶的Father
if(root == nil) {
node.LNode = last
last.Father = node
last.Part = LeftPart
head = node //因为root的头已经换成了node了,所以只有在这一处会改变head的内容
} else {
//经过一定的调整找到的位置,需要夹在last和root中间
if(last.Part == LeftPart) { //我们需要检查一下last属于head的哪个分支别接错了
root.LNode = node
node.Part = LeftPart
} else {
root.RNode = node
node.Part = RightPart
}
//在这种情况下新加入的节点左边就被安了新的节点,说明左边不会被调整了,那就可以将右边的数字放入左边了,详见上方的绝世好男人
node.LNode = last
last.Father = node
last.Part = LeftPart
}
}
return head
}