相關文章
算法(一)時間復雜度
前言
排序是算法的基礎,排序有很多種方法,有些方法實現起來很簡單,但是效率較差,我們可以將這些排序的方法稱之為初等排序。這篇文章我們就來學習初等排序中的插入排序和冒泡排序。
1.插入排序
插入排序比較容易想到,思路與打撲克時排列牌的順序是類似的。比如我們左手拿牌,然后用右手將牌從左到右,從小到大來排序,這就需要我們把需要進行排列的牌抽出來放到合適的位置,并且不斷的重復,直到牌的順序排好,這個過程就可以理解為插入排序。
圖解插入排序
插入排序過程中會將需要排序的數組,分為兩個部分:已排序部分和未排序部分,如下圖所示。
從圖中可以看出這個數組分為兩個部分,其中下標為0、1、2的元素為已排列部分,其余的則為未排列部分。
插入的排序規則:
將開頭元素視為以排序部分。接著執行如下的處理,直到沒有未排序部分。
- 取出未排序部分的開頭元素賦值給臨時保存數據的變量v。
- 在已排列的部分將所有比v大的元素向后移動一個位置。
- 將取出的元素v插入空位。
按照這個規則,我們來舉一個簡單的例子。我們對數組 a={8,3,1,5,2,1} 進行從小到大排序,數組a如下圖所示。
我們對數組a進行排序,共需要5個步驟:
1.接著我們將a[0]=8視為已排序,我們從a[1]開始操作,將a[1]的值3取出,3要小于a[0]的值8,因此將a[0]的值8移動到a[1],再把3插入到a[0],如下圖所示。
2.a[2]的值1要比a[0]和a[1]的值要小,則將a[0]和a[1]順次向后移一個位置,然后將1插入a[0],如下圖所示。
3.將a[3]中的5拿出來,比它大的是a[2]的8,因此8向后移,將5插入a[3]。如下圖所示。
4.將a[4]中2拿出來,發現a[1]、a[2]、a[3]中的值都比2大,因此將它們依次向后移,將2插入到a[1]中,如下圖所示。
5.最后將a[5]中的1移到合適的位置,過程和上面一樣,最后的排序結果如下圖所示。
實現插入排序
接下來要實現插入排序,針對下圖來定義變量。
如上圖所示,i代表未排序部分的開頭元素,v是臨時保存a[i]值的變量, j代表已排序部分v要插入的位置。
根據定義的這三個變量,插入排序的實現思路就是:外層循環i從1開始自增,并在每次循環開始時將a[i]的值保存在v中;內層循環則是j從i-1開始向前自減,并將比v大的元素從a[j]移動到a[j+1],并將v插入到當前j+1的位置(內層循環后j會先自減1,因此插入的地方則是j+1的位置),當j等于-1或者a[j]小于等于v則內層循環結束。
接下來我們用代碼來實現插入排序,如下所示。
public class InsertSort {
public static void main(String[] args) {
int a[] = {8, 3, 1, 5, 2, 1};
ArrayUtils.printArray(a);
int b[] = insert(a);
ArrayUtils.printArray(b);
}
public static int[] insert(int[] a) {
int i, j, v;
int n = a.length;
for (i = 1; i < n; i++) {
v = a[i];
j = i - 1;
while (j >= 0 && a[j] > v) {
a[j + 1] = a[j];
j--;
}
a[j + 1] = v;
}
return a;
}
}
其中負責打印數組的ArrayUtils類如下所示。
public class ArrayUtils {
public static void printArray(int[] array) {
System.out.print("{");
int len=array.length;
for (int i = 0; i < len; i++) {
System.out.print(array[i]);
if (i < len - 1) {
System.out.print(", ");
}
}
System.out.println("}");
}
}
輸出結果為:
{8, 3, 1, 5, 2, 1}
{1, 1, 2, 3, 5, 8}
插入排序的復雜度
根據算法(一)時間復雜度所講的,我們來算一下插入排序的時間復雜度。在最壞的情況下,每個i循環都需要執行i次移動,總共需要1+2+......+n-1=n2/2+n/2,根據此前講過的推導大O階的規則的我們得出插入排序的時間復雜度為O(n2)。
2.冒泡排序
冒泡排序應該是開發者最容易理解的排序算法,它的基本思想就是每次比較兩個相鄰的元素,如果它們的順序錯誤就把它們交換過來。需要進行排序的元素則向水中的氣泡一樣慢慢的移向水面。
圖解冒泡排序
與插入排序一樣,需要進行冒泡排序的數組也分為已排序部分和未排序部分。
冒泡排序的規則為:從數組末尾開始依次比較相鄰的兩個元素,如果大小關系相反則交換位置,直到數組中不再有順序相反的相鄰元素。
我們對數組 a={5,3,2,4,1} 進行從小到大排序,排序過程如下所示。
第一輪排序:
我們將數組末尾的a[4]的值和a[3]的值進行對比,發現a[4]的值比a[3]的值小,則將它們交換,再接著對剩下的相鄰的兩個元素進行對比和交換,最終得到的結果為a={1,5,3,2,4},已排序的部分的元素為1。
第二輪排序:
首先對比a[3]和a[4]的值,發現a[3]的值比a[4]的值小,則不需要進行排序。最終得到的結果為a={1,2,5,3,4},已排序部分的元素為1、2。
第三輪排序:
經過第三輪排序,已排序部分的元素為1、2、3。
第四輪排序:
經過四輪排序我們最終得到的結果為a={1,2,3,4,5}
實現冒泡排序
實現插入排序時,我們要先定義兩個變量,i為循環變量,表示未排序部分的開頭元素,從數組開頭向末尾移動。j也為循環變量,用于對未排序部分中相鄰元素兩兩比較,從數組的末尾n-1開始減小到 i 結束(i=1)。
代碼實現如下所示。
public class BubbleSort {
public static void main(String[] args) {
int a[] = {5, 3, 2, 4, 1};
ArrayUtils.printArray(a);
int b[] = bubble(a);
ArrayUtils.printArray(b);
}
public static int[] bubble(int[] a) {
int i, j, v;
int n = a.length;
for (i = 1; i <= n - 1; i++) {
for (j = n - 1; j >= i ; j--) {
if (a[j] < a[j - 1]) {
v = a[j];
a[j] = a[j - 1];
a[j - 1] = v;
}
}
}
return a;
}
}
其中ArrayUtils的printArray方法此前講過,這里就不再給出,打印結果為:
{5, 3, 2, 4, 1}
{1, 2, 3, 4, 5}
冒泡排序的復雜度
最壞的情況下,冒泡排序對未排序部分的相鄰元素進行了(n-1)+(n-2)+(n-3)+……+1次比較,也就是n2/2+n/2次比較,根據推導大O階的規則我們得出冒泡排序的時間復雜度為O(n2)。
歡迎關注我的微信公眾號,第一時間獲得博客更新提醒,以及更多成體系的Android相關技術干貨。
掃一掃下方二維碼即可關注: