TreeMap和红黑树

一、---使用方式---



TreeMap基于红黑树(Red-Black tree)实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。
TreeMap的使用过程中,主要一定要实现Comparable或者Comparator接口,其中Comparable是内部比较器,Comparator是外部比较器,用法稍有不同,产生的结果一样。
package ThreeWeek;

import java.util.*;
import java.util.Map.Entry;

public class TreeMapTest {  
	public static void main(String[] args) {  
        Food f1 = new Food("meet", 10);  
        Food f2 = new Food("egg", 15);  
        Food f3 = new Food("fruit", 5);  
        Food f4 = new Food("water", 20);  
   
        Map<Food, Integer> treeMap = new TreeMap<Food, Integer>();  
        treeMap.put(f1, 10);  
        treeMap.put(f2, 15);  
        treeMap.put(f3, 5);  
        treeMap.put(f4, 20);  
   
        // 将所有的事物都输出
        Iterator<Entry<Food, Integer>> iter = treeMap.entrySet().iterator();
        while(iter.hasNext()) {
            Map.Entry<Food, Integer> entry = (Map.Entry<Food, Integer>)iter.next();
            System.out.println(entry.getKey() + " - " + entry.getValue());  
        }

        System.out.println(treeMap.hashCode());
        
    }  
}  
 // 实现内部比较器的接口
class Food implements Comparable<Food>{  
    String name;  
    int price;
   
    Food(String name, int price) {  
    	this.name = name;
    	this.price = price;
    }  
   
    public String toString(){     
        return name+" 的价格是:";  
    }  
   
    public int compareTo(Food food){  
 
        if(food.price < this.price){  
            return -1;  
        }  
        if(food.price > this.price){  
            return 1;  
        }  
        if(food.price == this.price){  
            return this.name.compareTo(food.name);  
        }  
        return 0;  
    }

}  
--------------------output--------------------
water 的价格是: - 20
egg 的价格是: - 15
meet 的价格是: - 10
fruit 的价格是: - 5
55112011



二、---内部原理---




1、继承关系

java.lang.Object
   ↳     java.util.AbstractMap<K, V>
         ↳     java.util.TreeMap<K, V>

public class TreeMap<K,V>
    extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, java.io.Serializable {}
(1)TreeMap 继承于AbstractMap,所以它是一个Map,即一个key-value集合。
(2)TreeMap 实现了NavigableMap接口,意味着它支持一系列的导航方法。比如返回有序的key集合。
(3)TreeMap 实现了Cloneable接口,意味着它能被克隆。
(4)TreeMap 实现了java.io.Serializable接口,意味着它支持序列化 


2、构造函数


(1)TreeMap中提供了四个构造函数,如下:

// 默认构造函数。使用该构造函数,TreeMap中的元素按照自然排序进行排列。
TreeMap()

// 创建的TreeMap包含Map
TreeMap(Map<? extends K, ? extends V> copyFrom)

// 指定Tree的比较器
TreeMap(Comparator<? super K> comparator)

// 创建的TreeSet包含copyFrom
TreeMap(SortedMap<K, ? extends V> copyFrom)

(2)如下是对各个构造方法的详解

// 默认构造函数
public TreeMap() {
    comparator = null;
}

// 带比较器的构造函数
public TreeMap(Comparator<? super K> comparator) {
    this.comparator = comparator;
}

// 带Map的构造函数,Map会成为TreeMap的子集
public TreeMap(Map<? extends K, ? extends V> m) {
    comparator = null;
    putAll(m);
}

//带SortedMap的构造函数,SortedMap会成为TreeMap的子集
public TreeMap(SortedMap<K, ? extends V> m) {
    comparator = m.comparator();
    try {
        buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
    } catch (java.io.IOException cannotHappen) {
    } catch (ClassNotFoundException cannotHappen) {
    }
}


3、成员变量

(1)comparator是用来给比较器进行排序的

(2)root是红黑树的根节点

(3)size表示的是树中结点的数量

(4)modCount表示的是红黑树的修改次数

// 用于保持顺序的比较器,如果为空的话使用自然顺保持Key的顺序
private final Comparator<? super K> comparator;
// 根节点
private transient Entry<K,V> root = null;
// 树中的节点数量
private transient int size = 0;
// 多次在集合类中提到了,用于举了结构行的改变次数
private transient int modCount = 0;


4、Comparable和Comparator接口
 Comparable接口支持泛型,只有一个方法,该方法返回负数、零、正数分别表示当前对象“小于”、“等于”、“大于”传入对象o

 public interface Comparable<T> {
     public int compareTo(T o);
 }


compare(T o1,T o2)方法比较o1和o2两个对象,o1“大于”o2,返回正数,相等返回零,“小于”返回负数。

 public interface Comparable<T> {
     public int compareTo(T o);
 }




三、---内部类Entry---




内部类Entry是比较重要的内容了,包括value,left,right,parent,color,构造方法颜色默认为黑色,获取key,获取value,修改并返回当前结点的value等。

 static final class Entry<K,V> implements Map.Entry<K,V> {
 // 键值对的“键”
 K key;
 // 键值对的“值”
     V value;
     // 左孩子
     Entry<K,V> left = null;
     // 右孩子
     Entry<K,V> right = null;
     // 父节点
     Entry<K,V> parent;
     // 红黑树的节点表示颜色的属性
     boolean color = BLACK;
     /**
      * 根据给定的键、值、父节点构造一个节点,颜色为默认的黑色
      */
     Entry(K key, V value, Entry<K,V> parent) {
         this.key = key;
         this.value = value;
         this.parent = parent;
     }
     // 获取节点的key
     public K getKey() {
         return key;
     }
     // 获取节点的value
     public V getValue() {
         return value;
     }
     /**
      * 修改并返回当前节点的value
      */
     public V setValue(V value) {
         V oldValue = this.value;
         this.value = value;
         return oldValue;
     }
     // 判断节点相等的方法(两个节点为同一类型且key值和value值都相等时两个节点相等)
     public boolean equals(Object o) {
         if (!(o instanceof Map.Entry))
             return false;
         Map.Entry<?,?> e = (Map.Entry<?,?>)o;
         return valEquals(key,e.getKey()) && valEquals(value,e.getValue());
     }
     // 节点的哈希值计算方法
     public int hashCode() {
         int keyHash = (key==null ? 0 : key.hashCode());
         int valueHash = (value==null ? 0 : value.hashCode());
         return keyHash ^ valueHash;
     }
     public String toString() {
         return key + "=" + value;
     }
 }


四、---红黑树---


红黑树对我这个初学者来说还是比较难以理解的,下面参考大神的博客来理解一下一下红黑树
1、红黑树概念

 红黑树顾名思义就是节点是红色或者黑色的平衡二叉树,它通过颜色的约束来维持着二叉树的平衡。

(1)每个节点都只能是红色或者黑色

(2)根节点是黑色

(3)每个叶节点(NIL节点,空节点)是黑色的。

(4)如果一个结点是红的,则它两个子节点都是黑的。也就是说在一条路径上不能出现相邻的两个红色结点。

(5)从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。



从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。结果是这棵树大致上是平衡的。


2、增加结点

红黑树增加结点要满足上面的5条规则,当新增的结点不能满足上面的情况时,必须进行相应的调整,包括结点位置互换、颜色的互换和旋转等,旋转包括左旋和右旋。

(1)插入新节点总是红色节点 。

(2)如果插入节点的父节点是黑色, 能维持性质 。

(3)如果插入节点的父节点是红色, 破坏了性质. 故插入算法就是通过重新着色或旋转, 来维持性质 。

下面是TreeMap的put( )方法,通过它来具体分析一下怎么样进行增加结点的:

public V put(K key, V value) {  
           //用t表示二叉树的当前节点  
            Entry<K,V> t = root;  
            //t为null表示一个空树,即TreeMap中没有任何元素,直接插入  
            if (t == null) {  
                //比较key值,个人觉得这句代码没有任何意义,空树还需要比较、排序?  
                compare(key, key); // type (and possibly null) check  
                //将新的key-value键值对创建为一个Entry节点,并将该节点赋予给root  
                root = new Entry<>(key, value, null);  
                //容器的size = 1,表示TreeMap集合中存在一个元素  
                size = 1;  
                //修改次数 + 1  
                modCount++;  
                return null;  
            }  
            int cmp;     //cmp表示key排序的返回结果  
            Entry<K,V> parent;   //父节点  
            // split comparator and comparable paths  
            Comparator<? super K> cpr = comparator;    //指定的排序算法  
            //如果cpr不为空,则采用既定的排序算法进行创建TreeMap集合  
            if (cpr != null) {  
                do {  
                    parent = t;      //parent指向上次循环后的t  
                    //比较新增节点的key和当前节点key的大小  
                    cmp = cpr.compare(key, t.key);  
                    //cmp返回值小于0,表示新增节点的key小于当前节点的key,则以当前节点的左子节点作为新的当前节点  
                    if (cmp < 0)  
                        t = t.left;  
                    //cmp返回值大于0,表示新增节点的key大于当前节点的key,则以当前节点的右子节点作为新的当前节点  
                    else if (cmp > 0)  
                        t = t.right;  
                    //cmp返回值等于0,表示两个key值相等,则新值覆盖旧值,并返回新值  
                    else  
                        return t.setValue(value);  
                } while (t != null);  
            }  
            //如果cpr为空,则采用默认的排序算法进行创建TreeMap集合  
            else {  
                if (key == null)     //key值为空抛出异常  
                    throw new NullPointerException();  
                /* 下面处理过程和上面一样 */  
                Comparable<? super K> k = (Comparable<? super K>) key;  
                do {  
                    parent = t;  
                    cmp = k.compareTo(t.key);  
                    if (cmp < 0)  
                        t = t.left;  
                    else if (cmp > 0)  
                        t = t.right;  
                    else  
                        return t.setValue(value);  
                } while (t != null);  
            }  
            //将新增节点当做parent的子节点  
            Entry<K,V> e = new Entry<>(key, value, parent);  
            //如果新增节点的key小于parent的key,则当做左子节点  
            if (cmp < 0)  
                parent.left = e;  
          //如果新增节点的key大于parent的key,则当做右子节点  
            else  
                parent.right = e;  
            /*  
             *  上面已经完成了排序二叉树的的构建,将新增节点插入该树中的合适位置  
             *  下面fixAfterInsertion()方法就是对这棵树进行调整、平衡,具体过程参考上面的五种情况  
             */  
            fixAfterInsertion(e);  
            //TreeMap元素数量 + 1  
            size++;  
            //TreeMap容器修改次数 + 1  
            modCount++;  
            return null;  
        }  
上述的操作仅仅是插入代码,要想使红黑树平衡还需要进行平衡操作fixAfterInsertion(e);这个操作中包括左旋和右旋。

3、删除结点

(1)没有儿子,即为叶结点。直接把父结点的对应儿子指针设为NULL,删除儿子结点就OK了。

(2)只有一个儿子。那么把父结点的相应儿子指针指向儿子的独生子,删除儿子结点也OK了。

(3)有两个儿子。这种情况比较复杂,但还是比较简单。上面提到过用子节点C替代代替待删除节点D,然后删除子节点C即可。

找到一个替代子节点C来替代P,然后直接删除C,最后调整这棵红黑树。下面代码是寻找替代节点、删除替代节点。

private void deleteEntry(Entry<K,V> p) {  
        modCount++;      //修改次数 +1  
        size--;          //元素个数 -1  
 /* 
         * 被删除节点的左子树和右子树都不为空,那么就用 p节点的中序后继节点代替 p 节点 
         * successor(P)方法为寻找P的替代节点。规则是右分支最左边,或者 左分支最右边的节点 
         * ---------------------(1) 
         */  
        if (p.left != null && p.right != null) {    
            Entry<K,V> s = successor(p);  
            p.key = s.key;  
            p.value = s.value;  
            p = s;  
        }  
  
        //replacement为替代节点,如果P的左子树存在那么就用左子树替代,否则用右子树替代  
        Entry<K,V> replacement = (p.left != null ? p.left : p.right);  
  
        /* 
         * 删除节点,分为上面提到的三种情况 
         * -----------------------(2) 
         */  
        //如果替代节点不为空  
        if (replacement != null) {  
            replacement.parent = p.parent;  
            /* 
             *replacement来替代P节点 
             */  
            //若P没有父节点,则跟节点直接变成replacement  
            if (p.parent == null)  
                root = replacement;  
            //如果P为左节点,则用replacement来替代为左节点  
            else if (p == p.parent.left)  
                p.parent.left  = replacement;  
          //如果P为右节点,则用replacement来替代为右节点  
            else  
                p.parent.right = replacement;  
  
            //同时将P节点从这棵树中剔除掉  
            p.left = p.right = p.parent = null;  
  
            /* 
             * 若P为红色直接删除,红黑树保持平衡 
             * 但是若P为黑色,则需要调整红黑树使其保持平衡 
             */  
            if (p.color == BLACK)  
                fixAfterDeletion(replacement);  
        } else if (p.parent == null) {     //p没有父节点,表示为P根节点,直接删除即可  
            root = null;  
        } else {      //P节点不存在子节点,直接删除即可  
            if (p.color == BLACK)         //如果P节点的颜色为黑色,对红黑树进行调整  
                fixAfterDeletion(p);  
  
            //删除P节点  
            if (p.parent != null) {  
                if (p == p.parent.left)  
                    p.parent.left = null;  
                else if (p == p.parent.right)  
                    p.parent.right = null;  
                p.parent = null;  
            }  
        }  
    }  

这里简单对红黑树的插入和删除做一个介绍,更为详细的红黑树内容请参看大神们的博客。



尊重作者,尊重原创,参考文章:

http://www.cnblogs.com/skywang12345/p/3310928.html

http://blog.csdn.net/jzhf2012/article/details/8540705

http://cmsblogs.com/?p=1013


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值