# 数据结构与算法（八），查找

• 1、符号表
• 2、顺序查找
• 3、二分查找
• 4、插值查找
• 5、二叉查找树
• 6、平衡查找树
• 6.1、平衡二叉树（AVL树）
• 6.2、2-3查找树
• 6.3、红黑树
• 7、性能比较

## 1、符号表

• 表中不能有重复的键
• 键和值不能为空

``````public interface ST<K, V> {
//将键值对存入表中
void put(K key, V value);
//获取key对应的值
V get(K key);
}
``````

## 2、顺序查找

``````public class SequentialST<K, V> implements ST<K, V>{
private class Node {
K key;
V value;
Node next;
public Node(K key, V value, Node next) {
super();
this.key = key;
this.value = value;
this.next = next;
}
}

@Override
public void put(K key, V value) {
Node temp = sequentialSearch(key);
if(temp != null) {
temp.value = value;
}else {
}
}

//顺序查找,【关键】
private Node sequentialSearch(K key) {
for(Node cur= head; cur != null; cur=cur.next) {
if(key.equals(cur.key)) {
return cur;
}
}
return null;
}

@Override
public V get(K key) {
Node temp = sequentialSearch(key);
if(temp != null) {
return temp.value;
}
return null;
}

public static void main(String[] args) {
SequentialST<String, Integer> st = new SequentialST<>();
st.put("AA", 2);
st.put("BB", 2);
System.out.println(st.get("BB"));
}
}
``````

## 3、二分查找

``````/**
*基于二分查找的符号表
*/
public class BinarySearchST<K extends Comparable<K>, V>
implements ST<K, V> {

private K[] keys;
private V[] values;
private int size;

public BinarySearchST(int capacity) {
keys = (K[]) new Comparable[capacity];
values = (V[]) new Object[capacity];
}

@Override
public void put(K key, V value) {
int i = binarySearch(key, 0, size-1);
//查找到给定的键，则更新对应的值, size=0时，i=0
if(i < size && keys[i].compareTo(key) == 0) {
values[i] = value;
return;
}
for(int j=size; j>i; j--) {
keys[j] = keys[j-1];
values[j] = values[j-1];
}
keys[i] = key;
values[i] = value;
size++;
}

@Override
public V get(K key) {
int i = binarySearch(key, 0, size-1);
if(keys[i].compareTo(key) == 0) {
return values[i];
}
return null;
}

//二分查找，【关键】
private int binarySearch(K key, int down, int up) {
while(down <= up) {
int mid = down + (up-down)/2;
int temp = keys[mid].compareTo(key);
if(temp > 0) {
up = mid-1;
}else if(temp < 0) {
down = mid + 1;
} else {
return mid;
}
}
return down;
}

public static void main(String[] args) {
BinarySearchST<String, Integer> st = new BinarySearchST<>(10);
st.put("AA", 2);
st.put("BB", 2);
System.out.println(st.get("BB"));
}
}
``````

## 4、插值查找

``````private int binarySearch(K key, int down, int up) {
while(down <= up) {
int mid = down + (key-keys[down])/(keys[up]-keys[down])*(up-down);
int temp = keys[mid].compareTo(key);
if(temp > 0) {
up = mid-1;
}else if(temp < 0) {
down = mid + 1;
} else {
return mid;
}
}
return down;
}
``````

## 5、二叉查找树

``````//二叉查找树
public class BST <K extends Comparable<K>, V>
implements ST<K, V>  {
private Node root; //二叉树的根结点

private class Node {
K key;  //键
V value; //值
Node left, right; //左右子树
int N; //以该结点为根的结点总数
public Node(K key, V value, int n) {
this.key = key;
this.value = value;
N = n;
}
}

@Override
public void put(K key, V value) {
root = put(root, key, value);
}

//插入操作
private Node put(Node node, K key, V value) {
if(node == null)
return new Node(key,value,1);
int cmp = key.compareTo(node.key);
if(cmp < 0) {
node.left = put(node.left, key, value);
}else if(cmp > 0) {
node.right = put(node.right, key, value);
} else {
node.value = value;
}
node.N = node.left.N + node.right.N + 1; //递归返回时更新N
return node;
}

@Override
public V get(K key) {
return get(root, key);
}

//查找操作
private V get(Node node, K key) {
if(node == null)
return null;
int cmp = key.compareTo(node.key);
if(cmp < 0) {
return get(node.left, key);
}else if(cmp > 0) {
return get(node.right, key);
} else {
return node.value;
}
}
}
``````

BST插入元素

BST查找元素

``````@Override
public V get(K key) {
Node node = root;
while(node != null) {
int cmp = key.compareTo(node.key);
if(cmp == 0) {
return node.value;
}else if(cmp > 0) {
node = node.right;
}else {
node = node.left;
}
}
return null;
}
``````

``````//删除键key及其对应的值
public void delete(K key) {
root = delete(root, key);
}

private Node delete(Node node, K key) {
if(node == null)
return null;
int cmp = key.compareTo(node.key);
if(cmp < 0) {
node.left = delete(node.left, key);
}else if(cmp > 0) {
node.right = delete(node.right, key);
} else {
if(node.left == null) {
return node.right;
}
if(node.right == null) {
return node.left;
}
Node temp = node;
node = min(temp.right);
node.right = deleteMin(temp.right);
node.left = temp.left;
}
node.N = node.left.N + node.right.N + 1; //从栈返回时更新N
return node;
}

//删除一个子树的最小结点
private Node deleteMin(Node node) {
if(node.left == null) { //删除结点node
return node.right;
}
node.left = deleteMin(node.left);
node.N = node.left.N + node.right.N + 1; //更新子树的计数N
return node;
}

//查找一个子树的最小结点
private Node min(Node node) {
if(node.left == null) {
return node;
}
return min(node.left);
}
``````

## 6、平衡查找树

### 1、平衡二叉树（AVL树）

``````private Node rotateLeft(Node h) {
Node x = h.right;
h.right = x.left;
x.left = h;
x.N = h.N;
h.N = h.left.N + h.right.N + 1;
return x;
}
``````

``````private Node rotateRight(Node h) {
Node x = h.left;
h.left = x.right;
x.right = h;
x.N = h.N;
h.N = h.left.N + h.right.N + 1;
return x;
}
``````

• 第一种：当一个结点的BF值大于等于2，并且它的子结点的BF值为正，则右旋
• 第二种：当一个结点的BF值大于等于2，但它的子结点的BF值为负，对子结点进行左旋操作，变为第一种情况，然后再进行处理。
• 第三种：当一个结点的BF值小于等于-2，并且它的子结点的BF值为负，则左旋
• 第四种：当一个结点的BF值小于等于-2，但它的子结点的BF值为正，对子结点进行右旋操作，变为第三种情况，然后再进行处理。

AVL的实现：

``````//平衡二叉树（AVL树）的实现
public class AVL<K extends Comparable<K>, V> implements ST<K, V> {
private Node root; // 二叉树的根结点

private class Node {
K key; // 键
V value; // 值
Node left, right; // 左右子树
int N; // 以该结点为根的结点总数

public Node(K key, V value, int n) {
this.key = key;
this.value = value;
N = n;
}
}

// 左旋
private Node rotateLeft(Node h) {
Node x = h.right;
h.right = x.left;
x.left = h;
x.N = h.N;
h.N = h.left.N + h.right.N + 1;
return x;
}

// 右旋
private Node rotateRight(Node h) {
Node x = h.left;
h.left = x.right;
x.right = h;
x.N = h.N;
h.N = h.left.N + h.right.N + 1;
return x;
}

@Override
public void put(K key, V value) {
root = put(root, key, value);
}

// 插入操作
private Node put(Node node, K key, V value) {
if (node == null)
return new Node(key, value, 1);
int cmp = key.compareTo(node.key);
if (cmp < 0) {
node.left = put(node.left, key, value);
} else if (cmp > 0) {
node.right = put(node.right, key, value);
} else {
node.value = value;
}

if (BF(node) <= -2) {
if (BF(node.right) > 0) {
rotateRight(node.right);
}
rotateLeft(node);
}
if (BF(node) >= 2) {
if (BF(node.left) < 0) {
rotateLeft(node);
}
rotateRight(node);
}

node.N = node.left.N + node.right.N + 1; // 从栈返回时更新N
return node;
}

// 平衡因子BF的值
private int BF(Node node) {
return depth(node.left) - depth(node.right);
}

// 求子树的深度
private int depth(Node node) {
if (node == null)
return 0;
return Math.max(depth(node.right), depth(node.left)) + 1;
}

// 查找操作，和二叉查找树相同
@Override
public V get(K key) {
Node node = root;
while (node != null) {
int cmp = key.compareTo(node.key);
if (cmp == 0) {
return node.value;
} else if (cmp > 0) {
node = node.right;
} else {
node = node.left;
}
}
return null;
}
}
``````

### 2、2-3查找树

• 2-结点：含有一个键和两个链接，左链接指向键小于该结点的子树，右链接指向键大于该结点的子树。
• 3-结点：含有两个键和三个链接，左链接指向键都小于该结点的子树，中链接指向键位于该结点两个键之间的子树，右链接指向键大于该结点的子树。
2-3树

2-3树的查找操作和平衡二叉树一样，从根结点开始，根据比较结果，到相应的子树中去继续查找，直到命中或查找失败。

• 第一种：向2-结点中插入新键，只需用一个3-结点替换2-结点即可。
• 第二种：向3-结点中插入新键，这个3-结点没有父结点，此时可将其分解为一个含有3个结点的二叉查找树。
• 第三种：向一个父结点为2-结点的3-结点中插入新键，先将其分解为一个含有3个结点的二叉查找树，然后将中键移到父结点，父结点由2-结点变为3-结点。
• 第四种：向一个父结点为3-结点的3-结点中插入新键，和第三种情况一样，一直向上分解临时的4-结点，直到遇到一个2-结点将它替换为3-结点，或到达3-结点的根，然后直接分解成一个含有3个结点的二叉查找树。如图，此时2-3树依然平衡。

2-3查找树的插入和查找操作的时间复杂度不超过 O（logN）。

2-3查找树需要维护两种不同的结点，实现起来比较复杂，并且在结点的转换过程中需要大量的复制操作，这些都将产生额外的开销，使的算法的性能可能比二叉查找树要慢。

### 3、红黑树

• 红链接均为左链接，即红色结点必须为左结点。
• 没有任何结点同时和两个红链接相连，即不存在左右子结点都为红的结点。
• 任何空链接到根节点的路径上的黑链接（黑结点）数量相同。
• 根结点总是黑色的。

2-3查找树与红黑树的对应关系

``````//红黑树的实现
public class RedBlackBST <K extends Comparable<K>, V> implements ST<K, V> {
private static final boolean RED = true;
private static final boolean BLACK = false;
private Node root; // 二叉树的根结点

private class Node {
K key; // 键
V value; // 值
Node left, right; // 左右子树
int N; // 以该结点为根的结点总数
//由于每个结点都只有一个指向自己的链接，所以可以在结点中使用boolean值来表示红链接。
boolean color;

public Node(K key, V value, int n, boolean color) {
this.key = key;
this.value = value;
N = n;
this.color = color;
}
}

private boolean isRed(Node node) {
if(node == null)
return false;
return node.color == RED;
}

// 左旋
private Node rotateLeft(Node h) {
Node x = h.right;
h.right = x.left;
x.left = h;
x.color = h.color;
h.color = RED;
x.N = h.N;
h.N = h.left.N + h.right.N + 1;
return x;
}

// 右旋
private Node rotateRight(Node h) {
Node x = h.left;
h.left = x.right;
x.right = h;
x.color = h.color;
h.color = RED;
x.N = h.N;
h.N = h.left.N + h.right.N + 1;
return x;
}

@Override
public void put(K key, V value) {
root = put(root, key, value);
root.color = BLACK; //根节点总是黑色的，因为它没有父链接
}

// 插入操作
private Node put(Node node, K key, V value) {
if (node == null)
return new Node(key, value, 1,RED);
int cmp = key.compareTo(node.key);
if (cmp < 0) {
node.left = put(node.left, key, value);
} else if (cmp > 0) {
node.right = put(node.right, key, value);
} else {
node.value = value;
}

if(isRed(node.right) && !isRed(node.left)) {
node = rotateLeft(node);
}
if(isRed(node.left) && isRed(node.left.left)) {
node = rotateRight(node);
}
if(isRed(node.left) && isRed(node.right)) {
flipColors(node);
}

node.N = node.left.N + node.right.N + 1; // 从栈返回时更新N
return node;
}

//颜色转换
private void flipColors(Node node) {
node.color = RED;
node.left.color = BLACK;
node.right.color = BLACK;
}

// 查找操作，和二叉查找树一样
@Override
public V get(K key) {
Node node = root;
while (node != null) {
int cmp = key.compareTo(node.key);
if (cmp == 0) {
return node.value;
} else if (cmp > 0) {
node = node.right;
} else {
node = node.left;
}
}
return null;
}
}
``````