符号表的主要目的就是将键和值联系起来,并且能够根据键找到对应的值。
符号表分为有序符号表和无序符号表两大类,(Python中自带的字典就是一种无序符号表)
目录
一、无序符号表
无序符号表顾名思义就是键是无序的
无序符号表支持的各种操作,API如下
public class ST<Key,Value> |
ST() 创建一张符号表 |
void put(Key key,Value value) 插入一对键值对 |
Value get(Key key) 根据一个键得到对应的值 |
void delete(Key) 删除一个特定的键值对 |
boolean contains(Key key) 是否包含特定的键值对 |
int size() 符号表所包含键值对的数量 |
boolean isEmpty() 符号表是否为空 |
Iterable<Key> keys() 返回可迭代的键 |
(一)、基于链表结构的无序符号表
类包含属性和方法,SequentialSearchST的属性如下
public calss SequentialSearchST<Key extends comparable<Key> ,Value>
{
private Node first;
private int N;
private class Node
{//Node类是存放键值对的数据类型
Key key;
Value value;
Node next;
Node(Key key,Value value,Node next)
{//带参数的Node类型创建,多这一步主要是为了插入新结点时便于操作
this.key = key;
this.value = value;
this.next = next;
}
}
}
SequentialSearchST的方法如下
①使用迭代的put方法(Java称为方法,别的语言成为函数,二者没有本质区别)
public void put(Key key,Value value)
{
if(key==null) throw new IllegalArgumentException("键值不可为空");//键为空时,抛出异常
if(value == null) delete(key);//保证键的值不为空
for(Node x = first;x!=null;x = x.next)
{//表中已有该键,更新其值即可
if(x.key.equals(key))
{x.value = value;return;}
}
first = new Node(key,value,first);//Node的构造函数就是为了方便插入新值
N++;
}
使用递归的put方法,我不会!!!
②运用迭代的get方法
public Value get(Key key)
{
if(key==null) throw new IllegalArgumentException("要删除的键值不存在");
for(Node x = first;x!=null;x = x.next)
{
if(key.equals(x.key))
return x.val;
}
return null;
}
运用递归的get方法
public Value get(Key key)
{
if(key==null) throw new IllegalArgumentException("要删除的键值不存在");
Node x = get(first,key);//定义得到键key所在Node
if(x==null) return null;//未命中返回null
return x.value;
}
private Node get(Node node,Key key)
{
if(node==null) return null;
if(node.key.equals(key)) return node;
else return get(node.next,key);
}
③运用迭代的delete方法
public void delete(Key key)
{
if(first.key.equals(key)) first = first.next;
for(Node x = first;x!=null;x = x.next)
{
if(x.key.equals(key))
{
x = x.next;
}
}
}
运用递归的delete方法
public void delete(Key key)
{
if(key==null) throw new IllegalArgumentException("要删除的键值不存在");
first=delete(first,key);
}
private Node delete(Node node,Key key)
{
if(node == null) return null;
if(key.equals(node.key))
{
N--;
return node.next;
}
node.next = delete(node.next,key);
return node;
}
④其他方法
public int size()
{
return N;
}
public boolean isEmpty()
{
return N==0;
}
public boolean contains(Key key)
{
return get(key) !=null;
}
public Iterable<Key> keys()
{
Stack<Key> stack = new Stack<Key>();
for(Node x = first;x!=null;x= x.next)
stack.push(x.key);
return stack;
}
(二)、散列表
1、散列表定义
散列表就是用算术操作将键转化为数组的索引来访问数组中的键值对。
2、散列函数与处理碰撞的方法
使用散列的查找算法分为两步。
第一步是利用散列函数处理键将其转化为数组的一个索引;理想情况下不同的键能转化为不同的索引值,但是经常会出现不同的键转化为相同的索引值的情况,这时候就需要第二步处理冲突。
第一步散列函数,常见数据类型散列函数
第二步处理碰撞冲突,两大方法:拉链法和线性探测法
3、拉链法
例子如下
键 | S | E | A | R | C | H | E | X | A | M | P | L | E |
散列值 | 2 | 0 | 0 | 4 | 4 | 4 | 0 | 2 | 0 | 4 | 3 | 3 | 0 |
值 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
如上所示,不同的键会有相同的散列值,如果将键的散列值作为数组索引来存储键值对,将会出现冲突。而拉链法就是用一个链表的数组来处理冲突。
public class SeperateChainingHashS<Key,Value>
{
private int M; //数组大小
private int N; //键值对个数
private SequentialSearchST<Key,Value>[] sts; //存放键值对的链表
SeperateChainingHashST(int cap)
{//构造函数,指定数组大小
this.M = cap;
sts = (SequentialSearchST<Key,Value>[]) new SequentialSearchST<Key,Value>[M];
//创建数组后需要迭代创建链表对象,为后续操作基础
for(int i = 0;i < M;i++)
{
sts[i] = new SequentialSearchST();
}
}
SeperateChainingHashST()
{
this(997);
}
public int hash(Key key)
{//该方法得到散列值
return (key.hashCode() & 0x7fffffff)%M;
}
public void put(Key key, Value value)
{
int hash = hash(key);
if(!sts[hash].contains(key)) N++; //当符号表中不存在该键时,总键值对数量加一
sts[hash].put(key,value); //不需要考虑是否已存在该键,直接用链表符号表的put方法即可
}
public Value get(Key key)
{
return (Value)sts[hash(key)].get(key); //需要进行类型转换
}
}
拉链法的delete方法如下
public void delete(Key)
{
int hash = hash(key);
if(!sts[hash].contains(key)) return;
sts[hash].delete(key); //借用链表符号表的delete方法
N--;
}
其他方法如下
public int size()
{
return N;
}
public boolean contains(Key key)
{
return get(key) != null;
}
public boolean isEmpty()
{
return N==0;
}
public Iterable<key> keys()
{//返回一个可迭代的包含所有键的集合
Queue<Key> queue = new Queue<Key>(); //创建一个队列来存储键,因为队列是一种可迭代的数据类型
for(int i = 0;i<sts.length;i++)
{
if(sts[i]==null) continue;
for(Key key:sts[i].keys())
{
queue.enqueue(key);
}
}
return queue;
}
4、线性探测法
依靠数组中的空位解决碰撞冲突。基于这种策略的所有方法被统称为开放地址散列表。
开放地址散列表最简单的方法叫做线性探测法:当碰撞发生时(即一个键的散列值已经被另一个不同的键占用),我们直接检查散列表中的下一个位置(将索引值加1)。这样的线性探测可能会产生三种结果:
- 命中,该位置的键与被查找的键相同
- 未命中,键为空(该位置没有键)
- 继续查找,该位置的键与被查找的键不同
线性探测法代码如下
public class LinearProbingHashST<Key,Value>
{
private int M =16; //数组原始大小
private int N; //键值对个数
private Key[] keys; //键数组
private Value[] vals; //值数组
public LinearProbingHashST(int cap)
{//带参构造函数
this.M = cap;
keys = (Key[]) new Object[M];
vals = (Value[]) new Object[M];
}
public LinearProbingHashST()
{//无参构造函数
this(M);
}
public int hash(Key key)
{//得到哈希值
return (key.hashCode() & 0x7fffffff)%M;
}
}
①put方法
public void put(Key key,Value value)
{
//因为线性探测法必须要有空位,所以数组大小必须时刻大于键值对数量
if(N>=M/2) resize(2*M); //resize方法是动态调节数组长度的方法,详细代码见下文
int i;
for(i = hash(key);keys[i] !=null;i=(i+1)%M)
if(keys[i].equals(key))
{vals[i] = val;return;}
keys[i] = key;
vals[i] = val;
N++;
}
②get方法
public Value get(Key key)
{
for(int i = hash(key); keys[i] != null; i = (i+1)%M)
if(keys[i].equals(key)) return vals[i];
return null;
}
③delete方法
线性探测法不能像其他的方式那样直接将键所在位置设为null,因为这会使得此位置之后的元素无法被查找。因此需要在该箭簇中将删除的键后面的所有键重新插入散列表。
public void delete(Key key)
{
if(!contains(key)) return null;
int i = hash(key); //获得键的散列值
while(!keys[i].equals(key))
{//找到键的对应位置
if(keys[i].equals(key))
break;
i = (i+1) % M;
}
keys[i] = null;
vals[i] = null;
i = (i+1) % M;
while(keys[i]!=null)
{
Key keyToRedo = keys[i]; //创造一个变量存放下一个待重新插入的键
Value ValToRedo = vals[i]; //创造一个变量存放下一个待重新插入的键
keys[i] = null;
vals[i] = null;
N--;
put(keyToRedo,ValToRedo);
i = (i+1)%M;
}
N--;
if(N>0&&N==M/8) resize(M/2); //
}
public boolean contains(Key key)
{
int i = hash(key);
while(keys[i] != null)
{
if(keys[i].equals(key))
return true;
i = ( i + 1 ) % M;
}
retrun false;
}
④调整数组大小 resize()方法
public void resize(int cap)
{
LinearProbingHashST st = new LinearProbingSearchST(cap); //创建一个新散列表,大小是调整后的
for(int i = 0;i<M;i++)
{//将键值对重新插入
if(keys[i]==null) continue;
st.put(keys[i],vals[i]);
}
keys = st.keys;
vals = st.vals;
M = t.M;
}
但是上述调整数组大小的方法有不足之处,因为调整数组之后,散列表大小始终是2的幂,这样hash()方法只用了hashCode()返回值的低位。解决该问题的方法是先用一个大于M的素数来散列键值对。将hash函数改造如下:
private int hash(Key x)
{
int t = x.hashCode() & 0x7fffffff;
if(lgM > 26) t = t % primes[lgM+5];
return t % M;
}
primes | 21 | 61 | 127 | 251 | 509 | 1021 | 2039 | 4093 | 8191 | 16381 | 32749 | 65521 | 131071 | 262139 | 524287 |
二、有序符号表
有序符号表键都是comparable对象,可以使用a.compareTo(b)来比较a和b两个键
有序泛型符号表的API如下
public class ST<Key extends comparable<Key>,Value> | |
ST() | 创建一张有序符号表 |
void put (Key key,Value value) | 将键值对插入表中 |
Value get (Key key) | 获得对应值 |
void delete(Key key) | 删除对应键值对 |
boolean contains (Key key,Value value) | 是否包含键值对 |
boolean isEmpty() | 符号表是否为空 |
int size() | 返回键值对个数 |
Key max() | 最大的键 |
Key min() | 最小的键 |
Key floor(Key key) | 小于等于key的最大值 |
Key ceiling(Key key) | 大于等于key的最小值 |
int rank (Key key) | 小于键key的数量 |
Key select (int k) | 找出第k个键 |
void deleteMin() | 删除最小值 |
void deleteMax() | 删除最大值 |
int size(Key lo,Key hi) | lo到hi有多少个键 |
Iterable<Key> keys(Key lo, Key hi) | lo至hi之间所有的键 |
Iterable<Key> keys() | 可迭代的所有键的集合(已排序) |
(一)、有序符号表二分查找法
二分查找法的数据结构是两个平行的数组,核心是rank()方法---返回比key值小的键的个数
rank方法代码如下
//递归法的rank函数
public int rank(Key key)
{//返回的是表中小于目标键的数量,也就是数组中的索引
return rank(0,N-1,key); //N是键值对个数
}
public int rank(int lo,int hi,Key key)
{//该rank方法所使用的是迭代法,不是递归法(递归法见算法4第15页)
int lo = lo, hi = hi;
while(lo<=hi)
{
int mid = lo + (hi-lo)/2;
int n = keys[mid].compareTo(key);
if(n==0) return mid; //当keys[mid]等于key时,直接返回mid值
else if(n>0) {hi = mid - 1;} //当keys[mid]大于key时,说明key在左半部分
else {lo = mid + 1;} //当keys[mid]小于key时,说明key在右半部分
}
return -1;
}
二分查找法有序符号表的代码实现 ( 属性和构造函数)
public class BinarySearchST()
{
private Key[] keys;
private Value[] vals;
private int N;
BinarySearchST(int capacity)
{
keys[] = (Key[]) new Comparable[capacity];
vals[] = (Value[]) new Object[capacity];
N = 0;
}
BinarySearchST()
{
this(2);
}
}
① 二分查找法的put() 方法
public void put(Key key,Value val)
{
if(2*N>keys.length) resize(2*keys.length);
int n = rank(key); //比key小的键的个数
if(contains(key))
vals[n] = val; //当键已存在时,改变对应值即可
for(int i = N;i>n;i--)
{//从最后一个键值对开始,将比插入的键都大的键值对往后移一位
keys[i] = keys[i-1];
vals[i] = vals[i-1];
}
keys[n-1] = key;
vals[n-1] = val; //在对应位置插入键值对
}
②get() 方法
public Value get(Key key)
{
if(!contains(key)) return null;
int n = rank(key);
return vals[n-1];
}
③delete方法
public void delete(Key key)
{
if(isEmpty()) throw new IllegalArgumentException("符号表为空,删除错误")
if(N*4<keys.length) resize(keys.length/2);
if(!contains(key))
return;
int n = rank(key);
for(int i = n-1;i<N-1;i++)
{//从要删除的位置开始,将指定键后面的键往前移一位
keys[i] = keys[i+1];
vals[i] = vals[i+1];
}
keys[N-1] = null;
vals[N-1] = null; //最后一个键值对设为空
}
④Iterable<Key> keys()
public Iterable<Key> keys()
{
return keys[]; //数组本身也是可迭代的数据类型,直接返回数组即可
}
public Itreable<Key> keys(Key min, Key max)
{
int n1 = rank(min), n2 = rank(max);
Queue<Key> queue = new Queue<Key>();
for(int i = n1; i<n2;i++)
{
queue.enqueue(keys[i]); //因为不管符号表有没有min这个键,都能把大于等于min的键纳入
}
if(contains(max))
queue.enqueue(max); //而若符号表不含hi键,那么就不能把hi键纳入队列
}
//Queue(队列)是一种数据结构,先入先出的原则,可迭代
⑤ resize方法
public void resize(int cap)
{
Key[] temp_keys = (Key[]) Comparable[cap];
Value[] temp_vals = (Value[]) Object[cap];
for(int i = 0;i<N;i++)
{
temp_keys[i] = keys[i];
temp_vals[i] = vals[i];
}
keys = temp_keys;
vals = temp_vals;
}
⑥其他方法
public boolean contains(Key key)
{
int n = rank(key);
if(keys[n].equals(key))
return true;
esle {return false;}
}
public int size(){return N;}
public boolean isEmpty(){return N==0;}
public Key max() { return keys[N-1]; }
public Key min() { return keys[0]; }
public Key select(int k) { return keys[k];}
public Key ceiling(Key key)
{
int n = rank(key);
return keys[n];
}
public Key floor(Key key)
{
if(contains(key)) //当存在该键时直接返回该键
return key;
int n = rank(key);
if(i>=1) {return keys[i-1];}
else {return null;} //如果给定的key小于第一个键,那么返回null
}
(二)、二叉查找树
二叉查找树(BST)的数据结构由节点组成,比当前结点大的放在当前结点的右链接,比当前结点小的放在当前结点的左链接(如下图所示,下图是依次将 6、8、3、1、5、7、9、2、0插入二叉查找树)
符号表的二叉查找树每个结点都有键、值、左链接、右链接和一个结点计数器
二叉查找树的符号表代码实现
public calss BST()
{//私有属性是一个root结点
private Node root;
class Node()
{
private Key key;
private Value val;
private Node left;
private Node right;
private int N;
Node(Key key, Value val, int n)
{
this.key = key;
this.val = val;
this.N = n;
}
}
}
① put()方法
//递归实现put方法
public void put(Key key,Value val)
{
root = put(root, key, val);
}
public Node put(Node node, Key key, Value val)
{
if(node == null)
{//node 为空时新建一个结点并返回
return new Node(key,val,1);
}
int c = key.compareTo(node.key);
if(c == 0) node.val = val;
else if(c<0)
node.left = put(node.left,key,val);
else node.right = put(node.right,key,val);
node.N = size(node.left.N) + size(node.right.N)
}
②get()方法
public Value get(Key key)
{
if(get(root, key) == null)
return null;
return get(root, key).val;
}
public Node get(Node node, Key key)
{
if(node==null)
return null;
int t = key.compareTo(node.key)
if(t==0)
return node;
else if(t<0)
return get(node.left,key);
else return get(node.right, key);
}
③delete()方法
delete方法比较复杂,在介绍之前先了解一下deleteMax() 和deleteMin()
因为在二叉查找树中左链接小,右链接大,所以左连接为空的结点是最小结点,右链接为空的结点是最大结点
deleteMax()-----迭代法实现deleteMax(), 不用看了,下列方法写错了,但我不舍得删除。看递归法就好了
public void deleteMax()
{//代码冗长复杂,还没有结点计数器,就是坨shit
if(root == null)
{//根节点为空时报错
new throw IllegalArgumentException("符号表为空,删除错误");
}
if(root.right == null)
{当根节点没有右链接时,那么根节点就是最大值
root = root.left;
}
for(Node x = root; x.right != null; x = x.right)
{
if(x.right.right==null)
{ x.right = null;}
}
}
摸鱼刷B站看到大四毕业离校宿舍实录,突然绷不住了,我现在好难啊,好想回到曾经那个宿舍一块打游戏吹牛逼(赟哥,健哥,郑德润,傻逼张宇和王星哲,家荣,帅广,你们会看到吗),何老板,东阳,亮亮我还想去你们宿舍跟你们开黑,不带赟哥了,赟哥的打野太坑了。
deleteMax() -------- 递归法实现
public void deleteMax()
{
root = deleteMax(root);
}
public Node deleteMax(Node node)
{
if(node.right == null)
return node.left;
node.right = deleteMax(node.right);
node.N = size(node.left)+size(node.right)+1;
return node;
}
deleteMin() ------------ 同样是递归方法
public void deleteMin()
{
root = deleteMin(root);
}
public Node deleteMin(Node node)
{
if(node.left == null)
return node.right;
node.left = deleteMax(node.left);
node.N = size(node.left) + size(node.right) + 1;
return node;
}
delete()方法,实现delete方法需要四大步骤
1、找到要删除的结点x,并将该节点保存为t
2、将x结点指向他的后继结点min(t.right),也就是原结点右链接中的最小节点
3、x的右链接指向deleteMin(t.right),左链接是t.left。
4、递归调用结束后会修正被删除的结点的父节点的链接,并由此结点到根节点的路径上所有的结点计数器减1。
public void delete(Key key)
{
root = delete(root,key);
}
public Node delete(Node node, Key key)
{
if(node == null) reurn null;
int c = key.compareTo(node.key);
if(c>0)
{ node.right = delete(node.right,key);}
else if(c<0)
{ node.left = delete(node.left,key);}
else
{
if(node.left == null) return node.right;
if(node.right == null) return node.left; //当要被删除的结点左(右)链接为空时的情况,比较简单
Node t = node; //第一步
node = min(t.right); //第二步。min方法见下文,一个寻找最小节点的简单方法(函数)
node.right = deleteMin(t.right);
node.left = t.left; //第三步
}
node.N = size(node.left)+size(node.right)+1;
return node;
}
④keys()方法------范围查找
遍历二叉查找树有个方法叫做中序遍历,即通过递归的方法先遍历二叉树的左子树,再访问根节点,最后遍历二叉树的右子树。
public Itearble<Key> keys()
{
return keys(min(),max());
}
public Queue<Key> keys(Key lo, Key hi)
{
Queue<Key> queue = new Queue<Key>();
keys(root,queue,lo,hi);
return queue;
}
public void keys(Node node,Queue<key> queue,Key lo,Key hi)
{
if(node == null) return;
int cmplo = lo.compareTo(node.key);
int cmphi = hi.compareTo(node.key);
//不加条件可遍历所有键,加条件是为了保证所查找的键在目标范围之内
if(cmplo<0) keys(node.left,queue,lo,hi); //左子树上的结点入列
if(cmplo<=0 && cmphi>=0) queue.enqueue(x.key);
if(cmphi>0) keys(node.right,queue,lo,hi); //右子树上的结点入列
}
⑤本文中用到的一些其他方法
publci Key min()
{
Node n = min(root);
if(n==null) return null;
return n.key;
}
public Node min(Node x)
{
if(x.left==null) return x;
return min(x.left);
}
public int size()
{
return size(root);
}
public int size(Node x)
{
if(x==null) return 0;
return x.N;
}
public Key floor(Key key)
{//找出小于等于键key的最大键
Node t = floor(root,key);
if(t==null) return null;
return t.key;
}
public Node floor(Node x, Key key)
{
if(x == null) return null;
int cmp = key.compareTo(x.key);
if(cmp==0) return x;
if(cmp<0) floor(x.left,key);
Node t = floor(x.right,key);
if(t!=null) return t;
else return x;
}
floor()方法的思路如下
首先将指定的键与根节点的键对比:
①比根节点小,说明要查找的键在左子树,继续递归查找;
②如果当根节点右子树中存在小于等于key的结点时,小于等于key的最大键才会出现在右子树中,否则根节点就是小于等于key的最大键。
如上述代码所示,结点 t 接受右子树查找结果,t为空则“根”节点(当前结点)就是小于等于key的最大键,t不为空则t就要小于等于key的最大键
⑥select()方法和rank()方法
public key select(int k)
{//找出二叉树中排名为k的键(排名从0开始,也就是第k+1个)
Node t = select(root,k);
if(t == null) return null;
return t.key;
}
public Node select(Node x,int k)
{
if(x == null) return null;
int t = size(x.left);
if(t<k) return select(x.right,k-t-1);
else if(t>k) return select(x.left,0);
else return x;
}
public int rank(Key key)
{//返回比指定键key小的键的个数
return rank(root,key);
}
public int rank(Node x, Key key)
{
if(x == null) return 0;
int cmp = key.compareTo(x.key);
if(cmp<0) return rank(x.left,key);
else if(cmp>0) return 1+size(x.left)+rank(x.right,key);
else return size(x.left);
}
(三)、平衡查找树
二叉查找树的性能已经很高了,在进行N次插入随机键之后,平均情况下的查找命中运行时间是1.39lgN, 插入的运行时间也是是1.39lgN。
但是在升序或降序的键(最坏情况)插入二叉查找树中,运行效率依然不高
如在二叉树中插入1、2、3、4、5、6、7、8......插入和查找的运行时间都是N
public class BSTRedBlack<Key extends Comparable<Key>,Value> {
private static final boolean RED = true;
private static final boolean BLACK = false;
private Node root;
class Node{
private Key key;
private Value value;
private Node left,right;
private boolean color;
private int N;
public Node(Key key,Value value,int N,boolean color) {
this.key = key;
this.value = value;
this.N = N;
this.color = color;
}
}
/**
* {@code rotateLeft}将红树左翻转
* @param h
* @return
*/
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 = 1+size(h.left)+size(h.right);
return x;
}
/**
* {@code rotateRight}将红树右翻转
* @param h
* @return
*/
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 = 1+size(h.left)+size(h.right);
return x;
}
private void flipColors(Node h)
{
h.color = !h.color;
h.left.color = !h.left.color;
h.right.color = !h.right.color;
}
private boolean isRed(Node h)
{
if(h==null) return false;
return h.color==RED;
}
/**
* {@code put} 插入键值对
* @param key
* @param value
*/
public void put(Key key,Value value)
{
root = put(root,key,value);
root.color = BLACK;
}
public Node put(Node h,Key key,Value value)
{
if(h==null)
return new Node(key,value,1,RED);
int cmp = key.compareTo(h.key);
if(cmp<0) h.left = put(h.left,key,value);
else if(cmp>0) h.right = put(h.right,key,value);
else h.value = value;
if(isRed(h.right)&&!isRed(h.left)) h = rotateLeft(h);
if(isRed(h.left)&&isRed(h.left.left)) h = rotateRight(h);
if(isRed(h.left)&&isRed(h.right)) flipColors(h);
h.N = 1+size(h.left)+size(h.right);
return h;
}
public Value get(Key key)
{
return get(root,key);
}
public Value get(Node h,Key key)
{
if(h==null) return null;
int cmp = key.compareTo(h.key);
if(cmp<0) {
return get(h.left,key);
}
else if(cmp>0) {
return get(h.right,key);
}
else return h.value;
}
public Key min()
{
return min(root).key;
}
public Node min(Node h)
{
if(h.left==null) return h;
h = min(h.left);
return h;
}
public Key max()
{
return max(root).key;
}
public Node max(Node h)
{
if(h.right==null) return h;
h =max(h.right);
return h;
}
public Key floor(Key key)
{
Node t = floor(root,key);
if(t==null) return null;
return t.key;
}
public Node floor(Node x,Key key)
{
if(x==null) return null;
int cmp = key.compareTo(x.key);
if(cmp==0) return x;
if(cmp<0) return floor(x.left,key);
Node t = floor(x.right,key);
if(t!=null) return t;
else return x;
}
/**
* {@code ceiling} 向上取整
* @param key
* @return
*/
public Key ceiling(Key key) {
Node x= ceiling(root,key);
if(x==null) return null;
return x.key;
}
public Node ceiling(Node x,Key key)
{
if(x==null) return null;
int cmp = x.key.compareTo(key);
if(cmp==0) return x;
if(cmp>0) return ceiling(x.right,key);
Node t = ceiling(x.left,key);
if(t!=null) return t;
else return x;
}
/**
* {@code select} 选择指定位置的键 //不确定对不对,用数据考证一下//
* @return
*/
public Key select(int k) {
if(k>root.N||k<=0) return null;
return select(root,k).key;
}
public Node select(Node x,int k) {
if(k==size(x.left)+1) return x;
if(k<size(x.left)+1) return select(x.left,k);
int n = k-size(x.left)-1;
return select(x.right,n);
}
/**
* {@code rank} 返回键key的排名
* @param key
* @return
*/
public int rank(Key key) {
return rank(root,key);
}
public int rank(Node x,Key key) {
if(x==null) return 0;
int cmp = key.compareTo(x.key);
if(cmp==0) return size(x.left)+1;
else if(cmp<0) return rank(x.left,key);
else return size(x.left)+rank(x.right,key)+1;
}
public Node moveRedRight(Node h)
{
flipColors(h);
if(isRed(h.left.left))
{
h.left = rotateRight(h.left);
flipColors(h);
}
return h;
}
public Iterable<Key> keys(){
return keys(min(),max());
}
/**
* {@code keys(Key lo,Key hi)} 返回指定范围的keys
* @param lo
* @param hi
* @return
*/
public Iterable<Key> keys(Key lo,Key hi){
Queue<Key> queue = new Queue<Key>();
keys(root,queue,lo,hi);
return queue;
}
private void keys(Node x,Queue<Key> queue,Key lo,Key hi){
if(x==null) return;
int cmplo = lo.compareTo(x.key);
int cmphi = hi.compareTo(x.key);
if(cmplo<0) keys(x.left,queue,lo,hi);
if(cmplo<=0&&cmphi>=0) queue.enqueue(x.key);
if(cmphi>0) keys(x.right,queue,lo,hi);
}
/**
* {@code height}计算二叉树的高度
* @return
*/
public int height() {
return height(root);
}
public int height(Node x) {
if(x==null) return -1;
return 1+Math.max(height(x.left),height(x.right));
}
public Node moveRedLeft(Node h)
{
flipColors(h);
if(isRed(h.right.left))
{
h.right = rotateRight(h.right);
h = rotateLeft(h);
flipColors(h);
}
return h;
}
/**
* {@code deleteMin}删除最小键值对
*/
public void deleteMin()
{
if(!isRed(root.left)&&!isRed(root.right))
root.color = RED;
root = deleteMin(root);
if(!isEmpty()) root.color = BLACK;
}
private Node deleteMin(Node h)
{
if(h.left==null)
return null;
if(!isRed(h.left)&&!isRed(h.left.left))
h = moveRedLeft(h);
h.left = deleteMin(h.left);
return balance(h);
}
public void deleteMax()
{
if(!isRed(root.left)&&!isRed(root.right))
{
root.color = RED;
}
root = deleteMax(root);
if(!isEmpty()) root.color = BLACK;
}
private Node deleteMax(Node h)
{
if(isRed(h.left))
h = rotateRight(h);
if(h.right==null) return null;
if(!isRed(h.right)&&!isRed(h.right.left))
{
h = moveRedRight(h);
}
h.right = deleteMax(h.right);
return balance(h);
}
public void delete(Key key)
{
if(!isRed(root.left)&&!isRed(root.right))
root.color = RED;
root = delete(root,key);
if(!isEmpty()) root.color = BLACK;
}
public Node delete(Node h,Key key)
{
if(key.compareTo(h.key)<0)
{
if(!isRed(h.left)&&!isRed(h.left.left))
h = moveRedLeft(h);
h.left = delete(h.left,key);
}
else
{
if(isRed(h.left))
h = rotateRight(h);
if(key.compareTo(h.key)==0&&(h.right==null))
return null;
if(!isRed(h.right)&&!isRed(h.right.left))
{
h = moveRedRight(h);
}
if(key.compareTo(h.key)==0)
{
h.value = get(h.right,min(h.right).key);
h.key = min(h.right).key;
h.right = deleteMin(h.right);
}
else h.right = delete(h.right,key);
}
return balance(h);
}
/**
* {@code size}返回树的大小
* @return
*/
public int size() {
return size(root);
}
public int size(Node x) {
return x.N;
}
public Node balance(Node h)
{
if(isRed(h.right)) h = rotateLeft(h);
if(isRed(h.right)&&!isRed(h.left)) h = rotateLeft(h);
if(isRed(h.left)&&isRed(h.left.left)) h = rotateRight(h);
if(isRed(h.right)&&isRed(h.left)) flipColors(h);
h.N = 1+size(h.right)+size(h.left);
return h;
}
public boolean isEmpty() {
return root==null;
}
}