1. 程式人生 > >20172310 2017-2018《程式設計與資料結構》(下)第五週學習總結

20172310 2017-2018《程式設計與資料結構》(下)第五週學習總結

20172310 2017-2018《程式設計與資料結構》(下)第五週學習總結

教材學習內容總結

第九章_排序與查詢

學習幾種排序演算法,並討論這些演算法的複雜度

9.1查詢(線性查詢與二分查詢演算法)

  • 查詢(searching) 是在某個專案組中尋找某一指定目標元素, 或者確定該組中並不存在該目標元素的一個過程。對其進行查詢的專案組有時
    也稱為查詢池( search pool)。

  • 我們的目標就是儘可能高效地完成查詢。從演算法分析的角度而言,高效的查詢會使該過程所做的比較操作次數最小化。同時該查詢池中專案的數目定義了該問題的大小。

  • 查詢某一物件,必須能夠將其跟另一個物件進行比較。我們對這些演算法的實現就是對某個Comparable物件的陣列進行查詢。
    如:public class Searching<T extends Comparable<T>>

    這個泛型宣告的實際結果就是以用任何實現Comparable介面的類來例項化Searching類。

以Comparable介面的方式定義的Searching要求我們在使用查詢或排序方法時必須例項化於是引出了泛型靜態方法。


  • 靜態方法(static method),又稱為類方法(class mehod),可以通過類名來激話。通過使用static 修飾符就可以把它宣告為靜態的。
    例如,Math類的所有方法都是靜志的可以如下通過Math類來呼叫sqrt方法:

System. out.printinl("squace root of 27: ”+ Math.sqrt(27));

  • 靜態方法不是作用於具體的物件中,因此不能引用例項變數(例項變數只存在類的一個例項中)。如果某個靜態方法試圖使用一個非靜態的變數,編譯器將發出一個錯誤。但是,靜態方法可以引用靜態變數,因為靜態變數的存在與具體的物件無關。

  • 泛型方法:與建立泛型類相似,我們也可以建立泛型方法。即,不是建立一個引用泛 型引數的類,而是建立一個引用泛型的方法。泛型引數只應用於該方法。

要建立個泛型方法, 只需在方法頭的返回型別前插入一個泛型宣告即可:

public static<T extends Comparable< T> > boolean

lineatsearch (T[] data, int min, int max, T target)

泛型宣告必須位於返回型別之前,這樣泛型才可作為返回型別的部分。

  • 不一定非要指定用於替代泛型的資料型別。編譯器可以從所提供的引數中推匯出該資料型別。

  • 線性查詢法
    如果該查詢池組織成一個某型別的列表,那麼完成該查詢的一個簡單方式就是從該列表頭開始依次比較每一個值, 直至找到該目標元素。最後,我們要麼找到該目標,要麼到達列表尾並得出該組中不存在該目標的結論。這種方式之所以稱為線性查詢(linear search),是因為它是從一端開始並以線性方式搜尋該查詢池的

線性查詢法程式碼:

public static<T extends Comparable<?super T> >
 boolean lineatsearch (T[] data, int min, int max, T target)
int index = min;
boolean found = false;
while (!found && index <= max)
if (data [index]. compareToltarget) == 0
found=true;
index++;
return found;

該while迴圈將遍歷陣列元素,在找到元素或到達陣列尾時其會終止
- 線性查詢演算法相當容易理解,儘管它不是特別高效的,但線性查詢並不要求查詢池中的元素在陣列中具有任何特定順序。


  • 二分查詢法

    • 二分查詢演算法提升了查詢過程的效率,但是它只有在查詢池已持序時才起作用。二分查詢是從排序列表的中間開始查詢,而不是從一 端或另一端開始。 二分查詢都會將用於查詢的剩餘資料去掉大約一半(它同樣會去掉中間元素)。 通過一個精心選擇的比較操作,我們將消減半的查詢池。 剩下的一半查詢池將表示可行候選項(viable candidates),目格元素就有待於在它們中間數找到。

public static <T extends Comparable<T>>  
        boolean binarySearch(T[] data, int min, int max, T target)
    {  
        boolean found = false;
        int midpoint = (min + max) / 2;  // determine the midpoint(定義了用於查詢(可行候選項)的陣列部分)

        if (data[midpoint].compareTo(target) == 0)
            found = true;

        else if (data[midpoint].compareTo(target) > 0)
        {
            if (min <= midpoint - 1)
                found = binarySearch(data, min, midpoint - 1, target);
        }
        
        else if (midpoint + 1 <= max)
            found = binarySearch(data, midpoint + 1, max, target);

        return found;
    }
  }

binarySearch方法是遞迴實現的。如果沒有找到目標元素,且有更多待查詢資料,則該方法將呼叫其自身,同時傳遞引數,這些引數縮減了陣列內可行候選項的規模。min和max索引用於確定是否還具有更多待查詢資料。這就是說,如果削減後的查詢區域一個元素也不含有,則該方法將不會呼叫其自身且會返回一個false 值。

- 在該過程中的任何時刻, 我們可能會有偶數個待查詢值,因此就有兩個“中間”值,根據該演算法,所採用的中點可以是這兩個中間值的任何一個。在該分 查詢實現中,確定中點索引的計算丟棄了任何分數部分,因此它選擇的是兩個中間值中的第一個。

  • 查詢演算法的比較

    • 如果查詢池中有n個元素,平均而言,在我們找到所查詢的那個元素之前我們將不得不考察n/2個元素。因此,線性直找演算法具有線性時間複雜度O(n),因為是依次每回查詢一個元素, 所以複雜度是線性的一直接與待查詢元素數目成比例。

    • 找到位於該查詢地中某元素的預期情形是大約(log2n)/2次比較。因此,二分查詢具有一個對數演算法且具有時間複雜度O(log2n)。與線性查詢相比,n值較大時,二分查詢要快得多。

    • 兩種查詢演算法的優勢
      1.線性查詢般比查詢要簡單,線性查詢無需花費額外成本來排序該查詢列表。
      2.二分查詢的複雜度是對教級的,這使得它對於大型查詢池非常有效率。對於小型問題,這兩種型別的演算法之間幾乎不存在實用差別。但是,隨著n的變大,二分查詢就會變得吸引人。



9.2排序

  • 排序是基於某一標準,將某一組專案按照某個規定順序排列的一個過程。

  • 基於效率排序演算法通常也分為兩類:順序排序,它通常使用一對巢狀迴圈對n個元素排序,需要大約n^2次比較;以及對數排序, 它對n個元素進行排序通常需要大約nlog2 n次比較。與查詢演算法中一樣,在n較小時,這兩類演算法之間幾乎不存在實際差別。

  • 本章學習三種順序排序選擇排序、 插入排序以及氣泡排序,以及兩種對數排序快速排序和歸併排序。


  • 選擇排序法

    • 選擇排序(selctionsomnd)演算法通過反覆地將某特定 值放到它在列表中的最終已排序位置,從面完成對某列值的排序。換句話說,對於列表中的每一位置,該演算法都將選擇由應該放進這位置的值並將其放在那裡。

    -選擇持排字演算法的一般策略:打描整個列表以找出最小值。將這個值與該列表第一個位置處的值交換。掃描(除了第一個值的)剩餘部分列表並找出最小值,然後將它和該列表第二個位置處的值交換,掃描(除了前兩個值的)剩餘部分列表並找出最小值,然後將它和該列表第三個位置處的值交換,直到所有的數字排完。

  • 插入排序法
    • 插入排序演算法通過反覆地將某一特定值插入到該列表某個已排序的子集中來完成對列表值的排序。

    • 插入排序演算法的一般策略:對列表中的頭兩個值依據其相對大小對其進行排序,如果有必要則將它們互換。將列表的第三個值插入到頭兩個(已排序的)值中的恰當位置。然後將第四個值插入到列表頭三個值中的正確位置。每做出一次插入, 該排序子集中的值數目就會增加一個。繼續這一過程, 直至列表中的所有元素都得到完全排序。該插入過程需要對陣列中的其他元素移位,以給插入元素騰出空間。

  • 氣泡排序法
    • 氣泡排序演算法通過重複地比較相鄰元素且在必要時將它們互換,從而完成對某個列表的排序。

    • 氣泡排序演算法的一般策略:掃描該列表且比較鄰接元素,如果它們不是按相對順序排列則將其互換。這就像把最大值“冒泡”到列表的最後位置,這是它在最終已排序列表中的恰當位置。然後再次掃描該列表,冒泡出倒數第二個值。繼續這一過程, 直至所有元素都被冒泡到它們正確的位置。


  • 快速排序法

    • 快速排序(quick sort)演算法是這樣對列表進行排序的:通過使用個任意選定的分割槽元素(partition clement)將該列表分割槽,然後對分割槽元素的任邊的子列表進行遞迴排序。
    • 快速排序演算法的般策略:首先, 選擇一個列表元素作為分割槽元素。下步,分割該列表,使得小於該分割槽無素的所有元素位於該元素的左邊,所有大於該分割槽元素的元素位於右邊。最後,將該快速接序策略(遞迴式)應用於兩個分割槽。

  • 歸併排序法
    • 歸併排序(merge sort)演算法是另一種遞迴排序演算法, 通過將列表遞迴式分成兩半直至每一子列表都只含有一個元素,然後將這些子列表按順序重組,這樣就完成了對列表的排序。

    • 歸併排序演算法的 一般策:首先將該列表分成兩個大約相等的部分,然後對每一部分列表遞迴呼叫其自身。繼續該列表的遞迴分解,直至達到該遞迴的基本情形,這時活列表被分割成長度為1的列表,根據定義,它是已排序的了。然後,隨著程式控制權傳園率該通歸呼叫結構,該演算法將兩個遞迴呼叫所產生的那兩個排序子列表歸併為個排序列表。


  • 基數排序法
    • 基數排序法可以無需進行元素之間的相互比較來排序,是種更高效的排序演算法。
    • 基數排序是基於佇列處理的。排序要基於某個特定的值,這個值稱為排序關鍵字(sort key)。對於排序關鍵字中每個數字/字元的每種可能取值,都會建立個單獨的佇列。佇列的數目(或可能取值的種數)就稱為基數(radix)。 例如,如果要排序十進位制數,則基數應該是10, 0~9的每個數字都對應著一個佇列。



  • 各種排序演算法的比較

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

  • 問題1:

但標準的程式碼應該是

  • 問題1解決方案:這兩種寫法我都嘗試了,沒有體現出區別,那第一種程式碼是什麼意思呢?<T extends Comparable<T>><T extends Comparable<? super T>> 有什麼不同呢?

    <T extends Comparable<T>>
    型別 T 必須實現 Comparable 介面,並且這個介面的型別是 T。只有這樣,T 的例項之間才能相互比較大小。例如,在實際呼叫時若使用的具體類是 Dog,那麼 Dog 必須 implements Comparable <

<T extends Comparable<? super T>>
型別 T 必須實現 Comparable 介面,並且這個介面的型別是 T 或 T 的任一父類。這樣聲明後,T 的例項之間,T 的例項和它的父類的例項之間,可以相互比較大小。例如,在實際呼叫時若使用的具體類是 Dog (假設 Dog 有一個父類 Animal),Dog 可以從 Animal 那裡繼承 Comparable ,或者自己 implements Comparable 。<

按我理解這樣宣告的好處就是,例如對 Animal/Dog 這兩個有父子關係的類來說: <T extends Comparable<? super T>>可以接受 List<Animal> ,也可以接收 List 。 而 <T extends Comparable > 只可以接收 List
所以,<T extends Comparable<? super T>> 這樣的型別引數對所傳入的引數限制更少,提高了 API 的靈活性。總的來說,在保證型別安全的前提下,要使用限制最少的型別引數。

可以參考一下這篇文章:
如何理解 Java 中的 <T extends Comparable<? super T>>

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

  • 問題1:在完成藍墨雲線性表實踐時,實現選擇排序法的時候,排出來的結果是

  • 問題1解決方案:當時的程式碼是這樣的

選擇排序的邏輯確實是我的程式碼所體現的那樣,但是為什麼會出錯呢?
於是我就進行了單步除錯,發現因為while(temp2.getNext()!=null)這行程式碼是隻有temp2之後還存在元素時才會進入迴圈體,這樣就有一個問題,如果temp2就是最後一個元素了,就不會在進行比較,於是發生了上面的錯誤。現在的關鍵就是要解決最後兩個數比較的位置在哪。
我先將

這段程式碼放在了第二個迴圈體的後面,結果陷入了無限死迴圈;我又把他放在了第一個迴圈的外面,這個if條件句不會成立。最後我修改為

才解決了問題。

  • 問題2:pp9.3中要求編寫程式碼來計算每次程式碼執行時間,時間該如何計算呢?

  • 問題2解決方案:
    方案一:我們要計算程式,函式的執行之間,通常是在程式碼執行前後加入時間戳,兩者的差值即為執行時間,
var count=1000;
var begin=new Date();
for(var i=0;i<count;i++){
document.createElement("div");
}
var end=new Date();
var time=end-begin;
console.log("time is="+time);

方案二:我們可以使用System類的currentTimeMillis()方法來返回當前的毫秒數,並儲存到一個變數中,在方法執行完畢後再次呼叫 System的currentTimeMillis()方法,並計算兩次呼叫之間的差值,就是方法執行所消耗的毫秒數。

long startTime = System.currentTimeMillis(); //獲取開始時間
doSomething(); //測試的程式碼段
long endTime = System.currentTimeMillis(); //獲取結束時間
System.out.println("程式執行時間:" + (endTime - startTime) + "ms"); //輸出程式執行時間

Java計算程式程式碼執行時間的方法小結

程式碼託管

(statistics.sh指令碼的執行結果截圖)

上週考試錯題總結

  • 錯題1及原因,理解情況

    java集合API包含一個索引表__的實現。因為沒有注意課本關鍵概念的總結,其實120面有答案,含有索引列表的三種實現。

結對及互評

點評:

  • 部落格中值得學習的或問題:
    • xxx
    • xxx
    • ...
  • 程式碼中值得學習的或問題:
    • xxx
    • xxx
    • ...
  • 本週結對學習情況
    • 結對同學學號1

    • 結對學習內容
      • 學習第九章幾種排序演算法和查詢演算法,並討論這些演算法的複雜度。
      • 探討完成藍墨雲班課上線性表實踐的作業,並相互幫助修改錯誤。

點評過的同學部落格和程式碼

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

每天都要花好幾個小時站在操場,還落了好多課,感覺少了好多時間學習,而且這周的任務量也不輕,自己還是要多抽出時間來學習。

學習進度條

程式碼行數(新增/累積) 部落格量(新增/累積) 學習時間(新增/累積)
目標 5000行 30篇 400小時
第一週 0/0 1/1 10/10
第二週 326/326 1/2 18/28
第三週 784/1110 1/3 25/53
第四周 2529/3638 2/5 37/90
第五週 1254/4892 2/7 20/110
  • 計劃學習時間:28小時

  • 實際學習時間:20小時

  • 最近因為學校的活動少了一些時間來學習。

參考資料