1.搜索树模型
1)纯K模型(只需要判断关键字在不在集合中即可,没有关联的 value):Set
2)K-V模型(需要根据指定 Key 找到关联的 Value):Map
2.搜索树
2.1概念
二叉搜索树又称二叉排序树(可以是空树),它具备以下性质:
注意:key不能重复
如果节点存的是 key ,此时,key 不允许重复。
如果节点存的是 key-value 键值对,此时 key 不允许重复,value允许重复。
public static class Node {
int key;
// int value;
Node left;
Node right;
public Node(int key) {
this.key = key;
}
}
// 根节点, root 为 null 的时候表示这是个空树
private Node root = null;
2.2查找
类似于二分查找,具体步骤如下
< 1 >如果根节点不空:
I. if(cur.key = key){ return cur;}
II. if(cur.key > key){ cur = cur.left ; }
III. if(cur.key < key){ cur = cur.right ; }
< 2 >否则 返回null //没找到
public Node find(int key) {
// 查找 key 是否在树中存在. 如果存在返回对应的 Node
Node cur = root;
while (cur != null) {
if (key < cur.key) {
// 就去左子树中找
cur = cur.left;
} else if (key > cur.key) {
// 就去右子树中找
cur = cur.right;
} else {
// 相等就是找到了
return cur;
}
}
// 循环结束了也没找到, key 就不存在.
return null;
}
2.3插入
< 1 >如果树是空树(root = null),则直接插入 root=new Node(key);
然后 return true;
< 2 >如果树不是空树(root != null),则按照逻辑确定插入位置,插入新结点
I. 如果插入结点(key)已经存在,return false
;
II. 查找插入结点的位置,插入新节点
注意:结点插入需知道其parent,方便插入
注意:二叉搜索树的插入,插入元素都是插入到叶子节点的
// 二叉搜索树中不允许存在相同 key 的元素的.
// 如果发现新插入的 key 重复了, 那就插入失败, 返回 false
// 插入成功返回 true
public boolean insert(int key) {
if (root == null) {
// 当前如果为空树, 直接让 root 指向 key 对应的新节点即可.
root = new Node(key);
return true;
}
// 和查找类似, 需要先找到合适的位置. 再去插入元素
Node cur = root;
Node parent = null; // parent 始终指向 cur 的父节点. 和链表插入类似. 链表插入元素也需要记录指定位置的前一个元素
while (cur != null) {
if (key < cur.key) {
parent = cur;
cur = cur.left;
} else if (key > cur.key) {
parent = cur;
cur = cur.right;
} else {
// 如果当前树村的只是 key, 发现相同的 key 就认为插入失败.
// 如果当前树存的是键值对, 发现相同的 key 就修改值即可.
return false;
}
}
// 循环结束的时候, cur 就指向 null, 当前元素就要插入到 parent 的子树位置上.
// 具体是插到 parent 的左子树还是右子树呢? 就那 key 和 parent 再比较一次就知道了
if (key < parent.key) {
// 插入到 parent 的左侧
parent.left = new Node(key);
} else {
parent.right = new Node(key);
}
return true;
}
2.4删除
分情况讨论:设待删除结点为 cur, 待删除结点的双亲结点为 parent
假设待删除元素值是3:
I. 找到这个节点的左子树的最大值或右子树的最小值
II. 由于 2 是左子树最大值,将 2 赋值到 3 这个元素位置,此时赋值后只要删除最初的 2 这个结点即可
// 删除成功返回 true, 删除失败返回 false
// key 在树中存在, 就删除成功.
// key 在树中不存在, 就删除失败.
public boolean remove(int key) {
// 先找到要删除节点的位置, 再进行具体的删除
// 找到这个待删除元素后, 再去判定是 a - f 中的哪种情况
Node cur = root;
Node parent = null;
while (cur != null) {
if (key < cur.key) {
parent = cur;
cur = cur.left;
} else if (key > cur.key) {
parent = cur;
cur = cur.right;
} else {
// 找到要删除元素, 就是 cur 指向的节点
// 在这个方法中去判定 a - f 这些情况并进行删除
removeNode(parent, cur);
return true;
}
}
return false;
}
private void removeNode(Node parent, Node cur) {
if (cur.left == null) {
// 1. 要删除的元素没有左子树
if (cur == root) {
// 1.1 如果要删除节点为 root
root = cur.right;
} else if (cur == parent.left) {
// 1.2 cur 是 parent 的左子树, 对应画图板的情况 a
// 如果 cur 也没有右子树, 相当于
// parent.left = null
parent.left = cur.right;
} else {
// 1.3 cur 是 parent 的右子树, 对应画图板的情况 b
parent.right = cur.right;
}
} else if (cur.right == null) {
// 2. 要删除的元素没有右子树
if (cur == root) {
// 2.1 如果要删除节点是 root
root = cur.left;
} else if (cur == parent.left) {
// 2.2 要删除节点是父节点的左子树, 对应画图板的情况 c
parent.left = cur.left;
} else {
// 2.3 要删除节点是父节点的右子树, 对应画图板的情况 d
parent.right = cur.left;
}
} else {
// 3. 当前要删除节点有两个子树. 对应画图板的 e 和 f
// 1) 先找到右子树中的最小元素(替罪羊)
Node goatParent = cur; // 替罪羊节点的父节点
Node scapeGoat = cur.right; // 替罪羊节点
while (scapeGoat.left != null) {
goatParent = scapeGoat;
scapeGoat = scapeGoat.left;
}
// 循环结束时, scapeGoat 指向了右子树中的最小值
// 2) 把刚才找到的替罪羊的值赋给待删除节点.
cur.key = scapeGoat.key;
// 3) 删除替罪羊节点
// 替罪羊节点一定没有左子树(与1.2 1.3类似 )
if (scapeGoat == goatParent.left) {
goatParent.left = scapeGoat.right;
} else {
goatParent.right = scapeGoat.right;
}
}
}
3.纯K模型
public class BinarySearchTree {
public static class Node {
int key;
Node left;
Node right;
public Node(int key) {
this.key = key;
}
}
private Node root = null;
/**
* 在搜索树中查找 key,如果找到,返回 key 所在的结点,否则返回 null
* @param key
* @return
*/
public Node search(int key) {
Node cur = root;
while (cur != null) { //如果数不空
if (key == cur.key) {
return cur;
} else if (key < cur.key) {
cur = cur.left;
} else {
cur = cur.right;
}
}
return null; //没找到
}
/**
* 插入
* @param key
* @return true 表示插入成功, false 表示插入失败
*/
public boolean insert(int key) {
if (root == null) { //如果树为空树 直接插入
root = new Node(key);
return true;
}
Node cur = root;
Node parent = null;
//查找插入结点的位置
while (cur != null) {
if (key == cur.key) { //如果key结点存在 插入失败
return false;
} else if (key < cur.key) {
parent = cur;
cur = cur.left;
} else {
parent = cur;
cur = cur.right;
}
}
Node node = new Node(key);
if (key < parent.key) {
parent.left = node;
} else {
parent.right = node;
}
return true;
}
/**
* 删除成功返回 true,失败返回 false
* @param key
* @return
*/
public boolean remove(int key) {
Node cur = root;
Node parent = null;
while (cur != null) {
if (key == cur.key) {
// 找到,准备删除
removeNode(parent, cur);
return true;
} else if (key < cur.key) {
parent = cur;
cur = cur.left;
} else {
parent = cur;
cur = cur.right;
}
}
return false;
}
private void removeNode(Node parent, Node cur) {
if (cur.left == null) { //左孩子为空
if (cur == root) {
root = cur.right;
} else if (cur == parent.left) {
parent.left = cur.right;
} else {
parent.right = cur.right;
}
} else if (cur.right == null) { //右孩子为空
if (cur == root) {
root = cur.left;
} else if (cur == parent.left) {
parent.left = cur.left;
} else {
parent.right = cur.left;
}
} else { //左右孩子均不为空
Node goatParent = cur;
Node goat = cur.right; //寻找cur的右孩子中最小的
while (goat.left != null) {
goatParent = goat;
goat = goat.left;
}
cur.key = goat.key;
//cur.value = goat.value;
if (goat == goatParent.left) {
goatParent.left = goat.right;
} else {
goatParent.right = goat.right;
}
}
}
public static void main(String[] args) {
// 1. 创建搜索树
// 2. 随机插入一些数据
// 3. 打印前序 + 中序遍历
// 4. 查找
BinarySearchTree tree = new BinarySearchTree();
int[] keys = { 3, 9, 7, 4, 1, 6, 2, 8, 5 };
for (int key : keys) {
System.out.println(tree.insert(key));
}
System.out.println("插入重复数据");
System.out.println(tree.insert(7));
System.out.println("前序遍历");
preOrder(tree.root);
System.out.println("中序遍历");
inOrder(tree.root);
System.out.println(tree.search(7).key);
System.out.println(tree.search(8).key);
System.out.println(tree.search(5).key);
}
private static void inOrder(Node node) {
if (node != null) {
inOrder(node.left);
System.out.println(node.key);
inOrder(node.right);
}
}
private static void preOrder(Node node) {
if (node != null) {
System.out.println(node.key);
preOrder(node.left);
preOrder(node.right);
}
}
}
4.K-V模型
5.补充
查找时间复杂度最差O(n),会退化成单支树,怎么解决?
答:采取更平衡的二叉搜索树,eg:AVL树(绝对平衡)、红黑树(TreeSet、TreeMap)等。
在设计中那些方法是static,那些事非static?
答:和类(方法-行为)关联的是static; 和对象(属性-状态)关联的是非static。
如果要求时间复杂度更低?
答:哈希表可以按照O(1)的时间复杂度完成插入查找删除。