八大数据结构——二叉搜索树(七)
树是数据结构中非常重要的一项,有关树的数据结构有许多种,本文重点研究的是二叉搜索树。
二叉搜索树定义:
二叉查找树(Binary Search Tree),(又:二叉搜索树,二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。
树结构,顾名思义,就像一颗树一样,从根开始,一直往上不断有分支,只不过这里的数是倒过来的,根在最上面。
二叉搜索树就是其中一种,它有以下 特性 :
1.它是一个二叉树,二叉树就是一个父节点,它最多只有两个子节点。
2.这个树上任意一个节点,左子节点一定小于父节点。
3.这个树上任意一个节点,右子节点一定大于父节点。
优点:
1.有链表的快速插入与删除操作的特点。
2.又有数组快速查找的优势。
缺点:
1.二叉搜索树的构造与插入顺序有关。
2.某些情况下,有可能会退化成链表,且指针还浪费空间。
适用场景:
大量数据的存储和操作。它的构建,插入,删除,查询的复杂度都在O(
log
2
\log_2
log2N)~O(N),之间。
Java代码实现
下面通过Java来实现一个二叉搜索树,基于jdk1.8。
1.首先把树节点定义出来。
class BSTNode<T extends Comparable>{
public T val;//存储数据
public BSTNode<T> leftNode;//左子节点
public BSTNode<T> rightNode;//右子节点
}
2.定义二叉搜索树的属性。
private BSTNode<T> root;//根节点
private int size;//记录已存储元素个数
3.构造方法和一些常用方法。
public BinarySearchTree(){
root = null;
size = 0;
}
public BinarySearchTree(T val){
root = new BSTNode<>();
root.val = val;
size = 1;
}
public boolean isEmpty(){
return size==0;
}
public int size(){return size;}
4.查询操作。查询从根节点开始,通过不断比较一直往下走,大于当前节点就往右子树的方向走,小于当前节点就往左子树的方向走。
private BSTNode<T> find(T val){
if(root==null||val==null){
return null;
}
BSTNode<T> node = root;
while (node!=null){
if(node.val.compareTo(val)==0){
return node;
}else if(node.val.compareTo(val)>0){
node = node.leftNode;
}else {
node = node.rightNode;
}
}
return null;
}
private BSTNode<T> parent(BSTNode<T> current){
if(root==null||current==null){
return null;
}
BSTNode<T> node = root;
while (node!=null){
if(node.leftNode==current||node.rightNode==current){
return node;
}else if(node.val.compareTo(current.val)>0){
node = node.leftNode;
}else {
node = node.rightNode;
}
}
return null;
}
// 返回父节点的值
public T parent(T val){
BSTNode<T> node = parent(find(val));
return node==null?null:node.val;
}
// 返回左子节点的值
public T leftNode(T val){
BSTNode<T> node = find(val);
return node!=null&&node.leftNode!=null?node.leftNode.val:null;
}
// 返回右子节点的值
public T rightNode(T val){
BSTNode<T> node = find(val);
return node!=null&&node.rightNode!=null?node.rightNode.val:null;
}
// 返回根节点的值
public T getRoot(){
return root==null?null:root.val;
}
// 查询是否包含数据
public boolean contain(T val){
return find(val)!=null;
}
5.插入操作,插入同查询的逻辑一样,插入是插入到树的最下方,成为叶子节点,并且不允许有重复的值。
// 插入数据
public boolean insert(T val){
// 如果val为null直接返回
if(val==null){
return false;
}
// 如果root为null,那新建一个节点,并让保存为root
if(root==null){
root = new BSTNode<>();
root.val=val;
size++;
return true;
}
// root不为null,那就通过循环找到正确的插入位置
BSTNode<T> node = root;
while (true){
// 如果val与已有值相等,则不允许插入
if(node.val.compareTo(val)==0){
return false;
}
// val值小于当前节点,则插入到左子树
if(node.val.compareTo(val)>0){
// 如果左子节点为空,则插入到此处
if(node.leftNode==null){
BSTNode<T> leftNode = new BSTNode<>();
leftNode.val = val;
node.leftNode = leftNode;
size++;
return true;
}else {
// 不为空继续往下找
node = node.leftNode;
}
// val值大于当前节点,则插入到右子数
}else {
// 如果右子节点为空,则插入到此处
if(node.rightNode==null){
BSTNode<T> rightNode = new BSTNode<>();
rightNode.val = val;
node.rightNode = rightNode;
size++;
return true;
}else {
// 不为空继续往下找
node = node.rightNode;
}
}
}
}
6.删除操作,删除比较复杂一点。我们将要删除的节点分为三种情况:
1.度为0,就是子节点个数为0 。
2.度为1,就是子节点个数为1 。
3.度为2,就是子节点个数为2 。
具体操作:
1.找到要删除的节点。
2.如果要删除的节点度为2,那么
(1)先找到要删除节点的左子树中,最大的节点。
(2)交换要删除节点与其左子树中,最大的节点,的值。
(3)删除其左子树中,最大的节点。
3.如果要删除的节点度为0或1,那么
(1)获取要删除节点的父节点。
(2)获取要删除节点的子节点。
(3)如果要删除节点的父节点为空,那说明要删除的节点为根节点,那么将根节点的指针指向要删除节点的子节点。
(4)如果要删除节点的父节点不为空,那么判断要删除节点是其父节点的左子节点还是右子节点,然后将其父节点的左子指针或右子指针指向要删除节点的子节点。
// 要删除的节点有三种情况,度为2,度为1和度为0的
if(node.leftNode!=null&&node.rightNode!=null){
// 度为2的节点,删除步骤为
// 1.找到要删除节点的左子数中最大节点。
// 2.替换待删除节点的值为这个最大节点值
// 3.将这个最大节点删除
BSTNode<T> maxNode = node.leftNode;
while (maxNode.rightNode!=null){
maxNode = maxNode.rightNode;
}
remove(maxNode.val);
node.val=maxNode.val;
}else {
// 度为1和度为2的节点一起处理,分两种情况。
// 1.要删除的节点为根节点。
// 2.要删除的节点为非根节点。
BSTNode<T> parent = parent(node);
BSTNode<T> child = node.leftNode == null?node.rightNode:node.leftNode;
// 根节点无父节点,直接将根节点指向子节点即可。
if(parent==null){
root = child;
}else {
// 非根节点,找到它是父节点的左子节点,还是右子节点,然后通过修改父节点指针即可。
if(parent.leftNode!=null&&parent.leftNode==node){
parent.leftNode = child;
}else {
parent.rightNode = child;
}
}
}
size--;
return true;
}
完整代码
//二叉搜索树
public class BinarySearchTree<T extends Comparable> {
private BSTNode<T> root;//根节点
private int size;//记录已存储元素个数
public BinarySearchTree(){
root = null;
size = 0;
}
public BinarySearchTree(T val){
root = new BSTNode<>();
root.val = val;
size = 1;
}
public boolean isEmpty(){
return size==0;
}
public int size(){return size;}
private BSTNode<T> find(T val){
if(root==null||val==null){
return null;
}
BSTNode<T> node = root;
while (node!=null){
if(node.val.compareTo(val)==0){
return node;
}else if(node.val.compareTo(val)>0){
node = node.leftNode;
}else {
node = node.rightNode;
}
}
return null;
}
private BSTNode<T> parent(BSTNode<T> current){
if(root==null||current==null){
return null;
}
BSTNode<T> node = root;
while (node!=null){
if(node.leftNode==current||node.rightNode==current){
return node;
}else if(node.val.compareTo(current.val)>0){
node = node.leftNode;
}else {
node = node.rightNode;
}
}
return null;
}
// 返回父节点的值
public T parent(T val){
BSTNode<T> node = parent(find(val));
return node==null?null:node.val;
}
// 返回左子节点的值
public T leftNode(T val){
BSTNode<T> node = find(val);
return node!=null&&node.leftNode!=null?node.leftNode.val:null;
}
// 返回右子节点的值
public T rightNode(T val){
BSTNode<T> node = find(val);
return node!=null&&node.rightNode!=null?node.rightNode.val:null;
}
// 返回根节点的值
public T getRoot(){
return root==null?null:root.val;
}
// 插入数据
public boolean insert(T val){
// 如果val为null直接返回
if(val==null){
return false;
}
// 如果root为null,那新建一个节点,并让保存为root
if(root==null){
root = new BSTNode<>();
root.val=val;
size++;
return true;
}
// root不为null,那就通过循环找到正确的插入位置
BSTNode<T> node = root;
while (true){
// 如果val与已有值相等,则不允许插入
if(node.val.compareTo(val)==0){
return false;
}
// val值小于当前节点,则插入到左子树
if(node.val.compareTo(val)>0){
// 如果左子节点为空,则插入到此处
if(node.leftNode==null){
BSTNode<T> leftNode = new BSTNode<>();
leftNode.val = val;
node.leftNode = leftNode;
size++;
return true;
}else {
// 不为空继续往下找
node = node.leftNode;
}
// val值大于当前节点,则插入到右子数
}else {
// 如果右子节点为空,则插入到此处
if(node.rightNode==null){
BSTNode<T> rightNode = new BSTNode<>();
rightNode.val = val;
node.rightNode = rightNode;
size++;
return true;
}else {
// 不为空继续往下找
node = node.rightNode;
}
}
}
}
// 删除数据
public boolean remove(T val){
// 先通过循环找到要删除的节点
BSTNode<T> node = root;
while (node!=null&&node.val.compareTo(val)!=0) {
if(node.val.compareTo(val)>0){
node = node.leftNode;
}else if(node.val.compareTo(val)<0){
node = node.rightNode;
}
}
// 如果找不到则返回false
if(node==null){
return false;
}
// 要删除的节点有三种情况,度为2,度为1和度为0的
if(node.leftNode!=null&&node.rightNode!=null){
// 度为2的节点,删除步骤为
// 1.找到要删除节点的左子数中最大节点。
// 2.替换待删除节点的值为这个最大节点值
// 3.将这个最大节点删除
BSTNode<T> maxNode = node.leftNode;
while (maxNode.rightNode!=null){
maxNode = maxNode.rightNode;
}
remove(maxNode.val);
node.val=maxNode.val;
}else {
// 度为1和度为2的节点一起处理,分两种情况。
// 1.要删除的节点为根节点。
// 2.要删除的节点为非根节点。
BSTNode<T> parent = parent(node);
BSTNode<T> child = node.leftNode == null?node.rightNode:node.leftNode;
// 根节点无父节点,直接将根节点指向子节点即可。
if(parent==null){
root = child;
}else {
// 非根节点,找到它是父节点的左子节点,还是右子节点,然后通过修改父节点指针即可。
if(parent.leftNode!=null&&parent.leftNode==node){
parent.leftNode = child;
}else {
parent.rightNode = child;
}
}
}
size--;
return true;
}
// 查询是否包含数据
public boolean contain(T val){
return find(val)!=null;
}
}
class BSTNode<T extends Comparable>{
public T val;//存储数据
public BSTNode<T> leftNode;//左子节点
public BSTNode<T> rightNode;//右子节点
}