js高级进阶之数据结构“二叉排序树”的es5和es6实现与应用

“树”是一种较为复杂也非常重要的数据结构,特别是目前流行的vue react框架虚拟dom操作时都是以“树”为数据结构模型进行一系列的操作的,弄清楚树数据结构算法对阅读源码有很大的帮助。

认识树形结构

1.1树结构

一个树结构包含一系列存在父子关系的节点。每个节点都有一个父节点(除了顶部的第一个节点)以及零个或多个子节点:

在这里插入图片描述

1.2 树的定义

  • 节点:树中的每个元素都叫作节点;
  • 根节点:位于树顶部的节点叫作根节点;
  • 内部节点/分支节点:至少有一个子节点的节点称为内部节点或;外部节点/叶节点:没有子元素的节点称为外部节点或叶节点;
  • 子节点:P和Q为J的子节点;
  • 父节点:J为P和1Q的父节点;
  • 兄弟节点:同一个父节点的子节点互称为兄弟;P和Q互为兄弟节点
  • 祖先节点:从根节点到该节点所经过分支上的所有节点;如节点P的祖先节点为 J,E,A;
  • 子孙节点:以某一节点构成的子树,其下所有节点均为其子孙节点;如P和Q为A的子孙节点;
  • 节点所在层次:根节点为1层,依次向下;
  • 树的深度:树中距离根节点最远的节点所处的层次就是树的深度;上图中,树的深度是4;
  • 节点的度:结点拥有子结点的数量;
  • 树的度:树中节点的度的最大值;
  • 有序树:树中任意节点的子结点之间有顺序关系,这种树称为有序树;
  • 无序树:树中任意节点的子结点之间没有顺序关系,这种树称为无序树,也称为自由树
  • 二叉树:在计算机科学中,二叉树是每个结点最多有两个子树的树结构。通常子树被称作“左子树”(left subtree)和“右子树”(right subtree)。二叉树常被用于实现二叉查找树和二叉堆。
  • 二叉排序树,二叉查找树:
    二叉排序树或者是一颗空树,或者是具有下列性质的二叉树:

(1)若它的左子树不空,则左子树上的所有结点的值均小于它的根结点的值。
(2)若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值。
(3)它的左、右子树叶分别是二叉排序树。

算法结构中重点实现的就是二叉排序树/二叉查找树,以下的内容我们都是针对二叉排序树/二叉查找树进行说明;

1.3 二叉排序树的定义

下图就是一个二叉排序树(搜索树)
在这里插入图片描述

定义
二叉树是树的一种特殊情况,每个节点最多有有两个子女,分别称为该节点的左子女和右子女,就是说,在二叉树中,不存在度大于2的节点。

二叉搜索树(BST)是二叉树的一种,但是它只允许你在左侧节点存储(比父节点)小的值, 在右侧节点存储(比父节点)大(或者等于)的值。

特点
同一层,数值从左到右依次增加以某一祖先节点为参考,该节点左侧值均小于节点值,右侧值均大于节点值在二叉树的第i(i>=1)层,最多有x^(i-1)
个节点深度为k(k>=1)的二叉树,最少有k个节点,最多有2^k-1个节点
对于一棵非空二叉树,叶节点的数量等于度为2的节点数量加1。

满二叉树:深度为k的满二叉树,是有2^k-1个节点的二叉树,每一层都达到了可以容纳的最大数量的节点;

因为树有很多用法太过灵活,就前端而言最为常用的就是二叉排序树。本文后续内容只关注二叉排序树

二叉排序树的方法

  1. insert(key): 向树中插入一个新的键;
  2. inOrderTraverse: 通过中序遍历方式遍历所有节点;
  3. preOrderTraverse: 通过先序遍历方式遍历所有节点;
  4. postOrderTraverse: 通过后序遍历方式遍历所有节点;
  5. getMin: 返回树中最小的值/键;
  6. getMax: 返回树中最大的值/键;
  7. find(key): 在树中查找一个键,如果节点存在则返回该节点不存在则返回null;
  8. remove(key): 从树中移除某个键;

1.4 二叉排序树的作用

到这里,相信很多朋友已经迷糊了,既然树这么复杂,而且还有这么多类型,为什么要学习树而且还是排序二叉树。

现在我来回答这个问题,假设现在要对全班的总成绩进行录入并排名,在后一名学生成绩还未录入前求出当前已录入学生的第一名和最后一名(不要想着数据库sql查询语句实现),此时初学者想到的肯定是数组,ok我们看下如果数组来实现,每次找到第N名学生的成绩应该非常容易arry[i]就搞定,但插入时立即得出当前排序名次,然后将该名同学交换位置到排名位置,这是一件很浪费性能的事情,如果改用链表实现插入非常容易,但寻找查询成绩将消耗性能。

综上我们考虑到一种数据结构,它既满足方便的插入同时也具备快速的查找,那就是二叉排序树。

最重要的二叉排序树的前中后遍历在实际应用中非常有用。

前序遍历:快速展示结果;
中序遍历:排序输出结果;
后续遍历:实现目录结构或删除节点;

2 二叉排序树的实现

2.1二叉树基类

ES5实现:

 /**
         * 二叉排序树类
         * */
        var BinarySearchTree = function() {
            var Node = function(key,value) {
                this.key = key;
                this.value = value;
                this.left = null;
                this.right = null;
            };
            var root = null;
            this.util = util;

            this.insert = function(key,value) {
                var newNode = new Node(key,value);
                if (root === null) {
                    root = newNode;
                } else {
                    this.util.insertNode(root, newNode);
                }
            };

            // 中序遍历是一种以上行顺序访问BST所有节点的遍历方式,也就是以从最小到最大的顺序访
            // 问所有节点。中序遍历的一种应用就是对树进行排序操作。
            this.inOrderTraverse = function(cb) {
                this.util.orderTraverseNode("in",root, cb);
            };

            // 先序遍历是以优先于后代节点的顺序访问每个节点的。先序遍历的一种应用是打印一个结构化的文档。
            this.preOrderTraverse = function(cb) {
                this.util.orderTraverseNode("per",root, cb);
            };

            // 后序遍历则是先访问节点的后代节点,再访问节点本身。后序遍历的一种应用是计算一个目
            // 录和它的子目录中所有文件所占空间的大小。
            this.postOrderTraverse = function(cb) {
                this.util.orderTraverseNode("post",root, cb);
            };

            // Breadth-First-Search
            // 可以用来解决寻路径的问题。
            this.levelOrderTraverse = function(cb) {
                this.util.levelOrderTraverseNode(root, cb);
            }

            // Breadth-First-Search
            // 区分层次
            this.separateByLevel = function(cb) {
                this.util.separateByLevelFn(root, cb,"******");
            }

            this.min = function() {
                return this.util.minNode(root);
            };

            this.max = function() {
                return this.util.maxNode(root);
            };

            this.search = function(val) {
                this.util.searchNode(root, val);
            };

            this.remove = function(key) {
                root = this.util.removeNode(root, key);
            };
        };

树操作的工具方法集合:

       /**
         * 树的工具方法集合
         * */
        var util = {
            insertNode : function(node, newNode) {
                if (newNode.value < node.value) {
                    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);
                    }
                }
            },

            orderTraverseNode : function(type,node, cb) {
                if (node !== null) {
                    if(type === "in"){
                        this.orderTraverseNode(type,node.left, cb);
                        cb(node);
                        this.orderTraverseNode(type,node.right, cb);
                    }else if(type === "per"){
                        cb(node);
                        this.orderTraverseNode(type,node.left, cb);
                        this.orderTraverseNode(type,node.right, cb);
                    }else if(type === "post"){
                        this.orderTraverseNode(type,node.left, cb);
                        this.orderTraverseNode(type,node.right, cb);
                        cb(node);
                    }                   
                }
            },

            levelOrderTraverseNode : function(node, cb) {
                if (node === null) {
                    return null;
                }
                var list = [node];
                while (list.length > 0) {
                    node = list.shift();
                    cb(node);
                    if (node.left) {
                        list.push(node.left);
                    }
                    if (node.right) {
                        list.push(node.right);
                    }
                }
            },

            separateByLevelFn : function(node, cb, separator) {
                var list = [];
                var lev = 0;
                var END_FLAG = 'END_FLAG';
                list.push(node);
                list.push(END_FLAG);
                separator = separator || '---*---';
                while (list.length > 0) {
                    node = list.shift();
                    // 遇到结束信号,表示已经遍历完一层;若队列中还有元素,说明它们是刚刚遍历完的这一层的所有子元素。
                    if (node === END_FLAG && list.length > 0) {
                        lev++;
                        list.push(END_FLAG);
                        cb("第"+(lev)+"层"+separator+"有"+(list.length-1)+"个元素");
                    } else {
                        cb(node);
                        if (node.left) {
                        list.push(node.left)
                        }
                        if (node.right) {
                        list.push(node.right);
                        }
                    }
                }
            },

            minNode : function(node) {
                if (node) {
                    while (node.left !== null) {
                        node = node.left;
                    }
                    return node;
                }
                return null;
            },

            maxNode : function(node) {
                if (node) {
                    while (node.right !== null) {
                        node = node.right;
                    }
                    return node;
                }
                return null;
            },

            searchNode : function(node, val) {
                if (node === null) {
                    return false;
                }
                if (val < node.value) {
                    return this.searchNode(node.left, val);
                } else if (val > node.value) {
                    return this.searchNode(node.right, val);
                } else {
                    return true;
                }
            },

            findMinNode : function(node) {
                if (node) {
                    while (node.left !== null) {
                        node = node.left;
                    }
                    return node;
                }
                return null;
            },

            removeNode : function(node, value) {
                if (node === null) {
                    return null;
                }
                if (value < node.value) {
                    node.left = this.removeNode(node.left, value);
                    return node;
                } else if (value > node.value) {
                    node.right = this.removeNode(node.right, value);
                    return node;
                } else {
                    if (node.left === null && node.right === null) {
                        node = null;
                        return node;
                    }
                    if (node.left === null) {
                        node = node.right;
                        return node;
                    }
                    if (node.right === null) {
                        node = node.left;
                        return node;
                    }
                    var aux = this.findMinNode(node.right);
                    node.value = aux.value;
                    node.key = aux.key;
                    node.right = this.removeNode(node.right, aux.value);
                    return node;
                }
            },
            getObjType(obj) {
                if(obj === null){
                    return "null";
                }else if(typeof obj === "object"){
                    if(obj instanceof Array){
                        return "array";
                    } else if(obj instanceof RegExp){
                        return "regExp";
                    }else if(obj instanceof Date){
                        return "date";
                    }else if(obj instanceof Error){
                        return "error";
                    }  else{
                        return "object";
                    }
                }else{
                    return typeof obj;
                }
            }
        };

测试:

  tree.insert("zhan",110);
        tree.insert("wang",37);
        tree.insert("li",145);
        tree.insert("zhao",55);
        tree.insert("feng",68);
        tree.insert("sui",130);
        tree.insert("bai",46);
        tree.insert("hubf",130);

        var printNode = function(node) {
            if(this.util.getObjType(node) === "string"){
                console.log(node);
            }else if(this.util.getObjType(node) === "object"){
                console.log("key:"+node.key+"-------value:"+node.value);
            }  
        };
        console.log('中序遍历');
        tree.inOrderTraverse(printNode);
        console.log('\n');
        console.log('层级遍历');
        tree.levelOrderTraverse(printNode);
        console.log('\n')
        console.log('层级统计遍历');
        tree.separateByLevel(printNode);
        console.log('\n')        
        console.log('前序遍历');
        tree.preOrderTraverse(printNode);
        console.log('\n')        
        console.log('后序遍历');
        tree.postOrderTraverse(printNode);
        console.log('\n')        
        console.log('最大值:')
        printNode(tree.max());
        console.log('\n')        
        console.log('最小值:');
        printNode(tree.min());

结果:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
es6的实现:

“use strict”;

class Node {
constructor(key) {
this.key = key;
this.left = null;
this.right = null;
}
}

// insert(key):向树中插入一个新的键。
// search(key):在树中查找一个键,如果节点存在,则返回true;如果不存在,则返回false。
// inOrderTraverse:通过中序遍历方式遍历所有节点。
// preOrderTraverse:通过先序遍历方式遍历所有节点。
// postOrderTraverse:通过后序遍历方式遍历所有节点。
// min:返回树中最小的值/键。
// max:返回树中最大的值/键。
// remove(key):从树中移除某个键。
class BinarySearchTree {

constructor() {
  this.root = null;
}

static insertNode(node, newNode) {
  if (node.key > newNode.key) {
    if (node.left === null) {
      node.left = newNode;
    } else {
      BinarySearchTree.insertNode(node.left, newNode);
    }
  } else {
    if (node.right === null) {
      node.right = newNode;
    } else {
      BinarySearchTree.insertNode(node.right, newNode);
    }
  }
}

static searchNode(node, key) {
  if (node === null) {
    return false;
  }

  if (node.key === key) {
    return true;
  } else if (node.key > key) {
    BinarySearchTree.searchNode(node.left, key);
  } else if (node.key < key) {
    BinarySearchTree.searchNode(node.right, key);
  }
}

static inOrderTraverseNode(node, cb) {
  if (node === null) {
    return;
  }
  BinarySearchTree.inOrderTraverseNode(node.left, cb);
  cb(node.key);
  BinarySearchTree.inOrderTraverseNode(node.right, cb);
}

static preOrderTraverseNode(node, cb) {
  if (node === null) {
    return;
  }
  cb(node.key);
  BinarySearchTree.preOrderTraverseNode(node.left, cb);
  BinarySearchTree.preOrderTraverseNode(node.right, cb);
}

static postOrderTraverseNode(node, cb) {
  if (node === null) {
    return;
  }
  BinarySearchTree.postOrderTraverseNode(node.left, cb);
  BinarySearchTree.postOrderTraverseNode(node.right, cb);
  cb(node.key);
}

static levelOrderTraverseNode(node, cb) {
  if (node === null) {
    return null;
  }

  const list = [node];

  while (list.length > 0) {
    node = list.shift();
    cb(node.key);
    if (node.left) {
      list.push(node.left);
    }
    if (node.right) {
      list.push(node.right);
    }
  }
}

static separateByLevelFn(node, cb, separator = '---*---') {
  const list = [];
  const END_FLAG = 'END_FLAG';

  list.push(node);
  list.push(END_FLAG);

  while (list.length > 0) {
    node = list.shift();

    // 遇到结束信号,表示已经遍历完一层;若队列中还有元素,说明它们是刚刚遍历完的这一层的所有子元素。
    if (node === END_FLAG && list.length > 0) {
      list.push(END_FLAG);
      cb(separator);
    } else {
      cb(node.key);

      if (node.left) {
        list.push(node.left)
      }

      if (node.right) {
        list.push(node.right);
      }
    }
  }
}

static removeNode(node, key) {
  if (node === null) {
    return null;
  }

  if (node.key === key) {

    if (node.left === null && node.right === null) {
      node = null;
      return node;
    } else if (node.left === null) {
      node = node.right;
      return node;
    } else if (node.right === null) {
      node = node.left;
      return node;
    } else if (node.left && node.right) {
      let rightMinNode = node.right;

      while (rightMinNode.left !== null) {
        rightMinNode = rightMinNode.left;
      }

      node.key = rightMinNode.key;
      node.right = BinarySearchTree.removeNode(node.right, rightMinNode.key);
      return node;
    }

  } else if (node.key > key) {
    node.left = BinarySearchTree.removeNode(node.left, key);
    return node;
  } else if (node.key < key) {
    node.right = BinarySearchTree.removeNode(node.right, key);
    return node;
  }
}

static printNode(val) {
  console.log(val);
}

insert(key) {
  const newNode = new Node(key);

  if (this.root === null) {
    this.root = newNode;
  } else {
    BinarySearchTree.insertNode(this.root, newNode);
  }
}

search(key) {
  return BinarySearchTree.searchNode(key);
}

// 中序遍历是一种以上行顺序访问BST所有节点的遍历方式,也就是以从最小到最大的顺序访
// 问所有节点。中序遍历的一种应用就是对树进行排序操作。
inOrderTraverse(cb = BinarySearchTree.printNode) {
  BinarySearchTree.inOrderTraverseNode(this.root, cb);
}

// 先序遍历是以优先于后代节点的顺序访问每个节点的。先序遍历的一种应用是打印一个结构化的文档。
preOrderTraverse(cb = BinarySearchTree.printNode) {
  BinarySearchTree.preOrderTraverseNode(this.root, cb);
}

// 后序遍历则是先访问节点的后代节点,再访问节点本身。后序遍历的一种应用是计算一个目
// 录和它的子目录中所有文件所占空间的大小。
postOrderTraverse(cb = BinarySearchTree.printNode) {
  BinarySearchTree.postOrderTraverseNode(this.root, cb);
}

// Breadth-First-Search
// 可以用来解决寻路径的问题。
levelOrderTraverse(cb = BinarySearchTree.printNode) {
  BinarySearchTree.levelOrderTraverseNode(this.root, cb);
}

// Breadth-First-Search
// 区分层次
separateByLevel(cb = BinarySearchTree.printNode) {
  BinarySearchTree.separateByLevelFn(this.root, cb);
}

min() {
  let node = this.root;

  if (node === null) {
    return null;
  }

  while (node.left !== null) {
    node = node.left;
  }

  return node.key;
}

max() {
  let node = this.root;

  if (node === null) {
    return null;
  }

  while (node.right !== null) {
    node = node.right;
  }

  return node.key;
}

remove(key) {
  this.root = BinarySearchTree.removeNode(this.root, key);
}

}

export {BinarySearchTree};

总结以上二叉排序树可以方便的来处理排序问题,求最大值最小值,我对node节点的改造key–value方式可以方便传任何对象进行操作。

发布了69 篇原创文章 · 获赞 6 · 访问量 1868

猜你喜欢

转载自blog.csdn.net/weixin_40073115/article/details/103895133
今日推荐