数据结构与算法(四)之树结构(偏向JS)

数据结构(四)

说明:本文基于哔哩哔哩视频【JavaScript数据结构与算法】整理

树(tree)

  1. 概念:n(n>=0) 个节点构成的有限集合

  2. 特性:非线性,一对多。查找较快

  3. 概念术语:

    • root:根,用 r 表示
    • 空树:n=0
    • 子树:subtree
    • 节点的度 degree :子树个数
    • 父节点,子节点,兄弟节点
    • 节点度: 子节点的个数
    • 树的最大度:树的深度
    • 叶子节点:没有子节点的节点
    • 路径和路径长度:路径包含的边为路径长度
    • 节点层次:规定 根节点在一层,其他的是其父节点加一
    • 树的深度:树的最大层次
  4. 实现方式:普通树结构—从上到下;儿子兄弟表示法

  5. 分类

    • 二叉树(所有的树都可以转化为二叉树)
      • 形态:空,只有左节点TL,或者只有右节点TR,有两个节点;

      • 特性

        • 一个二叉树第 i 层的最大结点数为:2^(i-1), i >= 1;
        • 深度为k的二叉树有最大结点总数为: 2^k - 1, k >= 1;
        • 对任何非空二叉树 T,若n0表示叶结点的个数、n2是度为2的非叶结点个数,那么两者满足关系n0 = n2 + 1。
      • 特殊二叉树

        • 满二叉树:又叫完美二叉树:所有的子节点都是两个
        • 完全二叉树:叶子节点的子节点只缺少 TR,但是有 TL,其他节点都是满的
      • 二叉树的存储

        • 数组:完全二叉树:从上到下,从左到右;(非完全:转化为完全二叉树,效率低,不用)
        • 链表:最常见的二叉树表示方法
      • 二叉搜索树(Binary Search Tree,简称 BST):最常用的二叉树

        • 性质:左子树 的键值小于其父节点的键值,右子树 的键值大于其父节点的键值,左右子树本身也是二叉树;(查找效率高,类似于二分查找的思想)
        • 遍历二叉树:(看什么时候处理 根节点)
          • 先序遍历:根节点—左子树—左子树— (左子树遍历完成)父节点的右子树—父节点的右子树----根节点的右子树—左子树—左子树— (左子树遍历完成)父节点的右子树—父节点的右子树
          • 中序遍历:先处理左子树,根节点中间处理
          • 后序遍历;先处理左子树和右子树,根节点最后处理
          • 层序遍历:较少用。
        • 操作方法
          • insert(key):向树中插入一个新的键。
          • search(key):在树中查找一个键,如果结点存在,则返回true;如果不存在,则返回false。
          • inOrderTraverse:通过中序遍历方式遍历所有结点。preOrderTraverse:通过先序遍历方式遍历所有结点。
          • postOrderTraverse:通过后序遍历方式遍历所有结点。
          • min:返回树中最小的值/键。
          • max:返回树中最大的值/键。
          • remove(key):从树中移除某个键(特别麻烦)找 前驱或者后继 替换当前节点。
            • 二叉树中与删除相关的概念:
              • 前驱:比当前要删除的节点小一点点的节点,左子树中的最大值
              • 后继:比当前要删除的节点大一点点的节点,右子树中的最小值
        • 优点:查询,插入删除效率特别高(取决于深度)
        • 缺点 :如果插入的是均匀分布的数(765432),深度很大
          • 平衡树:效率高O(logN)
          • 非平衡树:效率低 O(N)
  • 二叉搜索树的基本操作代码
        function BinarySearchTree() {
            // 子节点
            function Node(key) {
                this.key = key;
                this.left = null;
                this.right = null;
            }
            // 属性
            this.root = null;

            // 方法
            // 插入   ======主要用到递归
            BinarySearchTree.prototype.insert = function(key) {
                var newNode = new Node(key);
                this.root == null ?
                    (this.root = newNode) :
                    this.insertNode(this.root, newNode);
            };
            BinarySearchTree.prototype.insertNode = function(node, newNode) {
                if (newNode.key < node.key) {
                    // 向左查找
                    if (node.left == null) {
                        node.left = newNode;
                    } else {
                        this.insertNode(node.left, newNode);
                    }
                } else {
                    // 向左查找
                    if (node.right == null) {
                        node.right = newNode;
                    } else {
                        this.insertNode(node.right, newNode);
                    }
                }
            };

          
            // 获取最大值(叶节点的左边值)和最小值(叶节点的最右边)
            BinarySearchTree.prototype.max = function() {
                var node = this.root;
                while (node.right != null) {
                    node = node.right;
                }
                return node.key;
            };
            BinarySearchTree.prototype.min = function() {
                var node = this.root;
                while (node.left != null) {
                    node = node.left;
                }
                return node.key;
            };

            // 获取特定的 key
            BinarySearchTree.prototype.search = function(key) {
                var node = this.root;
                while (node != null) {
                    if (key < node.key) {
                        node = node.left;
                    } else if (key > node.key) {
                        node = node.right;
                    } else {
                        return true;
                    }
                }
                return false;
            };

            // 删除节点
            
            // 找前驱
            // 找后继
        }
         // 测试代码
        var bst = new BinarySearchTree();

        // 插入数据
        bst.insert(11);
        bst.insert(7);
        bst.insert(15);
        bst.insert(5);
        bst.insert(3);
        bst.insert(9);
        bst.insert(8);
        bst.insert(10);
        bst.insert(13);
        bst.insert(12);
        bst.insert(14);
        bst.insert(20);
        bst.insert(18);
        bst.insert(25);
        // bst.insert(6);
        console.log(bst, "insert");

        var res = "";
        // 处理遍历之后得到的每个节点
        bst.preOrderTraverse(function(key) {
            return (res += key + " ");
        });
        console.log(res, "pre");

        res = "";
        bst.midOrderTraverse(function(key) {
            return (res += key + " ");
        });
        console.log(res, "mid");

        res = "";
        bst.postOrderTraverse(function(key) {
            return (res += key + " ");
        });
        console.log(res, "post");

        console.log(bst.max(), "max");
        console.log(bst.min(), "min");
        console.log(bst.search(99), "search");


        bst.remove(9);
        bst.remove(7);
        bst.remove(15);
        console.log(bst, "remove");
        res = "";
        bst.postOrderTraverse(function(key) {
            return (res += key + " ");
        });
        console.log(res, "post111");

上述测试中最终形成的树的结构

来自网络

  • 树的遍历
 		 // 树的遍历
            // 先序遍历   用到递归(函数内部自调用) 和 闭包(函数作为参数)
            BinarySearchTree.prototype.preOrderTraverse = function(handler) {
                // 递归:依次处理子节点
                this.preOrderTraverseNode(this.root, handler);
            };
            // 11--7
            BinarySearchTree.prototype.preOrderTraverseNode = function(
                node,
                handler
            ) {
                if (node != null) {
                    // 先序  先处理经过的 父节点
                    handler(node.key);

                    // 处理经过的左节点
                    //
                    this.preOrderTraverseNode(node.left, handler);

                    // 处理经过的右节点
                    this.preOrderTraverseNode(node.right, handler);
                }
            };
            // 中序遍历
            BinarySearchTree.prototype.midOrderTraverse = function(handler) {
                // 递归:依次处理子节点
                this.midOrderTraverseNode(this.root, handler);
            };
            BinarySearchTree.prototype.midOrderTraverseNode = function(
                node,
                handler
            ) {
                if (node != null) {
                    // 处理经过的左节点
                    //
                    this.midOrderTraverseNode(node.left, handler);

                    // 中序  中间处理经过的 父节点
                    handler(node.key);

                    // 处理经过的右节点
                    this.midOrderTraverseNode(node.right, handler);
                }
            };

            // 后序遍历
            BinarySearchTree.prototype.postOrderTraverse = function(handler) {
                // 递归:依次处理子节点
                this.postOrderTraverseNode(this.root, handler);
            };
            BinarySearchTree.prototype.postOrderTraverseNode = function(
                node,
                handler
            ) {
                if (node != null) {
                    // 处理经过的左节点
                    //
                    this.postOrderTraverseNode(node.left, handler);

                    // 处理经过的右节点
                    this.postOrderTraverseNode(node.right, handler);

                    // 后序  中间处理经过的 父节点
                    handler(node.key);
                }
            };

树的三种遍历方式图解

  • 先序
    来自网络

  • 中序
    来自网络

  • 后序
    来自网络

  • 树的删除(由于比较麻烦,所以抽离出来)

 // 删除节点
            BinarySearchTree.prototype.remove = function(key) {
                var current = this.root;
                var parent = null;
                var isLeftChild = true;
                // 找到
                while (!current.key == key) {
                    parent = current;
                    if (key < current.key) {
                        current = current.left;
                    } else {
                        current = current.right;
                        isLeftChild = false;
                    }

                    // 遍历完都没有找到
                    if (current == null) return false;
                }
                // 判断有几个几点 0,1,2

                // 叶子节点
                if (current.left == null && current.right == null) {
                    // 根节点
                    if (current == this.root) {
                        this.root = null;
                    } else if (isLeftChild) {
                        parent.left = null;
                    } else {
                        parent.right = null;
                    }
                }
                // 一个子节点
                else if (current.left != null && current.right == null) {
                    if (current == this.root) {
                        this.root = current.left;
                    } else if (isLeftChild) {
                        parent.left = current.left;
                    } else {
                        parent.right = current.left;
                    }
                } else if (current.right != null && current.left == null) {
                    if (current == this.root) {
                        this.root = current.right;
                    } else if (isLeftChild) {
                        parent.left = current.right;
                    } else {
                        parent.right = current.right;
                    }
                }
                // 两个子节点
                else {
                    var successor = this.getSuccessor(current)
                    if (current == this.root) {
                        this.root = current.right;
                    } else if (isLeftChild) {
                        parent.left = successor
                    } else {
                        parent.right = successor
                    }
                    // 删除节点的左子树指向
                    successor.left = current.left
                }
            };

            // 找前驱
            // 找后继
            BinarySearchTree.prototype.getSuccessor = function(delNode) {
                // 存储找到的后继
                var successor = delNode;
                var successorParent = delNode
                    // 当前要删除的节点的后继
                var current = delNode.right;

                // 循环查找
                while (current !== null) {
                    successorParent = successor
                    successor = current;
                    current = current.left;
                }
                // 判断找到的后继是否是 delNode 的右节点
                if (successor != delNode.right) {
                    successorParent.left = successor.right
                    successor.right = delNode.right
                }

                return successor
            };
  • 为了保持二叉搜索树的平衡性,衍生出了下面两种树

    • 平衡树(AVL):应用较少,效率不比红黑树
    • 红黑树(见下章)

其他数据结构可访问以下地址:

数据结构与算法(一)之数组,队列,栈
数据结构与算法(二)之单向链表和双向链表
数据结构与算法(三)之集合,字典

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值