【数据结构】二叉搜索树的功能实现详解

2024-09-03 06:20

本文主要是介绍【数据结构】二叉搜索树的功能实现详解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 二叉搜索树
  • 查找
  • 插入
  • 删除
    • 找到要删除的节点
    • 删除节点
      • 1. 要删除节点的左孩子为空
      • 2. 要删除节点的右孩子为空
      • 3. 要删除的节点的左右孩子都不为空
    • 完整代码

二叉搜索树

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:

  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 它的左右子树也分别为二叉搜索树image.png|545

其中序遍历是一颗有序的树

查找

二叉搜索树的查找效率非常高

  • 因为二叉搜索树的左边都比我小,右边都比我大
  • 要找比我小的树,就只需要在左树中找,直接最多可以去掉一半的数据
  • 每次到达一个根节点都可以一次性排除掉最多一半的数据

时间复杂度:

  • 最好情况下: O ( l o g N ) O(logN) O(logN)
  • 最坏情况下: O ( N ) O(N) O(N),单分支,将整棵树遍历完

因为这颗二叉搜索树是由一个一个节点构成的,所以先定义出节点

  • 左孩子
  • 右孩子
  • 节点的值
    并定义出头结点
public class BinarySearchTree {  static class TreeNode {  public int val;  public TreeNode left;  public TreeNode right;  public TreeNode(int val) {  this.val = val;  }   }  public TreeNode root = null;  
}

每次去看一下 curval 和我们要找的 key 的大小关系

  1. 如果 cur.val < key,那么就往右边走
  2. 如果 cur.val > key,那么就往左边走
  3. 如果 cur.val = key,那么就找到了|468
public TreeNode search(int key) {  TreeNode cur = root;  while(cur != null) {  if(cur.val < key) {  cur = cur.right;  } else if (cur.val > key) {  cur = cur.left;  }else  return cur;  }    return null;  
}

插入

所有的插入都是插入到了叶子节点

  1. 原来的树为空,则直接插入

  2. 当树不为空时
    若要找到需要插入到的叶子结点的位置,就需要定位到最后父亲节点的叶子结点为 null 的时候。但当 cur 走到叶子结点的时候,就找不到此叶子结点的父亲节点了,所以需要多一个 parent 节点,用来记录当前的父亲节点,好方便随时可以定位到目标叶子结点的父亲节点,后续通过父亲节点进行赋值操作

public void insert(int key) {  TreeNode node = new TreeNode(key);  TreeNode cur = root;  TreeNode parent = null;  while (cur != null) {  if (cur.val < key) {  parent = cur;  cur = cur.right;  } else if (cur.val > key) {  parent = cur;  cur = cur.left;  } else {  return;  }    }    //此循环走完,parent 指向的节点就是需要插入的节点位置的父亲节点  if (parent.val > key) {  parent.left = node;  } else (parent.val < key) {  parent.right = node;  }
}
  • 值相同的时候,不能进行重复插入
  • while 循环结束,cur 指向要插入的叶子结点,parent 指向需要插入的节点的父亲节点
  • 之后对父亲节点和 key 进行比较,选择插入哪一边

删除

删除包含很多种情况

  1. 需要删除的节点的左孩子为空
  2. 需要删除的节点的右孩子为空
  3. 需要删除的节点的左右孩子都不为空

找到要删除的节点

首先需要找到需要删除的节点

public void remove(int key) {  TreeNode cur = root;  TreeNode parent = null;  while (cur != null) {  if(cur.val < key) {  parent = cur;  cur = cur.right;  } else if (cur.val > key) {  parent = cur;  cur = cur.left;  }else {  //此时就是找到了要删除的节点  removeNode(parent,cur);  return;  }    }
}
  • 当执行到 else 的时候,就是找到要删除的节点了
  • 随后完善删除操作==> removeNode

删除节点

1. 要删除节点的左孩子为空

  1. curroot,则 root = cur.right image.png|214

  1. cur 不是 rootcurparent.left,则 parent.left = cur.rightimage.png|297

  1. cur 不是 rootcurparent.right,则 parent.right = cur.rightimage.png|365
// 1.当要删除的节点 cur 的左孩子为空  
if (cur.left == null) {  if (cur == root) {  // 1.1 要删除的 cur 为根节点  root = cur.right;  } else if (cur == parent.left) {  // 1.2 要删除的 cur 是 parent 的左节点  parent.left = cur.right;  } else {  // 1.3 要删除的 cur 是 parent 的右节点  parent.right = cur.right;  }  
}

2. 要删除节点的右孩子为空

  1. curroot,则 root = cur.leftimage.png|208

  1. cur 不是 rootcurparent.left,则 parent.left = cur.leftimage.png|304

  1. cur 不是 rootcurparent.right,则 parent.right = cur.leftimage.png|268
// 2. 要删除的节点 cur 的右孩子为空  
else if (cur.right == null) {  if (cur == root) {  // 2.1 要删除的 cur 是根节点  root = cur.left;  } else if (cur == parent.left) {  // 2.2 要删除的 cur 是 parent 的左节点  parent.left = cur.left;  } else {  // 2.3 要删除的 cur 是 parent 的右节点  parent.right = cur.left;  }  
}

3. 要删除的节点的左右孩子都不为空

需要使用 替换法 进行删除

  1. 在它的右子树中寻找一个最小的节点,用它的值填补到被删除节点中,再来处理该结点的删除问题

    • 因为要删除的节点 cur 左边都比它小,右边都比它大,所以就cur 的右边找到一个最小的节点,然后让目标节点覆盖掉 cur
    • 目标节点不会出现左右孩子都存在的情况。要么两边都为空,要么还存在一个右节点。(既然此节点是最小的,就不可能还有左子树,因为左子树肯定比此节点小)image.png
  2. 在它的左子树中寻找一个最大的节点,用它的值填补到被删除节点中,再来处理该结点的删除问题

    • 此时这个最大值一定是在左树的最右边,意味着它肯定没有右子树

所以找到最小值的特征是:

  • 此节点左子树为空,且一定在 cur 右树最左边
  • 此节点右子树为空,且一定在 cur 左树最右边

寻找右子树的最小值

// 3.1 右数的最小值  
TreeNode t = cur.right;  
TreeNode tp = cur;  
while (t.left != null) {  tp = t;  t = tp.left;  
}  
cur.val = t.val;  
if(t == tp.right) {  
//t 和 tp 在起始步就找到了最小值tp.right = t.right;  
}else{  
//t 和 tp 在继续移动的过程中找到最小值tp.left = t.right;  
}
  • t 是用来定位最小值的,当 t.left == null 的时候,t 就是最小值
  • tp 是用来定位 t 的父节点的,方便后续对节点进行删除(因为节点的删除都要依靠删除节点的父节点进行“改变连接对象”)
  • 在没找到最小节点之前,tpt 一起进行移动
    1. 最开始 tp 在要删除的节点 cur 的位置,tcur 的右节点(起始步)
    2. tp 走到 t 的位置
    3. t 再走向 tp 的左节点(一轮移动结束)
    4. t.left != nulltp 走到 t 的位置
    5. t 再走向 tp 的左节点(一轮移动结束)
  • 如果在起始步就满足 t.left == null 了,则直接进行

完整代码

public void remove(int key) {  TreeNode cur = root;  TreeNode parent = null;  while (cur != null) {  if (cur.val < key) {  parent = cur;  cur = cur.right;  } else if (cur.val > key) {  parent = cur;  cur = cur.left;  } else {  //此时就是找到了要删除的节点  removeNode(parent, cur);  return;  }  }  
}  private void removeNode(TreeNode parent, TreeNode cur) {  // 1.当要删除的节点 cur 的左孩子为空  if (cur.left == null) {  if (cur == root) {  // 1.1 要删除的 cur 为根节点  root = cur.right;  } else if (cur == parent.left) {  // 1.2 要删除的 cur 是 parent 的左节点  parent.left = cur.right;  } else {  // 1.3 要删除的 cur 是 parent 的右节点  parent.right = cur.right;  }  // 2. 要删除的节点 cur 的右孩子为空  } else if (cur.right == null) {  if (cur == root) {  // 2.1 要删除的 cur 是根节点  root = cur.left;  } else if (cur == parent.left) {  // 2.2 要删除的 cur 是 parent 的左节点  parent.left = cur.left;  } else {  // 2.3 要删除的 cur 是 parent 的右节点  parent.right = cur.left;  }  // 3. 要删除的节点的左右孩子都不为空  } else {  // 3.1 右数的最小值  TreeNode t = cur.right;  TreeNode tp = cur;  while (t.left != null) {  tp = t;  t = tp.left;  }  cur.val = t.val;  if(t == tp.right) {  //t 和 tp 在起始步就找到了最小值  tp.right = t.right;  }else{  //t 和 tp 在继续移动的过程中找到最小值  tp.left = t.right;  }  }  
}

这篇关于【数据结构】二叉搜索树的功能实现详解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1132265

相关文章

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

认识、理解、分类——acm之搜索

普通搜索方法有两种:1、广度优先搜索;2、深度优先搜索; 更多搜索方法: 3、双向广度优先搜索; 4、启发式搜索(包括A*算法等); 搜索通常会用到的知识点:状态压缩(位压缩,利用hash思想压缩)。

hdu1240、hdu1253(三维搜索题)

1、从后往前输入,(x,y,z); 2、从下往上输入,(y , z, x); 3、从左往右输入,(z,x,y); hdu1240代码如下: #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#inc

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【数据结构】——原来排序算法搞懂这些就行,轻松拿捏

前言:快速排序的实现最重要的是找基准值,下面让我们来了解如何实现找基准值 基准值的注释:在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。 在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。 快速排序实现主框架: //快速排序 void QuickSort(int* arr, int left, int rig

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time