该博客内容存在BUG,目前作者正在修复中
1.本篇博客所研究的内容为红黑树,平衡二叉树的特性为利用快速的利用二分法进行查找数据,数据结构如下图所示:
在上图中,节点4为TreeMap的根节点,根节点为我们进行寻址所使用的最初的节点,每个节点都具有左右两个节点的引用,左侧节点引用的对象是一个在比较时小于节点本身的值,而右边则是比节点本身要大的值。而如其名称,在该树的每一个节点上,都是一个单独的平衡二叉树结构。
2.每个平衡二叉树的节点上都具有一个平衡因子,这个平衡因子的值只能够为 -2,-1,0,1,2五个值。当任何一个节点的平衡因子的值为-2或者2时,则这颗二叉树就失去了平衡。平衡因子如下图所示:
在上图中最上方节点则为这棵平衡二叉树的根节点,我们可以看到图中有两个节点的平衡因子值为-2,在这时,这棵树的平衡就是被打破的,处于一种失衡状态。
3.在这里先不对这个树的失衡进行处理,而是来分析一下这个树的平衡因子的计算方式:树的平衡因子,是使用树结构两边的深度差来进行计算的,计算方式为:左节点深度 - 右节点深度。节点深度图如下图所示
我们可以看到一个节点的深度,是通过这个节点的左右两个子节点的深度进行计算的,如果子节点不存在,则视该子节点的深度值为0,子节点存在时,该节点的深度,则为左右两个子节点深度大的节点的深度 +1。
4.当我们理解了树的深度概念以后,对于二叉树的平衡因子的计算方式就会简单很多了。此时,这棵平衡二叉树中是存在失衡现象的,因此我们需要对这个树的节点进行旋转。在对树的节点进行旋转时,分为左旋与右旋两种形式。
右侧图片为进行左旋操作时的引用变化:
右旋操作为左旋操作的逆操作:
当我们对上图中的深度为3的节点进行左旋操作后,可以得到如下图所示的
左侧为原本深度为3的节点进行左旋操作以后的深度分部,而右侧为左旋操作以后的平衡因子值变化。
我们在进行左旋操作以后,树的深度就需要进行一次重新的处理,而平衡因子也需要在树的深度重新计算完成以后,也重新进行一次计算,这样在进行旋转操作以后仍未达到理想状态,可以进行多次的旋转操作。
先来看代码,然后针对代码进行分析:
package volatileStudy.model;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class BalanceTreeMap<K, V> implements Map<K, V>{
/**
* 树结构中的根节点
*/
private Entry<K,V> rootEntry;
/**
* 这个Map中已有键值对的数量
*/
private int size = 0;
public BalanceTreeMap() {
}
@Override
public int size() {
return this.size;
}
/**
* 如果该Map中不存在键值映射,则返回true
*/
@Override
public boolean isEmpty() {
return this.rootEntry == null;
}
/**
* 向平衡二叉树中添加一个键值对
*/
@Override
public V put(K key, V value) {
if(this.rootEntry == null){
this.rootEntry = new Entry<K, V>(key, value);
this.rootEntry.balance = 0;
this.rootEntry.deepness = 1;
this.size ++ ;
return value;
}
Entry<K, V> newEntry = new Entry<K, V>(key,value);
Entry<K, V> superEntry = rootEntry;
while(true){
if(key.hashCode() > superEntry.getKey().hashCode()){
if(superEntry.rightEntry != null){
superEntry = superEntry.rightEntry;
}else{
superEntry.rightEntry = newEntry;
newEntry.superEntry = superEntry;
break ;
}
}else if(key.hashCode() < superEntry.getKey().hashCode()){
if(superEntry.leftEntry != null){
superEntry = superEntry.leftEntry;
}else{
superEntry.leftEntry = newEntry;
newEntry.superEntry = superEntry;
break ;
}
}else{
superEntry.setValue(value);
return value;
}
}
this.checkDeepness(newEntry);
this.adjustLoadFactor(superEntry);
this.checkBalance(newEntry);
this.size ++ ;
return value;
}
/**
* 矫正一个节点及其所有父节点的平衡因子
* @param entry
*/
private void checkDeepness(Entry<K, V> entry){
entry.deepness = 1;
for(int i = 1; entry.superEntry != null && i == entry.superEntry.deepness ; i++ ){
entry.superEntry.deepness = i+1;
entry = entry.superEntry;
}
}
/**
* 如果在这个Map中包含指定key的映射关系,则返回true
*/
@Override
public boolean containsKey(Object key) {
// TODO Auto-generated method stub
return false;
}
/**
* 如果在键值映射中,存在该值,则返回true
*/
@Override
public boolean containsValue(Object value) {
// TODO Auto-generated method stub
return false;
}
/**
* 将一个Map中的数据全部存入这个Map中
*/
@Override
public void putAll(Map m) {
}
/**
* 移除该对象中的所有的键值关系
*/
@Override
public void clear() {
this.rootEntry = null;
this.size = 0;
}
/**
* 返回该Map中的键的set集合
*/
@Override
public Set<K> keySet() {
// TODO Auto-generated method stub
return null;
}
/**
* 返回该Map中的值的集合
*/
@Override
public Collection<V> values() {
List<Entry<K, V>> list = new LinkedList<Entry<K, V>>();
this.addEntryToList(this.rootEntry, list);
List<V> values = new LinkedList<V>();
for(Entry<K, V> entry : list){
values.add(entry.getValue());
}
return values;
}
/**
* 从一个节点开始,从最小的节点开始添加到List中
*/
private void addEntryToList(Entry<K, V> entry, List<Entry<K, V>> list){
if(entry.leftEntry != null){
this.addEntryToList(entry.leftEntry, list);
}
list.add(entry);
if(entry.rightEntry != null){
this.addEntryToList(entry.rightEntry, list);
}
}
/**
* 返回键值关系的映射视图
*/
@Override
public Set<Map.Entry<K, V>> entrySet() {
// TODO Auto-generated method stub
return null;
}
/**
* 根据key的值来获取value
*/
@Override
public V get(Object key) {
return getEntry(key).getValue();
}
/**
* 根据key的值来移除一个节点
*/
@Override
public V remove(Object key) {
return null;
}
/**
* 通过Key来获取对应的Entry键值对
*/
private Entry<K, V> getEntry(Object key){
Entry<K, V> entry = null;
for(entry = this.rootEntry ;entry == null || entry.getKey().hashCode() != key.hashCode() ; ){
if(entry.getKey().hashCode() > key.hashCode()){
entry = entry.leftEntry;
}else{
entry = entry.rightEntry;
}
}
return entry;
}
/**
* 对节点进行左旋
*/
private Entry<K, V> leftRotate(Entry<K, V> entry){
Entry<K, V> superEntry = entry.superEntry;
Entry<K, V> rightEntry = entry.rightEntry;
if(superEntry == null){
this.rootEntry = rightEntry;
}else if(superEntry.rightEntry == entry){
superEntry.rightEntry = rightEntry;
}else{
superEntry.leftEntry = rightEntry;
}
entry.rightEntry = rightEntry.leftEntry;
entry.superEntry = rightEntry;
rightEntry.leftEntry = entry;
rightEntry.superEntry = superEntry;
if(entry.rightEntry == null){
if(rightEntry.rightEntry == null){
entry.balance += 1;
}else{
entry.balance += 2;
}
}else{
entry.balance += 1;
}
rightEntry.balance += 1;
this.updateDeepness(entry);
this.adjustLoadFactor(rightEntry);
this.checkBalance(rightEntry);
return rightEntry;
}
/**
* 对接点进行右旋
*/
private Entry<K, V> rightRotate(Entry<K, V> entry){
Entry<K, V> superEntry = entry.superEntry;
Entry<K, V> leftEntry = entry.leftEntry;
if(superEntry == null){
this.rootEntry = leftEntry;
}if(superEntry.leftEntry == entry){
superEntry.leftEntry = leftEntry;
}else{
superEntry.rightEntry = leftEntry;
}
entry.leftEntry = leftEntry.rightEntry;
entry.superEntry = leftEntry;
leftEntry.rightEntry = entry;
leftEntry.superEntry = superEntry;
if(entry.leftEntry == null){
if(leftEntry.leftEntry == null){
entry.balance -= 1;
}else{
entry.balance -= 2;
}
}else{
entry.balance -= 1;
}
leftEntry.balance -= 1;
this.updateDeepness(entry);
this.adjustLoadFactor(leftEntry);
this.checkBalance(leftEntry);
return leftEntry;
}
/**
* 对一个节点的所有子节点进行深度修改操作
*/
private void updateDeepness(Entry<K, V> entry){
for(; entry != null ; entry = entry.superEntry){
int left = entry.leftEntry == null ? 0 : entry.leftEntry.deepness;
int right = entry.rightEntry == null ? 0 : entry.rightEntry.deepness;
entry.deepness = (left > right ? left : right )+1;
}
}
/**
* 调整一个节点的所有父节点及其自己的加载因子数
* @param entry
*/
private void adjustLoadFactor(Entry<K, V> entry){
for(;entry != null; entry = entry.superEntry){
if(entry.leftEntry != null && entry.rightEntry != null){
entry.balance = entry.leftEntry.deepness - entry.rightEntry.deepness;
}else if(entry.leftEntry == null && entry.rightEntry != null){
entry.balance = 0 - entry.rightEntry.deepness;
}else if(entry.leftEntry != null && entry.rightEntry == null){
entry.balance = entry.leftEntry.deepness - 0;
}else if(entry.leftEntry == null && entry.rightEntry == null){
entry.balance = 0;
}
}
}
/**
* 验证一个节点的所有父节点是否是平衡的,如果不是平衡的则进行旋转调整
* @param entry
*/
private void checkBalance(Entry<K, V> entry){
for(;entry != null; entry = entry.superEntry){
if(entry.balance > 1){
if(entry.leftEntry.balance > 0){
this.rightRotate(entry);
}else if(entry.leftEntry.balance < 0){
this.leftRotate(entry.leftEntry);
}
break ;
}else if(entry.balance < -1){
if(entry.rightEntry.balance > 0){
this.rightRotate(entry.rightEntry);
}else if(entry.rightEntry.balance < 0){
this.leftRotate(entry);
}
break ;
}
}
}
class Entry<K, V> implements Map.Entry<K, V>{
/**
* 该节点的父节点,可以为空
*/
public Entry<K, V> superEntry;
/**
* 节点的key值
*/
public K key;
/**
* 节点的Value
*/
public V value;
/**
* 平衡因子
*/
public int balance;
/**
* 树节点深度
*/
public int deepness;
/**
* 树节点所挂载的左侧节点对象
*/
public Entry<K, V> leftEntry;
/**
* 树节点所挂载的右侧节点对象
*/
public Entry<K, V> rightEntry;
public Entry(K key, V value){
this.key = key;
this.value = value;
}
@Override
public K getKey() {
return this.key;
}
@Override
public V getValue() {
return this.value;
}
@Override
public V setValue(V value) {
this.value = value;
return value;
}
}
}
在这段个类中,首先我们实现了Map接口,同时在内部使用一个内部类Entry类,Entry类同样也实现了Map.Entry接口。代码中并没有将所有的方法都进行实现,目前只是实现了主要方法。
这段代码中的核心代码即为put(key,value)方法,我们对这个方法进行分析:
/**
* 向平衡二叉树中添加一个键值对
*/
@Override
public V put(K key, V value) {
if(this.rootEntry == null){
this.rootEntry = new Entry<K, V>(key, value);
this.rootEntry.balance = 0;
this.rootEntry.deepness = 1;
this.size ++ ;
return value;
}
Entry<K, V> newEntry = new Entry<K, V>(key,value);
Entry<K, V> superEntry = rootEntry;
while(true){
if(key.hashCode() > superEntry.getKey().hashCode()){
if(superEntry.rightEntry != null){
superEntry = superEntry.rightEntry;
}else{
superEntry.rightEntry = newEntry;
newEntry.superEntry = superEntry;
break ;
}
}else if(key.hashCode() < superEntry.getKey().hashCode()){
if(superEntry.leftEntry != null){
superEntry = superEntry.leftEntry;
}else{
superEntry.leftEntry = newEntry;
newEntry.superEntry = superEntry;
break ;
}
}else{
superEntry.setValue(value);
return value;
}
}
this.checkDeepness(newEntry);
this.adjustLoadFactor(superEntry);
this.checkBalance(newEntry);
this.size ++ ;
return value;
}
1.判断的是这个类的对象是否已经具有了一个根节点对象,如果没有,则将这个K-V键值对存放到 根节点中,这个节点的初始化平衡因子是0,深度为1。
2.如果已经具有了一个根节点对象,则创建一个新的Entry对象,然后根据所传入的key去寻找到应该存放该entry对象的位置。如果这个entry对象key是已经存在的,则将value的值替换为新的value值,并结束put方法,否则,运行下面的验证方法。
3.下方代码块中的三个方法的作用分别是修正一个节点的所有父节点的深度,修正加载因子,检查树的加载因子是否处于平衡状态。这个顺序是不能进行修改的,因为我们需要先修正深度以后,才能根据深度修正加载因子,在最后根据加载因子来检查树是否是平衡的。
this.checkDeepness(newEntry);
this.adjustLoadFactor(superEntry);
this.checkBalance(newEntry);
4.这里我们重点看一下检查树的平衡:
/**
* 验证一个节点的所有父节点是否是平衡的,如果不是平衡的则进行旋转调整
* @param entry
*/
private void checkBalance(Entry<K, V> entry){
for(;entry != null; entry = entry.superEntry){
if(entry.balance > 1){
if(entry.leftEntry.balance > 0){
this.rightRotate(entry);
}else if(entry.leftEntry.balance < 0){
this.leftRotate(entry.leftEntry);
}
break ;
}else if(entry.balance < -1){
if(entry.rightEntry.balance > 0){
this.rightRotate(entry.rightEntry);
}else if(entry.rightEntry.balance < 0){
this.leftRotate(entry);
}
break ;
}
}
}
检查树的平衡时,与其他地方致都是向上进行遍历父节点,直到不存在父节点为止。当发现一个节点的平衡因子值 > 1时,这个节点则是一个左倾的节点,此时就需要对该节点的左侧节点进行判断,是否大于0,即是左倾还是右倾。如果左倾,则直接对原节点进行右旋即可,否则需要对原节点的左节点进行左旋操作。当原节点的平衡因子<1时,操作相反。
5.我们再来看一下树的左右旋操作:
/**
* 对节点进行左旋
*/
private Entry<K, V> leftRotate(Entry<K, V> entry){
Entry<K, V> superEntry = entry.superEntry;
Entry<K, V> rightEntry = entry.rightEntry;
if(superEntry == null){
this.rootEntry = rightEntry;
}else if(superEntry.rightEntry == entry){
superEntry.rightEntry = rightEntry;
}else{
superEntry.leftEntry = rightEntry;
}
entry.rightEntry = rightEntry.leftEntry;
entry.superEntry = rightEntry;
rightEntry.leftEntry = entry;
rightEntry.superEntry = superEntry;
if(entry.rightEntry == null){
if(rightEntry.rightEntry == null){
entry.balance += 1;
}else{
entry.balance += 2;
}
}else{
entry.balance += 1;
}
rightEntry.balance += 1;
this.updateDeepness(entry);
this.adjustLoadFactor(rightEntry);
this.checkBalance(rightEntry);
return rightEntry;
}
在对节点进行左右旋操作时, 要先对接点的引用进行修改,在修改完节点的引用后,为修改进行旋转操作的节点的平衡因子,由规律可得:
if(entry.rightEntry == null){
if(rightEntry.rightEntry == null){
entry.balance += 1;
}else{
entry.balance += 2;
}
}else{
entry.balance += 1;
}
if(entry.rightEntry == null){
if(rightEntry.rightEntry == null){
entry.balance += 1;
}else{
entry.balance += 2;
}
}else{
entry.balance += 1;
}
此时,我们在完成了旋转操作以后,还需要对这个节点的深度,平衡因子,平衡性进行再次的修改。
到此时,我们已经完成了对一个平衡二叉树的put操作,对该二叉树的遍历则使用递归的方式进行,详情可查看values方法。
如果这边博客对大家有所帮助,欢迎点赞,如果存在错误,更加欢迎在评论区指出。