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

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

教材學習內容總結

終於結束了各類不一樣類型的數據結構的學習,本章的內容轉向了對於不一樣數據結構中存儲的數據的處理方面,主要學習了兩部份內容——查找和排序,其中查找介紹了兩種方法,排序除上學期學過的兩種排序方法,又學習了四種新的排序方法。html

1、靜態方法

  • 靜態方法:使用時不須要實例化該類的一個對象,能夠直接經過類名來激活的方法。能夠經過在方法聲明中使用static修飾符將方法聲明爲靜態的。
  • 泛型方法:在方法頭的返回類型中有泛型聲明的方法。
public class MathTest {
 
    public static void main(String[] args) {
        MathTest mathTest = new MathTest();
        System.out.println(mathTest.max(1, 2));
        System.out.println(mathTest.max(2.0, 3.0));
    }
 
    //泛型方法
    private <T extends Comparable> T max(T t1, T t2) {
        return t1.compareTo(t2) > 0 ? t1 : t2;
    }
    
    //靜態泛型方法
    private static <T extends Comparable> T max(T t1, T t2) {
        return t1.compareTo(t2) > 0 ? t1 : t2;
    }
}

2、查找

  • 定義:在某個項目組(查找池)中尋找某一指定元素目標或肯定某一指定元素目標不在項目組中。
  • 目標:儘量高效地完成查找——使過程當中所作的比較次數最小化。
    • 決定比較次數的因素:查找的方法,查找池中元素的數目。

1.線性查找

  • 概念:在一個元素類型相同的項目組中,從頭開始依次比較每個值直至結尾,若找到指定元素返回索引值,不然返回false。
  • 時間複雜度分析:最好的狀況下是列表的第一個元素就是所要找的指定元素,此時的時間複雜度爲O(1)。最壞的狀況下,所找元素並不在列表中,那麼就須要遍歷列表直至尾部終止,這時要進行n次比較,時間複雜度爲O(n)。因此,線性查找的平均時間複雜度爲O(n)。

2.二分查找

  • 概念:在一個已排序的項目組中,從列表的中間開始查找,若是中間元素不是要找的指定元素,則削減一半查找池,從剩餘一半的查找池(可行候選項)中繼續以與以前相同的方式進行查找,屢次循環直至找到目標元素或肯定目標元素不在查找池中。
  • 特色:二分查找的每次比較都會刪除一半的元素。
  • 時間複雜度分析:最好的狀況是所要查找的元素就位於查找池的中央,此時的時間複雜度爲O(1)。當所查找元素不在查找池中時,須要一直削減項目組的一半直至項目組的元素只剩下一個,這種狀況下要進行log2n次比較。因此,二分查找的平均時間複雜度爲O(log2n)。

3.兩種查找方法的比較

  • 在元素個數較小時,兩種方法的效率幾乎沒有區別,可是當元素個數很是多時,二分查找要優於線性查找。
  • 所找元素位置的不一樣也會影響查找方法的選擇,好比在下面這個例子中,當使用線性檢索和二分檢索求23的位置和檢索1的位置時,兩者的時間存在差異很大。
  • 使用線性檢索和二分檢索求23的位置
  • 使用線性檢索和二分檢索求1的位置

3、排序

  • 概念:基於某一標準,將某一組項目按照某個規定順序排序。
  • 分類:順序排序(大概須要n^2次比較)對數排序(大概須要nlog2n次比較)
    • 順序排序——選擇排序、插入排序、冒泡排序
    • 對數排序——快速排序、歸併排序
    • 基數排序

三種順序排序的時間複雜度均爲O(n^2),緣由是它們都要經過兩層循環來實現,且每層循環都要進行n次,下面主要進行兩層循環做用的分析。java

1.選擇排序

  • 原理:經過反覆將某一特定值放到它在列表中的最終已排序位置來實現排序。
  • 代碼分析:選擇排序有兩層循環,外側循環控制下一個最值在列表中的存儲位置,內側循環經過遍歷和比較來找出剩餘列表的最值。
public static <T extends Comparable<T>> void selectionSort(T[] data)
    {
        int min;
        T temp;
        //循環次數爲n,時間複雜度爲O(n)
        for (int index = 0; index < data.length-1; index++)
        {
            min = index;
            //循環次數爲n,時間複雜度爲O(n)
            for (int scan = index+1; scan < data.length; scan++) {
                if (data[scan].compareTo(data[min])<0) {
                    min = scan;
                }
            }
            swap(data, min, index);
        }
    }

2.插入排序

  • 原理:經過反覆將一特定值插入列表中某一已排序的本身中來實現排序。
  • 代碼分析:與選擇排序相似,插入排序也使用了兩層循環,外側控制的是下一個插入值在列表中的位置,內側循環是將當前插入值與已排序子集中的值進行比較,二者一樣都要循環n次。
public static <T extends Comparable<T>> void insertionSort(T[] data)
    {
        //循環次數爲n,時間複雜度爲O(n)
        for (int index = 1; index < data.length; index++)
        {
            T key = data[index];
            int position = index;
            
            //循環次數爲n,時間複雜度爲O(n)
            while (position > 0 && data[position-1].compareTo(key) > 0)
            {
                data[position] = data[position-1];
                position--;
            }
            
            data[position] = key;
        }
    }

3.冒泡排序

  • 原理:經過反覆比較相鄰元素的大小並在必要時進行互換,最終實現排序。
  • 代碼分析:冒泡排序的兩層循環中,外層循環負責不斷進行遍歷剩餘全部列表(共n-1次),內層循環在每次外層循環的過程當中掃描全部相鄰元素並依據規則對他們進行互換。
public static <T extends Comparable<T>> void bubbleSort(T[] data)
    {
        int position, scan;
        T temp;
        //循環次數爲n-1,時間複雜度爲O(n)
        for (position =  data.length - 1; position >= 0; position--)
        {
            //循環次數爲n,時間複雜度爲O(n)
            for (scan = 0; scan <= position - 1; scan++)
            {
                if (data[scan].compareTo(data[scan+1]) > 0) {
                    swap(data, scan, scan + 1);
                }
            }
        }
    }

快速排序和歸併排序的平均時間複雜度相同,都是O(nlogn)。git

4.快速排序

  • 原理:經過將列表進行分區,對兩個分區內的數據進行遞歸式排序。
    • 遞歸:用一句簡單易懂的話來說就是——本身調用本身
  • 代碼分析:快速排序自己的代碼實現很是簡單,就是一個遞歸的實現。但它其中最核心的方法是使用了一個分區方法partition
    • 該方法有兩層循環,外層循環負責進行每次分區的選擇,內層循環中有兩個while循環,用於將兩個分區中對應位置錯誤的元素找到並進行交換,直至左索引與右索引相等。
  • 時間複雜度分析:快速排序每次要將列表分紅兩個分區,所以平均要進行log2n次分區,而每次分區後要進行n次比較操做,所以平均時間複雜度爲O(nlogn)。快速排序比大部分排序算法都要快,但快速排序是一個很是不穩定的排序,由於若初始序列按關鍵碼有序或基本有序時,快速排序反而蛻化爲冒泡排序,此時它的時間複雜度就爲O(n^2)了。

5.歸併排序

  • 原理:經過將列表進行遞歸式分區直至最後每一個列表中都只剩餘一個元素後,將全部列表從新按順序重組完成排序。
  • 代碼分析:和快速排序相似,歸併排序自己也是一個遞歸的實現,可是也使用了一個merge方法來重組數組已排序的部分。
    • merge方法中共有四個循環,第一個while循環將兩個子列表中的最小元素分別加入到一個臨時的數組temp中,而後第二個和第三個while循環的用處是分別將子列表中剩餘的元素加入到temp中,最後一個for循環就是將合併後的數據再複製到原始的數組中去。
  • 時間複雜度分析:每次歸併時要將待排序列表中的全部元素遍歷一遍,因次時間複雜度爲O(n)。與快速排序相似,歸併排序也先將列表不斷分區直至每一個列表只剩餘一個元素,這個過程須要進行log2n次分區。所以歸併排序的平均時間複雜度爲O(nlogn)

6.五種排序方法對比

  • 經過對比能夠發現,快速排序比大部分排序算法都要快。儘管咱們能夠在某些特殊的狀況下寫出比快速排序快的算法,可是就一般狀況而言,沒有比它更快的了。可是快速排序的穩定性較差,若是須要一個穩定性較好且排序較快的排序算法的話,能夠選擇使用歸併排序,可是歸併排序一樣存在缺點,由於它須要一個額外的數組,所以對內存空間有必定的要求。

與以前介紹的五種排序方法不一樣,基數排序法是一種不須要進行元素之間相互比較的排序方法。算法

7.基數排序

  • 原理:基數排序是基於排序關鍵字結構來排序的,對於關鍵字結構中的每個數字或者字符,都會建立一個單獨的隊列,而隊列的數目就稱之爲基數。而基數排序法到底是怎麼利用這些隊列的,來看看下面的GIF圖吧:
  • 時間複雜度分析:在基數排序中,每個元素都是不斷從一個隊列中移到另外一個隊列中,每次都要進行一次遍歷,時間複雜度爲O(n),排序的基數有多大,遍歷就要進行多少次,但基數的大小隻影響n的係數,因此,基數排序的時間複雜度爲O(n)
  • 既然基數排序的時間複雜度這麼低,爲何不是全部的排序都使用基數排序法呢?
    • 首先,基數排序沒法創造出一個使用於全部對象類型的泛型基數排序,由於在排序過程當中要進行關鍵字取值的切分,所以關鍵字的類型必須是肯定的。
    • 其次,當基數排序中的基數大小與列表中的元素數目很是接近時,基數排序法的實際時間複雜度接近於O(n^2)。

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

  • 問題1:爲何要使用泛型方法?
  • 問題1解決方案:若是你定義了一個泛型,不管是類仍是接口,那麼Java規定,你不能在靜態方法包括其餘全部靜態內容中使用泛型的類型參數。所以,若是想要在靜態方法中使用泛型,就要使用泛型方法了。
public class A<T> {
    public static void B(T t) {
    //報錯,編譯不經過
    }
}
  • 那麼應該如何定義泛型方法呢?
  • 定義泛型方法就像定義泛型類或接口同樣,在定義類名(或者接口名)的時候須要指定做用域中誰是泛型參數。
    • 例:public class A<T> {...}
    • 代表在類A的做用域中,T是泛型類型參數。
  • 具體格式是修飾符 <類型參數列表> 返回類型 方法名(形參列表) {方法體}
    • 例:public static <T> int A(List<T> list) { ... }
  • 泛型方法的定義和普通方法定義不一樣的地方在於須要在修飾符和返回類型之間加一個泛型類型參數的聲明,代表在這個方法做用域中誰纔是泛型類型參數。
  • 泛型方法的類型參數能夠指定上限,類型上限必須在類型參數聲明的地方定義上限,不能在方法參數中定義上限。規定了上限就只能在規定範圍內指定類型實參,超出這個範圍就會直接編譯報錯。
//正確
<T extends X> void func(List<T> list){ ... }
//正確
<T extends X> void func(T t){ ... }
//編譯錯誤
<T> void func(List<T extends X> list){ ... }
  • 問題2:在某些書上的某些代碼中出現的是怎麼回事?
  • 問題2解決方法:?是一種類型通配符,能夠表明範圍內任意類型。可是「?」泛型對象是隻讀的,不可修改,由於「?」類型是不肯定的,能夠表明範圍內任意類型。而全部能用類型通配符?解決的問題都能用泛型方法解決,而且泛型方法能夠解決的更好。這裏有一篇博客對於二者的對比介紹的很是好。
  • 問題3:爲何快速排序法和歸併排序法都要先定義一個私有方法,而後再定義一個公用方法來調用私有方法?
  • 問題3解決方法:我的理解是由於快速排序法和歸併排序法沒法經過只接受數組對象就能夠進行排序,可是爲了和其餘幾種方法保持一致,因此就先定義了一個私有方法接受數組對象、最大值和最小值進行排序,而後使用公有方法接受數組對象,在方法內調用私有方法來實現排序。

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

  • 問題1:在完成PP9.2的時候,實現的間隔排序沒法正確將數據進行排序

    代碼:
  • 問題1解決方法:經過屢次測試以後發現,當輸入的數是奇數時排列是正確的,可是當輸入的數是偶數時就不行了。緣由是因爲我設置的每次的減小了爲2,當輸入的數是偶數時,就會少一次循環過程,所以我對代碼進行了修改,增長了對輸入的數進行判斷,若是輸入的是奇數,仍然按原來的方法來,當輸入的是偶數時,每次減小的數量爲3。
  • 問題2:完成PP9.3時,不論使用什麼排序方法輸出的執行時間總爲0
  • 問題2解決方法:一開始我使用的是currentTimeMillis方法,它是以毫秒計時的,而因爲排序的元素較少時間還到不了毫秒級,因此顯示的都是0,在改爲以微秒計時的nanoTime方法後就能夠顯示了。
  • 問題3:仍是在完成PP9.3的過程當中,歸併排序和快速排序輸出了屢次比較次數的記錄

    代碼:
  • 問題3解決方法:雖然我把比較次數的輸出寫在了merge方法的循環以外,可是在遞歸的過程當中它仍是會屢次輸出。解決方法是將time這個變量放到方法外面去,設置成public static int time = 0便可。

    結果:

代碼託管

上週考試錯題總結(正確爲綠色,錯誤爲紅色)

  • 錯題1:The Java Collections API contains _________ implementations of an indexed list.
    • A .Two
    • B .Three
    • C .Four
    • D .Five
  • 錯題1解決方法:我原本理解的是Java API中提供了幾種方法來實現列表,所以選擇兩種由於一種是ArrayList另外一種是LinkedList。後來發現是本身看錯題了沒有看到「索引」兩個字,原話在書上120頁。
  • 錯題2:The elements of an unordered list are kept in whatever order the client chooses.
    • A .True
    • B .False
  • 錯題2解決方法:當時作題的時候想的是無序列表的順序確實是由使用者來決定的啊,後來想一想錯誤可能出在」whatever"上了。

結對及互評

點評模板:

  • 博客中值得學習的或問題:
    • 優勢:本週的博客大有長進!內容豐富了不少,終於作到了圖文並茂,值得誇獎!
    • 問題:圖片的排版還需增強。
  • 代碼中值得學習的或問題:
    • 優勢:提了幾周的commit提交終於有所改進,感受這周個人搭檔有了質的飛躍。多是一遍遍的吐槽起了做用,果真像馬原老師說的同樣,量變會引發質變!
    • 問題:本週代碼的備註不是不少。

點評過的同窗博客和代碼

  • 本週結對學習狀況
    • 20172322
    • 結對學習內容
      • 給我講解了課堂實驗ASL計算的方法。
      • 主要探討了歸併排序的計數方法。

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

  • 由於跳啦啦操的緣故感受最近的課程老是要落你們一些,如今啦啦操跳完了要趕忙追上你們ヾ(◍°∇°◍)ノ゙

學習進度條

代碼行數(新增/累積) 博客量(新增/累積) 學習時間(新增/累積) 重要成長
目標 5000行 30篇 400小時
第一週 10/10 1/1 10/10
第二週 246/366 2/3 20/30
第三週 567/903 1/4 10/40
第四周 2346/3294 2/6 20/60
第五週 1212/4506 2/8 30/90
  • 計劃學習時間:20小時
  • 實際學習時間:30小時
  • 改進狀況:本週的大部分時間基本都花在了對於查找算法和排序算法的理解上了,感受對時間複雜度理解和計算的應用變得更加熟練了。

參考資料

相關文章
相關標籤/搜索