数据结构之----二叉搜索树

169 阅读2分钟

1. 概念

二叉搜索树具有一下四点性质:
(1)所有节点关键码都互不相同
(2)左子树上所有节点的关键码都小于根节点的关键码
(3)右子树上所有节点的关键码都大于根节点的关键码
(4)左右子树也是二叉搜索树

关键码是节点所保存元素中的某个属性,它能够唯一的表示(区分)这个节点。对二叉搜索树进行中序遍历,就可以按照关键码的大小从小到大的顺序将各节点排列起来,因此,二叉搜索树也叫二叉排序树,下图是几个二叉搜索树的例子

插入时,从根节点开始,被插入元素的关键码如果比根节点关键码小,则进入到左子树中执行插入操作,如果左子树不存在,则被插入元素成为左孩子;反之,进入到右子树中执行插入操作,如果右子树不存在,则被插入元素成为右孩子,如果被插入元素的关键码已经存在,则返回false。

按照 19, 27, 40, 35, 25, 10, 5, 17, 13, 7, 8 的顺序插入到二叉搜索树中,下图演示了这个过程。

 var insert_data = function(node, data){
        if(root == null){
            root = new TreeNode(data);
            return true;
        }

        if(data < node.data){
            if(node.leftChild){
                // 往左子树里插入
                return insert_data(node.leftChild, data);
            }else{
                // 创建节点并插入
                var new_node = new TreeNode(data);
                node.leftChild = new_node;
                new_node.parent = node;
                return true;
            }
        }else if(data > node.data){
            if(node.rightChild){
                // 向右子树里插入
                return insert_data(node.rightChild, data);
            }else{
                // 创建节点并插入
                var new_node = new TreeNode(data);
                node.rightChild = new_node;
                new_node.parent = node;
                return true;
            }

        }else{
            // 如果相等,说明已经存在,不能再插入
            return false;
        }
    };

    this.insert = function(data){
        return insert_data(root, data);
    };

2.3 搜索

与插入算法非常接近,仍然是从树的根节点开始,如果被搜索元素的关键码比根节点关键码小,则进入到左子树中进行搜索,若左子树不存在,返回null,如果被搜索元素的关键码比根节点关键码大,则进入到右子树中进行搜索,若右子树不存在,返回null,如果根节点的关键码和被搜索元素的关键码相同,返回这个根节点。

    var search_data = function(node, data){
        if(node == null){
            return null;
        }

        if(data == node.data){
            return node;
        }else if(data < node.data){
            return search_data(node.leftChild, data);
        }else{
            return search_data(node.rightChild, data);
        }
    };

    this.search = function(data){
        return search_data(root, data);
    };

2.4 删除

删除一个节点时,要考虑到必须将被删除节点的子孙节点连接到树上,同时保证二叉搜索树的性质。

根据被删除节点的左右子孩子,可以总结一下几种情况:

  1. 被删除节点左右孩子都不存在
  2. 被删除节点没有右孩子
  3. 被删除节点没有左孩子
  4. 被删除节点左右孩子都存在

对于第1种情况,最为简单,只需要让其父节点指向它的指针指向null即可
对于第2种情况,用左孩子替代它的位置
对于第3种情况,用右孩子替代他的位置
对于第4中情况,稍稍有些复杂,首先,去被删除节点的右子树中找到中序遍历下的第一个节点,假设节点的data数据为x,将被删除节点的data替换成x,而后,在被删除节点的右子树中执行删除x的操作

下图演示了删除3个节点的过程,分别对应了第 2 3 4 种情况

在删除27时,它左右子孩子都存在,就去右子树中找中序遍历下的第一个节点,这个节点是35,于是将27替换成35,然后在右子树中执行删除35的操作,而在右子树中,35没有左孩子,有右孩子,问题演变成了第3种删除情况。
示例代码

    var link_parent = function(parent, node, next_node){
        // 连接父节点和子节点
        if(parent==null){
            root = next_node;
            root.parent = null;
        }else{
            if(parent.leftChild && parent.leftChild.data == node.data){
                parent.leftChild = next_node;
            }else{
                parent.rightChild = next_node;
            }
        }
    };

    var remove_data = function(node, data){
        if(node==null){
            return false;
        }

        if(data < node.data){
            // 去左子树里删除
            return remove_data(node.leftChild, data);
        }else if(data > node.data){
            // 去又子树里删除
            return remove_data(node.rightChild, data);
        }else{
            if(node.leftChild && node.rightChild){
                // 左右两个子树都存在,那么,找到中序下的第一个节点,这个节点在右子树里最小
                var tmp = node.rightChild;
                while(tmp.leftChild){
                    tmp = tmp.leftChild;
                }
                // 被删除点的值等于中序下第一个节点的值
                node.data = tmp.data;
                // 去右子树里删除中序下的第一个节点
                return remove_data(node.rightChild, tmp.data);

            }else{
                var parent = node.parent;   // 找到父节点
                if(!node.leftChild){
                    // 没有左孩子
                    link_parent(parent, node, node.rightChild);
                }else{
                    link_parent(parent, node, node.leftChild);
                }
                return true
            }
        }
    };

    this.remove = function(data){
        return remove_data(root, data);
    };