背景
对于一个size为N的链表或有序数组来说,进行第N+1次操作时最坏情况下的增长数量级如下表:
数据结构 \ 操作 | 查找 | 插入 |
---|---|---|
链表 | N | N |
有序数组 | logN | 2N |
链表的优势在于插入,有序数组的优势在于查找(二分查找),而二叉查找树综合了以上两者的优点。
特点
- 每个节点都有左右两条链接,分别指向左右两个子节点
- 对每一个子树,根节点大于左子树上任意节点,小于右子树上任意节点
- 每个节点只能有一个父节点(除了根节点外,根节点没有父节点
- 子节点可以为空
- 一个二叉树是一组键的集合,根据节点被插入的顺序不同,同一个集合可以有多个不同的二叉树表示
- 二叉树的操作复杂度依赖于二叉树的高度,如果所有的键都是顺序或逆序插入,则树的高度将变最大,操作复杂度最高
API
函数 | 返回 | 描述 |
---|---|---|
put(Key key, Value value) | void | 插入新元素到树中 |
get(Key key) | Value | 得到键值为key的元素的value |
min() | Key | 返回最小的key |
max() | Key | 返回最大的key |
ceiling(Key key) | Key | 返回>=给定key的最小key |
floor(Key key) | Key | 返回<=给定key的最大key |
select(int k) | Key | 返回树中排名为第k位的节点的key |
rank(Key key) | int | 返回键值为key的节点在树中的排名 |
deleteMin() | void | 删除最小key对应的节点 |
deleteMax() | void | 删除最大key对应的节点 |
delete(Key key) | void | 删除指定key对应的节点 |
keys(Key low, Key high) | queue | 返回给定范围内的key的集合 |
实现
定义对象
public class Node {
public Key key;
public Value value;
public Node left;//指向左子树的链接
public Node right;//指向右子树的链接
public int N;//以该节点为根节点的子树的大小(包括该节点自己在内的节点总数)
public Node(Key key, Value value) {
this.key = key;
this.value = value;
N = 1;//每个节点刚建立并插入二叉树时N为1,在有新节点被插入和删除过程中更新其子节点数的大小
}
}
1 put实现
- 如果子树中包含键值为key的元素,则更新其value即可;
- 不包含,则沿着根节点的左子树或右子树遍历,直到遇到插入到某个左节点或右节点为null的根节点
- 注意:即新节点只能插入在树的的最下层,不能在树的中间层插入。
public void put(Key key, Value value) {
root = put(root, key, value);
}
/** * @param node 指向待插入子树的根节点的链接 * @return Node 子树被插入新节点后的根节点,大部分情况下依旧返回@param node,除非有新节点生成 */
private Node put(Node node, Key key, Value value) {
if (node == null) {
return new Node(key, value);
}
//递归过程中会重置父节点指向子节点的链接,但唯一的新链接只有最底层指向新节点的链接
if (key > node.key) {
node.right = put(node.right, key, value);
} else if (key < node.key) {
node.left = put(node.left, key, value);
} else {
node.value = value;
}
node.N = size(node.left) + size(node.right) + 1;//回溯过程中更新子节点的个数(因为有新的节点插入)
return node;
}
2 get实现
public Value get(Key key) {
Node node = get(root, key);
if (node == null) {
return null;
}
return node.value;
}
private Node get(Node node, Key key) {
if (node == null) {
return null;
}
if (node.key == key) {
return node;
} else if (key < node.key) {
return get(node.left, key);
} else {
return get(node.right, key);
}
}
3 min实现
- 最小值肯定在左子树上,所以沿着左子树一直遍历,直到遇到node.left == null即可得到最小的节点node
public Key min() {
return min(root);
}
private Node min(Node node) {
if (node.left == null) {
return node;
} else {
return min(node.left);
}
}
4 max实现
和min()大体相同,这里不再单独给出
5 floor()的实现
- 如果给定的key等于某个根节点,直接返回即可
- 如果给定的key小于某个根节点,那么满足<=key的节点只可能存在于左子树(也可能不存在)
- 如果给定的key大于某个根节点,那么满足条件的节点可能是该根节点,也可能位于该根节点的右子树上
public Node floor(Key key){
Node node = floor(root, key);
if (node == null) {
return null;
}
return node.key;
}
private Node floor(Node node , Key key){
if (node == null) {
return null;
}
if (key == node.key) {
return node;
} else if (key < node.key) {
return floor(node.left, key);
} else (key > node.key) {
Node n = floor(node.right, key);
if (n == null) {
return node;
}
return n;
}
}
6 ceiling()实现
和floor大致相同,不再给出
public Key ceiling(Key key){
Node node = ceiling(root, key);
if (node == null) {
return null;
}
return node.key;
}
private Node ceiling(Node node, Key key){
if (node == null) {
return null;
}
if (key == node.key) {
return node;
} else if (key > node.key) {
return ceiling(node.left, key);
} else {
Node n = ceiling(node.right, key);
if (n == null) {
return node;
}
return n;
}
}
7 select(int k)实现
- 某个节点的排名 = 该节点左子树的大小 + 1
- 如果k小于左子树大小,则继续在左子树中寻找
- 如果k大于左子树大小,则在右子树中找排名为k – left.N – 1的节点,“1”指的是根节点
public Key select(int k){
Node node = select(root, k);
if (node == null) {
return null;
}
return node.key;
}
public Node select(Node node, int k){
if (node == null) {
return null;
}
if (k < size(node.left)) {
return select(node.left, k);
}
else if (k > size(node.left)) {
return select(node.right, k - size(node.left) - 1);
} else {
return node;
}
}
8 rank(Key key)实现
- 和select方法的思想类似
- 如果key小于左子树,则继续在左子树中找key对应的节点
- 如果key大于左子树,则在key的排名等于左子树的节点数 + 1 + key在右子树中的排名
public int rank(Key key){
return rank(root, key);
}
public int rank(Node node, Key key){
if (node == null) {
return 0;
}
if (key < node.left.key) {
return rank(node.left, key);
}else if (key > node.left.key) {
return size(node.left) + 1 + rank(node.right);
}else {
return size(node.left);
}
}
9 deleteMin实现
- 删除的意思是再没有指向该节点的链接,即原先指向该节点的链接要么指向新的节点,要么指向null
- 由于需要更新链接,即原本指向每一个根节点的链接可能在递归中指向新的根节点(也可能依旧指向原本的根节点)
- 最小节点一定是最底层左子树为null的根节点,此时我们返回根节点的右子树(当然右子树也可能为null)
public void deleteMin(){
root = deleteMin(root);
}
public Node deleteMin(Node node){
if (node == null) {
return null;
}
if (node.left == null) {
return node.right;//左子节点为空,说明根节点就是要被删除的节点,此时我们就返回根节点的右子节点来更新原本指向根节点的链接
}
node.left = deleteMin(node.left);//递归中更新指向左子树的链接
node.N = size(node.left) + size(node.right) + 1;
return node;
}
10 deleteMax实现
和deleMin大致相同,这里不再给出
public Key deleteMax(){
root = deleteMax(root);
}
private Node deleteMax(Node node){
if (node == null) {
return null;
}
Node t = node;
if (node.right == null) {
return node.left;;
}
node.right = deleteMax(node.right);
node.N = size(node.left) + size(node.right) + 1;
return node;
}
11 delete实现
- 删除过程中需要递归地更新指向左子树或右子树的链接
- 若某个节点的key等于给定的key,说明该节点是将要被删除的节点。此时我们需要一个后继节点来填补该节点的位置。
- 该节点若只有一个子链接,那么只需将原本指向该节点的链接重置为指向其唯一存在的子链接即可,即后继节点为子链接指向的节点
- 该节点若有两个子链接,后继节点选择该节点的右子树中的最小节点(即原本指向该节点的链接重新指向右子树最小节点,可借助deleteMin方法)
- 然后更新上一步中得到的后继节点的左右链接,其中左链接指向上一步中被删除的根节点的左链接,右链接指向deleteMin返回的新的根节点(右子树删除最小节点后的新的根节点)
public void delete(Key key){
delete(root, key);
}
public Node delete(Node node, Key key){
if (node == null) {
return null;
}
if (key < node.key) {
node.left = delete(node.left, key);
} else if (key > node.key) {
node.right = delete(node.right, key);
} else {
if (node.right == null) {
return node.left;
}
if (node.left == null) {
return node.right;
}
Node min = min(node.right);
min.right = deleteMin(node.right);
min.left = node.left;
return min;
}
return node;
}
12 keys方法实现
public Iterable<Key> keys(Key low, Key high){
keys(root, low, high, queue);
}
public void keys(Node node, Key low, Key high, Queue<Key> queue){
if (node == null) {
retutn;
}
if (low < node.key) {
keys(node.left, low, high, queue);
}
if (node.key >= low && node.key <= high) {
queue.enqueue(node.key);
}
if (high < node.key) {
keys(node.right, low, high, queue);
}
}
二叉树的前序、中序及后序遍历
(有时间补充)
– 前序:根节点在前面,根左右
– 中序:根节点在中间,左根右
– 后序:根节点在最后,左右根