二叉树和二叉查找树--基本方法

二叉树每个节点的子节点不允许超过两个。通过将子节点的个数限定为2,可以写出高效的程序在树中插入、查找和删除数据。

二叉查找树是一种特殊的二叉树,相对较小的值保存在左节点中,较大的值保存在右节点中。

那么,如何实现二叉查找树呢?

1、定义Node对象

Node 对象既保存数据,也保存和其他节点的链接(left 和right),show() 方法用来显示保存在节点中的数据。

function Node(element, left, right) {
        this.element = element;
        this.left = left;
        this.right = right;
        this.show = show;
    }

    function show() {
        return this.element;
    }

接下来,创建一个类,用来表示二叉查找树(BST)。我们让类只包含一个数据成员:一个表示二叉查找树根节点的Node 对象。该类的构造函数将根节点初始化为null,以此创建一个空节点。

function BST() {
        this.root = null;
}

后面的方法都是放在BST这个函数里的,在外部通过创建新的对象进行函数的调用。

2、插入节点

(1)创建一个Node对象,将数据传入该对象保存。当然,在此之前我们已经将根节点初始化为空,this.root=null;

(2)判断根节点是否为空,如果为空,说明二叉树中没有数据,直接让当前节点node等于根节点root;

(3)如果根节点不为空,就需要设置一个父节点parent,将父节点的值存入buffer。

这里我觉得还是举一个例子比较好理解,如果我现在只有一个根节点23,现在我要插入45,这个我们能够知道它是要放在根节点的右节点上的,那么,计算机怎么知道呢?

在这里,我的parent是不是应该等于根节点23,满足循环条件,buffer也等于23,node的元素是45,它和parent的元素,谁大谁小?很明显,node元素大于parent元素,这个时候应该是直接找parent的右节点,为什么呢?因为我需要继续和node元素比较,才能够确定node元素最终应该插入的位置。但是现在parent的右节点很明显已经为空,我现在只需要直接将之前暂存parent的buffer的右节点让它等于当前节点就可以了,然后再跳出这个循环,到此,45就已经插入成功了。

或许,到这里,还是不能够很好理解,所以,接下来我们再插入一个节点32,同样的道理,parent的初值是不是还是根节点23,满足while循环,buffer也等于了23。node节点元素32比parent节点元素23大,所以parent应该等于它的右边节点,parent的右边节点就是我们上面插入的45,所以parent现在等于45不为空继续循环;

buffer的值在这里就发生改变了,buffer现在应该等于45了,再来比较此时的node元素32和parent元素45,应该是node元素要小,对吧?那么,现在就应该是parent等于parent左节点了,parent的左节点为空,所以此时的parent为空,那么就令buffer的左节点指向当前节点node,45的左节点就是32,如此,32也成功插入了。

this.insert = function (element) {
            //创建新的节点
            var node = new Node(element, null, null);
            if (this.root == null) {
                this.root = node;
            }
            else {
                //设置父节点
                var parent = this.root;
                while (true) {
                    var buffer = parent;
                    if (node.element > parent.element) {
                        //和根节点的右边节点进行比较
                        parent = parent.right;
                        if (parent == null) {
                            buffer.right = node;
                            break;
                        }

                    } else {
                        //和根节点的左边节点进行比较
                        parent = parent.left;
                        if (parent == null) {
                            buffer.left = node;
                            break;
                        }
                    }
                }

            }
        }

3、先序遍历

关于先序遍历,中序遍历,后序遍历无非就是遍历的顺序不同,这里就不再详说了。

(1)根节点;

(2)左节点;

(3)右节点。

this.preOrder=function (node) {
                if (!(node == null)) {
                    console.log(node.show() + " ");
                    this.preOrder(node.left);
                    this.preOrder(node.right);
                }
            }

4、中序遍历

this.inOrder = function (node) {
                if (!(node == null)) {
                    this.inOrder(node.left);
                    console.log(node.show() + " ");
                    this.inOrder(node.right);
                }
            };

5、后序遍历

this.postOrder=function(node) {
                if (!(node == null)) {
                    this.postOrder(node.left);
                    this.postOrder(node.right);
                    console.log(node.show() + " ");
                }
            }

6、查找节点

首先,按照一般的思路,我们应该是从根节点开始找的,所以先让node等于根节点,添加一个while循环,再分情况讨论:

(1)如果node为空,说明在二叉树中并没有找到这个元素,返回node;

(2)如果node元素和当前元素相等,说明找到了和它相等的元素,直接返回node;

(3)如果当前元素小于node元素,说明我应该继续在比node小的节点中找,即node的左节点,这里即为重新给node赋值,以此循环,node = node.left,直到出现(1)(2)中的一种情况为止;

(4)同(3)是一样的,唯一不同的是(3)是比node小,这里是比node大。

this.find = function (element) {
            var node = this.root;
            while (true) {
                if (node == null) {
                    return node;
                }
                if (node.element == element) {
                    return node;
                } else if (element < node.element) {
                    node = node.left;
                } else if (element > node.element) {
                    node = node.right;
                }
            }
        }

7、最大值

第一步是要找到当前元素的节点,通过while循环判断,只要当前节点的右节点不为空,就把当前节点的右节点赋值给当前节点,那么最后返回的node就是最大值了,毕竟我的右边已经没有节点了。

this.getMax = function (element) {
            var node = this.find(element);
            while(node.right!=null){
                node = node.right;
            }
            return node;
        }

8、最小值

与上面的最大值问题是一样的思路,这里就不再过多阐述了。

this.getMin = function (element) {
            var node = this.find(element);
            while (node.left != null) {
                node = node.left;
            }
            return node;
        }

9、删除节点

这个方法应该是二叉树中最为复杂的了,当然了,这里并不是说它难,就是要分很多情况讨论。

下面我们通过一个实例来更好的理解如何删除二叉查找树节点:

《二叉树和二叉查找树--基本方法》

(1)首先,找到当前节点node的位置(调用查找节点的方法,具体实现可以看看上面讲过的find方法);

(2)如果当前节点node为空,说明我的二叉查找树中没有这个节点,直接返回;

比如说,我要删除15,通过查找二叉树中并没有这个节点,那么就应该直接返回。

(3)如果当前节点node的父节点为空,说明当前节点node是根节点;

    <1>  a:如果node的右节点不为空,让当前节点的右节点成为新的根节点,让新的根节点的父节点为空;

            b:获得根节点右边节点的最小值,目的是为了,如果原有根节点有左节点,那么右边节点的最小值需要让原有根节点的      左节点成为它的左节点;

    比如要删除根节点23,很明显它的父节点为空,右节点不为空,那么,此时的45就应该是新的根节点,它的父节点应该为空;

    找到根节点右边节点的最小值,根据图中,我们可以知道,这个值应该是37,接下来,就需要37的左节点等于根节点的左节点16,如此,就把根节点23删除了。

       《二叉树和二叉查找树--基本方法》

    <2>如果node的右节点为空,这就简单了,因为node就是根节点,直接让根节点node的左节点等于根节点,如果node.left不为空时,让新的根节点的父节点为空。为什么要加上不为空的这个条件呢?是因为如果我根节点的左节点为空,在它之前我已经让新的根节点等于了根节点的左节点,现在的根节点已经是一个空值,如果再给它的父节点定义,很明显会报错。

(4)如果当前节点node的父节点不为空,说明node不是根节点,定义一个parent,让它等于当前节点的父节点;

    <1> 如果当前节点node元素大于parent节点元素

    a:如果当前节点node的右节点不为空,找到右边节点的最小值,让最小值的左节点等于node的左节点,node左节点的父节点等于最小值;parent的右节点等于node的右节点,node右节点的父节点等于parent;

    还是最初的那个例子,现在我要删除45这个节点,它的父节点是23不为空,parent应该是等于45的父节点,所以parent为23;

    当前节点45是大于parent节点元素23的,同时45的右节点不为空,根节点右边节点的最小值为37,37即为node的左节点,37的父节点就是node;23的右节点等于node的右节点,即99,99的父节点等于23,所以现在99已经占据了45本来的位置,45被成功移除,37成了99的左节点。

    《二叉树和二叉查找树--基本方法》

    b:如果当前节点node的右节点为空,当前节点node的左节点成为parent的右节点,如果当前节点的左节点不为空,当前节点node的左节点的父节点等于parent。   

    这个就要以删除99为例,和上面一样的方法,这里就不再详说了。

    <2> 如果当前节点元素小于parent节点元素

    a:如果当前节点node的右节点不为空,找到右边节点的最小值,让最小值的左节点等于node的左节点,node左节点的父节点等于最小值;parent的左节点等于node的右节点,node右节点的父节点等于parent;

    b:如果当前节点node的右节点为空,当前节点node的左节点成为parent的左节点,如果当前节点的左节点不为空,当前节点node的左节点的父节点等于parent。 

    怎么说了,这个其实上面<1>中的方法差不多,只是改了两处地方。

    若是删除16这个节点,它的父节点是23不为空,parent应该是等于16的父节点,所以parent为23;

    当前节点16是小于parent节点元素23的,同时16的右节点不为空,根节点右节点的最小值为22,22即为node的左节点,22的父节点就是node;23的左节点等于node的右节点,即22,22的父节点等于23,所以现在22已经占据了16本来的位置,16被成功移除,3成了22的左节点。

    《二叉树和二叉查找树--基本方法》

        this.remove = function (element) {
            var node = this.find(element);
            if (node == null)
                return;
            if (node.parent == null) {
                if (node.right != null) {
                    this.root = node.right;
                    this.root.parent = null;

                    var minNode = this.getMin(node.right.element);
                    minNode.left = node.left;
                } else {
                    this.root = node.left;
                    this.root.parent = null;
                }

            }else{
                var parent = node.parent;
                if(node.element>parent.element){

                    if(node.right!=null){
                        var minNode = this.getMin(node.right.element);
                        minNode.left=node.left;
                        node.left.parent=minNode;

                        parent.right=node.right;
                        node.right.parent=parent;
                    }else{
                        parent.right=node.left;

                        if(node.left!=null)
                        node.left.parent=parent;
                    }

                }else{
                    if(node.right!=null){
                        var minNode = this.getMin(node.right.element);

                        minNode.left=node.left;
                        node.left.parent=minNode;

                        parent.left=node.right;
                        node.right.parent=parent;
                    }else{
                        parent.left=node.left;
                        if(node.left!=null)
                        node.left.parent=parent;
                    }
                }
            }
        }

10、中序遍历forEach

            this.inOrder = function (node) {
                if (node!= null) {
                    this.inOrder(node.left);
                    console.log(node.show() + " ");
                    this.inOrder(node.right);
                }
            };

            this.inOrderforEach = function (node,call) {
                if (node!= null) {
                    this.inOrder(node.left,call);
                    call(node);
                    this.inOrder(node.right,call);
                }
            };

            this.forEach=function (call,type) {
                if(type==1){
                    this.inOrderforEach(this.root,call);
                }
            }

这个方法是对中序遍历的扩展,通过调用创建对象,调用上面的forEach方法,在这里传入type等于1,调用inOrderforEach方法。将根节点传给node,回调后面的方法输出根节点,至于其他节点,还是调用的中序遍历的inOrder方法。

nums是创建的对象,调用forEach的方法:

        nums.forEach(function (node) {
            console.log(node.show() + " ");
        },1);

 

    原文作者:二叉查找树
    原文地址: https://blog.csdn.net/weixin_38629529/article/details/81171245
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞