一个 Swift 算法问题引发的思考

最近一直在写Swift方面的算法问题,写得多了,自然就有一定的收获,今天有个问题,感觉特别有趣。

这个问题是这样的:确定一个二叉树B是否是另外一个二叉树A的子树。
这个问题不难,百度能找到很多的答案,其基本思路是这样的,先遍历first二叉树,找到所有值等于second二叉树根结点值的节点群。然后,再分别比较节点群里的节点的子树节点和first节点的值是否相同,若能找到,就可以确定second二叉树是first二叉树的一个儿子。

在C语言里,代码是这样写的:

struct BinaryTreeNode {
    int value;
    BinaryTreeNode *left;
    BinaryTreeNode *right;
};
//  判断一个二叉树是否是另一个的子树
bool getAnswerWith(BinaryTreeNode * first,BinaryTreeNode * second){
    //遍历first二叉树,找到first节点与second根节点值相同的节点
    //⚠️  这里完全可以使用递归的方式去遍历A二叉树,但是那样不好,因为递归深度过于深的话可能造成函数压栈太多,造成栈溢出。
    //这里运用压栈的方式遍历二叉树
    std::stack myStack;
    BinaryTreeNode * tree = first;
    bool answer = false;
    while (myStack.size() != 0 || tree != NULL) {
        if (tree != NULL){
            myStack.push(tree);
            if (tree->value == second->value){
                answer = isHasCommonValue(tree, second);
            }
            tree = tree->left;
        }else{
            tree = myStack.top()->right;
            myStack.pop();
        }
    }
    return answer;
}

bool isHasCommonValue(BinaryTreeNode * first,BinaryTreeNode *second){
    if (second == NULL){ return true;}
    if (second->value != first->value){ return false;}
    return (isHasCommonValue(first->left, second->left) && (isHasCommonValue(first->right, second->right)));
}

如果把这个代码移植到Swift里,也很简单。照着翻译一遍就好了。
翻译好的代码是这样的:

class BinaryTreeNode {
    var value:T
    var left:BinaryTreeNode?
    var right:BinaryTreeNode?
    init(_ value:T) {
        self.value = value
        self.left = nil
        self.right = nil
   }
}

func getAnswerWithSwift(_ first:BinaryTreeNode,second:BinaryTreeNode?) -> Bool{
    
    if second == nil {
        return true
    }
    /*同样使用压栈的方式遍历二叉树,这里的栈是我使用链表实现的,跟apple的文档用Array的实现方式不太一样
      有兴趣的可以看下我的GitHub,下面会有我的GitHub地址
  */
    let stack:Stack> = Stack()
    var answer:Bool = false
    var tree:BinaryTreeNode? = first
    while !stack.isEmpty() || tree != nil  {
        if let aTree = tree {
            stack.push(value: aTree)
            if aTree.value == second!.value{
                answer = isHasCommonValue(aTree,second)
            }
            tree = aTree.left
        }else{
            tree = stack.top()?.right
            stack.pop()
        }
    }
    
    return answer
}
func isHasCommonValue(_ first:BinaryTreeNode?,_ second:BinaryTreeNode?) -> Bool{
    if second == nil{ return true}
    if first == nil { return false}
    if first!.value != second!.value{return false}
    return (isHasCommonValue(first!.left,second!.left) && isHasCommonValue(first!.right,second!.right))
}

如果仅仅是这样,也不会有什么特别的地方,最近一直在研究Swift的东西,所以想事情都会往Swift那边靠一靠,
Swift里允许我们对运算符进行重载,而且需要注意的是,树节点的value需要遵守Equatable协议才可以进行比较,于是代码就变成了这个样子:

class BinaryTreeNode:Equatable {
    var value:T
    var left:BinaryTreeNode?
    var right:BinaryTreeNode?
    init(_ value:T) {
        self.value = value
        self.left = nil
        self.right = nil
    }
    //重载 == 
    static func ==(lhs: BinaryTreeNode, rhs: BinaryTreeNode) -> Bool{
        if lhs.value != rhs.value{
            return false
        }
        return (isNil(lhs:lhs.left,rhs:rhs.left) && isNil(lhs:lhs.right,rhs:rhs.right))
    }
    
    static func isNil(lhs:BinaryTreeNode?,rhs:BinaryTreeNode?) -> Bool{
        if rhs == nil { return true}
        if lhs == nil {return false}
        return rhs! == lhs!
    }
}

func getAnswerWithSwift(_ first:BinaryTreeNode,second:BinaryTreeNode?) -> Bool{
    
    if second == nil {
        return true
    }
    let stack:Stack> = Stack()
    var answer:Bool = false
    var tree:BinaryTreeNode? = first
    while !stack.isEmpty() || tree != nil  {
        if let aTree = tree {
            stack.push(value: aTree)
            if aTree.value == second!.value{
                answer = (aTree == second!)    //注意这里
            }
            tree = aTree.left
        }else{
            tree = stack.top()?.right
            stack.pop()
        }
    }
    
    return answer
}

Swift里允许我们写运算符的重载,很强大的特性,在很多地方,或者是复杂的函数嵌套时,我们都可以使用运算符的重载,是代码更简洁,加强可读性。但是我把它用到这里,仔细想想,不大好,因为我在BinaryTreeNode里重载了==运算符,意味着以后用到二叉树的==都会是这样子,使得我们的二叉树有了很大的局限性,但是又想不到啥好的方法。为了更强的可读、扩展性和二叉树的应用范围,我把代码改成了这个样子:

class BinaryTreeNode {
    var value:T
    var left:BinaryTreeNode?
    var right:BinaryTreeNode?
    init(_ value:T) {
        self.value = value
        self.left = nil
        self.right = nil
    }
}
//将两个方法抽取出来
extension BinaryTreeNode where T:Equatable {
    static func ==(lhs: BinaryTreeNode, rhs: BinaryTreeNode) -> Bool{
        if lhs.value != rhs.value{
            return false
        }
        return (isNil(lhs:lhs.left,rhs:rhs.left) && isNil(lhs:lhs.right,rhs:rhs.right))
    }
    
    static func isNil(lhs:BinaryTreeNode?,rhs:BinaryTreeNode?) -> Bool{
        if rhs == nil {
            return true
        }
        if lhs == nil {
            return false
        }
        return rhs! == lhs!
    }
}

这样写有上述的好处,但是还是不太好。因为只要二叉树节点的值类型遵守了Equatable协议,使用==的时候,就会使用我们定义的重载方法,还是给二叉树增加了一定的局限性。

经过一番的思考,我最后决定,把代码改成最开始时候的样子。有时候,语言特性的东西不一定适合我们的应用场景,不要为了使用而去使用,否则得不偿失。

上文说到我最近在写一些Swift数据结构和算法上面的东西,有需要的朋友可以看一下。
GitHub地址:https://github.com/chaiyanpu/SwiftCustomAlgorithms

-------------------2016年11月19号更新-------------------------
New Idea

感谢Swift3.0新增加的关键字fileprivate,现在只需要把 BinaryTreeNode 定义 和 BinaryTreeNode拓展放到不同的文件中就可以了,修改后的代码是这样子的:

//注意,只需要前面加上fileprivate关键字,但是和BinaryTreeNode不在一个文件中就可以了
fileprivate extension BinaryTreeNode where T:Equatable {    
    static func ==(lhs: BinaryTreeNode, rhs: BinaryTreeNode) -> Bool{
        if lhs.value != rhs.value{
            return false
        }
        return (isNil(lhs:lhs.left,rhs:rhs.left) && isNil(lhs:lhs.right,rhs:rhs.right))
    }
    
    static func isNil(lhs:BinaryTreeNode?,rhs:BinaryTreeNode?) -> Bool{
        if rhs == nil {
            return true
        }
        if lhs == nil {
            return false
        }
        return rhs! == lhs!
    }
}

感觉胸前的红领巾更鲜艳了。

你可能感兴趣的:(一个 Swift 算法问题引发的思考)