20172313 2018-2019-1 《程序設計與數據結構》實驗二報告

20172313 2018-2019-1 《程序設計與數據結構》實驗一報告

課程:《程序設計與數據結構》
班級: 1723
姓名: 餘坤澎
學號:20172313
實驗教師:王志強
實驗日期:2018年9月26日
必修/選修: 必修html

1.實驗內容

  • 實驗一 參考教材p212,完成鏈樹LinkedBinaryTree的實現(getRight,contains,toString,preorder,postorder),用JUnit或本身編寫驅動類對本身實現的LinkedBinaryTree進行測試,提交測試代碼運行截圖,要全屏,包含本身的學號信息,課下把代碼推送到代碼託管平臺。
  • 實驗二 基於LinkedBinaryTree,實現基於(中序,先序)序列構造惟一一棵二㕚樹的功能,好比給出中序HDIBEMJNAFCKGL和後序ABDHIEJMNCFGKL,構造出附圖中的樹,用JUnit或本身編寫驅動類對本身實現的功能進行測試,提交測試代碼運行截圖,要全屏,包含本身的學號信息,課下把代碼推送到代碼託管平臺。
  • 實驗三 本身設計並實現一顆決策樹,提交測試代碼運行截圖,要全屏,包含本身的學號信息,課下把代碼推送到代碼託管平臺。
  • 實驗四 輸入中綴表達式,使用樹將中綴表達式轉換爲後綴表達式,並輸出後綴表達式和計算結果(若是沒有用樹,則爲0分),提交測試代碼運行截圖,要全屏,包含本身的學號信息,課下把代碼推送到代碼託管平臺。
  • 實驗五 完成PP11.3,提交測試代碼運行截圖,要全屏,包含本身的學號信息,課下把代碼推送到代碼託管平臺。
  • 實驗六 參考http://www.cnblogs.com/rocedu/p/7483915.html對Java中的紅黑樹(TreeMap,HashMap)進行源碼分析,並在實驗報告中體現分析結果。

2. 實驗過程及結果

實驗一

  • getRight方法只須要新定義一個LinkedBinaryTree類,令它的根結點等於原樹的右子樹的結點便可。注意:首先要判斷原樹的根結點是否爲空,若空,則拋出異常。
public LinkedBinaryTree<T> getRight()
    {
        if(root == null)
            throw new EmptyCollectionException("Get right operation failed. The tree is empty.");

        LinkedBinaryTree<T> result = new LinkedBinaryTree<>();
        result.root = root.getRight();
        return result;
    }
  • contains方法調用findNode方法便可。當findNode返回值不爲空時,該方法返回true,即存在,不然不存在。
public boolean contains(T targetElement)
    {

        if(findNode(targetElement,root) != null)
            return true;
        else
            return false;
    }
  • 在這裏toString我使用的是原先學習的按照樹的形狀打印樹的方法。
public String toString() {
        UnorderedListADT<BinaryTreeNode> nodes =
                new ArrayUnorderedList<BinaryTreeNode>();
        UnorderedListADT<Integer> levelList =
                new ArrayUnorderedList<Integer>();
        BinaryTreeNode 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)
            {
                result = result + "\n\n";
                previousLevel = currentLevel;
                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;
    }
  • preorder,postorder方法參考書上inorder方法便可。(在這裏只列舉preorder方法的代碼)
public Iterator<T> iteratorPreOrder()
    {
        ArrayUnorderedList<T> tempList = new ArrayUnorderedList<T>();
        preOrder(root, tempList);

        return new TreeIterator(tempList.iterator());
    }

protected void preOrder(BinaryTreeNode<T> node,
                            ArrayUnorderedList<T> tempList)
    {
        if (node != null)
        {
            tempList.addToRear(node.getElement());
            preOrder(node.getLeft(), tempList);
            preOrder(node.getRight(), tempList);
        }
    }
  • 全部方法實現以後,編寫驅動類對LinkedBinaryTree進行測試。

實驗二

  • 實驗一比較容易,全部的代碼基本上在之前都實現過,稍加參考就能完成。實驗二就比較難了,要使用中序序列和前序序列構造二叉樹。設計思路以下:
    • ①咱們要肯定根節點的位置,先序遍歷的第一個結點就是該二叉樹的根。
    • ② 肯定根的子樹,有中序遍歷可知,咱們在中序遍歷中找到根結點的位置,左邊是它的左孩子,右邊是它的右孩子,若是根結點左邊或右邊爲空,那麼在該方向上子樹爲空,若是根結點左邊和右邊都爲空,那麼該結點是葉子結點。
    • ③對二叉樹的左、右子樹分別進行步驟①②,直到求出二叉樹的結構爲止。
  • 該實驗的主要難度在於如何使用遞歸不斷對左右子樹重複進行①②步驟,可根據傳入的序列的數組的索引值進行分割,在中序序列中但凡比根元素索引值小的即爲根元素的左結點,大的即爲根元素的右結點。按照中序序列分紅的兩部分元素又能夠把前序序列分紅兩部分,接下來用遞歸,把新獲得的序列數組做爲參數,直到造成二叉樹的結構。
public void buildTree(T[] inorder, T[] postorder) {
        BinaryTreeNode temp=makeTree(inorder, 0, inorder.length, postorder, 0, postorder.length);
        root = temp;

    }


    public  BinaryTreeNode<T> makeTree(T[] inorder,int startIn,int lenIn,T[] postorder,int startPos,int lenPos){
        if(lenIn<1){
            return null;
        }
        BinaryTreeNode root;
        T rootelement=postorder[startPos];//postorder中的第一個元素就是當前處理的數據段的根節點
        root=new BinaryTreeNode(rootelement);
        int temp;
        boolean isFound=false;
        for(temp=0;temp<lenIn;temp++){
            if(inorder[startIn+temp]==rootelement){
                isFound=true;//此時找到結點
                break;
            }
        }
        if(!isFound)//若是不存在相等的狀況就跳出該函數
            return root;
        root.setLeft(makeTree(inorder, startIn, temp, postorder, startPos+1, temp));
        root.setRight(makeTree(inorder, startIn+temp+1, lenIn-temp-1, postorder, startPos+temp+1, lenPos-temp-1));
        return root;
    }

實驗三

  • 實驗三能夠說是最簡單的了,有了書上的示例進行參考,對文件進行修改便可,在這裏再也不過多贅述。

實驗四

  • 這個實驗是本次實驗中最有難度的一個,雖然原先學習過中綴表達式轉後綴表達式求值的問題,但使用樹來進行操做仍是第一次。使用後綴表達式進行求值課堂上學習過,書上也有示例代碼。這裏只列舉使用樹把中綴表達式變爲後綴表達式。設計思路以下:
    • 定義兩個列表分別用來操做符和「子樹「,注意:這裏的操做符列表裏只存「+」「-」,至於爲何將在下面進行分析。首先把中綴表達式用StringTokenizer方法進行分開,從前到後依次遍歷,若是是括號,就把括號裏面的內容做爲一個新的表達式傳入該方法,把返回的結點做爲一棵新樹的根存進numList。若是遍歷到數字,做爲一棵新樹的element存入numList,若是遍歷到「+」「-」,直接存入operLIst中。若是遇到「乘或除」,若是「乘或除」的日後遍歷到的是括號,則先把括號裏的內容做爲一個新的表達式傳入該方法,把返回的結點做爲「乘號」的右子樹的根結點,而後從numList取出最後一個元素做爲「乘號」的左子樹的根結點。若是「乘或除」的日後遍歷到的是數字,則直接做爲「乘號」的右子樹的根結點,而後從numList取出最後一個元素做爲「乘號」的左子樹的根結點。
    • 這裏的核心思想就是一旦遇到括號就把括號裏的內容做爲參數傳入該方法,因爲括號運算的優先級,能夠把獲得的該結點傳入numList,在造成二叉樹的過程當中直接取出。一旦遇到「乘或除」就從numList的末尾取出一位元素做爲它的左子樹,「乘或除」的下一位元素做爲它的右子樹,而後把這個子樹存入numList,這樣在保證了運算優先級的同時,也使得造成二叉樹的時候順序不會被打亂,這就是爲何操做符列表裏只存「+」「-」操做符列表裏只存「+」「-」,由於「乘或除」做爲子樹的根結點都存入到了numList。一直到遍歷完成,這時候咱們知道,最早進入numList的運算優先級最高,因此從索引爲0處開始取元素,把它們組裝成一棵二叉樹便可。
    public BinaryTreeNode buildTree(String str) {
          ArrayList<String> operList = new ArrayList<>();
          ArrayList<LinkedBinaryTree> numList = new ArrayList<>();
          StringTokenizer st = new StringTokenizer(str);
          String token;
          while (st.hasMoreTokens()) {
              token = st.nextToken();
              if (token.equals("(")) {
                  String str1 = "";
                  while (true) {
                      token = st.nextToken();
                      if (!token.equals(")"))
                          str1 += token + " ";
                      else
                          break;
                  }
                  LinkedBinaryTree temp = new LinkedBinaryTree();
                  temp.root = buildTree(str1);
                  numList.add(temp);
                  token = st.nextToken();
              }
              if (token.equals("+") || token.equals("-")) {
                  operList.add(token);
              } else if (token.equals("*") || token.equals("/")) {
                  LinkedBinaryTree left = numList.remove(numList.size() - 1);
                  String A = token;
                  token = st.nextToken();
                  if (!token.equals("(")) {
                      LinkedBinaryTree right = new LinkedBinaryTree(token);
                      LinkedBinaryTree node = new LinkedBinaryTree(A, left, right);
                      numList.add(node);
                  } else {
                      String str1 = "";
                      while (true) {
                          token = st.nextToken();
                          if (!token.equals(")"))
                              str1 += token + " ";
                          else
                              break;
                      }
                      LinkedBinaryTree temp2 = new LinkedBinaryTree();
                      temp2.root = buildTree(str1);
                      LinkedBinaryTree node1 = new LinkedBinaryTree(A, left, temp2);
                      numList.add(node1);
                  }
              } else
                  numList.add(new LinkedBinaryTree(token));
          }
          while (operList.size() > 0) {
              LinkedBinaryTree left = numList.remove(0);
              LinkedBinaryTree right = numList.remove(0);
              String oper = operList.remove(0);
    
              LinkedBinaryTree node2 = new LinkedBinaryTree(oper, left, right);
              numList.add(0, node2);
          }
          node = (numList.get(0)).root;
    
          return node;
      }

實驗五

  • 實驗五相較於實驗四來講又簡單了一些,因爲二叉查找樹的最小元素始終位於整棵樹左下角最後一個左子樹的第一個位置,最大元素始終位於整棵樹右下角最後一個右子樹的最後一個位置。從根結點進行遍歷便可。注意:這裏要判斷樹爲空和結點的左右子樹是否存在。
public T removeMax() throws EmptyCollectionException
    {
        T result = null;

        if (isEmpty())
            throw new EmptyCollectionException("LinkedBinarySearchTree");
        else
        {
            if (root.right == null)
            {
                result = root.element;
                root = root.left;
            }
            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
    {
        if(isEmpty())
            System.out.println("BinarySearchTree is empty!");

        return findMin(root).getElement();
    }

    private BinaryTreeNode<T> findMin(BinaryTreeNode<T> p) {
        if (p==null)//結束條件
            return null;
        else if (p.left==null)//若是沒有左結點,那麼t就是最小的
            return p;
        return findMin(p.left);
    }

    public T findMax() throws EmptyCollectionException
    {
        if(isEmpty())
            System.out.println("BinarySearchTree is empty!");

        return findMax(root).getElement();
    }

    private BinaryTreeNode<T> findMax(BinaryTreeNode<T> p){
        if (p==null)//結束條件
            return null;
        else if (p.right==null)
            return p;
        return findMax(p.right);
    }

實驗六

  • 紅黑樹(Red Black Tree) 是一種自平衡二叉查找樹,是在計算機科學中用到的一種數據結構,典型的用途是實現關聯數組。首先,咱們先來看看它的性質
    • 樹中的每個結點都儲存着一種顏色(紅色或黑色,一般使用一個布爾值來實現,值false等價於紅色)。
    • 根結點爲黑色。
    • 每一個葉子結點(null)是黑色。(**注意:這裏的葉子結點,是指爲空(null)的葉子結點!)
    • 從樹根到樹葉的每條路徑都包含有一樣數目的黑色結點。
    • 若是一個結點的顏色爲紅色,那麼它的子結點一定是黑色。
  • 紅黑樹示意圖:
    • TreeMap概述:TreeMap是基於紅黑樹實現的。因爲TreeMap實現了java.util.sortMap接口,集合中的映射關係是具備必定順序的,該映射根據其鍵的天然順序進行排序或者根據建立映射時提供的Comparator進行排序,具體取決於使用的構造方法。另外TreeMap中不容許鍵對象是null。
    • TreeMap類:
    public class TreeMap<K,V>
      extends AbstractMap<K,V>
      implements NavigableMap<K,V>, Cloneable, java.io.Serializable

    TreeMap 是一個有序的key-value集合,它是經過紅黑樹實現的。
    TreeMap 繼承於AbstractMap,因此它是一個Map,即一個key-value集合。
    TreeMap 實現了NavigableMap接口,意味着它支持一系列的導航方法。好比返回有序的key集合。
    TreeMap 實現了Cloneable接口,意味着它能被克隆。
    TreeMap 實現了java.io.Serializable接口,意味着它支持序列化。java

  • TreeMap經常使用方法:
    • TreeMap的構造函數
    //使用默認構造函數構造TreeMap時,使用java的默認的比較器比較Key的大小,從而對TreeMap進行排序。
    public TreeMap() {
      comparator = null;
    }
    //帶比較器的構造函數
    public TreeMap(Comparator<? super K> comparator) {
      this.comparator = comparator;
    }
    //帶Map的構造函數,Map會成爲TreeMap的子集
    ublic TreeMap(Map<? extends K, ? extends V> m) {
      comparator = null;
      putAll(m);
    }
    //該構造函數會調用putAll()將m中的全部元素添加到TreeMap中。從中,咱們能夠看出putAll()就是將m中的key-value逐個的添加到TreeMap中。putAll()源碼以下:
    public void putAll(Map<? extends K, ? extends V> m) {
      for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
          put(e.getKey(), e.getValue());
    }
    //帶SortedMap的構造函數,SortedMap會成爲TreeMap的子集,該構造函數不一樣於上一個構造函數,在上一個構造函數中傳入的參數是Map,Map不是有序的,因此要逐個添加。而該構造函數的參數是SortedMap是一個有序的Map,咱們經過buildFromSorted()來建立對應的Map。
    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) {
      }
    }
    • V put(K key,V value):將鍵值對(key,value)添加到TreeMap中
    public V put(K key, V value) {//插入或設置元素,返回原始value值(若是插入返回null)
          Entry<K,V> t = root;
          if (t == null) {//根元素爲空時直接創建根元素
              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;
          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);//相等直接進行value設置
              } while (t != null);
          }
          else {//不存在比較器,按compareTo方法查找
              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);
          }
          Entry<K,V> e = new Entry<K,V>(key, value, parent);
          if (cmp < 0)
              parent.left = e;
          else
              parent.right = e;
          fixAfterInsertion(e);
          size++;
          modCount++;
          return null;
      }
      private void fixAfterInsertion(Entry<K,V> x) {//插入數據後的樹形變化處理
          x.color = RED;//插入元素默認顏色爲紅色
          while (x != null && x != root && x.parent.color == RED) {//當父節點的顏色爲紅色時,須要進行變化
              if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {//若是父元素爲其父的左節點
                  Entry<K,V> y = rightOf(parentOf(parentOf(x)));//取右節點(叔節點)
                  if (colorOf(y) == RED) {//顏色爲紅
                      setColor(parentOf(x), BLACK);//父節點設置爲黑色
                      setColor(y, BLACK);//右節點設置爲黑色
                      setColor(parentOf(parentOf(x)), RED);//父元素的父元素設置爲紅色
                      x = parentOf(parentOf(x));//x設置爲父元素的父元素,繼續進行斷定
                  } else {//叔節點不可能爲黑色,故下面爲無叔節點狀況,必然須要進行旋轉
                      if (x == rightOf(parentOf(x))) {//若是當前元素爲其父的右節點
                          x = parentOf(x);//x設置爲父元素,繼續進行斷定
                          rotateLeft(x);//進行左旋操做
                      }
                      setColor(parentOf(x), BLACK);//父節點設置爲黑色
                      setColor(parentOf(parentOf(x)), RED);//父元素的父元素設置爲紅色
                      rotateRight(parentOf(parentOf(x)));//進行右旋操做
                  }
              } else {//父元素爲其父的右節點
                  Entry<K,V> y = leftOf(parentOf(parentOf(x)));//取左節點(叔節點)
                  if (colorOf(y) == RED) {//顏色爲紅
                      setColor(parentOf(x), BLACK);
                      setColor(y, BLACK);
                      setColor(parentOf(parentOf(x)), RED);
                      x = parentOf(parentOf(x));//x設置爲父元素的父元素,繼續進行斷定
                  } else {//叔節點不可能爲黑色,故下面爲無叔節點狀況,必然須要進行旋轉
                      if (x == leftOf(parentOf(x))) {//若是當前元素爲其父的左節點
                          x = parentOf(x);//x設置爲父元素,繼續進行斷定
                          rotateRight(x);//進行右旋操做
                      }
                      setColor(parentOf(x), BLACK);
                      setColor(parentOf(parentOf(x)), RED);
                      rotateLeft(parentOf(parentOf(x)));//進行左旋操做
                  }
              }
          }
          root.color = BLACK;//根節點設置爲黑色
      }
    • 按照key值進行查找
final Entry<K,V> getEntry(Object key) {//根據key值查找元素方法;final方法不容許被子類重寫
        // Offload comparator-based version for sake of performance
        if (comparator != null)//存在比較器,按比較器進行比較查找
            return getEntryUsingComparator(key);
        if (key == null)//key值爲null拋空指針異常
            throw new NullPointerException();
        Comparable<? super K> k = (Comparable<? super K>) key;
        Entry<K,V> p = root;
        while (p != null) {//從root開始循環查找,一直到葉子節點
            int cmp = k.compareTo(p.key);//採用key的compareTo方法進行比較
            if (cmp < 0)//小於繼續查找左邊
                p = p.left;
            else if (cmp > 0)//大於繼續查找右邊
                p = p.right;
            else
                return p;//等於返回當前元素
        }
        return null;
    }
  • 刪除操做
blic V remove(Object key) {
        Entry<K,V> p = getEntry(key);//先找到須要刪除的元素
        if (p == null)
            return null;
 
        V oldValue = p.value;
        deleteEntry(p);
        return oldValue;
    }
 
    private void deleteEntry(Entry<K,V> p) {
        modCount++;
        size--;
        // If strictly internal, copy successor's element to p and then make p
        // point to successor.
        if (p.left != null && p.right != null) {//若是有兩個孩子
            Entry<K,V> s = successor (p);//查找下一元素
            p.key = s.key;
            p.value = s.value;//p的數據替換爲該元素數據
            p = s;//將p指向該元素,做爲原始元素(被刪除元素)
        } // p has 2 children
 
        // Start fixup at replacement node, if it exists.
        Entry<K,V> replacement = (p.left != null ? p.left : p.right);//將替換元素設置爲左元素(沒有則爲右元素)
 
        if (replacement != null) {//替換元素不爲空
            // Link replacement to parent
            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;
 
            // Null out links so they are OK to use by fixAfterDeletion.
            p.left = p.right = p.parent = null;//原始元素鏈接清空
 
            // Fix replacement
            if (p.color == BLACK)//刪除元素爲黑色,須要進行刪除後樹形變化操做
                fixAfterDeletion(replacement);
        } else if (p.parent == null) { // return if we are the only node.
            root = null;//根節點的刪除
        } else { //  No children. Use self as phantom replacement and unlink.
            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;
            }
        }
    } 
    private void fixAfterDeletion(Entry<K,V> x) {//刪除數據後的樹形變化處理
        while (x != root && colorOf(x) == BLACK) {//當前節點爲黑(替換元素不可能爲黑,只有刪除自身的狀況)
            if (x == leftOf(parentOf(x))) {//左節點
                Entry<K,V> sib = rightOf(parentOf(x));//取父親的右節點(兄節點)
 
                if (colorOf(sib) == RED) {//顏色爲紅
                    setColor(sib, BLACK);
                    setColor(parentOf(x), RED);//着色
                    rotateLeft(parentOf(x));//按父左旋
                    sib = rightOf(parentOf(x));//指向左旋後的父親的右節點(爲黑)
                }
                //顏色爲黑
                if (colorOf(leftOf(sib))  == BLACK &&
                    colorOf(rightOf(sib)) == BLACK) {//兩個孩子均爲黑(實際只可能爲無孩子狀況)
                    setColor(sib, RED);//着色
                    x = parentOf(x);//x指向父節點繼續判斷
                } else {
                    if (colorOf(rightOf(sib)) == BLACK) {//右節點爲黑(實際只可能爲無右孩子)
                        setColor(leftOf(sib), BLACK);
                        setColor(sib, RED);//着色
                        rotateRight(sib);//按兄右旋
                        sib = rightOf(parentOf(x));//指向右旋後的父親的右節點
                    }
                    setColor(sib, colorOf(parentOf(x)));
                    setColor(parentOf(x), BLACK);
                    setColor(rightOf(sib), BLACK);//着色
                    rotateLeft(parentOf(x));//按父左旋
                    x = root;//結束循環
                }
            } else { // symmetric//右節點
                Entry<K,V> sib = leftOf(parentOf(x));//取父親的左節點(兄節點)
 
                if (colorOf(sib) == RED) {//顏色爲紅
                    setColor(sib, BLACK);
                    setColor(parentOf(x), RED);//着色
                    rotateRight(parentOf(x));//按父右旋
                    sib = leftOf(parentOf(x));//指向右旋後的父親的左節點(爲黑或空)
                }
                //顏色爲黑
                if (colorOf(rightOf(sib)) == BLACK &&
                    colorOf(leftOf(sib)) == BLACK) {//兩個孩子均爲黑
                    setColor(sib, RED);//着色
                    x = parentOf(x);//x指向父節點繼續判斷
                } else {
                    if (colorOf(leftOf(sib)) == BLACK) {//左節點爲黑
                        setColor(rightOf(sib), BLACK);
                        setColor(sib, RED);//着色
                        rotateLeft(sib);//按兄左旋
                        sib = leftOf(parentOf(x));//指向左旋後的父親的左節點
                    }
                    setColor(sib, colorOf(parentOf(x)));
                    setColor(parentOf(x), BLACK);
                    setColor(leftOf(sib), BLACK);//着色
                    rotateRight(parentOf(x));//按父右旋
                    x = root;//結束循環
                }
            }
        }
        setColor(x, BLACK);//將x置爲黑色
    }
  • 紅黑樹具體的添加或刪除示意圖可參照往期博客
  • 關於 firstEntry() 和 getFirstEntry()都是用於獲取第一個結點,那麼二者到底有什麼區別呢?
    • firstEntry() 是對外接口; getFirstEntry() 是內部接口。並且,firstEntry() 是經過 getFirstEntry() 來實現的。那爲何外界不能直接調用 getFirstEntry(),而須要畫蛇添足的調用 firstEntry() 呢?這麼作的目的是:防止用戶修改返回的Entry。getFirstEntry()返回的Entry是能夠被修改的,可是通過firstEntry()返回的Entry不能被修改,只能夠讀取Entry的key值和value值。
  • HashMap類
    • Java最基本的數據結構有數組和鏈表。數組的特色是空間連續(大小固定)、尋址迅速,可是插入和刪除時須要移動元素,因此查詢快,增長刪除慢。鏈表剛好相反,可動態增長或減小空間以適應新增和刪除元素,但查找時只能順着一個個節點查找,因此增長刪除快,查找慢。有沒有一種結構綜合了數組和鏈表的優勢呢?固然有,那就是哈希表(雖然說是綜合優勢,但實際上查找確定沒有數組快,插入刪除沒有鏈表快,一種折中的方式吧)。通常採用拉鍊法實現哈希表。
  • HashMap經常使用方法:
    • put()操做:在使用的時候,咱們必定會想到若是兩個key經過hash%Entry[].length獲得的index相同,會不會有覆蓋的危險?爲了解決這個問題,HashMap裏面用到鏈式數據結構的一個概念。上面咱們提到過Entry類裏面有一個next屬性,做用是指向下一個Entry。打個比方, 第一個鍵值對A進來,經過計算其key的hash獲得的index=0,記作:Entry[0] = A。一會後又進來一個鍵值對B,經過計算其index也等於0,如今怎麼辦?HashMap會這樣作:B.next = A,Entry[0] = B,若是又進來C,index也等於0,那麼C.next = B,Entry[0] = C;這樣咱們發現index=0的地方其實存取了A,B,C三個鍵值對,他們經過next這個屬性連接在一塊兒。因此疑問不用擔憂。也就是說數組中存儲的是最後插入的元素。
    public V put(K key, V value) {
          //若是table數組爲空數組{},進行數組填充(爲table分配實際內存空間),入參爲threshold,此時threshold爲initialCapacity 默認是1<<4(=16)
          if (table == EMPTY_TABLE) {
              inflateTable(threshold);//分配數組空間
          }
         //若是key爲null,存儲位置爲table[0]或table[0]的衝突鏈上
          if (key == null)
              return putForNullKey(value);
          int hash = hash(key);//對key的hashcode進一步計算,確保散列均勻
          int i = indexFor(hash, table.length);//獲取在table中的實際位置
          for (Entry<K,V> e = table[i]; e != null; e = e.next) {
          //若是該對應數據已存在,執行覆蓋操做。用新value替換舊value,並返回舊value
              Object k;
              if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                  V oldValue = e.value;
                  e.value = value;
                  e.recordAccess(this);//調用value的回調函數,其實這個函數也爲空實現
                  return oldValue;
              }
          }
          modCount++;//保證併發訪問時,若HashMap內部結構發生變化,快速響應失敗
          addEntry(hash, key, value, i);//新增一個entry
          return null;
      }
    • inflateTable的源碼以下:
    private void inflateTable(int toSize) {
          int capacity = roundUpToPowerOf2(toSize);//capacity必定是2的次冪
          threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);//此處爲threshold賦值,取capacity*loadFactor和MAXIMUM_CAPACITY+1的最小值,capaticy必定不會超過MAXIMUM_CAPACITY,除非loadFactor大於1
          table = new Entry[capacity];//分配空間
          initHashSeedAsNeeded(capacity);//選擇合適的Hash因子
      }
    • inflateTable這個方法用於爲主幹數組table在內存中分配存儲空間,經過roundUpToPowerOf2(toSize)能夠確保capacity爲大於或等於toSize的最接近toSize的二次冪,roundUpToPowerOf2中的這段處理使得數組長度必定爲2的次冪,Integer.highestOneBit是用來獲取最左邊的bit(其餘bit位爲0)所表明的數值。在對數組進行空間分配後,會根據hash函數計算散列值。經過hash函數獲得散列值後,再經過indexFor進一步處理來獲取實際的存儲位置。經過以上分析,咱們看到,要獲得一個元素的存儲位置,須要以下幾步:
      • ①獲取該元素的key值
      • ②經過hash方法獲得key的散列值,這其中須要用到key的hashcode值。
      • ③經過indexFor計算獲得存儲的下標位置。
    • 最後,獲得存儲的下標位置後,咱們就能夠將元素放入HashMap中,具體經過addEntry實現:
    void addEntry(int hash, K key, V value, int bucketIndex) {
          if ((size >= threshold) && (null != table[bucketIndex])) {
              resize(2 * table.length);//當size超過臨界閾值threshold,而且即將發生哈希衝突時進行擴容,新容量爲舊容量的2倍
              hash = (null != key) ? hash(key) : 0;
              bucketIndex = indexFor(hash, table.length);//擴容後從新計算插入的位置下標
          }
    
          //把元素放入HashMap的桶的對應位置
          createEntry(hash, key, value, bucketIndex);
      }
    //建立元素  
      void createEntry(int hash, K key, V value, int bucketIndex) {  
          Entry<K,V> e = table[bucketIndex];  //獲取待插入位置元素
          table[bucketIndex] = new Entry<>(hash, key, value, e);//這裏執行連接操做,使得新插入的元素指向原有元素。
    //這保證了新插入的元素老是在鏈表的頭  
          size++;//元素個數+1  
      }

    3. 實驗過程當中遇到的問題和解決過程

  • 問題1:在作第二個實驗的時候,遞歸完成後打印的二叉樹個預期的不同。
  • 問題1解決方案:我先是檢查了一遍循環,利用循環在中序序列中找到根節點,我debug了一遍,沒有在循環這裏發現錯誤。那就是遞歸存在問題,我從新捋了一遍本身的思路,根據傳入的序列的數組的索引值進行分割,在中序序列中但凡比根元素索引值小的即爲根元素的左結點,大的即爲根元素的右結點。按照中序序列分紅的兩部分元素又能夠把前序序列分紅兩部分,接下來用遞歸,把新獲得的序列數組做爲參數,直到造成二叉樹的結構。我又仔細檢查了一下本身的代碼,發現了問題所在:由於前序序列的第一個是根元素,因此在進行遞歸傳入參數時startpos變量須要加1,加上後,問題就解決了。

  • 問題2:在作第四個實驗的時候,始終不能正確的把中綴表達式轉換爲後綴表達式。
  • 問題2解決方案:我先對循環體進行debug,因爲代碼中的判斷條件和循環條件過多,因此的debug的效果並不理想。我仔細檢查了一下生成的後綴表達式,發現排列的順序是相反的,這就說明從numList彈出的時候順序是不對的。我又仔細想了一下,先進入numList的優先級是最高的,構造二叉樹的時候優先級高的成爲左子樹。因此numList是從前日後遍歷的,這樣一來,我就找到了個人問題所在。我誤在numList彈出元素的時候把索引值設置成了size()-1,改爲0問題就得以解決了。

其餘

  此次實驗的難度分佈的比較不均勻,大致上來講就是簡單的很簡單,難的很是難,在一些小地方也浪費了不少時間,致使沒有在預想的時間內完成實驗,但願本身能在之後的學習生活中繼續努力,不斷進步!node

參考資料

相關文章
相關標籤/搜索