课程:《程序设计与数据结构》
班级: 1723
姓名: 周亚杰
学号:20172308
实验教师:王志强
实验日期:2018年11月5日
必修/选修: 必修html
问题1:实验2.2:利用中序和先序构造出一棵树并输出,其实这个原理明白,但实现代码的时候殊不知道从哪里下手java
构造出树的原理就是:
由先序遍历的特色知道最早访问的是根结点(结点),而后是左右子树
再由中序遍历的结果找到根结点,并获得左右子树
再返回先序遍历,找到根结点左或右子树的结点,再返回中序遍历结果中找到此结点左右子树
如此递归下去......node
举个栗子:
先序遍历: GDAFEMHZ
中序遍历: ADEFGHMZ
画出树:
第一步,根据先序遍历的特色,咱们知道根结点为Ggit
第二步,观察中序遍历ADEFGHMZ。其中root节点G左侧的ADEF必然是root的左子树,G右侧的HMZ必然是root的右子树。算法
第三步,观察左子树ADEF,左子树的中的根节点必然是大树的root的leftchild。在先序遍历中,大树的root的leftchild位于root以后,因此左子树的根节点为D。数组
第四步,一样的道理,root的右子树节点HMZ中的根节点也能够经过前序遍历求得。在先序遍历中,必定是先把root和root的全部左子树节点遍历完以后才会遍历右子树,而且遍历的左子树的第一个节点就是左子树的根节点。同理,遍历的右子树的第一个节点就是右子树的根节点。安全
第五步,观察发现,上面的过程是递归的。先找到当前树的根节点,而后划分为左子树,右子树,而后进入左子树重复上面的过程,而后进入右子树重复上面的过程。最后就能够还原一棵树了。数据结构
该步递归的过程能够简洁表达以下:
1 肯定根,肯定左子树,肯定右子树
2 在左子树中递归
3 在右子树中递归
4 打印当前根
而后能够画出这个二叉树的形状:
并发
public void buildTree(T[] inorder, T[] postorder) {//调用makeTree方法,即利用递归方法获得树 BinaryTreeNode temp = makeTree(inorder, 0, inorder.length, postorder, 0, postorder.length); root = temp;
这个是调用了makeTree方法,以先序和中序做为形参从而构造出树的方法
下面是具体的makeTree方法实现:app
public BinaryTreeNode<T> makeTree(T[] inorder, int startInorder, int lenInorder, T[] preorder, int startPreorder, int lenPreorder) { if (lenInorder < 1) {//判断中序的字符串长度(即元素个数)小于1,则返回null,即树为空 return null; } BinaryTreeNode root;//建立根结点 T rootelement = preorder[startPreorder];//preorder中的第一个元素就是当前处理的数据段的根节点 root = new BinaryTreeNode(rootelement);//把给定的根结点元素放进root int temp; boolean isFound = false; for (temp = 0; temp < lenInorder; temp++) { if (inorder[startInorder + temp] == rootelement) { isFound = true;//此时找到结点,即将先序中的第一个元素(根元素)在中序中寻找到相同元素,获得根结点的左右子树 break; } } if (!isFound)//若是不存在相等的状况就跳出该函数 return root;//即没有左右子树 root.setLeft(makeTree(inorder, startInorder, temp, preorder, startPreorder + 1, temp));//递归找到并设置各结点左孩子 root.setRight(makeTree(inorder, startInorder + temp + 1, lenInorder - temp - 1, preorder, startPreorder + temp + 1, lenPreorder - temp - 1));//递归找到并设置各结点右孩子 //每次递归,先序序列中都要日后跳过temp个元素(由于他们都是从中序序列中得知的一个结点的左或右孩子),再加1,即为下一个结点的元素 return root; }
上面的代码实现是参考并理解了余坤澎同窗的代码以后进行了细微修改获得的,并做出了代码注释
【参考资料】
余坤澎同窗的码云连接
Java实现二叉树先序,中序,后序遍历
二叉树前序、中序、后序遍历相互求法 (原理,程序)
问题2:实验2.3:本身设计一颗决策树
问题2解决过程:
参考课本上的背部疼痛诊断类,在节点处放置问题,根据用户输入的Y或N,对应不一样的左右孩子,并返回当前节点的元素(即下一个问题内容)
即达到设计要求
设计想法有两种:
一是改变书上决策树的子树状况,设计其它的问题填充进去
二是直接改变原子树中的节点内容,使问题问题衔接的更紧凑(虽然这一种有点偷懒,但也是要在理解原决策树的结构基础上才能正确更改,不然会出现答非所问的状况)
问题3:实验2.4:输入中缀表达式,使用树将中缀表达式转换为后缀表达式,并输出后缀表达式和计算结果
问题3解决过程:
这个实验应该是六个实验里面最很差作的一个,其中最关键的问题是如何将中缀转换成后缀表达式,即如何把中缀里面的操做数和操做符存储在树中,而后输出的树即为后缀表达式
后面的后缀表达式经过课本上的后缀表达式计算出结果便可
首先,第一个问题:如何构造出后缀表达式树
表达式树的特色:树的树叶是操做数(常数或变量),而其余节点为操做符
每次找到“最后计算”的运算符,做为当前根节点,运算符左侧表达式做为左节点,右侧表达式做为右节点,而后递归处理
而后,这里就存在一个优先级的问题,也就是存储操做符时,先存储哪一个才能保证输出表达式时是正确的
咱们知道乘除的运算级要高于加减,因此要先按顺序存储乘除的运算符及其两边的操做数,而后将其做为一个节点放在原来的列表位置,最后再存储加减及其两边的操做数
这一段的代码实现以下:
while (operList.size() > 0) { //第三步,重复第二步,直到操做符取完为止 //第二,取出前两个数字和一个操做符,组成一个新的数字节点 for (int a = 0; a < operList.size(); a++){ if(operList.get(a).equals("*") || operList.get(a).equals("/")){ Node left = numList.remove(a); Node right = numList.remove(a); String oper = operList.remove(a); Node node = new Node(oper, left, right); numList.add(a, node); a--; } else time++; } Node left = numList.remove(0); Node right = numList.remove(0); String oper = operList.remove(0); Node node = new Node(oper, left, right); numList.add(0, node); //将新生的节点做为第一个节点,同时之前index=0的节点变为index=1 }
经过在一个循环里面判断操做符有没有乘除一级的运算,而后执行上述的相应操做
在for循环外面再按顺序对加减一级的操做符进行相应操做,便可达到转后缀的要求
最后一个,代码的问题:在写代码的时候,会报出下图中的错误
经过debug发现问题所在,是这个for循环的问题,这个循环不能正常结束,会超出范围
那么问题就是,为何会不能正常结束循环?
这里的变量a是当前乘除一级的操做符在列表中的索引值(这里的操做数和操做符是存储在列表里的)
因此就致使了问题出现:ArrayList没有空位置,删除后的元素会被后面的元素自动补全
因此当一个乘除一级运算不少的中缀表达式,在找到乘除运算符的索引时,对应操做数中的索引值可能已经超出了循环的终止条件count的值(count是操做符的个数)
count是个定值,没法保证对全部的表达式都能正确转换成后缀表达式
通过不少次尝试和思考,才找到了最终的终止条件,那就是上面给出的代码 a < operList.size() 操做符的元素个数会变化,这里用size()方法就解决了
【参考资料】
前缀,中缀,后缀表达式学习笔记(1)
前缀、中缀、后缀表达式和二叉树
二叉树应用——后缀表达式构建表达式树
用二叉树表示表达式
中缀表达式转后缀表达式---栈--二叉树---四则运算
1、首先要了解一下什么是treeMap和HashMap
Map:在数组中咱们是经过数组下标来对其内容索引的,而在Map中咱们经过对象来对对象进行索引,用来索引的对象叫作key,其对应的对象叫作value(就是咱们平时说的键值对)
2、 HashMap
HashMap 是基于哈希表的 Map 接口的实现。此实现提供全部可选的映射操做,并容许使用 null 值和 null 键。(除了非同步和容许使用 null 以外,HashMap 类与 Hashtable 大体相同。)此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
官方文档以下:
此实现假定哈希函数将元素适当地分布在各桶之间,可为基本操做(get 和 put)提供稳定的性能。迭代 collection 视图所需的时间与 HashMap 实例的“容量”(桶的数量)及其大小(键-值映射关系数)成比例。因此,若是迭代性能很重要,则不要将初始容量设置得过高(或将加载因子设置得过低)。
HashMap 的实例有两个参数影响其性能:初始容量 和加载因子。容量 是哈希表中桶的数量,初始容量只是哈希表在建立时的容量。加载因子 是哈希表在其容量自动增长以前能够达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操做(即重建内部数据结构),从而哈希表将具备大约两倍的桶数。
一般,默认加载因子 (.75) 在时间和空间成本上寻求一种折衷。加载因子太高虽然减小了空间开销,但同时也增长了查询成本(在大多数 HashMap 类的操做中,包括 get 和 put 操做,都反映了这一点)。在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减小 rehash 操做次数。若是初始容量大于最大条目数除以加载因子,则不会发生 rehash 操做。
若是不少映射关系要存储在 HashMap 实例中,则相对于按需执行自动的 rehash 操做以增大表的容量来讲,使用足够大的初始容量建立它将使得映射关系能更有效地存储。
注意,此实现不是同步的。若是多个线程同时访问一个哈希映射,而其中至少一个线程从结构上修改了该映射,则它必须 保持外部同步。(结构上的修改是指添加或删除一个或多个映射关系的任何操做;仅改变与实例已经包含的键关联的值不是结构上的修改。)这通常经过对天然封装该映射的对象进行同步操做来完成。若是不存在这样的对象,则应该使用 Collections.synchronizedMap 方法来“包装”该映射。最好在建立时完成这一操做,以防止对映射进行意外的非同步访问,以下所示:
Map m = Collections.synchronizedMap(new HashMap(…));
由全部此类的“collection 视图方法”所返回的迭代器都是快速失败 的:在迭代器建立以后,若是从结构上对映射进行修改,除非经过迭代器自己的 remove 方法,其余任什么时候间任何方式的修改,迭代器都将抛出 ConcurrentModificationException。所以,面对并发的修改,迭代器很快就会彻底失败,而不冒在未来不肯定的时间发生任意不肯定行为的风险。
//一个桶的树化阈值 //当桶中元素个数超过这个值时,须要使用红黑树节点替换链表节点 //这个值必须为 8,要否则频繁转换效率也不高 static final int TREEIFY_THRESHOLD = 8; //一个树的链表还原阈值 //当扩容时,桶中元素个数小于这个值,就会把树形的桶元素 还原(切分)为链表结构 //这个值应该比上面那个小,至少为 6,避免频繁转换 static final int UNTREEIFY_THRESHOLD = 6; //哈希表的最小树形化容量 //当哈希表中的容量大于这个值时,表中的桶才能进行树形化 //不然桶内元素太多时会扩容,而不是树形化 //为了不进行扩容、树形化选择的冲突,这个值不能小于 4 * TREEIFY_THRESHOLD static final int MIN_TREEIFY_CAPACITY = 64;
HashMap中树形化最重要的一个方法treeifyBin() 即树形化。在一个桶中的元素个数超过 TREEIFY_THRESHOLD(默认是8),就使用红黑树来替换链表。
final void treeifyBin(Node<K,V>[] tab, int hash) { int n, index; Node<K,V> e; //若是hash表为空或者hash表的容量小于MIN_TREEIFY_CAPACITY(64),那么就去新建或者扩容 if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) resize(); else if ((e = tab[index = (n - 1) & hash]) != null) { TreeNode<K,V> hd = null, tl = null; do { //新建一个树形节点,内容和当前链表节点一致 TreeNode<K,V> p = replacementTreeNode(e, null); if (tl == null) hd = p; //头节点 else { p.prev = tl; tl.next = p; } tl = p; } while ((e = e.next) != null); //以前获得的只是一个链表状的二叉树,下一步格式化红黑树 if ((tab[index] = hd) != null) hd.treeify(tab); } } TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) { return new TreeNode<>(p.hash, p.key, p.value, next); }
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable { // 序列号 private static final long serialVersionUID = 362498820763181265L; // 默认的初始容量是16 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 最大容量 static final int MAXIMUM_CAPACITY = 1 << 30; // 默认的填充因子 static final float DEFAULT_LOAD_FACTOR = 0.75f; // 当桶(bucket)上的结点数大于这个值时会转成红黑树 static final int TREEIFY_THRESHOLD = 8; // 当桶(bucket)上的结点数小于这个值时树转链表 static final int UNTREEIFY_THRESHOLD = 6; // 桶中结构转化为红黑树对应的table的最小大小 static final int MIN_TREEIFY_CAPACITY = 64; // 存储元素的数组,老是2的幂次倍 transient Node<k,v>[] table; // 存放具体元素的集 transient Set<map.entry<k,v>> entrySet; // 存放元素的个数,注意这个不等于数组的长度。 transient int size; // 每次扩容和更改map结构的计数器 transient int modCount; // 临界值 当实际大小(容量*填充因子)超过临界值时,会进行扩容 int threshold; // 填充因子 final float loadFactor; }
1.putVal函数(put操做的基础函数)
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; //检测table是否为空,若是为空,则使用扩容函数进行初始化 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; //若是经过hash值取模获得的桶为空,则直接把新生成的节点放入该桶 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else {//如下为该桶不为空的逻辑 Node<K,V> e; K k; //判断桶的第一个元素的key值是否相同(hash值相同,且能equals) //若是相同,则返回当前元素(函数末尾进行统一处理) if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode)//桶元素采用的是红黑树结构 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else {//桶元素采用的是链表结构 for (int binCount = 0; ; ++binCount) { //若是遍历到了链表末端,则直接在链表末端插入新元素 if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); //插入以后,检查是否达到了转成红黑树结构的标准 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } //若是在遍历过程当中,发现了key值相同,则返回当前元素(函数末尾进行统一处理) if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } //处理相同元素的状况 if (e != null) { // existing mapping for key V oldValue = e.value; //若是onlyIfAbsent为ture,则在oldValue为空时才替换 //不然直接替换 if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount;//修改次数+1 //map的size加1,而后判断是否达到了threshold,不然进行扩容 //threshold由Node[] table的长度及loadFactor控制 if (++size > threshold) resize(); //执行回调函数 afterNodeInsertion(evict); return null; }
2.put函数
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }
3.getNode函数(get操做的基础函数)
final Node<K,V> getNode(int hash, Object key) { Node<K,V>[] tab; Node<K,V> first, e; int n; K k; //若是table不为空,则再进行查询操做 if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) { //先检查第一个元素是否key相同 if (first.hash == hash && // always check first node ((k = first.key) == key || (key != null && key.equals(k)))) return first; if ((e = first.next) != null) { //若是为红黑树结构,则走红黑树的查询逻辑 if (first instanceof TreeNode) return ((TreeNode<K,V>)first).getTreeNode(hash, key); do {//不然遍历链表 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } while ((e = e.next) != null); } } return null; }
4.resize()函数
final Node<K,V>[] resize() { Node<K,V>[] oldTab = table; int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0; if (oldCap > 0) { //若是扩容以前的容量已经达到了最大值 //则只把threshold变成Integer.MAX_VALUE,即不限制map的最大size,以后无论插入多少元素也不触发resize if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; }//把新容量变成原来的2倍 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold } else if (oldThr > 0) // 初始capacity(构造函数输入的)被设置成了threshold newCap = oldThr; else { // oldThr=0的状况,此时代表采用默认的参数进行初始化 newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } //处理好newThr和newCap以后,开始resize()函数的真正逻辑 threshold = newThr;//设置threshold @SuppressWarnings({"rawtypes","unchecked"}) Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; if (oldTab != null) { for (int j = 0; j < oldCap; ++j) { Node<K,V> e; //若是原来桶的首元素不为空,则进行复制逻辑 if ((e = oldTab[j]) != null) { oldTab[j] = null; //若是该桶只装了一个元素,则直接复制 if (e.next == null) newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode)//红黑树的状况 ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); else { // 链式状况,此时会把原来的链分红两个链loHead和hiHead //loHead存储(e.hash & oldCap) == 0的元素 //hiHead存储(e.hash & oldCap) != 0的元素 //扩容以后,因为新的capacity为oldCap的2倍,且它们都为2的整数幂 //对于该链上的元素,若是(e.hash & oldCap) == 0,则新的槽位(hash%capacity)==旧的槽位(hash%oldCap) //不然,它们新的槽位都同样,且都为原来的槽位后移oldCap Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do { next = e.next; if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; }
3、TreeMap
(1)TreeMap():构建一个空的映像树
(2)TreeMap(Map m): 构建一个映像树,而且添加映像m中全部元素
(3)TreeMap(Comparator c): 构建一个映像树,而且使用特定的比较器对关键字进行排序
(4)TreeMap(SortedMap s): 构建一个映像树,添加映像树s中全部映射,而且使用与有序映像s相同的比较器排序。
基于红黑树(Red-Black tree)的 NavigableMap 实现。该映射根据其键的天然顺序进行排序,或者根据建立映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。
此实现为 containsKey、get、put 和 remove 操做提供受保证的 log(n) 时间开销。这些算法是 Cormen、Leiserson 和 Rivest 的 Introduction to Algorithms 中的算法的改编。
注意,若是要正确实现 Map 接口,则有序映射所保持的顺序(不管是否明确提供了比较器)都必须与 equals 一致。(关于与 equals 一致 的精肯定义,请参阅 Comparable 或 Comparator)。这是由于 Map 接口是按照 equals 操做定义的,但有序映射使用它的 compareTo(或 compare)方法对全部键进行比较,所以从有序映射的观点来看,此方法认为相等的两个键就是相等的。即便排序与 equals 不一致,有序映射的行为仍然是 定义良好的,只不过没有遵照 Map 接口的常规协定。
注意,此实现不是同步的。若是多个线程同时访问一个映射,而且其中至少一个线程从结构上修改了该映射,则其必须 外部同步。(结构上的修改是指添加或删除一个或多个映射关系的操做;仅改变与现有键关联的值不是结构上的修改。)这通常是经过对天然封装该映射的对象执行同步操做来完成的。若是不存在这样的对象,则应该使用 Collections.synchronizedSortedMap 方法来“包装”该映射。最好在建立时完成这一操做,以防止对映射进行意外的不一样步访问,以下所示:
SortedMap m = Collections.synchronizedSortedMap(new TreeMap(…));
collection(由此类全部的“collection 视图方法”返回)的 iterator 方法返回的迭代器都是快速失败 的:在迭代器建立以后,若是从结构上对映射进行修改,除非经过迭代器自身的 remove 方法,不然在其余任什么时候间以任何方式进行修改都将致使迭代器抛出 ConcurrentModificationException。所以,对于并发的修改,迭代器很快就彻底失败,而不会冒着在未来不肯定的时间发生不肯定行为的风险。
一、无参构造方法
public TreeMap() { comparator = null; }
采用无参构造方法,不指定比较器,这时候,排序的实现要依赖key.compareTo()方法,所以key必须实现Comparable接口,并覆写其中的compareTo方法。
二、带有比较器的构造方法
public TreeMap(Comparator<? super K> comparator) { this.comparator = comparator; }
采用带比较器的构造方法,这时候,排序依赖该比较器,key能够不用实现Comparable接口。
三、带Map的构造方法
public TreeMap(Map<? extends K, ? extends V> m) { comparator = null; putAll(m); }
该构造方法一样不指定比较器,调用putAll方法将Map中的全部元素加入到TreeMap中
四、带有SortedMap的构造方法
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) { } }
首先将比较器指定为m的比较器,这取决于生成m时调用构造方法是否传入了指定的构造器,然后调用buildFromSorted方法,将SortedMap中的元素插入到TreeMap中,因为SortedMap中的元素师有序的,实际上它是根据SortedMap建立的TreeMap,将SortedMap中对应的元素添加到TreeMap中。
插入操做即对应TreeMap的put方法,put操做实际上只需按照二叉排序树的插入步骤来操做便可,插入到指定位置后,再作调整,使其保持红黑树的特性。
put源码的实现:
public V put(K key, V value) { Entry<K,V> t = root; // 若红黑树为空,则插入根节点 if (t == null) { // TBD: // 5045147: (coll) Adding null to an empty TreeSet should // throw NullPointerException // // compare(key, key); // type check root = new Entry<K,V>(key, value, null); size = 1; modCount++; return null; } int cmp; Entry<K,V> parent; // split comparator and comparable paths Comparator<? super K> cpr = comparator; // 找出(key, value)在二叉排序树中的插入位置。 // 红黑树是以key来进行排序的,因此这里以key来进行查找。 if (cpr != null) { do { parent = t; cmp = cpr.compare(key, t.key); if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else return t.setValue(value); } while (t != null); } else { if (key == null) 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); } // 为(key-value)新建节点 Entry<K,V> e = new Entry<K,V>(key, value, parent); if (cmp < 0) parent.left = e; else parent.right = e; // 插入新的节点后,调用fixAfterInsertion调整红黑树。 fixAfterInsertion(e); size++; modCount++; return null; }
// 删除“红黑树的节点p” private void deleteEntry(Entry<K,V> p) { modCount++; size--; if (p.left != null && p.right != null) { Entry<K,V> s = successor (p); p.key = s.key; p.value = s.value; p = s; } Entry<K,V> replacement = (p.left != null ? p.left : p.right); if (replacement != null) { replacement.parent = p.parent; if (p.parent == null) root = replacement; else if (p == p.parent.left) p.parent.left = replacement; else p.parent.right = replacement; p.left = p.right = p.parent = null; if (p.color == BLACK) fixAfterDeletion(replacement); } else if (p.parent == null) { root = null; } else { if (p.color == BLACK) fixAfterDeletion(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; } } }
fixAfterDeletion方法即是节点删除后对树进行调整的方法
总结:
一、TreeMap是根据key进行排序的,它的排序和定位须要依赖比较器或覆写Comparable接口,也所以不须要key覆写hashCode方法和equals方法,就能够排除掉重复的key,而HashMap的key则须要经过覆写hashCode方法和equals方法来确保没有重复的key
二、TreeMap的查询、插入、删除效率均没有HashMap高,通常只有要对key排序时才使用TreeMap
三、TreeMap的key不能为null,而HashMap的key能够为null。
4、HashMap和TreeMap比较
(1)HashMap:适用于在Map中插入、删除和定位元素。
(2)Treemap:适用于按天然顺序或自定义顺序遍历键(key)。
(3)HashMap一般比TreeMap快一点(树和哈希表的数据结构使然),建议多使用HashMap,在须要排序的Map时候才用TreeMap.
(4)HashMap 非线程安全 TreeMap 非线程安全
(5)HashMap的结果是没有排序的,而TreeMap输出的结果是排好序的。
【参考资料】
Java Collections API源码分析
【Java集合源码剖析】TreeMap源码剖析
TreeMap(红黑树)源码分析
HashMap和TreeMap区别详解以及底层实现
本次实验对树的学习以后运用的检测性很强,只有真正对树的结构和原理熟练掌握才能作好实验 同时也让我知道了我对树的运用还很欠缺,仍然须要继续努力、学习