前端开发-- 二叉树的相关算法

二叉树和二叉搜索树

二叉树中的节点最多只能有2个子节点:一个是左侧子节点,另外一个是右侧子节点。
二叉搜索树(BST)是二叉树的一种,但是它只允许你在左侧节点存储(比父节点)小的值,在右侧节点存储(比父节点)大(或等于)的值
创建二叉搜索树
1.我们要先声明二叉搜索树的结构:

function BinarySearchTree(){
  let Node = function(key){
    this.key = key;
    this.left = null;
    this.right = null;
  };
  let root = null;
  function show(){
    return this.key;
  }
}
二叉搜索树的结构

上面的数据结构,和链表很相似,都是东莞指针表示节点之间的关系(术语称其为边),对于树来说,我们也是通过指针来指定下一个元素的,但是从图中,我们也看到了,节点的指针,一个指向左侧子节点,另外一个指向右侧的子节点,其中,键是树相关术语对节点的称呼。

向树中插入一个人键(节点)

向树中插入一个新的节点,要经历三个步骤
第一步:创建用来表示新节点的Node的实例,只要向构造函数中传入我们想要传入的节点的值,做指针和右指针会由构造函数自动的设置为null
第二步:需要验证这个插入的操作是否为一种特殊的情况,这个特殊的情况就是我们插入的节点是树的第一个节点,这个时候只需要将根节点指向新节点。
第三步:将节点加在非根节点的其他位置。

let insert = function(key){
  let newNode = new Node(key);
  if(root === null){
    root = newNode;
  }else{
    insertNode(root,newNode)
  }
};
//当为非空树的时候,我们进行节点的插入的时候,进行的步骤
let indsertNode = function(node,newNode){
  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);
    }
  }
 }

首先我们先构造出一个二叉搜索树:

let tree = new BinarySearchTree();
tree.insert (11);
tree.insert (7);
tree.insert (15);
tree.insert (5);
tree.insert (3);
tree.insert (9);
tree.insert (8);
tree.insert (10);
tree.insert (13);
tree.insert (12);
tree.insert (14);
tree.insert (20);
tree.insert (18);
tree.insert (25);

我们现在构造出来的二叉树是:

插入二叉树的结构

当我们想要插入一个键值为6的键,执行下面的代码:

tree.insert(6);

看一下我们究竟是怎么插入的:

插入二叉搜索树

中序遍历

中序遍历是一种以上行顺序访问BST所有节点的遍历方式,也就是是从小到大的顺序访问所有的节点,让我们看看具体的实现过程。

let inOrderTraverse = function (root){
  inOrderTraverseNode(root);
}

inOrderTraverse方法接受一个根节点作为参数。在回调函数中定义我们对遍历到的每一个节点进行操作。由于我们在BST中最常实现的算法就是递归。

var inOrderTraverseNode = function(node){
  if(node !== null){
    inOrderTraverseNode(node.left);
    console.log(node.show());
    inOrderTraverseNode(node.right);
  }
};

tree.inOrderTraverse (tree.root);
中序遍历的示意图

最终输出的结果是:3 5 6 7 8 9 10 11 12 13 14 15 18 20 25

先序遍历

先序遍历是以优先于后代节点的顺序访问每一个节点,先序遍历的一种应用是打印一个结构化的文件。我们看一下代码的实现方式

let preOrderTraverse = function (root){
  preOrderTraverseNode(root);
}

先序遍历和中序遍历的不同点是:先序遍历会访问节点的本身,然后访问它的左侧节点,最后是右侧节点,而中序遍历,先访问左子节点,自身,右子节点,还是上面的二叉搜索树,我们对其进行先序遍历。

var preOrderTraverseNode = function(node){
  if(node !== null){
     console.log(node.show());
    preOrderTraverseNode (node.left);
    preOrderTraverseNode (node.right);
  }
};

tree.preOrderTraverse (tree.root);
先序遍历

最终输出的结果是:11 7 5 3 6 9 8 10 15 13 12 14 20 18 25

后序遍历

后续遍历就是先访问节点的后代节点,再访问节点本身,后序遍历的一种应用是计算一个目录和它的子目录中所有的文件所占空间的大小。

let postOrderTraverse = function (root){
  postOrderTraverseNode(root);
}
var postOrderTraverseNode = function(node){
  if(node !== null){
    postOrderTraverseNode (node.left);
    postOrderTraverseNode (node.right);
    console.log(node.show());
  }
};

tree.preOrderTraverse (tree.root);
后序遍历

搜索树中的值

搜索树上的最大值和最小值
在上面的二叉搜索树中我们可以清楚的看见,最左侧的子节点的值是最小的,最右侧的子节点的值是最大的,这条信息为我们实现二叉搜索树上的最大值和最小值提供了很大的帮助。

查找最大值最小值

现在编写一下查找最小值的代码:

let min = function (){
  return minNode(root);
};

let minNode = function(node){
  if(node){
    while(node && node.left !== null){
      node = node.left;
    }
    return node.key;
  }
  return null;
}

minNode方法允许我们从树中任意一个节点开始寻找最小的键,我们可以使用来找到一棵树或是他子树最小的键。因此我们在调用minNode方法的时候传入了树的根节点,最终体现的结果是我们找到了最左边的子节点。类似的我们可以找到树中的最大值,代码如下:

let max= function (){
  return maxNode(root);
};

let maxNode= function(node){
  if(node){
    while(node && node.right !== null){
      node = node.right;
    }
    return node.key;
  }
  return null;
}

搜索特定的值
当我们想要查找在二叉树中存不存在一个特定的值的时候,我们需要搜索二叉树,现在看一下我们的搜索过程。

let search = function(key){
  return searchNode(root,key);
};

let searchNode = function(node, key){
  if(node === null){
    return false;
  }
  if(key < node.key){
    return searchNode(node.left,key);
  } else if(key > node.key){
    return searchNode(node.right,key);
  }else{
    return true;
  }
}

searchNode方法可以用来寻找一棵树或是它的任意子树中的一个特定的值,这也是为什么在调用的时候传入一个节点作为参数。我们在程序开始的时候,需要判断节点的合法性,如果是null的话,说明要找的键没有找到,返回false。如果传入的节点不为null,就要比较节点的键值和传入值的键值大小。如果传入值要大,那就向右边遍历二叉搜索树,否则,就向左边遍历二叉树。

移除节点

移除节点是我们在二叉搜索树中最复杂的一个,首先我们先创建这个方法。

let remove = function(key){
  root = removeNode (root,key);
};

这个方法接收要移除的键并且调用了removeNode方法,传入root和要移除的键作为参数。我们在移除的过程中,root被赋予removeNode方法的返回值,是有一定的意义的。我们会在下面进行详细的讲述。

let removeNode = function(node, key){
  if (node === null){
    return null;
  }
  if(key < node.key){
    node.left = removeNode (node.left, key);
    return mdoe;
  }else if(key > node.key){
    node.right = removeNode(node.right, key);
    return node;
  }else{  // 键等于node.key
    // 第一种情况-- 一个叶节点
    if(node.left === null && node.right === null){
      node = null;
      return node;
    }
    // 第二种情况-- 一个只有一个子节点的节点
    if(node.left=== null){
      node = node.right;
      return node;
    }else if(node.right === null){
      node = node.left;
      return node;
   }
   // 第三种情况-- 一个有两个子节点的节点
    let aux = findMinNode(node.right);
    node.key = aux.key;
    node.right = removeNode (node.right,aux.key);
    return node;
  }
}

检测到的节点是null,说明键值不存在于树中,返回null。我们要做的第一件事情就是从树中找到这个节点。因此,如果要找的键比当前节点的值小,就沿着树的左边一直找到下一个节点,如果找到的键比当前节点的键值大,那么就沿着树的右边找到下一个节点。
如果我们要找到的键(键和node.key相等),据需要三种不同的情况

let findMinNode = function (node){
  while(node && node.left !== null ){
    node = node.left;
  }
  return node;
}

移除一个叶节点
这种情况是最简单最易懂的删除方式。
我们要做的就是给这个节点赋予null值移除它。但是当学习了链表的实现之后,我们知道仅仅是赋予了一个null值是不够的,还需要处理指针,这个节点没有任何的子节点,但是有一个父节点,需要通过返回null来将对应的父节点指针赋予null值。
现在节点的值已经是null,父节点指向它的指针也会接受到这个值,这也是我们要在函数中返回节点的值的原因。父节点总是会接受到函数的返回值。另外一种可行的办法是将父节点和节点本身都作为参数传入方法的内部。

删除一个叶节点

移除有一个左侧或右侧子节点的节点
现在让我们来看第二种情况,移除一个有左侧子节点,或是右侧子节点的节点,在这种情况下,我们需要跳过这个节点,直接将父节点指向它指针指向的子节点。
如果这个节点没有左侧子节点,也就是它只有一个右侧子节点,因此我们那对他的引用改成了对它右侧子节点的引用,并返回更新后的节点。现在让我们以一张图片的形式来展现移除只有一个左子节点或右侧子节点的节点的过程。

移除只有一个子节点的节点

移除有两个子节点的节点
现在是第三种情况,也就是最复杂的情况,那么就是要移除节点的两个子节点-- 左侧子节点和右侧子节点。要移除有两个知己诶单的节点的时候,需要执行四个步骤:
(1)找到需要删除的节点。找到他右边子树最小的节点(来代替该节点的位置)
(2)然后,用他右边子树最小的节点来更新这个节点的值,通过这一步,我们改变了这个节点的键,也就是说,他被移除了
(3)但是,这样的话,树上就有两个拥有相同键的节点了,怎样是不行的,要继续把右子树的最小节点进行移除,毕竟它已经被移到被删除的节点的位置了。
(4) 最后,向它的父节点返回更新后的节点的引用。
findMinNode方法的实现和min方法的实现方式是一样的,唯一的不同之处在于,在min方法中只返回键,但是findMinNode返回了节点。

移除有两个子节点的节点
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容

  • 上一篇文章讲述了树的概念, 特征以及分类, 旨在让我们理解什么是树, 树的一些常用的概念是什么,树的分类有哪些等。...
    DevCW阅读 1,937评论 4 10
  • 如需转载, 请咨询作者, 并且注明出处.有任何问题, 可以关注我的微博: coderwhy, 或者添加我的微信: ...
    coderwhy阅读 8,665评论 12 35
  • 树的概述 树是一种非常常用的数据结构,树与前面介绍的线性表,栈,队列等线性结构不同,树是一种非线性结构 1.树的定...
    Jack921阅读 4,373评论 1 31
  • 【颜语心诗】保险事业的三七开。三分在产品学习,七分在市场开发。三分在钱的功能,七分在人性把握。三分在促成,七分在服...
    颜丙翔阅读 127评论 0 0
  • 昨天听了卢福汉老师讲广府文化清明禁忌,原来清明一定得祭自己的祖先。作为祖籍扬州的我,是否应该先自觉学些规矩了。那,...
    禅静一生阅读 835评论 1 4