进修JavaScript数据结构与算法(四):二叉搜刮树

本系列的第一篇文章: 进修JavaScript数据结构与算法(一),栈与行列
第二篇文章:进修JavaScript数据结构与算法(二):链表
第三篇文章:进修JavaScript数据结构与算法(三):鸠合
第四篇文章:进修JavaScript数据结构与算法(四):二叉搜刮树

我与二叉树的前尘往事

在刚学编程时,就晓得有一种数据结构叫“树”,树中的俊彦是“二叉树”,“红黑树”等。
听说“树”构在编程界呼风唤雨无所不能。让无数程序员心惊胆战。甚至在面试时,更是有“手写二叉树”,“翻转二叉树”等问题坐镇。

好吧,我认可这些在当时都把我吓住了。

然则当我发抖着翻开《进修JavaScript数据结构与算法》,最先敲下关于“树”的代码时,倏忽以为,彷佛也没有那末难呢。
因而心胸冲动,一口气敲完了书上的例子,半途也思索了良久,不停的在纸上演算等。但总的来讲,照样学的很高兴的。

树の简介

之前学的栈、行列、链表等数据结构,都是递次数据结构。而树,将会是我们学的第一种非递次数据结构。

放在实际里呢,有个很活泼的例子,公司组织架构图。长如许:
《进修JavaScript数据结构与算法(四):二叉搜刮树》

而我们要学的树,长如许:
《进修JavaScript数据结构与算法(四):二叉搜刮树》

节点简介

个中,树中的每一个元素,都叫做节点。从节点延长而下的,叫子节点
树顶部的节点叫根节点。每棵树只需一个根节点。(图中15就是根节点)
在节点中,有子节点的节点也称为内部节点,没有的话则被称为外部节点或许恭弘=叶 恭弘节点。
同时在节点中是有先人和子女关联的,比方节点9的先人就有13,7,6,15四个。

节点属性

深度: 节点的深度取决于其先人的数目,节点9的深度就是4。
树的高度,树的高度体现为节点深度的最大值。
比方上图,节点深度最大值为4,则树的高度为4。

二叉树与二叉搜刮树

二叉树的最大特性就在于,它的节点最多只需两个子节点:左边子节点和右边子节点。
二叉搜刮树则是二叉树的一种,但它只允许你在左边节点贮存比父节点小的值,右边只允许贮存比父节点大的值。
像适才的这幅图,就是二叉搜刮树。
《进修JavaScript数据结构与算法(四):二叉搜刮树》

而我们本文要进修的内容,就是如何写一个二叉搜刮树。

JavaScipt中二叉搜刮树的完成

起首,建立一个组织函数。

/**
 * 二叉搜刮树的组织函数
 */
function BinarySearchTree() {
  /**
   * 二叉搜刮树键的组织函数
   * @param {Number} key 要天生的键值
   */
  var Node = function(key) {
    // 键值
    this.key = key;
    // 左子节点
    this.left = null;
    // 右子节点
    this.right = null;
  }

  /**
   * 二叉树的根节点,不存在时示意为Null
   * @type {Null or Number}
   */
  var root = null;
}

在之前提到过的双向链表中,每一个节点包括两个指针,一个指向左边节点,一个指向右边节点。在二叉搜刮树中,每一个节点也有两个指针,一个指向左边子节点,一个指向右边子节点。但在二叉搜刮树中,我们把节点成为,这是术语。

二叉搜刮树须要有以下的要领:

  • insert(key): 向树中插进去一个新的键

  • inOrderTraverse(): 经由过程中序遍历体式格局,遍历一切节点

  • preOrderTranverse(): 经由过程先序遍历体式格局,遍历一切节点

  • postOrderTranverse(): 经由过程后序遍历体式格局,遍历一切节点

  • min(): 返回树中最小的值

  • max(): 返回树中最大的值

  • search(key): 搜刮某个值,在树中则返回true

  • remove(key): 从树中移除某个键

二叉搜刮树的完成,基础都与递归有关(对我来讲递归很绕,花了良久才明白)。如果不清楚递归相干观点,能够看看下面的参考链接。

什么是递归

insert要领:

申明:向树中插进去一个新的键
完成:

/**
 * 插进去某个键到二叉树中
 * @param  {Number} key 要插进去的键值
 */
this.insert = function(key) {
  // 用传入的值天生二叉树的键
  var newNode = new Node(key);

  // 根节点为Null时,传入的键则为根节点
  // 不然挪用insertNode函数来插进去子节点
  if (root === null) {
    root = newNode;
  } else {
    insertNode(root, newNode)
  }
};

/**
 * 用于插进去子节点。
 * @param  {Node} node    根节点
 * @param  {Node} newNode 要插进去的节点
 */
var insertNode = function(node, newNode) {
  //由于二叉搜刮树的性子,所以当键值小于当前地点节点的键值
  //则使得左子结点成为新的要比较的节点,举行递归挪用
  //如果左子结点为null,则将键值赋值给左子结点。
  //如果键值大于当前地点节点的键值,道理同上。
  if (newNode.key < node.key) {
    if (node.left === null) {
      node.left = newNode;
    } else {
      insertNode(node.left, newNode)
    }
  } else {
    if (node.right === null) {
      node.right = newNode
    } else {
      insertNode(node.right, newNode)
    }
  }
};

inOrderTraverse要领:

申明:经由过程中序遍历体式格局,遍历一切节点
完成:

/**
 * 中序遍历操纵,常用于排序。会把树中元素从小到大的打印出来。
 * 由于在javascript的递归中,碰到递归是,会优先挪用递归的函数。直到递归不再举行。
 * 然后会在递归挪用的末了一个函数中实行别的语句。再一层层的升上去。
 * 所以中序遍历会有从小到大的输出效果。
 * 后续的先序和后续遍历和这个道理差不多,取决于callback放在哪儿。
 * 
 * @param  {Function} callback 获取到节点后的回调函数
 */
this.inOrderTraverse = function(callback) {
  inOrderTraverseNode(root, callback);
};


/**
 * 中序遍历的辅佐函数,用于遍历节点
 * @param  {Node}   node     遍历最先的节点,默以为root
 * @param  {Function} callback 获取到节点后的回调函数
 * @return {[type]}            [description]
 */
var inOrderTraverseNode = function(node, callback) {
  // 当前节点不为NULL则继承递归挪用
  if (node != null) {
    inOrderTraverseNode(node.left, callback);
    // 获取到节点后,挪用的函数
    callback(node.key);
    inOrderTraverseNode(node.right, callback);
  }
};

如果我们这儿到场打印节点值的函数:

var printNode = function(value) {
  console.log(value);
};

inOrderTraverse(printNode) // 输出排序后树的值

preOrderTranverse要领:

申明:经由过程先序遍历体式格局,遍历一切节点
完成:

/**
 * 前序遍历操纵,常用于打印一个结构化的文档
 * @param  {Function} callback 获取到节点后的回调函数
 */
this.preOrderTranverse = function(callback) {
  preOrderTranverseNode(root, callback);
};

/**
 * 前序遍历的辅佐函数,用于遍历节点
 * @param  {Node}   node     遍历最先的节点,默以为root
 * @param  {Function} callback 获取到节点后的回调函数
 */
var preOrderTranverseNode = function(node, callback) {
  if (node != null) {
    callback(node.key);
    preOrderTranverseNode(node.left, callback);
    preOrderTranverseNode(node.right, callback);
  }
};

postOrderTranverse要领:

申明:经由过程后序遍历体式格局,遍历一切节点
完成:

/**
 * 后序遍历操纵,常用于盘算所占空间
 * @param  {Function} callback 获取到节点后的回调函数
 */
this.postOrderTranverse = function(callback) {
  postOrderTranverseNode(root, callback);
};

/**
 * 后序遍历的辅佐函数,用于遍历节点
 * @param  {Node}   node     遍历最先的节点,默以为root
 * @param  {Function} callback 获取到节点后的回调函数
 */
var postOrderTranverseNode = function(node, callback) {
  postOrderTranverseNode(node.left, callback);
  postOrderTranverseNode(node.right, callback);
  callback(node.key);
};

min要领:

申明:返回树中最小的值,由二叉搜刮树的性子易知,最左边的为最小值。则只需获得最左边的值即可。
完成:

/**
 * 返回树中最小的值
 * @return {Function} min函数的辅佐函数
 */
this.min = function() {
  return minNode(root);
};

/**
 * min函数的辅佐函数
 * @param  {Node} node 查找最先的节点,默以为root
 */
var minNode = function(node) {
  // 如果node存在,则最先搜刮。能防止树的根节点为Null的状况
  if (node) {
    // 只需树的左边子节点不为null,则把左子节点赋值给当前节点。
    // 若左子节点为null,则该节点肯定为最小值。
    while (node && node.left !== null) {
      node = node.left;
    }
    return node.key;
  }
  return null;
};

max要领:

申明:返回树中最大的值,由min函数易知,最大值在最右边。
完成:

/**
 * 返回树中最大的值
 * @return {Function} max函数的辅佐函数
 */
this.max = function() {
  return maxNode(root);
};

/**
 * max函数的辅佐函数
 * @param  {Node} node 查找最先的节点,默以为root
 * @return {Key}      节点的值
 */
var maxNode = function(node) {
  if (node) {
    while (node && node.right !== null) {
      node = node.right;
    }
    return node.key;
  }
  return null;
};

search要领:

申明: 搜刮某个值,在树中则返回true
完成:

/**
 * 搜刮某个值是不是存在于树中
 * @param  {Node} key 搜刮最先的节点,默以为root
 * @return {Function}     search函数的辅佐函数
 */
this.search = function(key) {
  return searchNode(root, key);
};

/**
 * search函数的辅佐函数
 * @param  {Node} node 搜刮最先的节点,默以为root
 * @param  {Key} key  要搜刮的键值
 * @return {Boolean}      找到节点则返回true,不然返回false
 */
var searchNode = function(node, key) {
  // 如果根节点不存在,则直接返回null
  if (node === null) {
    return false;
  } else if (key < node.key) {
    searchNode(node.left, key)
  } else if (key > node.key) {
    searchNode(node.right, key)
  } else {
    // 如果该节点值即是传入的值,返回true
    return true;
  }
};

remove要领:

申明:从树中移除某个键,要应对的场景:

  1. 只是一个恭弘=叶 恭弘节点

  2. 有一个子节点

  3. 有两个子节点的节点
    由于要敷衍差别的场景,所以这是最贫苦的要领了。让我思索了良久才明白。如果你以为看不懂的话,能够下载源代码把这一段写一遍。

完成:

/**
 * 从树中移除某个键
 * @param  {Key} key 要移除的键值
 * @return {Function}     remove函数的辅佐函数
 */
this.remove = function(key) {
  root = removeNode(root, key);
};

/**
 * remove函数的辅佐函数
 * @param  {Node} node 搜刮最先的节点,默以为root
 * @param  {Key} key   要移除的键值
 * @return {Boolean}   移除胜利则返回true,不然返回false
 */
var removeNode = function(node, key) {
  // 如果根节点不存在,则直接返回null
  if (node === root) {
    return null;
  }
  // 未找到节点前,继承递归挪用。
  if (key < node.key) {
    node.left = removeNode(node.left, key)
    return node;
  } else if (key > node.key) {
    node.right = removeNode(node.right, key)
    return node;
  } else {
    // 第一种场景:只是一个恭弘=叶 恭弘节点
    // 这类状况只须要直接把节点赋值为null即可
    if (node.left === null && node.right === null) {
      node = null;
      // 处理完直接return节点
      return node;
    }
    // 第二种场景:有一个子节点
    // 如果左节点为null,则代表右节点存在。
    // 因而把当前节点赋值为存在的那个子节点
    if (node.left === null) {
      node = node.right;
      // 处理完直接return节点
      return node;
    } else if (node.right == null) {
      node = node.left;
      // 处理完直接return节点
      return node;
    }
    // 第三种场景:有两个子节点
    // 起首到场辅佐节点,同时找寻右子节点中的最小节点
    // 并把当前节点替换为右子节点中的最小节点
    // 同时为了防止节点反复,移除右子节点中的最小节点
    var aux = findMinNode(node.right);
    node.key = aux.key;

    node.right = removeNode(node.right, aux.key);
    // 处理完直接return节点
    return node;
  }
};

/**
 * remove函数的辅佐函数
 * @param  {Node} node 查找最先的节点,默以为root
 * @return {Node}      最小的节点
 */
var findMinNode = function(node) {
  // 如果node存在,则最先搜刮。能防止树的根节点为Null的状况
  if (node) {
    // 只需树的左边子节点不为null,则把左子节点赋值给当前节点。
    // 若左子节点为null,则该节点肯定为最小值。
    while (node && node.left !== null) {
      node = node.left;
    }
    return node;
  }
  return null;
};

源代码:

源代码在此~

二叉搜刮树-源代码

感受

写文章的时刻,人有点伤风,晕晕乎乎的。不过写完以后就好多了,头脑清醒了很多。
二叉树这一章,就我而言感慨万分,也算是临时满足了本身对数据结构中“树”的憧憬与希望,也不是之前看数据结构中那种渺茫的觉得。
能用JavaScript亲手完成,照样异常高兴的。

前端路漫漫,且行且歌~

Lxxyx的前端乐土
原文链接:寒假前端进修(6)——进修JavaScript数据结构与算法(四):二叉搜刮树

    原文作者:Lxxyx
    原文地址: https://segmentfault.com/a/1190000004334208
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞