1. 程式人生 > >20172313 2018-2019-1 《程序設計與數據結構》第七周學習總結

20172313 2018-2019-1 《程序設計與數據結構》第七周學習總結

res png href 1970年1月1日 思考 相對 學習總結 再次 imp

20172313 2018-2019-1 《程序設計與數據結構》第七周學習總結

教材學習內容總結

  • 概述
    • 二叉查找樹:二叉查找樹是一種含有附加屬性的二叉樹,即其左孩子小於父結點,而父結點又小於或等於右孩子。二叉查找樹的定義是二叉樹定義的擴展
    • 二叉查找樹的各種操作
操作 說明
addElement 往樹中添加一個元素 
removeElement 從樹中刪除一個元素 
removeAllOccurrences 從樹中刪除所指定元素的任何存在 
removeMin 刪除樹中的最小元素 
removeMax 刪除樹中的最大元素 
findMin 返回一個指向樹中最小元素的引用 
findMax 返回一個指向樹中最大元素的引用 
  • 用鏈表實現二叉查找樹:每個BinaryTreeNode對象要維護一個指向結點所存儲元素的引用,另外還要維護指向結點的每個孩子的引用。
  • addElement操作:我們只要利用二叉查找樹的特性(即對每個父結點,它的左子樹中所有項的值小於父結點中的值,而它的右子樹中所有項的值都大於T中的值),找到只對應的插入位置即可,如果樹為空,則這個新元素就將成為新結點,如果樹非空,沿著樹查找(根據element的大小來判斷向左還是向右)。假如我們現在要插入element為4的結點,如果找到element(4),則什麽也不做,否則將element插入到遍歷的路徑上的最後一個點,如下圖所示:
    技術分享圖片
  • removeElement操作:對於二叉查找樹來說,刪除元素的時候要考慮三種情況:
    • ①如果要刪除的結點q恰好是葉子結點,那麽它可以立即被刪除
    • ② 如果要刪除的結點q擁有一個孩子結點,則應該調整要被刪除的父結點(p.left 或 p.right)指向被刪除結點的孩子結點(q.left 或 q.right)
    • ③如果要刪除的結點q擁有兩個孩子結點,則刪除策略是用q的右子樹的最小的數據替代要被刪除結點的數據,並遞歸刪除用於替換的結點(此時該結點已為空),此時二叉查找樹的結構並不會被打亂,其特性仍舊生效。采用這樣策略的主要原因是右子樹的最小結點的數據替換要被刪除的結點後可以滿足維持二叉查找樹的結構和特性,又因為右子樹最小結點不可能有左孩子,刪除起來也相對簡單些。
      技術分享圖片
  • removeAllOccurrences操作:可以看做調用了removeElement,當在樹中找不到指定元素是,則拋出ElementNotFoundException異常,如果指定的元素不是Comparable,則removeAllOccurrences方法也會拋出ClassCaseException異常。只要樹中還含有目標元素,就會再次調用removeElement方法。
    public void removeAllOccurrences(T targetElement)
            throws ElementNotFoundException
    {
        removeElement(targetElement);

        try
        {
            while (contains((T)targetElement))
                removeElement(targetElement);
        }

        catch (Exception ElementNotFoundException)
        {
        }
    }
  • removeMin和removeMax操作:對於findMin(),則需要從根結點開始並且只要有左孩子就向左進行即可,其終止點即為最小值的元素;而對於findMax(),也需要從根結點開始並且只要有右孩子就向右進行即可,終止點即為值最大的元素。
    技術分享圖片
  • 用有序列表實現二叉查找樹:add操作和remove操作都可能導致樹變得不平衡。
操作 說明 LinkedList BinarySearchTreeList
removeFirst 刪除列表的首元素  O(1) O(logn)
removeLast 刪除列表的末元素  O(n) (logn)
remove 刪除列表中的一個特定元素 O(n) O(logn)*
first 考察列表前端的那個元素  O(1) O(logn)
last 考察列表末端的那個元素  O(n) O(logn)
contains 判斷列表是否含有一個特定元素  O(n) O(logn)
is Empty 判定列表是否為空  O(1) O(1)
size 判定列表中的元素數目  O(1) O(1)
add(有序列表特有) 向列表添加一個元素  O(n) O(logn)*
  • 平衡二叉查找樹:如果沒有平衡假設,最壞情況下addElement操作的實踐復雜性是O(n)而不是O(logn),因為可能存在樹根是樹中的最小元素,而將被插入的元素可能是樹中的最大元素,這種情況下,它的效率比鏈表的還低,因為每個結點還附帶額外的開銷。
    技術分享圖片
  • AVL樹的旋轉:當一棵樹的最大路徑長度大於log2^n,或最小路徑長度小於log2^n-1時,就要平衡化該樹。對於AVL樹,樹中的每一個結點,我們都會跟蹤其左右子樹的高度。對於樹中的任何結點,如果其平衡因子(左右子樹的高度差)大於1或小於-1,則以該節點為樹根的子樹需要重新平衡。
    技術分享圖片
  • 上左圖是一棵平衡二叉樹,它每個結點的左子樹和右子樹的高度最多相差1,它同時也是一棵二叉查找樹,而上右圖雖然也是一棵二叉查找樹,但是它每個結點的左子樹和右子樹的高度相差為2,所以它不是平衡二叉樹。當引起結點數量變化時,即進行刪除和插入操作,如下圖,插入一個新的結點,原本的平衡二叉樹就失去了平衡。
    技術分享圖片
  • 既然二叉樹失去了平衡,我們就要使用適當的操作來使它恢復平衡。如果某結點的平衡因子為-2,左孩子的平衡因子是-1,這就意味著該結點的左子樹中有一條過長的路徑,所以應該采用右旋。在原始AVL樹插入7結點後,結點9變為失衡點,樹再滿足AVL性質,因此需要對9結點進行左左單旋轉(即向右旋轉)後,得到下右圖,我們發現此時並沒有操作樹的根結點(6),實際上這是因為正常情況下,不必從樹的根結點進行旋轉,而是從插入結點處開始,向上遍歷樹,並更新和修復在這個路徑上的每個結點的平衡及其平衡信息(高度)即可。(左旋類似,當某結點的平衡因子是+2,右孩子的平衡因子是+1的時候使用左旋,左旋中的“左”,意味著“被旋轉的結點將變成一個左結點”。右旋同理)
    技術分享圖片
  • 當某結點的平衡因子是+2,如果其右孩子的平衡因子是-1,這時子樹太“深”了,無論是左旋還是右旋,都無法使操作後的數成為AVL樹,這個時候就需要使用雙旋,首先讓出初始結點右孩子的左孩子,繞著初始結點的右孩子進行一次右旋,然後再讓初始結點的右孩子,繞著初始結點進行一次左旋。(左右旋類似,當某結點的平衡因子是-2,左孩子的平衡因子是+1的時候使用左右旋)
    技術分享圖片
  • 紅黑樹
    • 樹中的每一個結點都儲存著一種顏色(紅色或黑色,通常使用一個布爾值來實現,值false等價於紅色)。
    • 根結點為黑色。
    • 每個葉子結點(null)是黑色。(**註意:這裏的葉子結點,是指為空(null)的葉子結點!)
    • 從樹根到樹葉的每條路徑都包含有同樣數目的黑色結點。
    • 如果一個結點的顏色為紅色,那麽它的子結點必定是黑色。
    • 在紅黑樹中,元素的查找仍然是一種O(n)操作,由於紅色結點不能有紅色孩子,於是路徑中至多有一半結點時紅色結點、至少有一半結點是黑色結點,據此我們可以論證紅黑樹的最大高度約為2*logn,於是遍歷最長路徑的序仍然是logn。
      技術分享圖片
  • 紅黑樹的添加操作:紅黑樹本身就是一棵二叉查找樹,所以當添加元素或刪除元素後,我們仍然需要使所得到的是一棵二叉查找樹,這就使得我們要對紅黑樹進行重新著色。我們先來回頭看上面所說的紅黑樹的性質,如果要保證從樹根到樹葉的每條路徑都包含有同樣數目的黑色結點,那麽我們把插入的元素設置為紅色,就可以保持,接下來我們只需從該結點向上遍歷,保證紅色結點的子結點必定為紅色即可滿足紅黑樹的所有要求。我們把要插入的結點設置為紅色,那麽根據父結點的顏色又可以分為三種情況。
    • ①被插入的結點是根結點。(我們可以直接將該結點塗成黑色)
    • ②被插入的結點的父結點是黑色。 (我們無需進行操作,插入之後仍為紅黑樹)
    • ③被插入的結點的父結點是紅色。(我們對該種情況要進行著重討論,因為被插入的結點的父結點是紅色,所以該結點的祖父結點必定存在(即父結點的父結點),父結點的兄弟結點也必定存在。(即“叔叔”結點,即使叔叔結點為空,我們也視之為存在,空結點本身就是黑色結點)我們根據叔叔結點的顏色又可以分成三種情況)
情況 處理方式
當前結點的父結點是紅色,且當前結點的祖父結點的另一個子結點(叔叔結點)也是紅色。 ①將“父結點”設為黑色。
② 將“叔叔結點”設為黑色。
③ 將“祖父結點”設為“紅色”。
④ 將“祖父結點”設為“當前結點”(紅色結點);指針current由指向插入的結點變為“當前結點“”,之後繼續對“當前結點”向上進行操作。
當前結點的父結點是紅色,叔叔結點是黑色,且當前結點是其父結點的右孩子 ①將“父結點”作為“新的當前結點”。
②以“新的當前結點”為支點進行左旋。
當前結點的父結點是紅色,叔叔結點是黑色,且當前結點是其父結點的左孩子 ① 將“父節點”設為“黑色”。
②將“祖父節點”設為“紅色”。
③以“祖父結點”為支點進行右旋。
  • 如上圖介紹了如何進行操作,下面我們再來談談為什麽要這麽操作。
    • 第一種情況,由於紅色結點的子結點不能是紅色,所以我們把父結點要變為黑色,但當我們把父結點變為黑色以後,從樹根到樹葉之間的黑色結點的個數就不相等了,所以把祖父結點變為紅色,同樣的,因為紅色結點的子孩子不能是紅色,所以要把叔叔結點變為黑色。祖父結點一定是黑色嗎?答案是肯定的,因為在元素添加之前,該二叉樹就是紅黑樹,父結點是紅色的,那麽祖父結點一定是黑色的。按照上述步驟處理之後,當前結點,父結點,叔叔結點都滿足了紅黑樹的性質。若此時,祖父結點是根結點,直接將祖父結點設為“黑色”,那就完全解決這個問題了;若祖父結點不是根結點,那我們需要將“祖父結點”設為“新的當前結點”,接著對“新的當前結點”進行分析。
    • 第二種情況,我們在上面表中說到,要以父結點為支點進行左旋,那麽為什麽要進行左旋呢?處理紅黑樹的核心思想:將紅色的節點移到根節點;然後,將根節點設為黑色。新插入的結點為紅色,破壞了整棵紅黑樹,所以我們要通過左旋來將它上移。上移之後,如果該結點變成了根結點,那麽直接把它塗成黑色,若該結點不是 根結點,那麽我們需要對父結點進行操作(在下圖中也就是40) ,為什麽不直接對60的當前結點進行操作,而是轉而處理原來的父結點40呢?因為通過左旋之後,原來的父結點(40)變成了子結點(60)的子結點,而處理紅黑樹的時候需要從下往上處理,所以要先對40的結點操作。
      技術分享圖片
    • 第三種情況,當按照上圖第二種情況左旋後,就變成了下面這種情況。由於(40)和(60)兩個結點都是紅色,所以我們可以先把(60)結點變為黑色,但變為黑色的話,由根結點經過(60)結點的路徑黑色結點數就會增加,所以我們可以讓(60)的父結點(即(80))變為紅色,並以該父結點作為支點進行右旋。
      技術分享圖片
  • 紅黑樹的刪除操作:紅黑樹的刪除和常規二叉樹刪除元素的操作一樣,也分為三種情況。
    • ①被刪除的結點沒有子結點(即葉子節點),直接將該結點刪除。
    • ②被刪除的結點有一個子結點,將該結點刪除,並讓父結點指向該結點的子結點。(可以看前文的示意圖)
    • ③被刪除的結點有兩個子結點,用該結點的右子樹的最小的數據替代要被刪除結點的數據,並遞歸刪除用於替換的結點。(前文有,在此不再過多贅述)
  • 在刪除結點的時候,我們先來想一下所刪結點的位置,如果刪除的是根結點,那麽根結點就可能不為黑色,如果它有子結點,刪除後可能會導致紅色結點的子結點有紅色結點還有可能會導致從根到各個路徑的黑色結點的數量不同。我們就來解決上面上說的這些問題。

教材學習中的問題和解決過程

  • 問題一:在學習教材的時候,p225的代碼中有這樣一行判斷條件“(!(element instanceof Comparable))”,不是很理解這行代碼的意思。
  • 問題一解決方法:原來是學習過的,時間久了有些忘記。java 中的instanceof 運算符是用來在運行時指出對象是否是特定類的一個實例。instanceof通過返回一個布爾值來指出,這個對象是否是這個特定類或者是它的子類的一個實例。用法:result = object instanceof class參數:Result:布爾類型。Object:必選項。任意對象表達式。Class:必選項。任意已定義的對象類。說明:如果 object 是 class 的一個實例,則 instanceof 運算符返回 true。如果 object 不是指定類的一個實例,或者 object 是 null,則返回 false。 舉例:
interface A{}
    class B implements A{

    }
    class C extends B {

    }

    class instanceoftest {
        public static void main(String[] args){
            A a=null;
            B b=null;
            boolean res;

            System.out.println("instanceoftest test case 1: ------------------");
            res = a instanceof A;
            System.out.println("a instanceof A: " + res);

            res = b instanceof B;
            System.out.println("b instanceof B: " + res);

            System.out.println("/ninstanceoftest test case 2: ------------------");
            a=new B();
            b=new B();

            res = a instanceof A;
            System.out.println("a instanceof A: " + res);

            res = a instanceof B;
            System.out.println("a instanceof B: " + res);

            res = b instanceof A;
            System.out.println("b instanceof A: " + res);

            res = b instanceof B;
            System.out.println("b instanceof B: " + res);

            System.out.println("/ninstanceoftest test case 3: ------------------");
            B b2=(C)new C();

            res = b2 instanceof A;
            System.out.println("b2 instanceof A: " + res);

            res = b2 instanceof B;
            System.out.println("b2 instanceof B: " + res);

            res = b2 instanceof C;
            System.out.println("b2 instanceof C: " + res);
        }
    }

技術分享圖片

  • 問題二:在學習紅黑樹的時候,書上提到終止叠代的條件也可以是“current.parent.color == black”,子樹的結點不也可以是黑色的嗎?為什麽判斷當前結點的父結點的顏色就可以終止叠代了呢?

代碼調試中的問題和解決過程

  • 問題1:在做編程項目pp9_3的時候,不知道怎麽計算程序的執行時間。
  • 問題一解決方案:
一般輸出日期時間經常會用到Date這個類:

 SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//設置日期格式
 System.out.println(df.format(new Date()));// new Date()為獲取當前系統時間
(1)以毫秒為單位計算
  static long currentTimeMillis() , 該方法返回值是從1970年1月1日淩晨到此時刻的毫秒數
  
 long startTime=System.currentTimeMillis();   //獲取開始時間
 doSomeThing();  //測試的代碼段
 long endTime=System.currentTimeMillis(); //獲取結束時間
 System.out.println("程序運行時間: "+(end-start)+"ms");
(2)以納秒為單位計算
 long startTime=System.nanoTime();   //獲取開始時間
 doSomeThing();  //測試的代碼段
 long endTime=System.nanoTime(); //獲取結束時間
 System.out.println("程序運行時間: "+(end-start)+"ns");

代碼托管

(statistics.sh腳本的運行結果截圖)

上周考試錯題總結

??這周沒有錯題哦~

結對及互評

  • 參考示例

點評過的同學博客和代碼

  • 本周結對學習情況
    • 結對同學學號1
    • 結對照片
    • 結對學習內容
      • XXXX
      • XXXX
      • ...
  • 上周博客互評情況
    • 學號1
    • 學號2
    • 學號3
    • 學號4
    • ...

其他(感悟、思考等,可選)

xxx
xxx

學習進度條

代碼行數(新增/累積) 博客量(新增/累積) 學習時間(新增/累積) 重要成長
目標 5000行 30篇 400小時
第一周 200/200 2/2 20/20
第二周 300/500 2/4 18/38
第三周 500/1000 3/7 22/60
第四周 300/1300 2/9 30/90
  • 計劃學習時間:XX小時

  • 實際學習時間:XX小時

  • 改進情況:

(有空多看看現代軟件工程 課件
軟件工程師能力自我評價表)

參考資料

  • java數據結構之樹

20172313 2018-2019-1 《程序設計與數據結構》第七周學習總結