前言
数组和链表作为比较基础的数据类型,二者均有各自优点。
数组本身适合查找,对于一个元素数量为n的数组,其查找时间复杂度为O(1),但是如果需要插入或删除元素,则意味着插入位置的元素需要全部后移,删除则意味着删除位置元素位置全部前移一位,两种操作时间复杂度均为O(n);
链表适合插入或删除,只需要修改指针指向即可实现插入和删除,在已知插入或删除位置的情况下,时间复杂度为O(1),而查找某个元素时间复杂度为O(n).
那么有没有一种数据结构能同时实现数组的查找和链表的插入删除呢?
有,那就是二叉搜索树。
一、 二叉树的构成
1. 二叉搜索树构成要求: 对于一颗二叉树,若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。
简而言之就是要求这个二叉树所有节点数据大小满足左节点<根节点<右节点
下图即为一个二叉搜索树:(圈内表示节点数据)
2. 二叉树的节点类
public class Node{
public Node(int data) {
this.data = data;
}
private int data;
private Node leftTree; //左子树
private Node rightTree; //右子树
}
二、二叉搜索树的相关算法
1. 算法接口类
接口包括查找元素,插入元素以及删除元素,构成如下:
import tree.BinarySearchTree.Node;
public interface BinaryTreeInterface {
public Node find(Node root, int key); //搜索
public boolean insert(Node root, int element); //插入元素
public boolean delete(Node root, int element); //删除元素
}
2. 算法实现
在开始实现这些算法之前,我们先来回忆一下二叉树的遍历:
①前序遍历:根节点——>左子树——>右子树
②中序遍历:左子树——>根节点——>右子树
③后序遍历:左子树——>右子树——>根节点
代码实现:
//前序遍历
public void preorderTraversal(Node current){
if(current != null){
System.out.print("data:"+current.data);
preorderTraversal(current.leftTree);
preorderTraversal(current.rightTree);
}
}
//中序遍历
public void inorderTraversal(Node current){
if(current != null){
inorderTraversal(current.leftTree);
System.out.print("data:"+current.data);
inorderTraversal(current.rightTree);
}
}
//后序遍历
public void postorderTraversal(Node current){
if(current != null){
postorderTraversal(current.leftTree);
postorderTraversal(current.rightTree);
System.out.print("data:"+current.data);
}
}
基于这三种遍历方式,大家很容易就能发现,二叉搜索树的中序遍历能按从小到大的顺序遍历整个树。
2.1 查找元素
算法思路: 从根节点开始,若key小于节点值,则说明元素应该在节点的左子树,于是搜索节点的左子树;如果key大于节点值,则说明元素应该在该节点的右子树,于是搜索右子树;直至节点值等于key,如果遍历整个树后依旧没有找到,则说明元素不存在。
代码实现:
@Override
public Node find(Node root, int key) {
Node current = root;
while(current != null) {
if (key > current.data) {
current = current.rightTree;
}else if(key < current.data){
current = current.leftTree;
}else {
return current;
}
}
return null;
}
算法分析:查找所需时间与元素所在层数有关,时间复杂度为O(logN),注意这里的N表示二叉树的节点总数。
2.2 插入元素
算法思路: 理解查找算法后,其他都很类似。从根节点开始,若待插入值element小于节点值,则说明元素应该插在节点的左子树上,于是搜索节点的左子树;如果element大于节点值,则说明元素应该插在该节点的右子树,于是搜索右子树;直至element小于当前节点值且当前节点没有左子树或element大于当前节点值且当前节点没有右子树。
代码实现:
@Override
public boolean insert(Node root, int element) {
Node current = root;
Node parentTree = null;
if (current == null) {
current.data = element;
return true;
}
while(current != null) {
if (element < current.data) {
current = current.leftTree;
if (current == null) {
current.data = element;
return true;
}
}else if (element > current.data) {
current = current.rightTree;
if (current == null) {
current.data = element;
return true;
}
} else {
//二叉搜索树中不允许出现重复的数据
return false;
}
}
return false;
}