版权声明:本文为博主原创,未经博主允许不得转载。 https://blog.csdn.net/weixin_36904568/article/details/88640757
符号表
1:定义
是一种存储键值对的数据结构,支持将新的键值对插入表中,根据键查找值
2:API
- ST():创建符号表
- void put(Key key,Value value):存储键值对
- void delete(Key key):删除键值对
- Value get(Key key):根据键获取值
- boolean contains(Key key):判断是否存在键值对
- boolean isEmpty():判断表是否为空
- int size():返回键的数量
- int size(Key low,Key high):返回范围内的键的数量
- Iterable < key > keys():获取所有键
- Iterable < key > keys(Key low,Key high):获取范围内的所有键
- Key min():获取最小值
- Key max():获取最大值
- void delMax():删除最大值
- void delMin():删除最小值
- Key floor(Key key):小于等于key的最大键
- Key ceil(Key key):大于等于key的最小键
- int rank(Key key):键的排名
- Key select(int k):排名为k的键
3:实现
(1):线性表
1:无序符号表的链表实现
构造:使用结点表示键值对,多个键值对连成链表
特点:在查找时使用顺序查找,再插入删除时直接插入删除。
public class Sequential_Unordered_ST<K,V> {
//头结点
private SymbolTableNode<K,V> head;
//链表长度
private int size;
public Sequential_Unordered_ST(){
head = new SymbolTableNode<>();
size = 0;
}
public Sequential_Unordered_ST(K key, V value){
head = new SymbolTableNode<>();
SymbolTableNode<K,V> node = new SymbolTableNode<>(key,value);
head.setNext(node);
size = 1;
}
//判断表是否为空
public boolean isEmpty(){
return size == 0;
}
//判断是否存在键值对
public boolean contains(K key){
return get(key) != null;
}
//获取值
public V get(K key){
SymbolTableNode<K,V> node = head;
SymbolTableNode<K,V> temp = null;
while (node.getNext() != null)
{
if(node.getNext().getKey().equals(key))
{
temp = node.getNext();
break;
}
node = node.getNext();
}
if(temp != null)
return temp.getValue();
return null;
}
//添加值
public void put(K key,V value){
if(key == null)
return;
if(value == null)
delete(key);
SymbolTableNode<K,V> node = head;
while (node.getNext() != null && (node.getKey() == null || !node.getKey().equals(key) ))
node = node.getNext();
if(node.getKey() != null && node.getKey().equals(key))
node.setValue(value);
else
{
SymbolTableNode<K,V> temp = new SymbolTableNode<>(key,value);
node.setNext(temp);
size++;
}
}
//删除键值对
public void delete(K key){
SymbolTableNode<K,V> node = head;
SymbolTableNode<K,V> temp = null;
while (node.getNext() != null)
{
if(node.getNext().getKey().equals(key))
{
temp = node.getNext();
node.setNext(temp.getNext());
size--;
break;
}
node = node.getNext();
}
}
//获取表长度
public int size() {
return size;
}
//获取所有键
public Iterable<K> keys(){
Iterator<K> iterator = new Iterator<K>() {
SymbolTableNode<K,V> node = head.getNext();
@Override
public boolean hasNext() {
return node != null;
}
@Override
public K next() {
K key = node.getKey();
node = node.getNext();
return key;
}
};
return new Iterable<K>() {
@Override
public Iterator<K> iterator() {
return iterator;
}
};
}
}
2:有序符号表的数组实现
构造:使用两个数组分别表示键和值
特点:在查找时根据索引使用二分查找,在插入删除时需要移动元素
//二分查找
public int binart_search(Comparable[] arr,K key,int size){
int low = 0,high = size - 1,mid = 0;
while (low <= high)
{
mid = (low+high)/2; //中间值
if(arr[mid].compareTo(key) > 0) //比中间的小,修改high
high = mid-1;
else if(arr[mid].compareTo(key) < 0) //比中间的大,修改low
low = mid+1;
if (arr[mid].compareTo(key) == 0) //命中
return mid;
}
return low;
}
/**
* 有序,用数组实现的符号表
* @param <K>
* @param <V>
*/
public class Sequential_Ordered_ST<K extends Comparable, V> {
//元素个数
private int size;
//保存键
private K[] keys;
//保存值
private V[] values;
public Sequential_Ordered_ST(int len) {
size = 0;
keys = (K[]) new Comparable[len];
values = (V[]) new Object[len];
}
public void resize(int max) {
K[] array1 = (K[]) new Comparable[max];
array1 = Arrays.copyOf(keys, max);
keys = array1;
V[] array2 = (V[]) new Object[max];
array2 = Arrays.copyOf(values, max);
values = array2;
}
//判断表是否为空
public boolean isEmpty() {
return size == 0;
}
//返回键的数量
public int size() {
return size;
}
//判断是否存在键值对
public boolean contains(K key) {
return get(key) != null;
}
//键的排名
public int rank(K key) {
if (isEmpty())
return 0;
BinarySearch search = new BinarySearch();
return search.binart_search((Comparable[]) keys, key, size);
}
//根据排名获取键
public K select(int rank) {
if (rank < 0 || rank > size - 1)
return null;
return keys[rank];
}
//获取值
public V get(K key) {
if (isEmpty())
return null;
int index = rank(key);
if (index < size && keys[index].compareTo(key) == 0)
return values[index];
return null;
}
//更新值
public void put(K key, V value) {
int index = rank(key);
if (index < size && keys[index].compareTo(key) == 0) {
values[index] = value;
if (value == null) {
for (int i = index; i < size; i++) {
values[i] = values[i + 1];
keys[i] = keys[i + 1];
}
size--;
if (size == keys.length / 4)
resize(keys.length / 2);
}
return;
}
if (size == keys.length)
resize(keys.length * 2);
for (int i = size; i > index; i--) {
values[i] = values[i - 1];
keys[i] = keys[i - 1];
}
keys[index] = key;
values[index] = value;
size++;
}
//删除键值对
public void delete(K key) {
if (isEmpty())
return;
put(key, null);
}
//获取长度
public int size(K low, K high) {
if (high.compareTo(low) < 0)
return 0;
else 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) {
LinkedQueue<K> queue = new LinkedQueue<>();
if (low != null && high != null)
{
for (int i = rank(low); i < rank(high); i++)
queue.enQueue(keys[i]);
if (contains(high))
queue.enQueue(high);
}
return queue;
}
//获取最大值
public K max() {
if (isEmpty())
return null;
return keys[size - 1];
}
//获取最小值
public K min() {
if (isEmpty())
return null;
return keys[0];
}
//删除最大值
public void delMax() {
delete(max());
}
//删除最小值
public void delMin() {
delete(min());
}
//小于等于key的值
public K floor(K key) {
if (isEmpty())
return null;
int index = rank(key);
if (index < size && key.compareTo(keys[index]) == 0)
return keys[index];
if (index == 0)
return null;
return keys[index - 1];
}
//大于等于key的值
public K ceiling(K key) {
if (isEmpty())
return null;
int index = rank(key);
if (index >= size)
return null;
return keys[index];
}
(2):树
1:二叉排序树
定义
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结构的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 它的左、右子树也分别为二叉排序树。
构造:使用结点表示键值对,结点带有子结点个数的信息,结点通过指针连接子树
特点:插入和查找的时间复杂度决定于树的结构
/**
* 二叉搜索树
*/
public class BinarySearchTree<K extends Comparable, V> {
//结点
private TreeNode<K, V> root;
public BinarySearchTree(K key, V value) {
root = new TreeNode<>(key, value,1);
}
//结点个数
public int size() {
return size(root);
}
private int size(TreeNode<K, V> node) {
if (node != null)
return node.getSize();
return 0;
}
//判断是否是空树
public boolean isEmpty() {
return size() == 0;
}
//是否包含关键字
public boolean contains(K key){
return get(key) != null;
}
//获取值
public V get(K key) {
return get(root, key);
}
private V get(TreeNode<K, V> node, K key) {
if (node == null)
return null;
if (key.compareTo(node.getKey()) > 0)
return get(node.getRight(), key);
else if (key.compareTo(node.getKey()) < 0)
return get(node.getLeft(), key);
else
return node.getValue();
}
//插入或更新值
public void put(K key, V value) {
put(root, key, value);
}
private TreeNode<K, V> put(TreeNode<K, V> node, K key, V value) {
if (node == null)
return new TreeNode<>(key, value,1);
else if (key.compareTo(node.getKey()) > 0)
node.setRight( put(node.getRight(), key, value));
else if (key.compareTo(node.getKey()) < 0)
node.setLeft( put(node.getLeft(), key, value));
else
node.setValue(value);
node.setSize(size(node.getLeft())+size(node.getRight())+1); //维护结点数量
return node;
}
//获取最大值,也就是最右边的树
public K max(){
return max(root).getKey();
}
private TreeNode<K,V> max(TreeNode<K,V> node){
if (node == null)
return null;
if (node.getRight() == null)
return node;
return max(node.getRight());
}
//获取最小值,也就是最左边的树
public K min(){
return min(root).getKey();
}
private TreeNode<K,V> min(TreeNode<K,V> node){
if (node == null)
return null;
if (node.getLeft() == null)
return node;
return min(node.getLeft());
}
//获取小于等于Key的键值对,也就是不断找比KEY小的左子树,然后在左子树的右边找稍小于KEY
public K floor(K key){
TreeNode<K,V> node = floor(root,key);
if (node == null)
return null;
return node.getKey();
}
private TreeNode<K,V> floor(TreeNode<K,V> node,K key){//左子树最右的树
if (node == null)
return null;
if (node.getKey().compareTo(key) == 0)
return node;
else if (node.getKey().compareTo(key) > 0) //找出比关键字小的树
return floor(node.getLeft(),key);
TreeNode<K,V> temp = floor(node.getRight(),key); //在比关键字的小的树中,看看相对而言大的树中是否存在一个值比关键字小
if(temp == null) //如果没有比根节点略大的话,那么应该就是根节点相对而言是最大的了
return node;
return temp;
}
//获取大于等于Key的键值对,也就是不断找比KEY大的右子树,然后在右子树的左边找稍大于KEY
public K ceiling(K key){
TreeNode<K,V> node = ceiling(root,key);
if (node == null)
return null;
return node.getKey();
}
private TreeNode<K,V> ceiling(TreeNode<K,V> node,K key) {//右子树最左的树
if (node == null)
return null;
if (node.getKey().compareTo(key) == 0)
return node;
else if (node.getKey().compareTo(key) < 0) //找出比关键字大的树
return floor(node.getRight(), key);
TreeNode<K, V> temp = floor(node.getLeft(), key); //在比关键字的大的树中,看看相对而言小的树中是否存在一个值比关键字大
if (temp == null) //如果没有比根节点略大的话,那么应该就是根节点相对而言是最大的了
return node;
return temp;
}
//比关键字小的节点数量,也就是不断找比结点小的左子树的结点数量,然后再、左子树的右子树中也有可能比结点小
public int rank(K key){
return rank(root,key);
}
private int rank(TreeNode<K,V> node,K key){
if(node == null)
return 0;
if(node.getKey().compareTo(key) > 0) //找出比关键字小的左子树
return rank(node.getLeft(),key);
else if (node.getKey().compareTo(key) < 0) //在左边的树中找出总结点数,和右边的树中比关键字小的树
return 1 + size(node) + rank(node.getRight(),key);
return size(node.getLeft()); //计算左子树的总结点
}
//选择排名对应结点
public K select(int rank) {
TreeNode<K,V> node = select(rank,root);
if (node == null)
return null;
return node.getKey();
}
private TreeNode<K,V> select(int rank,TreeNode<K,V> node){
if(node == null)
return null;
if(size(node.getLeft()) > rank) //如果左边的结点比排名大,那肯定结点在左子树
return select(rank,node.getLeft());
if (size(node.getLeft()) < rank) //如果左边的结点比排名小,那么结点应该在右子树
return select(rank - size(node.getLeft()) - 1,node.getRight());
return node;
}
//删除最大值
public void delMax(){
root = delMax(root);
}
private TreeNode<K,V> delMax(TreeNode<K,V> node){
if(node == null)
return null;
if(node.getRight() == null) //如果没有右子树,则当前结点为最大值,删除当前结点,用比顶点小的值代替
return node.getLeft();
node.setRight(delMax(node.getRight())); //如果有右子树,则需要删除最右边的结点,用右子树的第二个大的结点代替
node.setSize(size(node.getLeft())+size(node.getLeft())+1); //重新更新结点个数
return node;
}
//删除最小值
public void delMin(){
root = delMin(root);
}
private TreeNode<K,V> delMin(TreeNode<K,V> node){
if(node == null)
return null;
if(node.getLeft() == null) //如果没有左子树,则当前结点为最小值,删除当前结点,用比顶点大的值代替
return node.getRight();
node.setLeft(delMin(node.getLeft())); //如果有左子树,则需要删除最左边的结点,用左子树的第二个大的结点代替
node.setSize(size(node.getLeft())+size(node.getLeft())+1); //重新更新结点个数
return node;
}
//删除结点
public void delete(K key){
delete(root,key);
}
private TreeNode<K,V> delete(TreeNode<K,V> node,K key){
if(node == null)
return null;
if (node.getKey().compareTo(key) > 0)
node.setLeft(delete(node.getLeft(),key));
else if (node.getKey().compareTo(key) < 0)
node.setRight(delete(node.getRight(),key));
else {
//用一个孩子顶替
if (node.getLeft() == null)
return node.getRight();
else if (node.getRight() == null)
return node.getLeft();
//找出孩子中较大的顶替,也就是右孩子中的最左结点,把原来的左子树作为左子树,右孩子的剩余结点作为右子树
else
{
TreeNode<K,V> temp = node;
node = min(temp.getRight());
node.setRight(delMin(temp.getRight()));
node.setLeft(temp.getLeft());
}
}
node.setSize(node.getLeft().getSize()+node.getRight().getSize()+1);
return node;
}
//获取所有键
public Iterable<K> keys(){
return keys(min(),max());
}
//获取指定范围的键,按中序遍历,先找左子树,再找根节点,最后找右子树
public Iterable<K> keys(K low,K high){
LinkedQueue<K> queue = new LinkedQueue<>();
keys(low,high,queue,root);
return queue;
}
private void keys(K low,K high,LinkedQueue<K> queue,TreeNode<K,V> node){
if(node == null)
return;
if (node.getKey().compareTo(low) > 0)
keys(low, high, queue, node.getLeft());
if(node.getKey().compareTo(low) >= 0 && node.getKey().compareTo(high) <= 0)
queue.enQueue(node.getKey());
if(node.getKey().compareTo(high) < 0)
keys(low, high, queue, node.getRight());
}
public void print(){
print(root);
}
private void print(TreeNode<K,V> node){
if(node == null)
return;
print(node.getLeft());
System.out.printf("%s-%s,",node.getKey(),node.getValue());
print(node.getRight());
}
}
(2)平衡二叉树
2-3 树
(1)定义
2-3树的每一个结点都具有两个孩子(2结点)或三个孩子(3结点)。并且2-3树中所有的叶子都在同一层次上。
- 2结点包含一个元素和两个孩子(或没有孩子,不能只有一个孩子),左子树包含的元素小于该元素,右子树包含的元素大于该元素。
- 一个3结点包含一小一大两个元素和三个孩子(或没有孩子),左子树包含小于较小元素的元素,右子树包含大于较大元素的元素,中间子树包含介于两元素之间的元素。
(2)操作
查找
- 判断关键字和根节点,如果相同则命中
- 在相应的区间继续查找
- 查找失败则未命中
插入
- 空树:直接插入包含一个元素的2结点即可。
- 2结点:由于2结点只有一个元素,可以将其升级为3结点,拥有两个元素。插入的元素与当前叶子结点的元素比较大小后,决定左右位置。
- 3结点。因为3结点本身已经有两个元素了,需要将其拆分和移动。
- 如果3结点A没有双亲结点,则关键字与A结合为4结点,在三者中选择中间值向上移动一层,作为双亲节点,剩下两个分别为子结点。
- 如果3结点A的双亲结点B是一个2结点,则关键字与A结合为4结点,在三者中选择中间值向上移动一层,与双亲结点B结合为3结点,剩下两个分别为子结点(2结点)。
- 如果3结点A的双亲结点B是一个3结点,B的双亲结点C是一个2结点,则关键字与A结合为4结点,在三者中选择中间值向上移动一层,与双亲结点B结合为4结点,剩下两个分别为子结点(2结点)。然后在B结点中选择中间值向上移动一层,与结点C结合为3结点,剩下两个分别为子节点(2结点)。
- 如果3结点A的双亲结点B也是一个3结点,B的双亲结点C也是一个3结点,或者C是根节点,则关键字与A结合为4结点,在三者中选择中间值向上移动一层,与双亲结点B结合为4结点,剩下两个分别为子结点(2结点)。然后在B结点中选择中间值向上移动一层,与结点C结合为4结点,剩下两个分别为子节点(2结点)。结点C继续选择中间值向上移动一层,剩下两个分别为子节点(2结点)。
删除
- 3结点:在该结点处删除该元素。
- 叶子结点:将树按中序遍历后得到此元素的前驱或后继元素,补位
- 2结点:
- 如果该结点的双亲结点是2结点,拥有一个3结点的孩子,则将孩子进行旋转。
- 如果该结点的双亲结点是2结点,拥有一个2结点的孩子,则先将孩子与较大的元素成为3结点,再旋转
- 如果该结点的双亲结点是2结点,拥有一个1结点孩子,也就是满二叉树。则需要整一个树变形,孩子域双亲合成为3结点,同时它的双亲结点也要合成为3结点。
- 如果该结点的双亲结点是3结点,则将双亲结点拆分,成为2结点。
(3)特点:
在大小为N的2-3树中,查找和插入的次数小于等于lgN次
(4)实现:红黑树
定义
红黑树:使用左斜的红色的链接连接两个2结点,表示一个3结点,使用黑色的链接指向普通的2结点。
平衡二叉树:毎一个节点的左子树和右子树的髙度差至多为1。
- 平衡因子(BF):二叉树上结点的左子树深度减去右子树深度的值。(平衡二叉树上所有结点的平衡因子只可能是-1、0和1)
- 最小不平衡子树:距离插入结点最近的,且平衡因子的绝对值大于1的结点为根的子树。
结构:使用结点保存键值对,同时附带子结点数量的信息,与父节点链接的颜色信息。
实现:
/**
* 红黑树
*/
public class RedBlackTree<K extends Comparable,V> extends BinarySearchTree<K,V>{
private RedBlackNode<K,V> root;
public RedBlackTree(K key,V value) {
super(key,value);
this.root = new RedBlackNode<>(key,value,1,RedBlackNode.BLACK);
}
//结点的链接,空连接默认为黑色
private boolean isRed(RedBlackNode<K,V> node){
if (node == null) return false;
return node.isColor();
}
//左旋转
private RedBlackNode<K,V> rotateLeft(RedBlackNode<K,V> node){
if (node == null || !isRed(node.getRight()))
return null;
//转换根节点
RedBlackNode<K,V> temp = node.getRight();
node.setRight(temp.getLeft());
temp.setLeft(node);
//维护链接颜色
temp.setColor(node.isColor());
node.setColor(RedBlackNode.RED);
//维护结点
temp.setSize(node.getSize());
node.setSize(size(node.getLeft())+size(node.getRight())+1);
return temp;
}
//右旋转
private RedBlackNode<K,V> rotateRight(RedBlackNode<K,V> node){
if (node == null || !isRed(node.getLeft()))
return null;
//转换根节点
RedBlackNode<K,V> temp = node.getLeft();
node.setLeft(temp.getRight());
temp.setRight(node);
//维护链接颜色
temp.setColor(node.isColor());
node.setColor(RedBlackNode.RED);
//维护结点
temp.setSize(node.getSize());
node.setSize(size(node.getLeft())+size(node.getRight())+1);
return temp;
}
//转换颜色,把两个孩子变为黑色链接,同时自己变为红色链接
private void changeColor(RedBlackNode<K,V> node){
if (node == null || node.getLeft() == null || node.getRight() == null)
return;
if (
(!isRed(node) && isRed(node.getLeft()) && isRed(node.getRight())) ||
(isRed(node) && !isRed(node.getLeft()) && !isRed(node.getRight()))
)
{
node.setColor(!node.isColor());
RedBlackNode<K,V> temp = node.getLeft();
temp.setColor(!temp.isColor());
temp = node.getRight();
temp.setColor(temp.isColor());
}
}
//升级结点为3结点
private RedBlackNode<K,V> moveRedLeft(RedBlackNode<K,V> node){
if(node == null)
return null;
if(!isRed(node) || isRed(node.getLeft()) || isRed(node.getLeft().getLeft()))
return null;
//父节点与孩子结合成为4节点
changeColor(node);
//如果父节点的右孩子是3节点,则可以借键给左孩子
if (node.getRight() != null && isRed(node.getRight().getLeft()))
{
//父节点向右孩子借键,也就是把右孩子中比较小的结点移入父节点
node.setRight(rotateRight(node.getRight()));//右旋转,使得小节点移入父节点
//左孩子向父节点借键,也就是把父节点移入左孩子
node = rotateLeft(node);
}
return node;
}
//升级结点为3结点
private RedBlackNode<K,V> moveRedRight(RedBlackNode<K,V> node){
if(node == null)
return null;
if(!isRed(node) || isRed(node.getRight()) || isRed(node.getRight().getLeft()))
return null;
//父节点与孩子结合成为4节点
changeColor(node);
//如果父节点的左孩子是3结点,可以借键给右孩子
//右孩子向父节点借键,也就是把父节点移入右孩子
if (node.getLeft() != null && isRed(node.getLeft().getLeft()))
node = rotateRight(node); //不用旋转,因为右边肯定是大节点
return node;
}
//平衡树
private RedBlackNode<K,V> balance(RedBlackNode node) {
//右斜调整
if (isRed(node.getRight()))
node = rotateLeft(node);
//连续两条左斜链接
if (isRed(node.getLeft()) && isRed(node.getLeft().getLeft()))
node = rotateRight(node);
//4结点调整
if (isRed(node.getLeft()) && isRed(node.getRight()))
changeColor(node);
node.setSize(size(node.getLeft()) + size(node.getRight()) + 1);
return node;
}
//结点个数
public int size() {
return size(root);
}
private int size(RedBlackNode<K, V> node) {
if (node != null)
return node.getSize();
return 0;
}
//判断是否是空树
public boolean isEmpty() {
return root == null;
}
//是否包含关键字
public boolean contains(K key){
return get(key) != null;
}
//获取值
public V get(K key) {
return get(root, key);
}
private V get(RedBlackNode<K, V> node, K key) {
if (node == null)
return null;
if (key.compareTo(node.getKey()) > 0)
return get(node.getRight(), key);
else if (key.compareTo(node.getKey()) < 0)
return get(node.getLeft(), key);
else
return node.getValue();
}
//插入,初始化根节点颜色
public void put(K key,V value){
root = put(key,value,root);
root.setColor(RedBlackNode.BLACK);
}
private RedBlackNode<K,V> put(K key,V value,RedBlackNode<K,V> node){
//创建新结点
if(node == null)
return new RedBlackNode<>(key,value,1,RedBlackNode.RED);
//查找结点
if(key.compareTo(node.getKey()) < 0)
node.setLeft(put(key,value,node.getLeft()));
else if(key.compareTo(node.getKey()) > 0)
node.setRight(put(key,value,node.getRight()));
else
node.setValue(value);
//平衡树
if(isRed(node.getRight()) && !isRed(node.getLeft()))
node = rotateLeft(node);
if (isRed(node.getLeft()) && isRed(node.getLeft().getLeft()))
node = rotateRight(node);
if(isRed(node.getRight()) && isRed(node.getLeft()))
changeColor(node);
node.setSize(size(node.getLeft()) + size(node.getRight()) + 1);
return node;
}
//获取最大值,也就是最右边的树
public K max(){
return max(root).getKey();
}
private RedBlackNode<K,V> max(RedBlackNode<K,V> node){
if (node == null)
return null;
if (node.getRight() == null)
return node;
return max(node.getRight());
}
//获取最小值,也就是最左边的树
public K min(){
return min(root).getKey();
}
private RedBlackNode<K,V> min(RedBlackNode<K,V> node){
if (node == null)
return null;
if (node.getLeft() == null)
return node;
return min(node.getLeft());
}
//删除最大值
public void delMax(){
if(isEmpty())
return;
//先将根节点升级为3结点
if (!isRed(root.getLeft()) && !isRed(root.getRight()))
root.setColor(RedBlackNode.RED);
root = delMax(root);
//再初始化根节点的链接
if (!isEmpty()) root.setColor(RedBlackNode.BLACK);
}
private RedBlackNode<K,V> delMax(RedBlackNode<K,V> node){
//先将所有的红链接右斜
if(isRed(node.getLeft()))
node = rotateRight(node);
if(node.getRight() == null) //如果没有右子树,则当前结点为最小值,直接删除
return null;
//右孩子的红链接左斜
if (!isRed(node.getRight()) && !isRed(node.getRight().getLeft()))
node = moveRedRight(node);
//递归删除
node.setRight(delMax(node.getRight()));
//重新调整树
return balance(node);
}
//删除最小值
public void delMin(){
if(isEmpty())
return;
//先将根节点升级为3结点
if (!isRed(root.getLeft()) && !isRed(root.getRight()))
root.setColor(RedBlackNode.RED);
root = delMin(root);
//再初始化根节点的链接
if (!isEmpty()) root.setColor(RedBlackNode.BLACK);
}
private RedBlackNode<K,V> delMin(RedBlackNode<K,V> node){
if(node.getLeft() == null) //如果没有左子树,则当前结点为最小值,直接删除
return null;
//当前结点不是3结点,结点的左孩子也不是3结点,先升级
if (!isRed(node.getLeft()) && !isRed(node.getLeft().getLeft()))
node = moveRedLeft(node);
//递归删除
node.setLeft(delMin(node.getLeft()));
//重新调整树
return balance(node);
}
//删除
public void delete(K key){
if (!contains(key))
return;
//先将根节点升级为3结点
if (!isRed(root.getLeft()) && !isRed(root.getRight()))
root.setColor(RedBlackNode.RED);
root = delete(root,key);
if(!isEmpty())
root.setColor(RedBlackNode.BLACK);
}
private RedBlackNode<K,V> delete(RedBlackNode<K,V> node,K key){
//关键字在左子树,先把左结点由上至下升级
if (node.getKey().compareTo(key) > 0){
if (!isRed(node.getLeft()) && !isRed(node.getLeft().getLeft()))
node = moveRedLeft(node);
node.setLeft(delete(node.getLeft(),key));
}
//关键字在右子树,先把右结点由上至下升级
else
{
//升级前先右斜
if (isRed(node.getLeft()))
node = rotateRight(node);
//找到了,并且在叶子结点可以直接删除,在向下查找的过程中,已经保证了结点不可能是2-结点
if (node.getKey().compareTo(key) == 0 && (node.getRight() == null))
return null;
//把右结点由上至下升级
if (!isRed(node.getRight()) && !isRed(node.getRight().getLeft()))
node = moveRedRight(node);
//找到了,并且不在叶子节点,用后面的结点代替
if (node.getKey().compareTo(key) == 0) {
RedBlackNode<K,V> right = min(node.getRight());
node.setKey(right.getKey());
node.setValue(right.getValue());
node.setRight(delMin(node.getRight()));
}
//没找到,继续在右子树找
else
node.setRight(delete(node.getRight(),key));
}
//平衡树
return balance(node);
}
//获取所有键
public Iterable<K> keys(){
return keys(min(),max());
}
//获取指定范围的键,按中序遍历,先找左子树,再找根节点,最后找右子树
public Iterable<K> keys(K low,K high){
LinkedQueue<K> queue = new LinkedQueue<>();
keys(low,high,queue,root);
return queue;
}
private void keys(K low, K high, LinkedQueue<K> queue, RedBlackNode<K,V> node){
if(node == null)
return;
if (node.getKey().compareTo(low) > 0)
keys(low, high, queue, node.getLeft());
if(node.getKey().compareTo(low) >= 0 && node.getKey().compareTo(high) <= 0)
queue.enQueue(node.getKey());
if(node.getKey().compareTo(high) < 0)
keys(low, high, queue, node.getRight());
}
public void print(){
print(root);
}
private void print(RedBlackNode<K,V> node){
if(node == null)
return;
print(node.getLeft());
System.out.printf("%s-%s,",node.getKey(),node.getValue());
print(node.getRight());
}
(3)多路查找树
1:2-3树(3阶B树)
2: 2-3-4树(4阶B树)—— 与2-3树相比,多个一个4结点
定义:
一个4结点包含小中大三个元素和四个孩子(或没有孩子),左子树包含小于最小元素的元素;第二子树包含大于最小元素,小于第二元素的元素;第三子树包含大于第二元素,小于最大元素的元素;右子树包含大于最大元素的元素。
3: B树
定义
B树是一种平衡的多路査找树,2 - 3树和2 - 3 - 4树都是B树的特例。结点最大的孩子数目 称为B树的阶。
B树的阶数与硬盘存储的页面大小相匹配,B树的结点就是一个硬盘的一个页面,每一次访问内存都可以获得最大数量的数据。
特点
- 如果根结点不是叶结点,则其至少有两棵子树。
- 每一个非根的分支结点都有k-1个元素和k个孩子,每一个叶子结点n都有k-1个元素,其中[m/2l<=k<m。
- 所有叶子结点都位于同一层次。
- 所有分支结点包含下列信息数据(n,A0,K1,A1,K2,A2…Kn,An)。其中:K为关键字,A为指向子树根结点的指针,n为关键字的个数(n+1为子树的个数),Ki < Ki+1。
查找
- 根据指针查找结点
- 根据结点寻找关键字
4: B+树(适合带有范围的査找)
定义
如果使用B树,需要在硬盘页面之间多次访问。
在B树中,每一个元素在该树中只出现一次,有可能在叶子结点上,也有可能在分支结点上。
在B+树中,出现在分支结点中的元素会在该分支结点位置的叶子结点中再次列出。另外,每一个叶子结点都会保存一个指向后一叶子结点的指针。
区别
- 有n棵子树的结点中包含有n个关键字
- 所有的叶子结点包含全部关键字的信息,及指向含这些关键字记录的指针,叶子结点本身依关键字的大小自小而大顺序链接
- 所有分支结点可以看成是索引,结点中仅含有其子树中的最大(或最小)
关键字。
查找
- 随机査找:从根结点出发, 与B树的査找方式相同,只不过即使在分支结点找到了待査找的关键字,它也只是用来索引的,不能提供实际记录的访问,还是需要到达包含此关键字的终端结点。
- 顺序査找:从最左侧的叶子结点出发,不经过分支结点,而是延着指向下一叶子的指针就可遍历所有的关键字。
(3):散列表
用散列函数把键转换为数组的索引,同时处理碰撞冲突。
1:基于链地址法的实现
定义:
数组中的元素指向一条链表,如果关键字的散列值相同,则键值对存储在同一条链表中
结构:使用数组存放链表对象,使用链表的结点存放键值对
实现:
/**
* 链地址法
* @param <K>
* @param <V>
*/
public class ChainAddressST<K,V> {
//数组长度
private int size;
//哈希表
private LinkedST<K,V>[] table;
private HashSearch<K> search;
//初始化数组的链表
public ChainAddressST(int size) {
table = (LinkedST<K,V>[]) new LinkedST[size];
for (int i = 0; i < table.length; i++) {
table[i] = new LinkedST<>();
}
search = new HashSearch<K>();
}
//动态调整数组大小
public void resize(int max) {
LinkedST<K,V>[] array = (LinkedST<K,V>[]) new LinkedST[max];
array = Arrays.copyOf(table, max);
table = array;
ChainAddressST<K,V> st = new ChainAddressST<>(max);
for (int i = 0; i < table.length; i++) {
if (table[i].isEmpty())
continue;
for (K key:
table[i].keys()) {
st.put(key,table[i].get(key));
}
}
table = st.table;
}
//产生数组长度以内的哈希值
private int hash(K key){
return search.hash(table.length,key);
}
public int getSize() {
return size;
}
public boolean isEmpty(){
return size == 0;
}
public boolean contains(K key){
return get(key) != null;
}
//查找
public V get(K key){
if (isEmpty())
return null;
int index = hash(key);
V value = (V) table[index].get(key);//解决碰撞,查找链表
return value;
}
//添加或更新键值对
public void put(K key,V value){
if(size >= table.length / 2)
resize(table.length * 2);
int index = hash(key);
if (value == null)
{
if (table[index].contains(key))
size--;
table[index].delete(key);
return;
}
if (!table[index].contains(key))
size++;
table[index].put(key,value); //解决碰撞,直接加入链表
}
//删除键值对
public void delete(K key){
if(size > 0 && size <= table.length / 8)
resize(table.length / 2);
if(!isEmpty())
put(key,null);
}
//遍历键值对
public Iterable<K> keys(){
LinkedQueue<K> queue = new LinkedQueue<>();
for (int i = 0; i < table.length; i++) {
if (table[i].isEmpty())
continue;
for (Object key:
table[i].keys()) {
queue.enQueue((K)key);
}
}
return queue;
}
}
2:基于线性勘测法的实现
定义:依靠数组的空位解决冲突
结构:使用两个数组分别存放键值对
实现:
/**
* 线性勘测法
* @param <K>
* @param <V>
*/
public class LinearSurveyST<K extends Comparable,V> {
//元素个数
private int size;
//保存键
private K[] keys;
//保存值
private V[] values;
private HashSearch<K> search;
public LinearSurveyST(int len) {
keys = (K[]) new Comparable[len];
values = (V[]) new Object[len];
search = new HashSearch<K>();
}
//动态调整数组大小
public void resize(int max) {
LinearSurveyST<K,V> st = new LinearSurveyST<>(max);
for (int i = 0; i < keys.length; i++) {
if (keys[i] == null)
continue;
st.put(keys[i],values[i]);
}
keys = st.keys;
values = st.values;
}
//产生数组长度以内的哈希值
private int hash(K key){
return search.hash(keys.length,key);
}
public int getSize() {
return size;
}
public boolean isEmpty(){
return size == 0;
}
public boolean contains(K key){
return get(key) != null;
}
//查找键值对
public V get(K key){
if (isEmpty())
return null;
int index = hash(key);
while (keys[index] != null) {
if (keys[index].compareTo(key) == 0) //命中
return values[index];
index = (index + 1) % keys.length; //线性勘测法
}
return null; //未命中
}
//插入或更新键值对
public void put(K key,V value){
int index = hash(key);
while (keys[index] != null) {
if (keys[index].compareTo(key) == 0) //命中,更新键值对
{
values[index] = value;
if(value == null)
delete(key);
return;
}
index = (index + 1) % keys.length; //线性勘测法
}
//没有键,插入新的键值对
if (size >= keys.length / 2)
resize(keys.length * 2);
keys[index] = key;
values[index] = value;
size++;
}
public void delete(K key){
if(!contains(key))
return;
int index = hash(key);
//命中,删除键值对
while (!(keys[index].compareTo(key) == 0))
index = (index + 1) % keys.length; //线性勘测法
values[index] = null;
keys[index] = null;
size--;
if(size > 0 && size <= keys.length / 8)
resize(keys.length / 2);
//把之后的键值对重新插入
index = (index + 1) % keys.length; //线性勘测法
while (keys[index] != null) {
K k = keys[index];
V v = values[index];
keys[index] = null;
values[index] = null;
size--;
put(k,v);
index = (index + 1) % keys.length; //线性勘测法
}
}
public Iterable<K> keys() {
LinkedQueue<K> queue = new LinkedQueue<>();
for (int i = 0; i < keys.length; i++)
if (keys[i] != null)
queue.enQueue(keys[i]);
return queue;
}
}