冒泡排序
- 排序原理
冒泡排序方法是最簡單的排序方法。這種方法的基本思想是,將待排序的元素看作是豎着排列的“氣泡”,較小的元素比較輕,從而要往上浮。在冒泡排序算法中我們要對這個“氣泡”序列處理若干遍。所謂一遍處理,就是自底向上檢查一遍這個序列,並時刻注意兩個相鄰的元素的順序是否正確。如果發現兩個相鄰元素的順序不對,即“輕”的元素在下面,就交換它們的位置。顯然,處理一遍之後,“最輕”的元素就浮到了最高位置;處理二遍之後,“次輕”的元素就浮到了次高位置。在作第二遍處理時,由於最高位置上的元素已是“最輕”元素,所以不必檢查。一般地,第i遍處理時,不必檢查第i高位置以上的元素,因爲經過前面i-1遍的處理,它們已正確地排好序。
- 複雜度的計算O(n^2)
對於n位的數列則有比較次數爲 (n-1) + (n-2) + … + 1 = n * (n - 1) / 2,這就得到了最大的比較次數
而O(N^2)表示的是複雜度的數量級。舉個例子來說,如果n = 10000,那麼 n(n-1)/2 = (n^2 - n) / 2 = (100000000 - 10000) / 2,相對10^8來說,10000小的可以忽略不計了,所以總計算次數約爲0.5 * N^2。用O(N^2)就表示了其數量級(忽略前面係數0.5)。
- 代碼實現
/**
* 冒泡排序算法
*/
public static void bubble(int[] arr){
for(int i = arr.length-1;i>=0;i--){//比較次數根據數組長度
for(int j = 0;j<i;j++){
if(arr[j]>arr[j+1]){//判斷前一個是否比後一個數大,是的話將位置掉換
change(j,j+1,arr);
}
}
}
}
/**
* 將兩個變量值調換
* @param a
* @param b
*/
private static void change(int a, int b,int[] arr) {
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
選擇排序
- 排序思想
對比數組中前一個元素跟後一個元素的大小,如果後面的元素比前面的元素小則用一個變量k來記住他的位置,接着第二次比較,前面“後一個元素”現變成了“前一個元素”,繼續跟他的“後一個元素”進行比較如果後面的元素比他要小則用變量k記住它在數組中的位置(下標),等到循環結束的時候,我們應該找到了最小的那個數的下標了,然後進行判斷,如果這個元素的下標不是第一個元素的下標,就讓第一個元素跟他交換一下值,這樣就找到整個數組中最小的數了。然後找到數組中第二小的數,讓他跟數組中第二個元素交換一下值,以此類推。
- 時間複雜度計算O(n^2)
總的比較次數N=(n-1)+(n-2)+…+1=n*(n-1)/2。交換次數O(n),最好情況是,已經有序,交換0次;最壞情況交換n-1次,逆序交換n/2次。交換次數比冒泡排序少多了,由於交換所需CPU時間比比較所需的CPU時間多,n值較小時,選擇排序比冒泡排序快。
- 代碼實現
/**
* 選擇排序
*/
public static void select(int[] arr){
for(int i = 0; i<arr.length -1;i++){//比較n-1次,每次遍歷得到最小數的索引
int minIndex = i; //默認第一個數的爲最小值的索引
for(int j = i+1;j<arr.length;j++){ //每次從第i+1個數開始尋找最小數的索引
if(arr[j]<arr[minIndex]){
minIndex = j;
}
}
change(i,minIndex,arr);//將該次遍歷得到的最小數與第i位數交換
}
}
插入排序
- 排序原理
將初始序列中的第一個元素作爲一個有序序列,然後將剩下的 n-1 個元素按關鍵字大小依次插入該有序序列,每插入一個元素後依然保持該序列有序,經過 n-1 趟排序後使初始序列有序。
- 時間複雜度計算 O(n^2)
如果目標是把n個元素的序列升序排列,那麼採用插入排序存在最好情況和最壞情況。最好情況就是,序列已經是升序排列了,在這種情況下,需要進行的比較操作需(n-1)次即可。最壞情況就是,序列是降序排列,那麼此時需要進行的比較共有n(n-1)/2次。插入排序的賦值操作是比較操作的次數加上 (n-1)次。平均來說插入排序算法的時間複雜度爲O(n^2)。
- 代碼實現
/**
*插入排序
*/
public static void insert(int[] arr){
for(int i=1;i<arr.length;i++){//定義外層循環,從第二個數開始進行比較
int temp = arr[i];//記錄每次循環需要比較的數
int j = i-1;//設置要比較的短數組的長度(初始序列長度)
for(;j>=0;j--){
if(arr[j]>temp){//如果前面的數比temp大,則將該書後移到j+1的位置
arr[j+1] = arr[j];
}else{//如果前面的數比temp小,終止循環,將該數的下個位置的值改爲temp
arr[j+1] = temp;
break;
}
}//循環結束後j==-1
if(j==-1){//當前面的數都比temp小時,將第一個數的值改爲temp
arr[0] = temp;
}
}
}
快速排序
- 排序原理
找一個值作爲參考值,比參考值大的就放在右邊,比參考值小的就放在左邊。那麼一趟完成後就將數組分成了兩部分:參考值左邊的都是小於參考值的數,參考值右邊的都是大於參考值的數,然後分別遞歸求這兩部分,最後得到的就是一個排好序的數組了。
- 時間複雜度計算O(n)
快速排序每次將待排序數組分爲兩個部分,在理想狀況下,每一次都將待排序數組劃分成等長兩個部分,則需要logn次劃分。
而在最壞情況下,即數組已經有序或大致有序的情況下,每次劃分只能減少一個元素(中間位置元素),這樣的結果就好比是冒泡排序,所以快速排序時間複雜度下界爲O(nlogn),最壞情況爲O(n^2)。
- 代碼實現
public static void quicksort(int[] arr,int low, int high){
int l = low;
int h = high;
int key = arr[low];//每次比較將左側的第一個數作爲關鍵數
while(l<h){
while(l<h && arr[h]>=key) h--;//左右沒有出現交叉,並且右邊沒有找到比關鍵數小的,則索引左移
if(l<h){
//從右邊找到了將找到的比關鍵數小的值與左邊索引對應的值交換,再從左側開始找,左側索引值右移移
int temp = arr[l];
arr[l] = arr[h];
arr[h] = temp;
l++;
}
while(l<h && arr[l]<=key) l++;//左右兩邊沒有出現交叉,並且左邊沒由找到比關鍵數大的,則索引右移
if(l<h){
//將在左側找到的比關鍵數大的值與右邊索引對應的值交換,開始從右側查找,右側索引值左移
int temp = arr[h];
arr[h] = arr[l];
arr[l] = temp;
h--;
}
}//當l==h出現交叉時,跳出循環,也就是找到了中間點,接下來從中間點左右分開,再次分別查找
//此時左邊的數均小於右邊的數
if(l>low){//l>low代表沒由找到比關鍵數大的數,那麼就需要跟換關鍵數從新找
quicksort(arr,low,l-1);
}
if(h<high){
quicksort(arr,l+1,high);
}
}
在這裏再介紹下隨機化快排:
隨機化快排是建立在基本快排的算法上做出的改進,利用概率事件降低快排出現的最不利情況的可能性,也就是說:基本的快速排序選取第一個元素作爲關鍵數。這樣在數組已經有序的情況下,每次劃分將得到最壞的結果。而隨機快排是選取一個元素作爲關鍵數。這種情況下雖然最壞情況仍然是O(n^2),但最壞情況不再依賴於輸入數據,而是由於隨機函數取值不佳。實際上,隨機化快速排序得到理論最壞情況的可能性僅爲1/(2^n)。所以隨機化快速排序可以對於絕大多數輸入數據達到O(nlogn)的期望時間複雜度。
測試
這裏使用十萬條數據進行測試,分別用每一種算法進行(之所以使用十萬條數據而不是百萬條千萬條,是受到個人電腦配置限制,而且快排時使用到遞歸後,數據量過大,造成棧內存溢出)