今天想要复习的数据结构是符号表,也称为字典。可能说到这里大家还是没明白我要复习一种什么样的数据结构。此时我不得不承认本人文笔真的是不太好。所以,还是说的更加具体点吧,这种数据结构的功能对使用者来讲,与JDK中的HashMap极其相似:可以按键值对进行数据的存储,然后根据键可以查找到与其对应的值。
只不过,我这里讲的符号表在底层实现上:存储数据利用的是两个大小相等的数组。其中一个数组存储键,另一个数组存储对应键的值。然后,在插入键值对或者根据键进行查找的时候,为了提高效率,又利用了很普通的二分查找法。说到二分查找法,对这种算法还有印象的同学应该立刻猜测到,这个存储键的数组中存储的键一定是有序的。对!能想到这,想必下面的代码要看懂是一件非常容易的事情。当然啦,在生产环境中使用它代替HashMap等数据结构可就值得商榷了,为什么,当然是效率问题啦。why? 鉴于个人理论性不强,就不在这里造次了。有兴趣的大家自己多思考啦,总之,思考有百利而无一害。有想法朋友也不妨在下面留言咯。
好了,废话不多说了,直接看代码吧。代码中有注释,我就不唠叨啦。
import java.util.LinkedList;
import java.util.NoSuchElementException;
import java.util.Queue;
/**
* 一个基于二分查找法的字典类,可以根据键值对进行存储或根据键进行查询
*/
public class BinarySearchBasedDictionary<K extends Comparable<K>, V> {
/**
* keys数组和values数组的初始大小(也即数组长度)
*/
private static final int INITIAL_CAPACITY = 4;
/**
* 存储键的数组
*/
private K[] keys;
/**
* 存储值的数组
*/
private V[] values;
/**
* keys数组或者values数组中存储的元素的数目
*/
private int N = 0;
/**
* 初始化存储键和值得数组的大小
*
* @param initialCapacity
*/
public BinarySearchBasedDictionary(int initialCapacity) {
keys = (K[]) new Comparable[INITIAL_CAPACITY];
values = (V[]) new Object[INITIAL_CAPACITY];
}
public BinarySearchBasedDictionary() {
this(INITIAL_CAPACITY);
}
/**
* 求存储的键值对的数量
*
* @return
*/
public int size() {
return N;
}
public boolean isEmpty() {
return size() == 0;
}
public boolean contains(K key) {
if (key == null) {
throw new NullPointerException("key不能为空!!!");
}
return get(key) != null;
}
/**
* 根据key查找相应的值
*
* @param key
* @return
*/
public V get(K key) {
if (key == null) {
throw new NullPointerException("key不能为空!!!");
}
if (isEmpty()) {
return null;
}
int i = rank(key);
if (i < N && keys[i].compareTo(key) == 0) {
return values[i];
}
return null;
}
/**
* 计算指定的键存在于数组中的哪一个位置,此处用的是二分查找法。 因为存入数组中的键是在数组中进行有序存储的。
*
* @param key
* @return
*/
public int rank(K key) {
if (key == null) {
throw new NullPointerException("key不能为空!!!");
}
int low = 0, high = N - 1;
while (low <= high) {
int mid = low + (high - low) / 2;
int comp = key.compareTo(keys[mid]);
if (comp < 0) {
high = mid - 1;
} else if (comp > 0) {
low = mid + 1;
} else {
return mid;
}
}
return low;
}
public void put(K key, V value) {
if (key == null) {
throw new NullPointerException("key不能为空!!!");
}
if (value == null) {
remove(key);
return;
}
int i = rank(key);
/*
* 倘若对应的键已经存在,则替换旧值为新值
*/
if (i < N && keys[i].compareTo(key) == 0) {
values[i] = value;
return;
}
/*
* 空间不够,则进行扩容
*/
if (N == keys.length)
resize(2 * keys.length);
for (int j = N; j > i; j--) {
keys[j] = keys[j - 1];
values[j] = values[j - 1];
}
keys[i] = key;
values[i] = value;
N++;
assert check();
}
public void remove(K key) {
if (key == null)
throw new NullPointerException("key不能为空!!!");
if (isEmpty())
return;
// 计算键所在的位置
int i = rank(key);
// 如果不存在该键,直接返回
if (i == N || keys[i].compareTo(key) != 0) {
return;
}
for (int j = i; j < N - 1; j++) {
keys[j] = keys[j + 1];
values[j] = values[j + 1];
}
N--;
keys[N] = null; // 置空, 避免游离
values[N] = null; // 置空, 避免游离
// 降低数组容量,避免大量空间空闲
if (N > 0 && N == keys.length / 4) {
resize(keys.length / 2);
}
assert check();
}
private boolean check() {
return isSorted() && rankCheck();
}
/**
* 判断存储键的数组是否有序
*
* @return
*/
private boolean isSorted() {
for (int i = 1; i < size(); i++) {
if (keys[i].compareTo(keys[i - 1]) < 0) {
return false;
}
}
return true;
}
private boolean rankCheck() {
for (int i = 0; i < size(); i++) {
if (i != rank(select(i))) {
return false;
}
}
for (int i = 0; i < size(); i++) {
if (keys[i].compareTo(select(rank(keys[i]))) != 0) {
return false;
}
}
return true;
}
public K select(int i) {
if (i < 0 || i >= N) {
return null;
}
return keys[i];
}
/**
* 重新调整底层数组的大小
*
* @param capacity
*/
private void resize(int capacity) {
assert capacity >= N;
K[] tempk = (K[]) new Comparable[capacity];
V[] tempv = (V[]) new Object[capacity];
/*
* 把已有的元素移植到临时数组中
*/
for (int i = 0; i < N; i++) {
tempk[i] = keys[i];
tempv[i] = values[i];
}
/*
* 用临时数组替换旧的数组,实现扩容
*/
keys = tempk;
values = tempv;
}
public K min() {
if (isEmpty()) {
return null;
}
return keys[0];
}
public K max() {
if (isEmpty()) {
return null;
}
return keys[N - 1];
}
public K floor(K key) {
if (key == null) {
throw new NullPointerException("key不能为空!!!");
}
int i = rank(key);
if (i < N && key.compareTo(keys[i]) == 0) {
return keys[i];
}
if (i == 0) {
return null;
} else {
return keys[i - 1];
}
}
public K ceiling(K key) {
if (key == null) {
throw new NullPointerException("key不能为空!!!");
}
int i = rank(key);
if (i == N) {
return null;
} else {
return keys[i];
}
}
public void deleteMin() {
if (isEmpty()) {
throw new NoSuchElementException("删除失败!没有任何元素存储其中");
}
remove(min());
}
public void deleteMax() {
if (isEmpty()) {
throw new NoSuchElementException("删除失败!没有任何元素存储其中");
}
remove(max());
}
public int size(K low, K high) {
if (low == null) {
throw new NullPointerException("low不能为空");
}
if (high == null) {
throw new NullPointerException("high不能为空");
}
if (low.compareTo(high) > 0) {
return 0;
}
if (contains(high)) {
return rank(high) - rank(low) + 1;
} else {
return rank(high) - rank(low);
}
}
public Iterable<K> keys() {
return keys(min(), max());
}
public Iterable<K> keys(K low, K high) {
if (low == null) {
throw new NullPointerException("low不能为空");
}
if (high == null) {
throw new NullPointerException("high不能为空");
}
Queue<K> queue = new LinkedList<K>();
if (low.compareTo(high) > 0) {
return queue;
}
for (int i = rank(low); i < rank(high); i++) {
queue.offer(keys[i]);
}
if (contains(high)) {
queue.offer(keys[rank(high)]);
}
return queue;
}
public static void main(String[] args) {
BinarySearchBasedDictionary<String, String> map = new BinarySearchBasedDictionary<String, String>();
map.put("China", "leader");
map.put("America", "savage");
map.put("Japan", "dog");
System.out.println("now this size is: " + map.size());
map.remove("China");
map.remove("America");
System.out.println("now this size is: " + map.size());
}
}