20172308 《程序設計與數據結構》第五週學習總結

教材學習內容總結

第 九 章 排序與查找

1、查找:在查找池中查找目標元素或肯定查找池中不存在該目標元素html

  • 常見查找方式:線性查找、二分查找
  • 高效的查找:查找過程作出的比較次數更少
  • 線性查找(時間複雜度O(n)):不要求數組中元素有任何特定順序;從第一個元素依次比較直至找到目標元素或到達最後一個元素得出元素不存在的結論
  • 二分查找(時間複雜度O(log2 n)):查找元素已排序(則效率高於線性查找)
    只比較目標元素與可行候選項的中間元素
    而後刪除一半的可行候選項(包括中間元素)
    二分查找過程當中可能會有偶數個待查找值,即出現兩個中間值,該算法計算中間索引時會丟棄小數部分,即中間值會選擇兩個中間值的第一個
  • 查找算法比較
    與線性查找相比,二分查找的複雜度是對數級的,這使得它對大型查找池很是有效率
    可是,線性查找通常比二分查找要簡單,編程與調試更容易且無需花費額外成本排序查找列表

2、排序:基於一個標準,將一組項目按照某個順序排列java

  • 排序算法
    順序排序:選擇排序、插入排序、冒泡排序
    對數排序:快速排序、歸併排序
    n個元素排序:順序排序大約n^2次比較,對數排序大約nlog2 n次比較
    n較小時,這兩類算法幾乎不存在實際差異git

  • 選擇排序:
    算法

第1趟排序,在待排序數據arr[1]~arr[n]中選出最小的數據,將它與arrr[1]交換;
第2趟,在待排序數據arr[2]~arr[n]中選出最小的數據,將它與r[2]交換;
以此類推,第i趟在待排序數據arr[i]~arr[n]中選出最小的數據,將它與r[i]交換,直到所有排序完成。編程

  • 插入排序:

反覆地將某一特定值插入到元素列表的已排序的子集中來完成排序
須要注意的是,每次插入可能須要元素移位,而且每插入一次已排序子集都將多一個元素數組

  • 冒泡排序:重複地比較相鄰元素且在必要時將他們互換,從而達到排序目的

n個元素,每一輪排序都將最大值移到最終位置,需比較n-1輪;
每一輪事後,下一輪須要比較的值就會少一個
冒泡排序的算法彷佛還能夠設計兩邊一塊兒冒。。。
函數

  • 快速排序:經過使用一個任意選定的分區元素將該列表分區,而後對分區元素的任一邊的子列表進行遞歸排序

分區元素的選擇是任意的,但最好選擇列表的第一個元素,從而第一輪快速排序分區元素能把列表大體分爲兩半
持續對兩個分區進快速排序,直至分區只含有一個元素,排序即完成
值得注意的是,決定放置好了初始分區元素,就不會對其進行考慮和移動了性能

  • 歸併排序:算法經過將列表遞歸分紅兩半直至每一子列表都含有一個元素,而後將這些子列表歸併到一個排序順序中

歸併排序包括"從上往下"和"從下往上"2種方式
如圖所示:
學習

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

問題1:對歸併排序算法的理解

問題1解析:

首先看到這個排序算法的時候,有一個疑惑:算法好像只是一半一半地將原列表元素分紅只含有一個元素的子列表,而後再將只含有一個元素的子列表歸併成一個新的已排好序的列表,即完成了排序。
那問題是,歸併的時候是怎麼把序排好的?
將兩個已經有序的子序列合併成一個有序序列,好比下圖中的一次合併,要將[4,5,7,8]和[1,2,3,6]兩個已經有序的子序列,
合併爲最終序列[1,2,3,4,5,6,7,8],步驟爲:
測試

【參考資料】
圖解排序算法(四)之歸併排序

問題2:在總結課本知識點的時候,百度到冒泡排序還能夠從兩邊一塊兒冒

問題2解析:冒泡排序的改進方法(即雙向冒泡):

比較相鄰兩個元素的大小。若是前一個元素比後一個元素大,則兩元素位置交換;
奇數趟時從左向右進行比較和交換;
偶數趟時從右向左進行比較和交換;
當從左端開始遍歷的指針與從右端開始遍歷的指針相遇時,排序結束;

通俗來講就是:
首先將第一個數和第二個數比較,若第二個數比第一個數小,則交換,而後比較第二第三個,並以此類推,
直到第n-1個數和第n個數比較爲止,這時最大的數在第n個位置,這是第一次比較;
而後第二次比較從第n-1個數依次到第一個數,此時最小的數在第一個位置;
第三次比較則是從第二個數開始依次比較,直到第n-1個數...依次類推,直到中間兩個數比較完爲止

代碼實現:

int arrayLength = array.length;

        int preIndex = 0;
        int backIndex = arrayLength - 1;
        while(preIndex < backIndex) {
            preSort(array, arrayLength, preIndex);
            preIndex++;

            if (preIndex >= backIndex) {
                break;
            }

            backSort(array, backIndex);
            backIndex--;
        }
    }

    // 從前向後排序
    private void preSort(int[] array, int length, int preIndex) {
        for (int i = preIndex + 1; i < length; i++) {
            if (array[preIndex] > array[i]) {
                ArrayUtils.swap(array, preIndex, i);
            }
        }
    }

    // 從後向前排序
    private void backSort(int[] array, int backIndex) {
        for (int i = backIndex - 1; i >= 0; i--) {
            if (array[i] > array[backIndex]) {
                ArrayUtils.swap(array, i, backIndex);
            }
        }

【參考資料】
排序算法系列:冒泡排序與雙向冒泡排序
排序-----冒泡排序(單向冒泡,雙向冒泡,優化版本)

代碼運行中的問題及解決過程

問題1:對於PP9.2的編程,一開始以爲思路不是很清晰,後來發現題目給的算法跟冒泡排序的算法好像不太同樣:冒泡排序的嵌套循環,外層是控制每一輪要找到的最小元素要放置的位置,內層循環是找到每一輪最小的元素;而間隔排序的算法是內層循環都是每一輪從第一個元素開始間隔i個元素找到元素作比較,外層循環是減少i的值,而後接着內層循環

這裏會出現的問題就是,間隔的元素i加上去以後可能超過數組的長度,即不存在這個元素,就會出現如圖的錯誤:

問題1解決過程:這個時候我想到的思路是:

(1)對i進行限制,先把i定義成數組的長度少一,而後在每一輪比較前先對掃描到的索引處+i是否超過數組長度,不超過則進行比較,超過則對i遞減1,直到不超過數組長度
(2)也可對加i以後超過數組長度的索引處元素不予比較,緊接着比較下一索引處元素(固然這就更不可能啦。。。),也能夠遇到超過數組長度的索引處元素,直接將i減1,進行下一輪循環
思路(1)代碼以下:

int i,scan;
        for (i = data.length - 1;i>0;i--){
        for (scan = 0; scan < data.length - 1; scan++) {
            if (scan + i < data.length) {
                if (data[scan].compareTo(data[scan + i]) > 0)
                    swap(data, scan, scan + i);
            }

        }
        }

運行結果如圖:

思路(2)的代碼以下:

int i,scan;
        for (i = data.length - 1;i>0;i--){
            for (scan = 0; scan < data.length - 1; scan++) {
                if (scan + i >= data.length)
                    continue;
                    if (data[scan].compareTo(data[scan + i]) > 0)
                        swap(data, scan, scan + i);
            }
        }

用一個if語句來判斷是否間隔i個元素後過界,而後直接continue跳出循環,進行下一輪循環
運行結果跟上圖一致。

【更新】
通過與侯澤洋同窗的一番探討,我發現我看題有點不仔細:
每一輪迭代中,i減小的數量是一個大於1的數
這樣的話,在外層循環對i進行修改操做就沒問題了;

問題2:PP9.3存在的問題是,算法的執行時間如何去記錄、計算
問題2解決過程:

百度了方法以後很簡單,代碼以下:

long startTime=System.nanoTime();   //獲取開始時間  
doSomeThing(); //測試的代碼段  
long endTime=System.nanoTime(); //獲取結束時間  
System.out.println("程序運行時間: "+(endTime-startTime)+"ns");

一開始用的是毫秒計算,可是結果顯示都是0ms,因而換了納秒計算;
有一個頗有趣的現象,就是已經排好序的列表排序的時間甚至比沒排好序的列表花費的時間還要多,如圖:

又嘗試運行了幾十次程序,也有亂序的運行時間長於順序的狀況,但大多數狀況仍是順序花費的時間更多

這是跟電腦有關係仍是一種巧合,仍是其它的什麼緣由?
暫時並無百度到相關的解釋,等找到了再來補充

還存在一個問題,就是,遞歸的計數與時間計算好像跟其它排序算法不同,不能直接一次輸出結果,每次調用本身,就會又一次把結果打印一遍,像這樣:

因此不能在遞歸方法裏寫計算時間差的方法,調用次數仍是能夠的,設一個全局變量,每次調用都會自增1,便可(可是輸出還得寫在測試類裏)

那時間的話,能夠統計開始調用這個方法到結束時計算機的時間,作差
那問題就是如何得到計算機時間(但時間精確度可能不高)
百度的方法有說能夠調用這個方法1000次,而後取千分之一,可是通過前面的體驗,方法的每一次調用花費的時間跟電腦的性能和狀態有很大關係,所以一次計算不是很準確,1000次求平均的話,可能更有表明性,更合理;

【更新】
有百度到能夠獲取當前精確時間(毫秒)的方法,這樣就簡單了,代碼以下:

Calendar Cld = Calendar.getInstance();
int YY = Cld.get(Calendar.YEAR) ;//年
int MM = Cld.get(Calendar.MONTH)+1;//月
int DD = Cld.get(Calendar.DATE);//日
int HH = Cld.get(Calendar.HOUR_OF_DAY);//時
int mm = Cld.get(Calendar.MINUTE);//分
int SS = Cld.get(Calendar.SECOND);//秒
int MI = Cld.get(Calendar.MILLISECOND);//毫秒    
//由整型而來,所以格式不加0,如  2017/5/5-1:1:32:694
System.out.println(YY + "/" + MM + "/" + DD + "-" + HH + ":" + mm + ":" + SS + ":" + MI);

emmm,好像。。。
經過從新寫一個方法來調用這個排序的遞歸方法,而後在這個方法裏面再進行時間統計,就能夠避免重複打印屢次了,也就不用在測試類裏統計時間了,更符合題意,運行結果如圖:

也百度到一些計算遞歸方法運行的時間,但可能涉及到後面的內容,也沒有詳細講,看的不是很明白

【參考資料】
java如何計算程序運行時間
怎麼記錄遞歸函數的使用次數
解遞歸算法的運行時間的三種方法

本週錯題

代碼託管

結對及互評

  • 博客中值得學習的或問題:
    • 侯澤洋同窗的博客排版工整,界面很美觀
    • 問題總結作得很全面:每一種排序方法都能找到適當的圖片描述,說明問題
    • 對各類排序的時間複雜度作了總結比較,值得我去學習
    • 對教材中的細小問題都可以關注,而且主動去百度學習,例如代碼中「@SuppressWarnings("unchecked")」的含義理解
  • 代碼中值得學習的或問題:
    • 對於編程的編寫總能找到角度去解決
  • 本週結對學習狀況
    • 20172302
    • 結對學習內容
      • 第九章內容:排序與查找

學習進度條

代碼行數(新增/累積) 博客量(新增/累積) 學習時間(新增/累積) 重要成長
目標 5000行 30篇 400小時
第一週 0/0 1/1 4/4
第二週 560/560 1/2 6/10
第三週 415/975 1/3 6/16
第四周 1055/2030 1/4 14/30
第五週 1051/3083 1/5 8/38
相關文章
相關標籤/搜索