• 数据结构之二叉搜索树


    在学习完二叉树的基本特性后,我们再增加一个限制就可以形成一个二叉搜索树

    二叉搜索树,又称二叉排序树或二叉查找树;

    其的本质就是一颗二叉树,可以为空,当不为空时需满足以下要求:

    1、非空左子树的所有键值小于其根结点的键值。

    2、非空右子树的所有键值大于其根结点的键值。

    3、左、右子树本身也都是二叉搜索树。


    二叉树的常见操作

    首先封装一个BinarySearchTree的类,来实现树的功能。

    还要封装一个结点类,来存储结点数据,和分别指向左子树和右子树。

    代码:

    1. //结点类
    2. class Node{
    3. constructor(key){
    4. this.data=key
    5. this.left=null
    6. this.right=null
    7. }
    8. }
    9. //二叉树类
    10. class BinarySearchTree{
    11. constructor(){
    12. this.root=null
    13. }
    14. }

    常见操作:

    在BinarySerachTree类中封装:

    添加新结点insert(key)

    思路:1、首先 new 结点类得到一个新的结点

    2、判断根结点是否为空,如果根结点为空,直接将新创建的结点加在根结点上

    3、如果root不为空时,我们就要封装一个insertNode 方法判断新建的结点放在左子树还是右子树上,并且判断对应的结点是否为空,如果不为空继续自调用寻找位置插入。

    代码实现:

    1. class Node {
    2. constructor(data) {
    3. this.left = null;
    4. this.data = data;
    5. this.right = null;
    6. }
    7. }
    8. class BinarySearchTree{
    9. constructor() {
    10. this.root = null;
    11. }
    12. //添加新结点
    13. insert(ele) {
    14. // 创建新节点
    15. let newnode = new Node(ele);
    16. // console.log(newnode);
    17. if (this.root == null) { // 空树
    18. this.root = newnode
    19. } else {
    20. this.insertNode(this.root, newnode)
    21. }
    22. }
    23. //封装判断加在左,还是右的方法
    24. insertNode(root,newnode){
    25. //根结点的值大于新结点的
    26. // console.log(root,newnode);
    27. if (newnode.datadata) {
    28. if (root.left==null) {
    29. root.left=newnode
    30. }else{
    31. //左子树不为空时,函数自调用再进行判断,当前左子树上的结点和新建的结点传入比较
    32. this.insertNode(root.left,newnode)
    33. }
    34. }else{
    35. //小于等于的情况 添加到右子树
    36. if (root.right==null) {
    37. root.right=newnode
    38. }else{
    39. this.insertNode(root.right,newnode)
    40. }
    41. }
    42. }
    43. }
    44. let bst=new BinarySearchTree()
    45. bst.insert(10)
    46. bst.insert(1)
    47. bst.insert(15)
    48. bst.insert(8)
    49. bst.insert(7)
    50. console.log(bst);

    遍历二叉搜索树

    二叉树的遍历常见的有三种方式: 先序遍历/中序遍历/后续遍历. (还有程序遍历, 使用较少, 可以使用队列来完成)

    先序遍历步骤:1、访问根结点   2、先序遍历其左子树   3、先序遍历其右子树(根左右)

    中序遍历步骤:1、中序遍历其左子树   2、访问根结点    3、中序遍历其右子树  (左根右)

    后续遍历步骤:1、后序遍历其左子树    2、后序遍历其右子树   3、访问根结点   (左右根)

    先序遍历代码:

    1. preOrderTraversal(){
    2. this.preOrderTranversalNode(this.root)
    3. }
    4. preOrderTranversalNode(root){
    5. if (root!=null) {
    6. //根
    7. console.log(root.data)
    8. //左
    9. this.preOrderTranversalNode(root.left)
    10. //右
    11. this.preOrderTranversalNode(root.right)
    12. }
    13. }

    中序遍历代码:

    1. inOrderTraversal(){
    2. this.inOrderTraversalNode(this.root)
    3. }
    4. inOrderTraversalNode(root){
    5. if (root!=null) {
    6. //左
    7. this.inOrderTraversalNode(root.left)
    8. //根
    9. console.log(root.data)
    10. //右
    11. this.inOrderTraversalNode(root.right)
    12. }
    13. }

    后续遍历代码:

    1. postOrderTraversal(){
    2. this.postOrderTraversalNode(this.root)
    3. }
    4. postOrderTraversalNode(root){
    5. if (root!=null) {
    6. //左
    7. this.postOrderTraversalNode(root.left)
    8. //右
    9. this.postOrderTraversalNode(root.right)
    10. //根
    11. console.log(root.data)
    12. }
    13. }

    获取最大值和最小值

    获取搜索二叉树最值得方法非常简单,因为二叉树得结构为左中右的数值依次变大,即最左边最小,最右边最大。

    求最小值只需要找到最左边那个,代码:

    1. min(){
    2. let node=this.root
    3. while(node.left!=null){
    4. node=node.left
    5. }
    6. return node.data
    7. }

    求最大值只需要找到最大那个,代码:

    1. max(){
    2. let node=this.root
    3. while(node.right!=null){
    4. node=node.right
    5. }
    6. return node.data
    7. }

     

    搜索特定值

    二叉搜索树的查询效率非常的快,这里我们可以通过两种方式实现,1、递归  2、循环,具体用那种视情况而定

    1、递归求特定值

    1. search(el){
    2. return this.searchnode(this.root,el)
    3. }
    4. searchnode(root,el){
    5. //判断是否为空
    6. if (root==null) {
    7. return false
    8. }
    9. //判断在左还是在右
    10. if (root.data>el) {
    11. //左
    12. return this.searchnode(root.left,el)
    13. }else if (root.data
    14. //右
    15. return this.searchnode(root.right,el)
    16. }else{
    17. //找到了
    18. return true
    19. }
    20. }

    2、循环求值

    1. searchc(el){
    2. let node=this.root
    3. while (node!=null) {
    4. if (node.data>el) {
    5. //左
    6. node=node.left
    7. }else if(node.data
    8. //右
    9. node=node.right
    10. }else{
    11. //相等的情况
    12. return true
    13. }
    14. }
    15. return false
    16. }

    二叉搜索树节点的删除

    可分为三种情况:

    1、该节点是叶结点(没有子节点, 比较简单) 没有子节点的根节点

    2、该节点有一个子节点(也相对简单)

    3、该节点有两个子节点.(情况比较复杂)

    在删除节点之前,我们要先做一件事就是找到需要删除的节点,并且要让其的父节点断开连接,所以我们要定义的变量存放当前结点的父结点,因为二叉树没有办法直接取到父结点。

    查找的代码:

    1. remove(el) {
    2. let current = this.root
    3. let parent = null
    4. let isLeftChild = null
    5. //判断根是否为空的情况
    6. if (this.root == null) {
    7. return false
    8. }
    9. while (current.data != el) {
    10. parent = current
    11. if (key < current.data) {
    12. isLeftChild = true
    13. current = current.left
    14. } else {
    15. isLeftChild = false
    16. current = current.right
    17. }
    18. // 如果发现current已经指向null, 那么说明没有找到要删除的数据
    19. if (current === null) return false
    20. }
    21. //找到要删除的结点 current现在就指向该结点
    22. }

    逐一编写

    1、叶子节点的情况

    直接将其父结点指向它的链指向为空

    1. if (current.left === null && current.right === null) {
    2. if (current == this.root) {
    3. this.root == null
    4. } else if (isLeftChild) {
    5. parent.left = null
    6. } else {
    7. parent.right = null
    8. }
    9. }

    2、该节点有一个子节点

    这个时候要将删除结点的父结点指向它改为指向它的子节点

    1. else if (current.right === null) {
    2. //current存在左子树
    3. if (current == this.root) {
    4. this.root = current.left
    5. } else if (isLeftChild) {
    6. //current为左结点
    7. parent.left = current.left
    8. } else {
    9. parent.right = current.left
    10. }
    11. } else if (current.left === null) {
    12. //current存在右子树
    13. if (current == this.root) {
    14. this.root = current.right
    15. } else if (isLeftChild) {
    16. parent.left = current.right
    17. } else {
    18. parent.right = current.right
    19. }
    20. }

    3、该节点有两个子节点

    这种情况相对来说是有点复杂的,如果我们要删除的节点有两个子节点, 甚至子节点还有子节点, 这种情况下我们需要从下面的子节点中找到一个节点, 来替换当前的节点,使其任然符合二叉搜索树的规则。即非空左子树的所有键值小于其根结点的键值,非空右子树的所有键值大于其根结点的键值。

    但是找到的这个节点有什么特征呢?

    应该是current节点下面所有节点中最接近current节点的

    这个节点怎么找呢?

    比current小一点点的节点, 一定是current左子树的最大值。

    比current大一点点的节点, 一定是current右子树的最小值。

    而在二叉搜索树中, 这两个特别的节点, 有两个特别的名字

    比current小一点点的节点, 称为current节点的前驱

    比current大一点点的节点, 称为current节点的后继

    那么删除有两个子节点的current, 要么找到它的前驱, 要么找到它的后继来代替current。

    寻找后继的代码:

    1. getsuccessor(delNode){
    2. var successorParent = delNode
    3. var current = delNode.right // 要从右子树开始找
    4. // 2.寻找节点
    5. while (current != null) {
    6. successorParent = current
    7. current = current.left
    8. }
    9. // 3.如果是删除结点的后续不为它的右结点时
    10. if ( current != delNode.right) {
    11. successorParent.left = current.right
    12. current.right = delNode.right
    13. }
    14. return current
    15. }

    将找到的后续结点返回给remove方法中有两个子节点的情况,代码:

    1. else {
    2. // 1.获取后继节点
    3. var successor = this.getSuccessor(current)
    4. // 2.判断是否是根节点
    5. if (current == this.root) {
    6. this.root = successor
    7. } else if (isLeftChild) {
    8. parent.left = successor
    9. } else {
    10. parent.right = successor
    11. }
    12. // 3.将删除节点的左子树赋值给successor
    13. successor.left = current.left
    14. }

  • 相关阅读:
    代码随想录算法训练营第五十九天| 647. 回文子串 516.最长回文子序列
    vue3异步加载js文件
    go语言中的读写操作以及文件的复制
    问卷调查小程序功能清单
    《Python趣味工具》——ppt的操作(1)
    图扑软件智慧能源一体化管控平台
    企业数字化转型从哪些系统入门比较好
    GUI进阶:用Java实现可DIY的音乐计算器v0.33
    在Docker里安装FastDFS分布式文件系统详细步骤
    对汉诺塔n=4的情况进行解析
  • 原文地址:https://blog.csdn.net/m0_59345890/article/details/126513563