1. 程式人生 > >20172308 《程序設計與數據結構》第五周學習總結

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

之一 並排 ade 並且 就是 com 移位 一半 不能

教材學習內容總結

第 九 章 排序與查找

一、查找:在查找池中查找目標元素或確定查找池中不存在該目標元素

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

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

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

  • 選擇排序:
    技術分享圖片

第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解析:

【參考資料】

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

問題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如何計算程序運行時間
怎麽記錄遞歸函數的使用次數
解遞歸算法的運行時間的三種方法

本周錯題

代碼托管

技術分享圖片

結對及互評

  • 博客中值得學習的或問題:
    • 侯澤洋同學的博客排版工整,界面很美觀
    • 問題總結做得很全面
    • 對於書上的疑惑總會想辦法解決它,這種探索的精神值得我去學習
  • 代碼中值得學習的或問題:
    • 對於編程的編寫總能找到角度去解決
  • 本周結對學習情況
    • 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

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