数据结构-06 二叉树和二叉树查找

一,树的定义

树由一组以边连接的节点组成。。一棵树最上面的节点称为根节点,如果一个节点下面连接多个节点,那么该节点称为父节点,它下面的节点称为子节点。一个节点可以有0 个、1 个或多个子节点。没有任何子节点的节点称为叶子节点。



沿着一组特定的边,可以从一个节点走到另外一个与它不直接相连的节点。从一个节点到另一个节点的这一组边称为路径,在图中用虚线表示。以某种特定顺序访问树中所有的节点称为树的遍历。

树可以分为几个层次,根节点是第0 层,它的子节点是第1 层,子节点的子节点是第2层,以此类推。树中任何一层的节点可以都看做是子树的根,该子树包含根节点的子节点,子节点的子节点等。我们定义树的层数就是树的深度。

二,二叉树和二叉查找树

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

一个父节点的两个子节点分别称为左节点和右节点。在一些二叉树的实现中,左节点包含一组特

定的值,右节点包含另一组特定的值。


当考虑某种特殊的二叉树,比如二叉查找树时,确定子节点非常重要。二叉查找树是一种
特殊的二叉树,相对较小的值保存在左节点中,较大的值保存在右节点中。这一特性使得

查找的效率很高,对于数值型和非数值型的数据,比如单词和字符串,都是如此。

(1)实现二叉查找树

二叉查找树由节点组成,所以我们要定义的第一个对象就是Node,该对象和前面介绍链表

时的对象类似。Node 类的定义如下:

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

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

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

首先要创建一个Node 对象,将数据传入该对象保存。

其次检查BST 是否有根节点,如果没有,那么这是棵新树,该节点就是根节点,这个方法到此也就完成了;否则,进入下一步。

如果待插入节点不是根节点,那么就需要准备遍历BST,找到插入的适当位置。该过程类似于遍历链表。用一个变量存储当前节点,一层层地遍历BST。

进入BST 以后,下一步就要决定将节点放在哪个地方。找到正确的插入点时,会跳出循环。查找正确插入点的算法如下。

  1. 设根节点为当前节点。
  2. 如果待插入节点保存的数据小于当前节点,则设新的当前节点为原节点的左节点;反之,执行第4 步。
  3. 如果当前节点的左节点为null,就将新的节点插入这个位置,退出循环;反之,继续执行下一次循环。
  4. 设新的当前节点为原节点的右节点。
  5. 如果当前节点的右节点为null,就将新的节点插入这个位置,退出循环;反之,继续执行下一次循环。
function Node(data, left, right) {
            this.data = data;
            this.left = left;
            this.right = right;
            this.show = show;
        }
        function show() {
            return this.data;
        }
        function BST() {
            this.root = null;
            this.insert = insert;this.inOrder = inOrder;
        }
        function insert(data) {
            var n = new Node(data, null, null);
            if (this.root == null) {
                this.root = n;
            }
            else {
                var current = this.root;
                var parent;
                while (true) {
                    parent = current;
                    if (data < current.data) {
                        current = current.left;
                        if (current == null) {
                            parent.left = n;
                            break;
                        }
                    }
                    else {
                        current = current.right;
                        if (current == null) {
                            parent.right = n;
                            break;
                        }
                    }
                }
            }
        }
(2)遍历二叉查找树
有三种遍历BST 的方式:中序、先序和后序。中序遍历按照节点上的键值,以升序访问BST 上的所有节点。先序遍历先访问根节点,然后以同样方式访问左子树和右子树。后序遍历先访问叶子节点,从左子树到右子树,再到根节点。

中序遍历:该方法需要以升序访问树中所有节点,先访问左子树,再访问根节点,最后访问右子树。

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

先序遍历:

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

inOrder() 和preOrder() 方法的唯一区别,就是if 语句中代码的顺序。在inOrder()方法中,show() 函数像三明治一样夹在两个递归调用之间;在preOrder() 方法中,show()函数放在两个递归调用之前。



后序遍历:

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

三,在二叉查找树上进行查找

对BST 通常有下列三种类型的查找:

  1. 查找给定值;
  2. 查找最小值;
  3. 查找最大值。
(1)查找最小值和最大值

查找BST 上的最小值和最大值非常简单。因为较小的值总是在左子节点上,在BST 上查找最小值,只需要遍历左子树,直到找到最后一个节点。

该方法沿着BST 的左子树挨个遍历,直到遍历到BST 最左边的节点

 function getMin() {
            var current = this.root;
            while (!(current.left == null)) {
                current = current.left;
            }
            return current.data;
        }

在BST 上查找最大值,只需要遍历右子树,直到找到最后一个节点,该节点上保存的值即为最大值。

        function getMax() {
            var current = this.root;
            while (!(current.right == null)) {
                current = current.right;
            }
            return current.data;
        }

(2)查找给定值

在BST 上查找给定值,需要比较该值和当前节点上的值的大小。通过比较,就能确定如果给定值不在当前节点时,该向左遍历还是向右遍历。

        function find(data) {
            var current = this.root;
            while (current != null) {
                if (current.data == data) {
                    return current;
                }
                else if (data < current.data) {
                    current = current.left;
                }
                else {
                    current = current.right;
                }
            }
            return null;
        }
如果找到给定值,该方法返回保存该值的节点;如果没找到,该方法返回null。


四,从二叉查找树上删除节点

从BST 上删除节点的操作最复杂,其复杂程度取决于删除哪个节点。如果删除没有子节点的节点,那么非常简单。如果节点只有一个子节点,不管是左子节点还是右子节点,就变得稍微有点复杂了。删除包含两个子节点的节点最复杂。

为了管理删除操作的复杂度,我们使用递归操作,同时定义两个方法:remove() 和removeNode()。

从BST 中删除节点的第一步是判断当前节点是否包含待删除的数据,如果包含,则删除该节点;如果不包含,则比较当前节点上的数据和待删除的数据。如果待删除数据小于当前节点上的数据,则移至当前节点的左子节点继续比较;如果删除数据大于当前节点上的数据,则移至当前节点的右子节点继续比较。

如果待删除节点是叶子节点(没有子节点的节点),那么只需要将从父节点指向它的链接指向null。

如果待删除节点只包含一个子节点,那么原本指向它的节点久得做些调整,使其指向它的子节点。

最后,如果待删除节点包含两个子节点,正确的做法有两种:要么查找待删除节点左子树上的最大值,要么查找其右子树上的最小值。这里我们选择后一种方式。我们需要一个查找子树上最小值的方法,后面会用它找到的最小值创建一个临时节点。将临时节点上的值复制到待删除节点,然后再删除临时节点。


整个删除过程由两个方法完成。remove() 方法只是简单地接受待删除数据,调用removeNode()

删除它,后者才是完成主要工作的方法。两个方法的定义如下:

        function remove(data) {
            root = removeNode(this.root, data);
        }
        function removeNode(node, data) {
            if (node == null) {
                return null;
            }
            if (data == node.data) {
                 // 没有子节点的节点
                if (node.left == null && node.right == null) {
                    return null;
                }
                 // 没有左子节点的节点
                if (node.left == null) {
                    return node.right;
                }
                 // 没有右子节点的节点
                if (node.right == null) {
                    return node.left;
                }
                // 有两个子节点的节点
                var tempNode = getSmallest(node.right);
                node.data = tempNode.data;
                node.right = removeNode(node.right, tempNode.data);
                return node;
            }
            else if (data < node.data) {
                node.left = removeNode(node.left, data);
                return node;
            }
            else {
                node.right = removeNode(node.right, data);
                return node;
            }
        }

五,计数

BST 的一个用途是记录一组数据集中数据出现的次数。比如,可以使用BST 记录考试成绩的分布。给定一组考试成绩,可以写一段程序将它们加入一个BST,如果某成绩尚未在BST 中出现,就将其加入BST;如果已经出现,就将出现的次数加1。

我们来修改Node 对象,为其增加一个记录成绩出现频次的成员,同时我们还需要一个方法,当在BST 中发现某成绩时,需要将出现的次数加1,并且更新该节点。

        function update(data) {
            var grade = this.find(data);
            grade.count++;
            return grade;
        }
        function prArray(arr) {
            console.log(arr[0].toString() + ' ');
            for (var i = 1; i < arr.length; ++i) {
                console.log(arr[i].toString() + ' ');
                if (i % 10 == 0) {
                    console.log("\n");
                }
            }
        }
        function genArray(length) {
            var arr = [];
            for (var i = 0; i < length; ++i) {
                arr[i] = Math.floor(Math.random() * 101);
            }
            return arr;
        }









评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值