二叉搜索树简介说明
下文笔者讲述二叉搜索树的简介说明,如下所示
二叉搜索树的简介
二叉搜索树: 是一种数据结构 用于存储数据并支持快速的插入、删除和搜索操作 二叉搜索树是一种树形结构
二叉搜索树的特点
1.每个节点最多有两个子节点 分别称为左子节点和右子节点。 2.对于每个节点,其左子节点的值小于该节点的值 右子节点的值大于该节点的值 3.中序遍历二叉搜索树可以得到有序的元素序列。 由于二叉树的某些特性导致 二叉搜索树在插入、删除和搜索操作上具有较高的效率 在平均情况下,这些操作的时间复杂度为 O(log n) 其中 n 为树中节点的数量 然而,如果树的结构不平衡 最坏情况下这些操作的时间复杂度可能会达到 O(n) 由于其高效的搜索特性 二叉搜索树常被用于实现关联数组和集合等数据结构 为了避免树的结构不平衡导致性能下降,人们也发展了平衡二叉搜索树(如红黑树、AVL树)等变种
二叉搜索树的成员变量及其构造方法
外部类成员变量有:根节点、节点类(内部类)。 外部类构造方法:默认的构造方法,对外公开二叉搜索树的核心方法。 节点类的成员变量有: key 关键字:相对比一般的二叉树 二叉搜索树可以明显提高增删查改的效率原因在于关键字 可以根据比较两个关键字的大小进行操作 value 值:作用则为存放值。 left :链接左节点。 right:链接右节点。 节点类的构造方法: 带两个参数的构造方法:参数为 key 、value 带四个参数的构造方法:参数为 key 、value 、left 、right例:代码示例
public class BinaryTree { BinaryNode root = null; static class BinaryNode { int key; Object value; BinaryNode left; BinaryNode right; public BinaryNode(int kty, Object value) { this.key = kty; this.value = value; } public BinaryNode(int key, Object value, BinaryNode left, BinaryNode right) { this.key = key; this.value = value; this.left = left; this.right = right; } } }
实现二叉树的核心接口
public interface BinarySearchTreeInterface { /** *查找 key 对应的 value */ Object get(int key); /** * 查找最小关键字对应值 */ Object min(); /** * 查找最大关键字对应值 */ Object max(); /** * 存储关键字与对应值 */ void put(int key, Object value); /** * 查找关键字的后驱 */ Object successor(int key); /** * 查找关键字的前驱 */ Object predecessor(int key); /** * 根据关键字删除 */ Object delete(int key); }
实现二叉搜索树 - 获取值 get(int key)
从根节点开始 先判断当前的节点 p.key 与 key 进行比较 当p.key > key,则向左子树下潜 p = p.left ; 当p.key < key ,则向右子树下潜 p = p.right ; 当p.key == key ,则找到到了关键字,返回该节点的值 p.value 按这样的规则一直循环下去,直到 p == null 退出循环,则说明没有找到对应的节点,则返回 null 。例
@Override public Object get(int key) { if (root == null) { return null; } BinaryNode p = root; while(p != null) { if (p.key > key) { p = p.left; }else if (p.key < key) { p = p.right; }else { return p.value; } } return null; }
二叉搜索树 - 获取最小的关键字min(BinaryNode node)
在某一个树中 需要得到最小的关键字 由根据数据结构的特点,最小的关键字在数的最左边 实现思路: 一直向左子树遍历下去 直到 p.left == null时 则该p节点就是最小的关键字 然后找到了最小的节点,返回该节点的值即可例
非递归实现: @Override public Object min() { if (root == null) { return null; } BinaryNode p = root; while(p.left != null) { p = p.left; } return p.value; } //重载了一个方法,带参数的方法。 public Object min(BinaryNode node) { if (node == null) { return null; } BinaryNode p = node; while (p.left != null) { p = p.left; } return p.value; } 递归实现: //使用递归实现找最小关键字 public Object minRecursion() { return doMin(root); } private Object doMin(BinaryNode node) { if (node == null) { return null; } if (node.left == null) { return node.value; } return doMin(node.left); }
二叉搜索树 - 获取最大的关键字 max(BinaryNode node)
在某一个树中,需要得到最大的关键字 根据数据结构的特点,最大的关键字在数的最右边 实现思路: 一直向右子树遍历下去,直到 p.right == null 时 则该 p 节点就是最大的关键字了 然后找到了最大的节点,返回该节点的值即可。例
非递归实现: @Override public Object max() { if (root == null) { return null; } BinaryNode p = root; while(p.right != null) { p = p.right; } return p.value; } //重载了一个带参数的方法 public Object max(BinaryNode node) { if (node == null) { return null; } BinaryNode p = node; while (p.right != null) { p = p.right; } return p.value; } 递归实现: //使用递归实现找最大关键字 public Object maxRecursion() { return doMax(root); } private Object doMax(BinaryNode node) { if (node == null) { return null; } if (node.right == null) { return node.value; } return doMax(node.right); }
二叉搜索树 - 增、更新 put( int key, Object value)
在二叉搜索树中 先试着查找是否存在与key对应的节点p.key 当找到时 则为更新该值 p.value = value 即可 当找不到,则需要新增该关键字节点例
@Override public void put(int key, Object value) { if (root == null) { root = new BinaryNode(key,value); return; } BinaryNode p = root; BinaryNode parent = null; while (p != null) { parent = p; if (p.key > key) { p = p.left; } else if (p.key < key) { p = p.right; }else { p.value = value; return; } } //该树没有该关键字,因此需要新建节点对象 BinaryNode newNode = new BinaryNode(key,value); if (newNode.key < parent.key) { parent.left = newNode; }else { parent.right = newNode; } }
二叉搜索树 - 查找关键字的后驱节点 successor(int key)
实现思路: 先遍历找到该关键字的节点 若找不到,则返回 null 若找到了,判断以下的两种情况 第一种情况:该节点有右子树,则该关键字的后驱为右子树的最小关键字 第二种情况:该节点没有右子树,则该关键字的后驱为从右向左而来的祖宗节点 最后返回该后驱节点的值例
@Override public Object successor(int key) { if (root == null) { return null; } //先找到该关键字节点 BinaryNode p = root; BinaryNode sParent = null; while (p != null) { if (p.key > key) { sParent = p; p = p.left; } else if (p.key < key) { p = p.right; }else { break; } } //没有找到关键字的情况 if (p == null) { return null; } //情况一:该节点存在右子树,则该后继为右子树的最小关键字 if (p.right != null) { return min(p.right); } //情况二:该节点不存在右子树,那么该后继就需要到祖宗从右向左的节点 if (sParent == null) { //可能不存在后继节点,比如最大关键字的节点就没有后继节点了 return null; } return sParent.value; }
二叉搜索树 - 查找关键字的前驱节点 predecessor(int key)
实现思路: 先对该二叉树进行遍历寻找 key 的节点 若遍历结束还没找到,则返回 null 若找到了,需要判断以下两种情况: 第一种情况:该节点有左子树,则该前驱节点为该左子树的最大关键字节点 第二种情况:该节点没有左子树,则该前驱节点为从左向右而来的祖宗节点 最后返回该前驱节点的值例
@Override public Object predecessor(int key) { if (root == null) { return null; } BinaryNode p = root; BinaryNode sParent = null; while (p != null) { if (p.key > key) { p = p.left; } else if (p.key < key) { sParent = p; p = p.right; }else { break; } } if (p == null) { return null; } //情况一:存在左子树,则该前任就为左子树的最大关键字节点 if (p.left != null) { return max(p.left); } //情况二:不存在左子树,则该前任为从祖宗自左向右而来的节点 if (sParent == null) { return null; } return sParent.value; }
二叉搜索树 - 删除关键字节点 delete(int key)
实现思路: 先遍历二叉树,查找该关键字节点 若遍历结束了还没有找到,则返回 null 若找到了,则需要以下四种情况: 第一种情况:找到该删除的节点只有左子树。则直接让该左子树 "托付" 给删除节点的双亲节点, 这就删除了该节点了。至于左子树是链接到双亲节点的左边还有右边这个问题,根据该数据结构的特点, 由该删除节点来决定。若删除的节点之前是链接该双亲节点的左边,则左子树也是链接到该双亲节点的左边; 若删除的节点之前是链接该双亲节点的右边,则左子树也是链接到该双亲节点的右边。 第二种情况:找到该删除的节点只有右子树。则直接让该右子树 "托付" 给删除节点的双亲节点, 这就删除了该节点了。至于右子树是链接到双亲节点的左边还有右边这个问题, 根据该数据结构的特点,由该删除节点来决定。若删除的节点之前是链接该双亲节点的左边, 则右子树也是链接到该双亲节点的左边;若删除的节点之前是链接该双亲节点的右边, 则右子树也是链接到该双亲节点的右边。 第三种情况:找到该删除节点都没有左右子树。 该情况可以归并到以上两种情况的任意一种处理均可。 第四种情况:找到该删除节点都有左右子树 分两步: 第一步,先找后继节点来替换删除节点,找该后继节点直接到删除节点的右子树中找最小的关键字节点即可 第二步,需要先将后继节点的右子树处理好,需要将该右子树交给替换节点的双亲节点链接 还需要判断两种情况: 第一种情况:若删除节点与替换节点是紧挨着的,对替换节点的右子树无需要求, 只对左子树重新赋值; 第二种情况:若删除节点与替换节点不是紧挨着的关系,对替换节点的左右子树都要重新赋值。例
@Override public Object delete(int key) { if (root == null) { return null; } BinaryNode p = root; BinaryNode parent = null; while (p != null) { if (p.key > key) { parent = p; p = p.left; } else if (p.key < key) { parent = p; p = p.right; }else { break; } } //没有找到该关键字的节点 if (p == null) { return null; } //情况一、二、三:只有左子树或者右子树或者都没有 if (p.right == null) { shift(parent,p,p.left); } else if (p.left == null) { shift(parent,p,p.right); }else { //情况四:有左右子树 //替换节点采用删除节点的后继节点 //先看被删的节点与替换的节点是否为紧挨在一起 BinaryNode s = p.right; BinaryNode sParent = p; while (s.left != null) { sParent = s; s = s.left; } if (sParent != p) { //说明没有紧挨在一起,则需要将替换节点的右子树进行处理 shift(sParent,s,s.right); s.right = p.right; } shift(parent,p,s); s.left = p.left; } return p.value; } private void shift(BinaryNode parent, BinaryNode delete, BinaryNode next) { if (parent == null) { root = next; } else if (parent.left == delete) { parent.left = next; }else if (parent.right == delete){ parent.right = next; } }
//使用递归实现删除关键字节点 public BinaryNode deleteRecursion(BinaryNode node , int key) { if (node == null) { return null; } if (node.key > key) { node.left = deleteRecursion(node.left,key); return node; } else if (node.key < key) { node.right = deleteRecursion(node.right,key); return node; }else { if (node.right == null) { return node.left; } else if (node.left == null) { return node.right; }else { BinaryNode s = node.right; while (s.left != null) { s = s.left; } s.right = deleteRecursion(node.right,s.key); s.left = node.left; return s; } } }
二叉搜索树 - 查找范围小于关键字的节点值 less(int key)
实现思路: 使用中序遍历,来遍历每一个节点的 key 若小于 key 的节点,直接放到数组容器中 若大于 key 的,可以直接退出循环。最后返回该数组容器即可。例
//找 < key 的所有 value public list<Object> less(int key) { if (root == null) { return null; } ArrayList<Object> result = new ArrayList<>(); BinaryNode p = root; Stack<BinaryNode> stack = new Stack<>(); while (p != null || !stack.isEmpty()) { if (p != null) { stack.push(p); p = p.left; }else { BinaryNode pop = stack.pop(); if (pop.key < key) { result.add(pop.value); }else { break; } p = pop.right; } } return result; }
二叉搜索树 - 查找范围大于关键字的节点值 greater(int key)
实现思路 使用中序遍历 遍历每一个节点的 key 若大于 key 的节点,直接放到数组容器中例
//找 > key 的所有 value public List<Object> greater(int key) { if (root == null) { return null; } ArrayList<Object> result = new ArrayList<>(); Stack<BinaryNode> stack = new Stack<>(); BinaryNode p = root; while (p != null || !stack.isEmpty()) { if (p != null) { stack.push(p); p = p.left; }else { BinaryNode pop = stack.pop(); if (pop.key > key) { result.add(pop.value); } p = pop.right; } } return result; }
方法优化: 遍历方向进行调整 先从右子树开始,再访问根节点,最后才到左子树 因此只要小于 key 的关键字节点,直接退出循环例
//改进思路:遍历方向进行调整,先从右子树开始,再访问根节点,最后才到左子树 public List<Object> greater1(int key) { if (root == null) { return null; } ArrayList<Object> result = new ArrayList<>(); Stack<BinaryNode> stack = new Stack<>(); BinaryNode p = root; while (p != null || !stack.isEmpty()) { if (p != null ) { stack.push(p); p = p.right; }else { BinaryNode pop = stack.pop(); if (pop.key > key) { result.add(pop.value); }else { break; } p = pop.left; } } return result; }
二叉搜索树 - 查找范围大于 k1 且小于 k2 关键字的节点值 between(int k1, int k2)
实现思路: 同上 注意事项: 当前节点的 key > k2 ,则可以退出循环例
//找到 >= k1 且 =< k2 的所有value public List<Object> between(int k1, int k2) { if (root == null) { return null; } ArrayList<Object> result = new ArrayList<>(); Stack<BinaryNode> stack = new Stack<>(); BinaryNode p = root; while(p != null || !stack.isEmpty()) { if (p != null) { stack.push(p); p = p.left; }else { BinaryNode pop = stack.pop(); if (pop.key >= k1 && pop.key <= k2) { result.add(pop.value); } else if (pop.key > k2) { break; } p = pop.right; } } return result; }
二叉搜索树完整代码示例
import java.util.ArrayList; import java.util.List; import java.util.Stack; public class BinaryTree implements BinarySearchTreeInterface{ BinaryNode root = null; static class BinaryNode { int key; Object value; BinaryNode left; BinaryNode right; public BinaryNode(int kty, Object value) { this.key = kty; this.value = value; } public BinaryNode(int key, Object value, BinaryNode left, BinaryNode right) { this.key = key; this.value = value; this.left = left; this.right = right; } } @Override public Object get(int key) { if (root == null) { return null; } BinaryNode p = root; while(p != null) { if (p.key > key) { p = p.left; }else if (p.key < key) { p = p.right; }else { return p.value; } } return null; } @Override public Object min() { if (root == null) { return null; } BinaryNode p = root; while(p.left != null) { p = p.left; } return p.value; } public Object min(BinaryNode node) { if (node == null) { return null; } BinaryNode p = node; while (p.left != null) { p = p.left; } return p.value; } //使用递归实现找最小关键字 public Object minRecursion() { return doMin(root); } private Object doMin(BinaryNode node) { if (node == null) { return null; } if (node.left == null) { return node.value; } return doMin(node.left); } @Override public Object max() { if (root == null) { return null; } BinaryNode p = root; while(p.right != null) { p = p.right; } return p.value; } public Object max(BinaryNode node) { if (node == null) { return null; } BinaryNode p = node; while (p.right != null) { p = p.right; } return p.value; } //使用递归实现找最大关键字 public Object maxRecursion() { return doMax(root); } private Object doMax(BinaryNode node) { if (node == null) { return null; } if (node.right == null) { return node.value; } return doMax(node.right); } @Override public void put(int key, Object value) { if (root == null) { root = new BinaryNode(key,value); return; } BinaryNode p = root; BinaryNode parent = null; while (p != null) { parent = p; if (p.key > key) { p = p.left; } else if (p.key < key) { p = p.right; }else { p.value = value; return; } } //该树没有该关键字,因此需要新建节点对象 BinaryNode newNode = new BinaryNode(key,value); if (newNode.key < parent.key) { parent.left = newNode; }else { parent.right = newNode; } } @Override public Object successor(int key) { if (root == null) { return null; } //先找到该关键字节点 BinaryNode p = root; BinaryNode sParent = null; while (p != null) { if (p.key > key) { sParent = p; p = p.left; } else if (p.key < key) { p = p.right; }else { break; } } //没有找到关键字的情况 if (p == null) { return null; } //情况一:该节点存在右子树,则该后继为右子树的最小关键字 if (p.right != null) { return min(p.right); } //情况二:该节点不存在右子树,那么该后继就需要到祖宗从右向左的节点 if (sParent == null) { //可能不存在后继节点,比如最大关键字的节点就没有后继节点了 return null; } return sParent.value; } @Override public Object predecessor(int key) { if (root == null) { return null; } BinaryNode p = root; BinaryNode sParent = null; while (p != null) { if (p.key > key) { p = p.left; } else if (p.key < key) { sParent = p; p = p.right; }else { break; } } if (p == null) { return null; } //情况一:存在左子树,则该前任就为左子树的最大关键字节点 if (p.left != null) { return max(p.left); } //情况二:不存在左子树,则该前任为从祖宗自左向右而来的节点 if (sParent == null) { return null; } return sParent.value; } @Override public Object delete(int key) { if (root == null) { return null; } BinaryNode p = root; BinaryNode parent = null; while (p != null) { if (p.key > key) { parent = p; p = p.left; } else if (p.key < key) { parent = p; p = p.right; }else { break; } } //没有找到该关键字的节点 if (p == null) { return null; } //情况一、二、三:只有左子树或者右子树或者都没有 if (p.right == null) { shift(parent,p,p.left); } else if (p.left == null) { shift(parent,p,p.right); }else { //情况四:有左右子树 //替换节点采用删除节点的后继节点 //先看被删的节点与替换的节点是否为紧挨在一起 BinaryNode s = p.right; BinaryNode sParent = p; while (s.left != null) { sParent = s; s = s.left; } if (sParent != p) { //说明没有紧挨在一起,则需要将替换节点的右子树进行处理 shift(sParent,s,s.right); s.right = p.right; } shift(parent,p,s); s.left = p.left; } return p.value; } private void shift(BinaryNode parent, BinaryNode delete, BinaryNode next) { if (parent == null) { root = next; } else if (parent.left == delete) { parent.left = next; }else if (parent.right == delete){ parent.right = next; } } //使用递归实现删除关键字节点 public BinaryNode deleteRecursion(BinaryNode node , int key) { if (node == null) { return null; } if (node.key > key) { node.left = deleteRecursion(node.left,key); return node; } else if (node.key < key) { node.right = deleteRecursion(node.right,key); return node; }else { if (node.right == null) { return node.left; } else if (node.left == null) { return node.right; }else { BinaryNode s = node.right; while (s.left != null) { s = s.left; } s.right = deleteRecursion(node.right,s.key); s.left = node.left; return s; } } } //找 < key 的所有 value public List<Object> less(int key) { if (root == null) { return null; } ArrayList<Object> result = new ArrayList<>(); BinaryNode p = root; Stack<BinaryNode> stack = new Stack<>(); while (p != null || !stack.isEmpty()) { if (p != null) { stack.push(p); p = p.left; }else { BinaryNode pop = stack.pop(); if (pop.key < key) { result.add(pop.value); }else { break; } p = pop.right; } } return result; } //找 > key 的所有 value public List<Object> greater(int key) { if (root == null) { return null; } ArrayList<Object> result = new ArrayList<>(); Stack<BinaryNode> stack = new Stack<>(); BinaryNode p = root; while (p != null || !stack.isEmpty()) { if (p != null) { stack.push(p); p = p.left; }else { BinaryNode pop = stack.pop(); if (pop.key > key) { result.add(pop.value); } p = pop.right; } } return result; } //改进思路:遍历方向进行调整,先从右子树开始,再访问根节点,最后才到左子树 public List<Object> greater1(int key) { if (root == null) { return null; } ArrayList<Object> result = new ArrayList<>(); Stack<BinaryNode> stack = new Stack<>(); BinaryNode p = root; while (p != null || !stack.isEmpty()) { if (p != null ) { stack.push(p); p = p.right; }else { BinaryNode pop = stack.pop(); if (pop.key > key) { result.add(pop.value); }else { break; } p = pop.left; } } return result; } //找到 >= k1 且 =< k2 的所有value public List<Object> between(int k1, int k2) { if (root == null) { return null; } ArrayList<Object> result = new ArrayList<>(); Stack<BinaryNode> stack = new Stack<>(); BinaryNode p = root; while(p != null || !stack.isEmpty()) { if (p != null) { stack.push(p); p = p.left; }else { BinaryNode pop = stack.pop(); if (pop.key >= k1 && pop.key <= k2) { result.add(pop.value); } else if (pop.key > k2) { break; } p = pop.right; } } return result; } }
版权声明
本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。