學習幾種排序算法,並討論這些算法的複雜度html
查找(searching) 是在某個項目組中尋找某一指定目標元素, 或者肯定該組中並不存在該目標元素的一個過程。對其進行查找的項目組有時
也稱爲查找池( search pool)。java
咱們的目標就是儘量高效地完成查找。從算法分析的角度而言,高效的查找會使該過程所作的比較操做次數最小化。同時該查找池中項目的數目定義了該問題的大小。git
查找某一對象,必須可以將其跟另外一個對象進行比較。咱們對這些算法的實現就是對某個Comparable對象的數組進行查找。
如:public class Searching<T extends Comparable<T>>
這個泛型聲明的實際結果就是以用任何實現Comparable接口的類來實例化Searching類。算法
以Comparable接口的方式定義的Searching要求咱們在使用查找或排序方法時必須實例化因而引出了泛型靜態方法。數組
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)
泛型聲明必須位於返回類型以前,這樣泛型纔可做爲返回類型的部分。函數
線性查找法代碼:學習
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循環將遍歷數組元素,在找到元素或到達數組尾時其會終止測試
二分查找法
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的變大,二分查找就會變得吸引人。
排序是基於某一標準,將某一組項目按照某個規定順序排列的一個過程。
基於效率排序算法一般也分爲兩類:順序排序,它一般使用一對嵌套循環對n個元素排序,須要大約n^2次比較;以及對數排序, 它對n個元素進行排序一般須要大約nlog2 n次比較。與查找算法中同樣,在n較小時,這兩類算法之間幾乎不存在實際差異。
本章學習三種順序排序選擇排序、 插入排序以及冒泡排序,以及兩種對數排序快速排序和歸併排序。
選擇排序法
-選擇持排字算法的通常策略:打描整個列表以找出最小值。將這個值與該列表第一個位置處的值交換。掃描(除了第一個值的)剩餘部分列表並找出最小值,而後將它和該列表第二個位置處的值交換,掃描(除了前兩個值的)剩餘部分列表並找出最小值,而後將它和該列表第三個位置處的值交換,直到全部的數字排完。
插入排序算法經過反覆地將某一特定值插入到該列表某個已排序的子集中來完成對列表值的排序。
插入排序算法的通常策略:對列表中的頭兩個值依據其相對大小對其進行排序,若是有必要則將它們互換。將列表的第三個值插入到頭兩個(已排序的)值中的恰當位置。而後將第四個值插入到列表頭三個值中的正確位置。每作出一次插入, 該排序子集中的值數目就會增長一個。繼續這一過程, 直至列表中的全部元素都獲得徹底排序。該插入過程須要對數組中的其餘元素移位,以給插入元素騰出空間。
冒泡排序算法經過重複地比較相鄰元素且在必要時將它們互換,從而完成對某個列表的排序。
冒泡排序算法的通常策略:掃描該列表且比較鄰接元素,若是它們不是按相對順序排列則將其互換。這就像把最大值「冒泡」到列表的最後位置,這是它在最終已排序列表中的恰當位置。而後再次掃描該列表,冒泡出倒數第二個值。繼續這一過程, 直至全部元素都被冒泡到它們正確的位置。
快速排序法
歸併排序(merge sort)算法是另外一種遞歸排序算法, 經過將列表遞歸式分紅兩半直至每一子列表都只含有一個元素,而後將這些子列表按順序重組,這樣就完成了對列表的排序。
歸併排序算法的 通常策:首先將該列表分紅兩個大約相等的部分,而後對每一部分列表遞歸調用其自身。繼續該列表的遞歸分解,直至達到該遞歸的基本情形,這時活列表被分割成長度爲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<? super T>> 這樣的類型參數對所傳入的參數限制更少,提升了 API 的靈活性。總的來講,在保證類型安全的前提下,要使用限制最少的類型參數。
能夠參考一下這篇文章:
如何理解 Java 中的 <T extends Comparable<? super T>>
問題1:在完成藍墨雲線性表實踐時,實現選擇排序法的時候,排出來的結果是
問題1解決方案:當時的代碼是這樣的
選擇排序的邏輯確實是個人代碼所體現的那樣,可是爲何會出錯呢?
因而我就進行了單步調試,發現由於while(temp2.getNext()!=null)
這行代碼是隻有temp2以後還存在元素時纔會進入循環體,這樣就有一個問題,若是temp2就是最後一個元素了,就不會在進行比較,因而發生了上面的錯誤。如今的關鍵就是要解決最後兩個數比較的位置在哪。
我先將
這段代碼放在了第二個循環體的後面,結果陷入了無限死循環;我又把他放在了第一個循環的外面,這個if條件句不會成立。最後我修改成
才解決了問題。
問題2:pp9.3中要求編寫代碼來計算每次代碼執行時間,時間該如何計算呢?
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的nanoTime()方法,並計算兩次調用之間的差值,就是方法執行所消耗的納秒數。由於隊友建議ms爲單位對於計算機來講時間有點長,因此獲得的可能值會爲0,因此使用ns爲單位。
long startTime = System.nanoTime(); //獲取開始時間 doSomething(); //測試的代碼段 long endTime = System.nanoTime(); //獲取結束時間 System.out.println("程序運行時間:" + (endTime - startTime) + "ns"); //輸出程序運行時間
(statistics.sh腳本的運行結果截圖)
博客中值得學習的或問題:
教材內容總結很精簡,我以爲能夠添加一下對方法的簡單的解釋。
教材問題2,其實能夠寫在教材內容總結裏,有點缺乏圖示。但都是本身總結,進行了學習和簡化。
天天都要花好幾個小時站在操場,還落了好多課,感受少了好多時間學習,並且這周的任務量也不輕,本身仍是要多抽出時間來學習。
代碼行數(新增/累積) | 博客量(新增/累積) | 學習時間(新增/累積) | ||
---|---|---|---|---|
目標 | 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小時
最近由於學校的活動少了一些時間來學習。