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

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

二叉搜索树

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

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

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

查找

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

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

时间复杂度:

  • 最好情况下: O ( l o g 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,那么就找到了
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

  1. cur 不是 rootcurparent.left,则 parent.left = cur.right

  1. cur 不是 rootcurparent.right,则 parent.right = cur.right
// 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.left

  1. cur 不是 rootcurparent.left,则 parent.left = cur.left

  1. cur 不是 rootcurparent.right,则 parent.right = cur.left
// 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
  • 目标节点不会出现左右孩子都存在的情况。要么两边都为空,要么还存在一个右节点。(既然此节点是最小的,就不可能还有左子树,因为左子树肯定比此节点小)
  1. 在它的左子树中寻找一个最大的节点,用它的值填补到被删除节点中,再来处理该结点的删除问题
  • 此时这个最大值一定是在左树的最右边,意味着它肯定没有右子树

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

  • 此节点左子树为空,且一定在 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;  
        }  
    }  
}


相关文章
|
2月前
|
存储 Java Serverless
【数据结构】哈希表&二叉搜索树详解
本文详细介绍了二叉搜索树和哈希表这两种数据结构。二叉搜索树是一种特殊二叉树,具有左子树节点值小于根节点、右子树节点值大于根节点的特点,并且不允许键值重复。文章给出了插入、删除和搜索等方法的具体实现。哈希表则通过哈希函数将键名映射为数组下标,实现快速查找,其插入、删除和查找操作时间复杂度理想情况下为O(1)。文中还讨论了哈希函数的设计原则、哈希冲突的解决方法及哈希表的实现细节。
48 8
【数据结构】哈希表&二叉搜索树详解
|
1月前
【高阶数据结构】深度探索二叉树进阶:二叉搜索树概念及其高效实现(三)
【高阶数据结构】深度探索二叉树进阶:二叉搜索树概念及其高效实现
|
1月前
|
存储
【高阶数据结构】深度探索二叉树进阶:二叉搜索树概念及其高效实现(二)
【高阶数据结构】深度探索二叉树进阶:二叉搜索树概念及其高效实现
|
1月前
【高阶数据结构】深度探索二叉树进阶:二叉搜索树概念及其高效实现(一)
【高阶数据结构】深度探索二叉树进阶:二叉搜索树概念及其高效实现
01_设计一个有getMin功能的栈
01_设计一个有getMin功能的栈
|
4月前
|
SQL 自然语言处理 网络协议
【Linux开发实战指南】基于TCP、进程数据结构与SQL数据库:构建在线云词典系统(含注册、登录、查询、历史记录管理功能及源码分享)
TCP(Transmission Control Protocol)连接是互联网上最常用的一种面向连接、可靠的、基于字节流的传输层通信协议。建立TCP连接需要经过著名的“三次握手”过程: 1. SYN(同步序列编号):客户端发送一个SYN包给服务器,并进入SYN_SEND状态,等待服务器确认。 2. SYN-ACK:服务器收到SYN包后,回应一个SYN-ACK(SYN+ACKnowledgment)包,告诉客户端其接收到了请求,并同意建立连接,此时服务器进入SYN_RECV状态。 3. ACK(确认字符):客户端收到服务器的SYN-ACK包后,发送一个ACK包给服务器,确认收到了服务器的确
190 1
|
5月前
数据结构学习记录——判断是否为同一颗二叉搜索树(题意理解、求解思路、程序搭建框架、具体函数的实现)
数据结构学习记录——判断是否为同一颗二叉搜索树(题意理解、求解思路、程序搭建框架、具体函数的实现)
48 2
|
5月前
|
机器学习/深度学习
数据结构学习记录——堆的小习题(对由同样的n个整数构成的二叉搜索树(查找树)和最小堆,下面哪个说法是不正确的)
数据结构学习记录——堆的小习题(对由同样的n个整数构成的二叉搜索树(查找树)和最小堆,下面哪个说法是不正确的)
37 1
|
4月前
|
存储
【数据结构】AVL树——平衡二叉搜索树
【数据结构】AVL树——平衡二叉搜索树
|
4月前
|
存储 Linux 数据库
【数据结构】二叉搜索树——高阶数据结构的敲门砖
【数据结构】二叉搜索树——高阶数据结构的敲门砖

热门文章

最新文章