javascript实现二叉搜索树

在使用javascript实现基本的数据结构中,练习了好几周,对基本的数据结构如 栈、队列、链表、集合、哈希表、树、图等内容进行了总结并且写了笔记和代码。

在 github中可以看到  点击查看,可以关注一下我哈。

 

树的基本术语

二叉树节点的存储结构

创建一个二叉搜索树

二叉树的先序、中序、后续遍历算法

二叉树的非递归先序、中序、后续遍历算法

 

 

 

文章对树了解的不多的人有点不友好,这里简单介绍(从书上抄下来)那些基本的一点概念吧。

 

看下面这个示意图

 

 

 

树的基本术语:

结点:A、B、C等都是结点,结点不仅包含数据元素,而且包含指向子树的分支。例如,A结点不仅包含数据元素A、还包含3个指向子树的指针。

结点的度:结点拥有的子树个数或者分支的个数,例如A结点有3棵子树,所以A结点的度为3.

树的度:树中各结点度的最大值。如例子中结点度最大为3(A、D结点)。最小为0(F、G、I、J、K、L、M),所以树的度为3。

叶子节点:又叫做终端节点,指度为0的节点,F、G、I、J、K、L、M节点都是叶子节点。

孩子:结点的子树的根,如A节点的孩子为B、C、D。

双亲:与孩子的定义对应,如B C D结点的双亲都是A。

兄弟:同一个双亲的孩子之间互为兄弟。如B、C、D互为兄弟,因为它们都是A节点的孩子。

祖先:从根到某节点的路径上的所有结点,都是这个节点的祖先。如K的祖先是A B E,因为从A到K的路径为 A---B---E--K。

子孙: 以某节点为根的子树中的所有结点,都是该结点的子孙。如D的子孙为H I J M。

层次:从根开始,根为第一层,根的孩子为第二层,根的孩子的孩子为第三层,以此类推。

树的高度(或者深度):树中结点的最大层次,如例子中的树共有4层,所以高度为4.

 

理解了上面的树一些基本一些的概念后,我们来看一下什么是二叉树。

1)每个结点最多只有两棵子树,即二叉树中的节点的度只能为0、1、2

2) 子树有左右之分,不能颠倒。

 

以下二叉树的5中基本状态

 

 

构造一个二叉树的节点存储结构

我们会发现,二叉树中的存储结构一个是值,一个是左边有一个,右边有一个。他们分别叫左孩子/左子树  右孩子/右子树。

所以我们会很容易的写出来一个节点的构造函数。

1 // 树的结构
2 class BTNode {
3   constructor() {
4     this.key = key;
5     this.lchild = null;
6     this.rchild = 7   }
8 }

 

实现一个二叉搜索树 / 二叉排序树

看一下定义

二叉排序树或者是空树,或者是满足以下性质的二叉树。

1) 若它的左子树不空,则左子树上的所有关键字的值均小于根关键字的值。

2)若它的右子树不空,则右子树上所有关键字的值均大于根关键字的值。

3)左右子树又是一棵二叉排序树。

 

举个例子

假如要插入一堆数字,数字为 20 10 5 15 13 18 17 30

 

 

 

 

 那么用代码如何实现呢?

 1 let BST = (function () {
 2 
 3   let ROOT = Symbol();
 4 
 5    节点结构
 6   let BTNode = class {
 7     constructor(key) {
 8        9       10       11     }
12 13 
14    递归插入节点
15   let recursionInsert =  (root,node) {
16     if (node.key < root.key) {
17       if (root.lchild) {
18         recursionInsert(root.lchild,node);
19       } else {
20         root.lchild = node;
21       }
22     } 23        (root.rchild) {
24         recursionInsert(root.rchild,1)">25       } 26         root.rchild =27 28 29 30 
31    二叉搜索树类
32   return33     constructor() {
34       this[ROOT] = 35 36 
37      插入
38     insert(key) {
39       let node = new BTNode(key);
40       let root = this[ROOT];
41       if (!root) {
42         this[ROOT] =43         44 45        递归插入
46       recursionInsert(root,1)">47 48 49 
50 })();
51 
52 
53 let bst =  BST();
54 
55 
56 bst.insert(20);
57 bst.insert(1058 bst.insert(559 bst.insert(1560 bst.insert(1361 bst.insert(1862 bst.insert(1763 bst.insert(3064 
65 console.log(bst);

 

 在浏览器中一层一层的展开看看是否和我们的一样。

 

二叉树的遍历算法 

二叉树的遍历算法,二叉树的遍历就是按照某种规则将二叉树中的所有数据都访问一遍。

二叉树的遍历方式有先序遍历、中序遍历、后续遍历和层次遍历,很多算法都是基于这几种遍历方式衍生出来的。

递归方式的具体的代码实现

  1 let BST = (  2 
  3   let ROOT =  4 
  5     6   let BTNode =  7   8         9        10        11  12  13 
 14    15   let recursionInsert =  16      17        18  19       }  20         root.lchild = 21  22     }  23        24  25       }  26         root.rchild = 27  28  29   };
 30 
 31    用于中序遍历二叉树的方法
 32   let inorderTraversal =  33     if (!root)  34     inorderTraversal(root.lchild,arr);
 35     arr.push(root.key);
 36     inorderTraversal(root.rchild,1)"> 37  38 
 39    用于先序遍历的递归函数
 40   let preOrderTraversal =  41      42  43     preOrderTraversal(root.lchild,1)"> 44     preOrderTraversal(root.rchild,1)"> 45  46 
 47    用于后续遍历的递归函数
 48   let lastOrderTraversal =  49      50     lastOrderTraversal(root.lchild,1)"> 51     lastOrderTraversal(root.rchild,1)"> 52  53  54 
 55    56    57  58        59  60 
 61      62  63       let node =  64       let root =  65        66          67          68  69        70  71  72 
 73 
 74      中序遍历二叉树
 75     inorderTraversal() {
 76       let arr = [];
 77       inorderTraversal([ROOT],1)"> 78        arr;
 79  80 
 81      先序遍历二叉树
 82     preOrderTraversal() {
 83       let arr = 84       preOrderTraversal( 85        86  87 
 88      后续遍历
 89     lastOrderTraversal() {
 90       let arr = 91       lastOrderTraversal( 92        93  94  95 
 96  97 
 98 
 99 let bst = 100 
101 bst.insert(20102 bst.insert(15103 bst.insert(7104 bst.insert(40105 bst.insert(30106 bst.insert(45107 bst.insert(50108 
109 
110 console.log(bst);
111 
112 
113 let a = bst.inorderTraversal();
114 let b = bst.preOrderTraversal();
115 let c = bst.lastOrderTraversal();
116 
117 console.log(a);
118 console.log(b);
119 console.log(c);

 

广度优先遍历

 广度优先遍历
breadthRirstSearch() {
     初始化用于广度优先遍历的队列
    let queue =  Queue();
    console.log('根节点',[ROOT]);

    let arr = [];
    let root = [ROOT];
    ;

    queue.enqueue(root);

    while (queue.size()) {
        let queueFirst = queue.dequeue();
        arr.push(queueFirst.key);
        queueFirst.lchild && queue.enqueue(queueFirst.lchild);
        queueFirst.rchild && queue.enqueue(queueFirst.rchild);
    }

     arr;
}

 

 

二叉树遍历算法的改进

上面介绍的二傻叔的深度优先遍历算法都是用递归函数实现的,这是很低效的,原因就在于系统帮你调用了一个栈并做了诸如保护现场和恢复现场等复杂的操作,才使得遍历可以用非常简洁的代码实现。

有的人可能会想到,关于二叉树深度优先遍历算法的非递归实现和递归实现,一个是用户自己定义栈,一个是系统栈,为什么用户自己定义的栈要比系统栈执行高效?
一个较为通俗的解释是:递归函数所申请的系统栈,是一个所有递归函数都通用的栈,对于二叉树深度优先遍历算法,系统栈除了记录访问过的节点信息之外,还有其他信息需要记录,以实现函数的递归调用,用户自己定义的栈仅保存了遍历所需的节点信息,是对遍历算法的一个针对性的设计,对于遍历算法来说,显然要比递归函数通用的系统栈更高,也就是一般情况下,专业的要比通用的要好一些。


然而在实际应用中不是这样的,如尾递归在很多机器上都会被优化为循环,因此递归函数不一定就比非递归函数执行效率低。

 

栈数据结构,满足后进先出的规则用来辅佐我们遍历

 栈结构 用来辅助非递归遍历
class Stack {
  constructor() {
    this.items = [];
  }
  push(data) {
    .items.push(data);
  }
  pop() {
    return .items.pop();
  }
  peek() {
    this.items[this.items.length - 1];
  }
  size() {
    .items.length;
  }
}

非递归的先序遍历

preOrderTraversal() {
    console.log('先序遍历');
    let root =  初始化辅助遍历存储的栈
    let stack =  Stack();

    let arr = [];  用于存储先序遍历的顺序

    stack.push(root);

     如果栈不为空 则一直走
     (stack.size()) {
        let stackTop = stack.pop();

         访问栈顶元素
        arr.push(stackTop.key);
        stackTop.rchild && stack.push(stackTop.rchild);
        stackTop.lchild && stack.push(stackTop.lchild);
    }

     arr;
}

 

 非递归的中序排序

inorderTraversal() {
     初始化用于辅助排序的栈
    let stack =  Stack;

    let p = null;  用于指向当前遍历到的元素
    let arr = [];  用户记录排序的顺序
    p = [ROOT];

    while (stack.size() || p !== ) {
        while (p !== ) {
            stack.push(p);
            p = p.lchild;
        }

         如果栈不为空 出栈
         (stack.size()) {
            p = stack.pop();
            arr.push(p.key);
            p = p.rchild;
        }
    }
     arr;
}

非递归的后序排序

 arr;
}

 

 

 想一下,如果我们的插入顺序第一个数非常大,然后后面的数字都是越来越小的会有什么问题产生呢?

 

下一篇文章讲述这种问题的一个解决方案,平衡二叉树。

 

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


kindeditor4.x代码高亮功能默认使用的是prettify插件,prettify是Google提供的一款源代码语法高亮着色器,它提供一种简单的形式来着色HTML页面上的程序代码,实现方式如下: 首先在编辑器里面插入javascript代码: 确定后会在编辑器插入这样的代码: <pre
这一篇我将介绍如何让kindeditor4.x整合SyntaxHighlighter代码高亮,因为SyntaxHighlighter的应用非常广泛,所以将kindeditor默认的prettify替换为SyntaxHighlighter代码高亮插件 上一篇“让kindeditor显示高亮代码”中已经
js如何实现弹出form提交表单?(图文+视频)
js怎么获取复选框选中的值
js如何实现倒计时跳转页面
如何用js控制图片放大缩小
JS怎么获取当前时间戳
JS如何判断对象是否为数组
JS怎么获取图片当前宽高
JS对象如何转为json格式字符串
JS怎么获取图片原始宽高
怎么在click事件中调用多个js函数
js如何往数组中添加新元素
js如何拆分字符串
JS怎么对数组内元素进行求和
JS如何判断屏幕大小
js怎么解析json数据
js如何实时获取浏览器窗口大小
原生JS实现别踩白块小游戏(五)
原生JS实现别踩白块小游戏(一)