20172332 2017-2018-2 《程序设计与数据结构》实验二报告
课程:《程序设计与数据结构》
班级: 1723
姓名: 于欣月
学号:20172332
实验教师:王志强
实验日期:2018年11月8日
必修/选修: 必修
1.实验内容
-
实验一:实现二叉树。
-
参考教材p212,完成链树LinkedBinaryTree的实现(getRight,contains,toString,preorder,postorder)
-
实验二:中序先序序列构造二叉树
-
基于LinkedBinaryTree,实现基于(中序,先序)序列构造唯一一棵二㕚树的功能,比如给出中序HDIBEMJNAFCKGL和后序ABDHIEJMNCFGKL,构造出附图中的树
-
实验三:决策树
-
自己设计并实现一颗决策树
-
实验四:表达式树
-
输入中缀表达式,使用树将中缀表达式转换为后缀表达式,并输出后缀表达式和计算结果(如果没有用树,则为0分)
-
实验五:二叉查找树
-
完成PP11.3
-
实验六:红黑树分析
-
参考http://www.cnblogs.com/rocedu/p/7483915.html对Java中的红黑树(TreeMap,HashMap)进行源码分析,并在实验报告中体现分析结果。
2. 实验过程及结果
前期准备:
- 1.了解二叉树的构造与实现原理。
过程:
- 1.实验一
- 关键代码:
public BinaryTreeNode<T> getRight() {
return root.right;
}
public boolean contains(T targetElement) {
BinaryTreeNode<T> current = findAgain(targetElement, root);
if (current == null)
return false;
else
return true;
}
//先判断根root是否为空,如果为空则null,如果元素相等则返回root结点,
// 否则开始进行递归,先判断左孩子是否为空,为空则null,如果元素相等则返回左孩子结点,不相等再进行递归
// 直到为null时,temp也为null,开始判断右孩子,与上同理,进行递归,最后得到结果。
private BinaryTreeNode<T> findAgain(T targetElement, BinaryTreeNode<T> next) {
if (next == null)
return null;
if (next.element.equals(targetElement))
return next;
BinaryTreeNode<T> temp = findAgain(targetElement, next.left);
if (temp == null)
temp = findAgain(targetElement, next.right);
return temp;
}
//toString代码的理解在第六周博客中有详细的过程
public String toString() {
UnorderedListADT<BinaryTreeNode<T>> nodes =
new ArrayUnorderedList<BinaryTreeNode<T>>();
UnorderedListADT<Integer> levelList =
new ArrayUnorderedList<Integer>();
BinaryTreeNode<T> current;
String result = "";
int printDepth = this.getHeight();
int possibleNodes = (int) Math.pow(2, printDepth + 1);
int countNodes = 0;
nodes.addToRear(root);
Integer currentLevel = 0;
Integer previousLevel = -1;
levelList.addToRear(currentLevel);
while (countNodes < possibleNodes) {
countNodes = countNodes + 1;
current = nodes.removeFirst();
currentLevel = levelList.removeFirst();
if (currentLevel > previousLevel) {
previousLevel = currentLevel;
result = result + "
" + "
";
for (int j = 0; j < ((Math.pow(2, (printDepth - currentLevel))) - 1); j++) {
result = result + " ";
}
} else {
for (int i = 0; i < ((Math.pow(2, (printDepth - currentLevel + 1)) - 1)); i++) {
result = result + " ";
}
}
if (current != null) {
result = result + (current.getElement()).toString();
nodes.addToRear(current.getLeft());
levelList.addToRear(currentLevel + 1);
nodes.addToRear(current.getRight());
levelList.addToRear(currentLevel + 1);
} else
{
nodes.addToRear(null);
levelList.addToRear(currentLevel + 1);
nodes.addToRear(null);
levelList.addToRear(currentLevel + 1);
result = result + " ";
}
}
return result;
}
public void preOrder(BinaryTreeNode<T> root) {
if (root != null) {
System.out.print(root.element + " ");
preOrder(root.left);
preOrder(root.right);
}
}
public void postOrder(BinaryTreeNode<T> node, ArrayUnorderedList<T> tempList) {
if (node != null) {
postOrder(node.getLeft(), tempList);
postOrder(node.getRight(), tempList);
tempList.addToRear(node.getElement());
- 测试结果:
- 2.实验二
- 关键代码:
public void buildTree(T[] preorder, T[] inorder) {
BinaryTreeNode temp = makeTree(preorder, 0, preorder.length, inorder, 0, inorder.length);
root = temp;
}
public BinaryTreeNode<T> makeTree(T[] preorder, int start, int len, T[] inorder, int start1, int len1) {
if (len < 1) {
return null;
}
BinaryTreeNode root;
//先序的第一个元素为根
root = new BinaryTreeNode(preorder[start]);
int n = 0;
for (int temp = 0; temp < len1; temp++) {
//找到在中序中的根的位置并记录
if (inorder[start1 + temp] == root.element) {
n = temp;
break;
}
}
//左子树的建立,先序的左子树开始是下一位,结束是中序的开始到记录的位置,中序的左子树开始不变,结束的记录的位置
root.setLeft(makeTree(preorder, start + 1, n, inorder, start1, n));
//右子树的建立,先序的右子树开始是左子树的根位置加上中序的记录的位置,结束是最后的索引值减去记录的位置。
root.setRight(makeTree(preorder, start + n + 1, len - n - 1, inorder, start1 + n + 1, len1 - n - 1));
return root;
}
- 测试结果:
- 3.实验三
- 关键代码:
public DecisionTree(String filename) throws FileNotFoundException
{
File inputFile = new File(filename);
Scanner scan = new Scanner(inputFile);
int numberNodes = scan.nextInt();
scan.nextLine();
int root = 0, left, right;
List<LinkedBinaryTree<String>> nodes = new java.util.ArrayList<LinkedBinaryTree<String>>();
for (int i = 0; i < numberNodes; i++)
nodes.add(i,new LinkedBinaryTree<String>(scan.nextLine()));
while (scan.hasNext())
{
root = scan.nextInt();
left = scan.nextInt();
right = scan.nextInt();
scan.nextLine();
nodes.set(root, new LinkedBinaryTree<String>((nodes.get(root)).getRootElement(),
nodes.get(left), nodes.get(right)));
}
tree = nodes.get(root);
}
- 测试结果:
- 4.实验四
- 关键代码:
//大体思路是把数字放在一个数组中,把+ - 放在另一个数组中,如果遇见* /,把* / 作为根,从数字组中取出最后的数作为左孩子,把* / 的后一个数作为右孩子,把整个树再放在数字组中。遇见括号就把括号里的全部读出作为新的表达式递归进入该方法,再得出该子表达式的结果放入数字组中。
public BinaryTreeNode buildTree(String str) {
//创建一个结点类的ArrayList存放数字,创建一个String的ArrayList存放符号
ArrayList<BinaryTreeNode> num = new ArrayList<BinaryTreeNode>();
ArrayList<String> fuhao = new ArrayList<String>();
StringTokenizer st = new StringTokenizer(str);
String token;
while (st.hasMoreTokens()) {
token = st.nextToken();
//如果是括号,就读出括号里的所有内容形成子表达式。
if (token.equals("(")) {
String str1 = "";
token = st.nextToken();
while (!token.equals(")")) {
str1 += token + " ";
token = st.nextToken();
}
//得出子表达式的结果之后,如果父表达式结束,也就是“)”后再没内容,则跳出该循环,否则接着读下一个元素。
num.add(buildTree(str1));
if (st.hasMoreTokens()) {
token = st.nextToken();
}
else
break;
}
//如果是数,则直接存入数字组中。
if (!token.equals("+") && !token.equals("-") && !token.equals("*") && !token.equals("/")) {
num.add(new BinaryTreeNode(token));
}
//如果是+或者-,先把该元素作为结点,再读取下一个元素,如果下一个元素不为“(”,则把该元素放入符号组,把下一个元素(肯定是数字)放入数字组。如果下一个元素是“(”,则把该元素放入符号组之后,再读取下一个的下一个元素(也就是括号后的第一个元素),再把括号里的元素读出形成子表达式调用该方法得出结果,放入数字组中。
if (token.equals("+") || token.equals("-")) {
BinaryTreeNode<String> tempNode = new BinaryTreeNode<>(token);
token = st.nextToken();
if (!token.equals("(")) {
fuhao.add(tempNode.element);
num.add(new BinaryTreeNode(token));
}
else {
fuhao.add(tempNode.element);
String temp = st.nextToken();
String s = "";
while (!temp.equals(")")) {
s += temp + " ";
temp = st.nextToken();
}
num.add(buildTree(s));
}
}
//如果是*或者/,先把该元素形成新的结点,如果下一个元素不为“(”,则把数字组的最后一个元素作为左孩子,下一个元素作为右孩子,形成新的树存入数字组中。如果下一个元素为“(”,则把数字组的最后一个元素作为左孩子,把括号得出的子表达式的结果作为右孩子,得出的树放入数字组中。
if (token.equals("*") || token.equals("/")) {
BinaryTreeNode<String> tempNode = new BinaryTreeNode<>(token);
token = st.nextToken();
if (!token.equals("(")) {
tempNode.setLeft(num.remove(num.size() - 1));
tempNode.setRight(new BinaryTreeNode<String>(token));
num.add(tempNode);
}
else {
String temp = st.nextToken();
tempNode.setLeft(num.remove(num.size() - 1));
String s = "";
while (!temp.equals(")")) {
s += temp + " ";
temp = st.nextToken();
}
tempNode.setRight(buildTree(s));
num.add(tempNode);
}
}
}
//表达式处理完之后,当符号组有符号,则把符号组从后弹出,作为根,数字组的最后一个元素作为右孩子,倒数第二个元素作为左孩子,依次循环直至符号组的符号全部弹出,最后得出的结果放入数字组中。
int i = fuhao.size();
while (i > 0) {
BinaryTreeNode<T> root = new BinaryTreeNode(fuhao.remove(fuhao.size() - 1));
root.setRight(num.remove(num.size() - 1));
root.setLeft(num.remove(num.size() - 1));
num.add(root);
i--;
}
//返回数字组索引值为0的位置就是树存在的位置。
return num.get(0);
}
- 测试结果:
- 5.实验五
- 关键代码:
public T removeMax() throws EmptyCollectionException {
T result = null;
if (isEmpty())
throw new EmptyCollectionException("LinkedBinarySearchTree");
else {
if (root.right == null) {
result = root.element;
root = root.right;
} else {
BinaryTreeNode<T> parent = root;
BinaryTreeNode<T> current = root.right;
while (current.right != null) {
parent = current;
current = current.right;
}
result = current.element;
parent.right = current.left;
}
modCount--;
}
return result;
}
public T findMin() throws EmptyCollectionException {
T result = null;
if (isEmpty())
throw new EmptyCollectionException("LinkedBinarySearchTree");
else {
if (root.left == null) {
result = root.element;
root = root.right;
} else {
BinaryTreeNode<T> parent = root;
BinaryTreeNode<T> current = root.left;
while (current.left != null) {
parent = current;
current = current.left;
}
result = current.element;
}
}
return result;
}
public T findMax() throws EmptyCollectionException {
T result = null;
if (isEmpty())
throw new EmptyCollectionException("LinkedBinarySearchTree");
else {
if (root.right == null) {
result = root.element;
root = root.right;
} else {
BinaryTreeNode<T> parent = root;
BinaryTreeNode<T> current = root.right;
while (current.right != null) {
parent = current;
current = current.right;
}
result = current.element;
}
modCount--;
}
return result;
}
}
- 测试结果:
- 实验六
- 红黑树的特性:
- (1)每个节点或者是黑色,或者是红色。
- (2)根节点是黑色。
- (3)每个叶子节点(NIL)是黑色。
- [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
- (4)如果一个节点是红色的,则它的子节点必须是黑色的。
- (5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
- 注意:
- (01) 特性(3)中的叶子节点,是只为空(NIL或null)的节点。
- (02) 特性(5),确保没有一条路径会比其他路径长出俩倍。因而,红黑树是相对是接近平衡的二叉树。
- TreeMap类:TreeMap还有一个性质,就是他的左子树比他小,右子树比他大,这里的比较是按照key排序的。存放的时候如果key一样就把他替换了。
- 代码分析:
//结点类
static final class Entry<K,V> implements Map.Entry<K,V> {
K key;
V value;
Entry<K,V> left;
Entry<K,V> right;
Entry<K,V> parent;
boolean color = BLACK;
/**
* Make a new cell with given key, value, and parent, and with
* {@code null} child links, and BLACK color.
*/
Entry(K key, V value, Entry<K,V> parent) {
this.key = key;
this.value = value;
this.parent = parent;
}
/**
* Returns the key.
*
* @return the key
*/
public K getKey() {
return key;
}
/**
* Returns the value associated with the key.
*
* @return the value associated with the key
*/
public V getValue() {
return value;
}
/**
* Replaces the value currently associated with the key with the given
* value.
*
* @return the value associated with the key before this method was
* called
*/
public V setValue(V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
}
//比较两个结点的key值是否相等
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());
}
//hashCode要不是keyHash要不是valueHash,只能二者选一。
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;
}
}
//得到最小值也就是最左下角的数
final Entry<K,V> getFirstEntry() {
Entry<K,V> p = root;
if (p != null)
while (p.left != null)
p = p.left;
return p;
}
//得到最大值也就是最右下角的数
final Entry<K,V> getLastEntry() {
Entry<K,V> p = root;
if (p != null)
while (p.right != null)
p = p.right;
return p;
}
public V put(K key, V value)
{
// 先以 t 保存链表的 root 节点
Entry<K,V> t = root;
// 如果 t==null,表明是一个空链表,即该 TreeMap 里没有任何 Entry
if (t == null)
{
// 将新的 key-value 创建一个 Entry,并将该 Entry 作为 root
root = new Entry<K,V>(key, value, null);
// 设置该 Map 集合的 size 为 1,代表包含一个 Entry
size = 1;
// 记录修改次数为 1
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
Comparator<? super K> cpr = comparator;
// 如果比较器 cpr 不为 null,即表明采用定制排序
if (cpr != null)
{
do {
// 使用 parent 上次循环后的 t 所引用的 Entry
parent = t;
// 拿新插入 key 和 t 的 key 进行比较
cmp = cpr.compare(key, t.key);
// 如果新插入的 key 小于 t 的 key,t 等于 t 的左边节点
if (cmp < 0)
t = t.left;
// 如果新插入的 key 大于 t 的 key,t 等于 t 的右边节点
else if (cmp > 0)
t = t.right;
// 如果两个 key 相等,新的 value 覆盖原有的 value,
// 并返回原有的 value
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 所引用的 Entry
parent = t;
// 拿新插入 key 和 t 的 key 进行比较
cmp = k.compareTo(t.key);
// 如果新插入的 key 小于 t 的 key,t 等于 t 的左边节点
if (cmp < 0)
t = t.left;
// 如果新插入的 key 大于 t 的 key,t 等于 t 的右边节点
else if (cmp > 0)
t = t.right;
// 如果两个 key 相等,新的 value 覆盖原有的 value,
// 并返回原有的 value
else
return t.setValue(value);
} while (t != null);
}
// 将新插入的节点作为 parent 节点的子节点
Entry<K,V> e = new Entry<K,V>(key, value, parent);
// 如果新插入 key 小于 parent 的 key,则 e 作为 parent 的左子节点
if (cmp < 0)
parent.left = e;
// 如果新插入 key 小于 parent 的 key,则 e 作为 parent 的右子节点
else
parent.right = e;
// 修复红黑树
fixAfterInsertion(e); // ①
size++;
modCount++;
return null;
}
private void deleteEntry(Entry<K,V> p)
{
modCount++;
size--;
// 如果被删除节点的左子树、右子树都不为空
if (p.left != null && p.right != null)
{
// 用 p 节点的中序后继节点代替 p 节点
Entry<K,V> s = successor (p);
p.key = s.key;
p.value = s.value;
p = s;
}
// 如果 p 节点的左节点存在,replacement 代表左节点;否则代表右节点。
Entry<K,V> replacement = (p.left != null ? p.left : p.right);
if (replacement != null)
{
replacement.parent = p.parent;
// 如果 p 没有父节点,则 replacemment 变成父节点
if (p.parent == null)
root = replacement;
// 如果 p 节点是其父节点的左子节点
else if (p == p.parent.left)
p.parent.left = replacement;
// 如果 p 节点是其父节点的右子节点
else
p.parent.right = replacement;
p.left = p.right = p.parent = null;
// 修复红黑树
if (p.color == BLACK)
fixAfterDeletion(replacement); // ①
}
// 如果 p 节点没有父节点
else if (p.parent == null)
{
root = null;
}
else
{
if (p.color == BLACK)
// 修复红黑树
fixAfterDeletion(p); // ②
if (p.parent != null)
{
// 如果 p 是其父节点的左子节点
if (p == p.parent.left)
p.parent.left = null;
// 如果 p 是其父节点的右子节点
else if (p == p.parent.right)
p.parent.right = null;
p.parent = null;
}
}
}
final Entry<K,V> getEntry(Object key)
{
// 如果 comparator 不为 null,表明程序采用定制排序
if (comparator != null)
// 调用 getEntryUsingComparator 方法来取出对应的 key
return getEntryUsingComparator(key);
// 如果 key 形参的值为 null,抛出 NullPointerException 异常
if (key == null)
throw new NullPointerException();
// 将 key 强制类型转换为 Comparable 实例
Comparable<? super K> k = (Comparable<? super K>) key;
// 从树的根节点开始
Entry<K,V> p = root;
while (p != null)
{
// 拿 key 与当前节点的 key 进行比较
int cmp = k.compareTo(p.key);
// 如果 key 小于当前节点的 key,向“左子树”搜索
if (cmp < 0)
p = p.left;
// 如果 key 大于当前节点的 key,向“右子树”搜索
else if (cmp > 0)
p = p.right;
// 不大于、不小于,就是找到了目标 Entry
else
return p;
}
return null;
}
final Entry<K,V> getEntryUsingComparator(Object key)
{
K k = (K) key;
// 获取该 TreeMap 的 comparator
Comparator<? super K> cpr = comparator;
if (cpr != null)
{
// 从根节点开始
Entry<K,V> p = root;
while (p != null)
{
// 拿 key 与当前节点的 key 进行比较
int cmp = cpr.compare(k, p.key);
// 如果 key 小于当前节点的 key,向“左子树”搜索
if (cmp < 0)
p = p.left;
// 如果 key 大于当前节点的 key,向“右子树”搜索
else if (cmp > 0)
p = p.right;
// 不大于、不小于,就是找到了目标 Entry
else
return p;
}
}
return null;
}
- HashMap类:
//通过hash计算插入的项的槽位,如果有是一样的key则根据设置的参数是否执行覆盖,如果相应槽位空的话直接插入,如果对应的槽位有项则判断是红黑树结构还是链表结构的槽位,链表的话则顺着链表寻找如果找到一样的key则根据参数选择覆盖,没有找到则链接在链表最后面,链表项的数目大于8则对其进行树化,如果是红黑树结构则按照树的添加方式添加项。
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)
{
Node<K, V>[] tab;
Node<K, V> p;
int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else
{
Node<K, V> e;
K k;
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
// 如果当前的bucket里面已经是红黑树的话,执行红黑树的添加操作
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);
// TREEIFY_THRESHOLD = 8,判断如果当前bucket的位置链表长度大于8的话就将此链表变成红黑树。
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
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;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
//第一次循环会将链表中的首节点作为红黑树的根,而后的循环会将链表中的的项通过比较hash值然后连接到相应树节点的左边或者右边,插入可能会破坏树的结构所以接着执行balanceInsertion。
final void treeifyBin(Node<K, V>[] tab, int hash)
{
int n, index;
Node<K, V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
// resize()方法这里不过多介绍,感兴趣的可以去看上面的链接。
resize();
// 通过hash求出bucket的位置。
else if ((e = tab[index = (n - 1) & hash]) != null)
{
TreeNode<K, V> hd = null, tl = null;
do
{
// 将每个节点包装成TreeNode。
TreeNode<K, V> p = replacementTreeNode(e, null);
if (tl == null)
hd = p;
else
{
// 将所有TreeNode连接在一起此时只是链表结构。
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
if ((tab[index] = hd) != null)
// 对TreeNode链表进行树化。
hd.treeify(tab);
}
}
final void treeify(Node<K, V>[] tab)
{
TreeNode<K, V> root = null;
// 以for循环的方式遍历刚才我们创建的链表。
for (TreeNode<K, V> x = this, next; x != null; x = next)
{
// next向前推进。
next = (TreeNode<K, V>) x.next;
x.left = x.right = null;
// 为树根节点赋值。
if (root == null)
{
x.parent = null;
x.red = false;
root = x;
} else
{
// x即为当前访问链表中的项。
K k = x.key;
int h = x.hash;
Class<?> kc = null;
// 此时红黑树已经有了根节点,上面获取了当前加入红黑树的项的key和hash值进入核心循环。
// 这里从root开始,是以一个自顶向下的方式遍历添加。
// for循环没有控制条件,由代码内break跳出循环。
for (TreeNode<K, V> p = root;;)
{
// dir:directory,比较添加项与当前树中访问节点的hash值判断加入项的路径,-1为左子树,+1为右子树。
// ph:parent hash。
int dir, ph;
K pk = p.key;
if ((ph = p.hash) > h)
dir = -1;
else if (ph < h)
dir = 1;
else if ((kc == null && (kc = comparableClassFor(k)) == null)
|| (dir = compareComparables(kc, k, pk)) == 0)
dir = tieBreakOrder(k, pk);
// xp:x parent。
TreeNode<K, V> xp = p;
// 找到符合x添加条件的节点。
if ((p = (dir <= 0) ? p.left : p.right) == null)
{
x.parent = xp;
// 如果xp的hash值大于x的hash值,将x添加在xp的左边。
if (dir <= 0)
xp.left = x;
// 反之添加在xp的右边。
else
xp.right = x;
// 维护添加后红黑树的红黑结构。
root = balanceInsertion(root, x);
// 跳出循环当前链表中的项成功的添加到了红黑树中。
break;
}
}
}
}
// Ensures that the given root is the first node of its bin,自己翻译一下。
moveRootToFront(tab, root);
}
static <K, V> TreeNode<K, V> balanceInsertion(TreeNode<K, V> root, TreeNode<K, V> x)
{
// 正如开头所说,新加入树节点默认都是红色的,不会破坏树的结构。
x.red = true;
// 这些变量名不是作者随便定义的都是有意义的。
// xp:x parent,代表x的父节点。
// xpp:x parent parent,代表x的祖父节点
// xppl:x parent parent left,代表x的祖父的左节点。
// xppr:x parent parent right,代表x的祖父的右节点。
for (TreeNode<K, V> xp, xpp, xppl, xppr;;)
{
// 如果x的父节点为null说明只有一个节点,该节点为根节点,根节点为黑色,red = false。
if ((xp = x.parent) == null)
{
x.red = false;
return x;
}
// 进入else说明不是根节点。
// 如果父节点是黑色,那么大吉大利(今晚吃鸡),红色的x节点可以直接添加到黑色节点后面,返回根就行了不需要任何多余的操作。
// 如果父节点是红色的,但祖父节点为空的话也可以直接返回根此时父节点就是根节点,因为根必须是黑色的,添加在后面没有任何问题。
else if (!xp.red || (xpp = xp.parent) == null)
return root;
// 一旦我们进入到这里就说明了两件是情
// 1.x的父节点xp是红色的,这样就遇到两个红色节点相连的问题,所以必须经过旋转变换。
// 2.x的祖父节点xpp不为空。
// 判断如果父节点是否是祖父节点的左节点
if (xp == (xppl = xpp.left))
{
// 父节点xp是祖父的左节点xppr
// 判断祖父节点的右节点不为空并且是否是红色的
// 此时xpp的左右节点都是红的,所以直接进行上面所说的第三种变换,将两个子节点变成黑色,将xpp变成红色,然后将红色节点x顺利的添加到了xp的后面。
// 这里大家有疑问为什么将x = xpp?
// 这是由于将xpp变成红色以后可能与xpp的父节点发生两个相连红色节点的冲突,这就又构成了第二种旋转变换,所以必须从底向上的进行变换,直到根。
// 所以令x = xpp,然后进行下下一层循环,接着往上走。
if ((xppr = xpp.right) != null && xppr.red)
{
xppr.red = false;
xp.red = false;
xpp.red = true;
x = xpp;
}
// 进入到这个else里面说明。
// 父节点xp是祖父的左节点xppr。
// 祖父节点xpp的右节点xppr是黑色节点或者为空,默认规定空节点也是黑色的。
// 下面要判断x是xp的左节点还是右节点。
else
{
// x是xp的右节点,此时的结构是:xpp左->xp右->x。这明显是第二中变换需要进行两次旋转,这里先进行一次旋转。
// 下面是第一次旋转。
if (x == xp.right)
{
root = rotateLeft(root, x = xp);
xpp = (xp = x.parent) == null ? null : xp.parent;
}
// 针对本身就是xpp左->xp左->x的结构或者由于上面的旋转造成的这种结构进行一次旋转。
if (xp != null)
{
xp.red = false;
if (xpp != null)
{
xpp.red = true;
root = rotateRight(root, xpp);
}
}
}
}
// 这里的分析方式和前面的相对称只不过全部在右测不再重复分析。
else
{
if (xppl != null && xppl.red)
{
xppl.red = false;
xp.red = false;
xpp.red = true;
x = xpp;
} else
{
if (x == xp.left)
{
root = rotateRight(root, x = xp);
xpp = (xp = x.parent) == null ? null : xp.parent;
}
if (xp != null)
{
xp.red = false;
if (xpp != null)
{
xpp.red = true;
root = rotateLeft(root, xpp);
}
}
}
}
}
}
3. 实验过程中遇到的问题和解决过程
- 问题1:实验三中
- 问题1解决方案:在输入的文本中多一行空行。因为nextLine会多读一个换行符,如果没有那一行空行,没有东西读就会报错。
- 问题2:实验四中索引值应该是个数-1,但是如果我变size()-1会出现越界的错误情况
- 问题2解决方案:这里的size()并不是num的size(),而是该类中自己的size(),应改为下图就可以了。
-
问题3:实验六中TreeMap类是什么?
-
问题3解决方案:
TreeMap是一个有序的key-value集合,它是通过红黑树实现的。
TreeMap 继承于AbstractMap,所以它是一个Map,即一个key-value集合。
TreeMap 实现了NavigableMap接口,意味着它支持一系列的导航方法。比如返回有序的key集合。
TreeMap 实现了Cloneable接口,意味着它能被克隆。
TreeMap 实现了java.io.Serializable接口,意味着它支持序列化。 -
问题4:实验六中下列代码中的^是什么意思。
public int hashCode() {
int keyHash = (key==null ? 0 : key.hashCode());
int valueHash = (value==null ? 0 : value.hashCode());
return keyHash ^ valueHash;
}
-
问题4解决方案:是进行异或运算的意思。使异或表达式为true的条件是:有且仅有一个操作为true;其他情况都为false;
-
问题5:下列代码中的>>是什么意思。
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
- 问题5解决方案:表示带符号右移,按二进制形式把所有的数字向右移动对应的位数,低位移出(舍弃),高位的空位补符号位,即正数补零,负数补1
其他(感悟、思考等)
- 这次的实验是关于树的实验,二叉查找树,决策树这些都是在做pp就实现过的,最难的就是用表达式树中缀转后缀的过程,原理是需要把中缀先变成树,然后进行后序遍历就可以得到后缀表达式,先序遍历得到前缀表达式(注意有括号的情况)。实验六的分析,因为源代码太多了不知道关键,所以查了相关资料,看了相关博客,简单的写了一下。代码的解释我都作为注释写在代码块内了。
(划重点!!!我觉得我实验四的创建中缀表达式树写的非常完美,一点bug都没有,加几个括号都是对的!)