1. 程式人生 > >20172314 2018-2019-1《程式設計與資料結構》第五週學習總結

20172314 2018-2019-1《程式設計與資料結構》第五週學習總結

教材學習內容總結

查詢

  • 查詢:在某個專案組(查詢池)中尋找某一指定目標元素,或者確定該指定目標並不存在。
  • 目標是高效的完成查詢,使得為了尋找目標所做的比較操作次數最小化,且查詢池中專案的數目就定義了問題的大小。
  • 在查詢物件的過程中需要進行物件之間的比較,我們用Searching類實現Comparable介面,但使用查詢排序方法時必須例項化Searching類,為了解決這個問題需要把所有方法宣告為靜態或泛型的。靜態方法(類方法)通過使用static修飾符宣告,呼叫靜態方法不用例項化該類的一個物件。靜態方法不是作用於具體的物件中,因此他們不能引用例項變數,但可以引用靜態變數,因為靜態變數的存在於具體的物件無關。main方法必須用static修飾符修飾,只能方法靜態或區域性變數。
  • 建立一個泛型方法:在方法頭的返回型別前插入一個泛型宣告即可:

    public static <T extends Comparable< T>> boolean linearSearch (T[] data, int min, int max, T target)

    這樣含有返回型別和引數型別的方法就可以使用泛型引數了。需注意,泛型宣告需位於返回型別之前,這樣泛型才可作為返回型別的一部分。建立泛型方法之後就不用在每次使用Searching類的方法之後都去例項化該類,只需要用類名和要用來替換泛型的具體資料型別,就可以呼叫靜態方法。

  • 兩種常見的查詢方式
    • 線性查詢法
      • 從列表頭開始依次比較每一個值,直到找到目標元素,最終找到目標元素或者到達列表尾得出不存在目標元素。
    • 二分查詢法
      • 使用二分查詢法的前提是查詢池中的專案組是已經排好序的。
      • 二分查詢法是從排序列表的中間開始查詢,如果沒有找到,根據中間數值與目標數值的大小比較,從前一半或後一半接著查詢,每次比較操作之後,將消減一半查詢池,剩下的一半查詢池將表示可行候選項。
    • 查詢演算法中的比較
      • 二分查詢的優勢:

        線性查詢的複雜度為O(n),二分查詢的複雜度是對數級的,為O(log2n),這使得他對於大型查詢池非常有效率。
      • 線性查詢的優勢:

        線性查詢比二分查詢簡單,程式設計除錯容易,並且線性查詢無需花費額外成本來排序該查詢列表。
      • 對於二者的選擇需要在將查詢池保持為排序狀態和提高查詢效率的努力之間權衡。

排序

  • 排序:基於某一標準,基於某個規定順序將某一組專案排序。
  • 基於效率排序演算法
    • 順序排序:使用一對巢狀迴圈對n個元素排序,大約需要n^2次比較。
    • 對數排序:對n個元素進行排序通常需要大約nlog2n次比較。
    • n較小時,兩種演算法之間幾乎不存在實際差別。
  • 選擇排序:
    • 演算法通過反覆地將某一特定值放到它在列表中的最終已排序位置,從而完成對某一列值的排序。
    • 一般為:掃描整個列表找出最小值,將這個值與該列表第一個位置處的值交換。然後掃描剩下的找出最小的與第二個位置的數交換,以此類推,這一過程結束時就排好序了。
  • 插入排序法
    • 演算法通過反覆將某一特定值插入到該列表某個已排序的子集中來完成對列表值的排序。
    • 對頭兩個值依據其相對大小進行排序,第三個值插入到前兩個之間的恰當位置,以此類推,該插入過程需要對陣列中的其他元素移位。
  • 氣泡排序法
    • 使用了兩個巢狀迴圈的順序排序演算法。他通過反覆重複比較列表中的相鄰元素,並在他們彼此不符順序時將他們互換來完成對值的排序。
    • 掃描列表比較臨近的元素,如果他們不是按相對順序排序則互換,把最大值,冒泡到列表最後一個值,然後再次掃描列表,冒泡出倒數第二個值。直至所有元素都被冒泡到正確位置。
  • 快速排序法
    • 通過使用一個任意選定的分割槽元素將該列表分割槽,然後對分割槽元素的任一邊的子列表進行遞迴排序。
    • 一般策略:先選擇一個列表元素作為分割槽元素,分隔列表,使得小於分割槽元素的所有元素位於該元素的左邊,所有大於該分割槽元素的位於右邊。然後對這兩個分割槽進行遞迴式排序。
  • 歸併排序法
    • 是一種遞迴演算法,將列表遞迴式分成兩半,直至每一子列表中都含有一個元素,然後將這些子列表按順序重組。


  • 基數排序法
    • 基於佇列處理的。對三位數的排序,首先要對個位、十位、百位數字依次進行排序,
    • 關鍵字中的每個數字/字元的每種可能值,都會建立一個單獨的佇列,佇列的數目稱為基數。例如對12 34 32 進行排序,這些兩位數的每個數字位都取1~4之間的數,就意味著需要4個佇列,基數為4。

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

  • 問題一:在看課本的時候,對於基數排序法不怎麼理解,沒能理解前兩次遍歷的意義,我覺得直接進行第三次遍歷就行了。而且為什麼要先排個位數最後是百位數,這樣做的原因是什麼。
  • 問題一解決:結合圖示

    發現由於數字被儲存在佇列中,所以進隊列出佇列的順序是很重要的,先進先出,對於圖中的例子來說,第一次遍歷對個位數進行排序,出佇列時按照數字0~5分別從自己的佇列中出來,對於十位的第四個數的那個佇列來說,第一次遍歷的價值就體現出來,145 143 442 341按順序從右到左先進入佇列,到第三次遍歷時,對於數字1的佇列,143比145先出來進入到新佇列,由於在第一次遍歷時已經對個位進行了排序,143比145小,在百位上已經完成了排序。所以說,先比較個位然後十位百位,可以巧妙的按從小到大的順序排序。

  • 問題二:在書上基數排序法中看到程式碼
    if(temp.length()>position){ digit = Character.digit(temp.charAt(temp.length()-1 - position), 10);//十進位制 }else{ digit = Character.digit('0', 10);//十進位制 }

    不知道方法digit = Character.digit('0', 10);是什麼意思。同時也不是很理解基數排序程式碼的含義。

  • 問題二解決:通過查詢資料發現java.lang.Character.digit()方法

    意思是輸出ch的indax進製表示。
    所以說課本程式碼的意思是輸出temp.charAt(temp.length()-1 - position)的十進位制表示。對程式碼的理解為:

       for (int scan = 0; scan < list.length; scan++) {
               //scan查詢每個數字
               temp = String.valueOf(list[scan]);// /轉化為字串
               if(temp.length()>position){
                   //例如三位數在對百位排序,就把對應位次的字元換為十進位制的數字
                   digit = Character.digit(temp.charAt(temp.length()-1 - position), 10);//十進位制
               }else{
                   //例如對於兩位數在對百位排序,就在高位新增0,對數字不影響
                   digit = Character.digit('0', 10);//十進位制
               }
               //對本次遍歷的數排序。例如第一次遍歷對個位數進行排序,則按照個位數排序的結果存入佇列中
               digitQueues[digit].add(new Integer(list[scan]));// 佇列
           }

程式碼除錯中的問題和解決過程

  • 問題一:執行出現錯誤

  • 問題一解決: for迴圈的條件必須滿足在間隔固定數後的也比position小,所以就會產生出界,需要新增條件scan + i <= position; scan++),但由於i是大於1的,所以滿足scan + i <= position,一定有scan <= position,所以改為for (scan = 0; scan + i <= position; scan++)
  • 問題二:如何計算程式碼執行時間?
  • 問題二解決:從網上找到了兩種辦法
    • 第一種:

      long startTime = System.currentTimeMillis(); //獲取開始時間

      doSomething(); //測試的程式碼段

      long endTime = System.currentTimeMillis(); //獲取結束時間

      System.out.println("程式執行時間:" + (endTime - startTime) + "ms"); //輸出程式執行時間
    • 第二種(以納秒為單位):

      long startTime=System.nanoTime(); //獲取開始時間

      doSomeThing(); //測試的程式碼段

      long endTime=System.nanoTime(); //獲取結束時間

      System.out.println("程式執行時間: "+(endTime-startTime)+"ns");

  • 問題三:出現多次輸出

    一開始只是在每個方法的開始和結尾計時,輸出結束時間-開始時間的差值,執行結果卻發現對於5種排序方法竟然有17個輸出。並且有些比較次數為0.
  • 問題三解決:

    對於插入排序法來說,由於count2在迴圈內部,輸出的count2直接是宣告變數的值0,而輸出比預計多猜測是因為迭代時造成累計輸出,然後添加了每個方法的輸出語句,證明猜測是正確的

    然後發現插入排序的次數一直是0

    後來發現需要在while迴圈外面再加一次計數。

程式碼託管

上週考試錯題總結

  • 錯題一:

  • 錯題一解析:無序列表的元素按照特殊順序放置,這種順序與元素本身無關,列表的使用者決定元素的順序。
  • 錯題二:

  • 錯題二解析:Java Collections API包含三個索引列表的實現,add(int index,E element),remove(int index),set(int index,E element)

結對及互評

其他

這次的作業小組配合很多,在做PP9.3時,我們三個人的比較次數都不完全一樣,最後在群裡一個方法一個方法測試,討論正確的比較計數。感覺小組合作的作用是非常大的!

學習進度條

程式碼行數(新增/累積) 部落格量(新增/累積) 學習時間(新增/累積)
目標 5000行 30篇 400小時
第一週 0/0 1/1 8/8
第二週 1163/1163 1/2 15/23
第三週 774/1937 1/3 12/50
第四周 3596/5569 2/5 12/62
第五週 3329/8898 2/7 12/74

參考: