- 基本思想:遍歷數組,找到最小的元素和第一個元素(nums[0])交換。次小的和第二個元素交換,也就是每次找到未排序部分中的最小值放在適當的位置。
- 複雜度分析:長度爲N的數組,比較次數:N^2/2 交換次數:N 時間複雜度:N^2
- 特點:運行時間和輸入無關,每次都需遍歷未排序部分找到最小值;數據的移動交換次數是最少的;
- 代碼:
public void select_sort(int[] nums){ int len = nums.length; int k; for(int i=0;i<len-1;i++){ k = i;//標記最小值的下標 for(int j=i+1;j<len;j++){ if(nums[j] < nums[k]) k = j; } if(k != i){ int tmp = nums[i]; nums[i] = nums[k]; nums[k] = tmp; } } }
- 改進:簡單選擇排序,每趟循環只能確定一個元素排序後的定位。我們可以考慮改進爲每趟循環確定兩個元素(當前趟最大和最小記錄)的位置,從而減少排序所需的循環次數。改進後對n個數據進行排序,最多隻需進行[n/2]趟循環即可。
public void select_sort_next(int[] nums){ int len = nums.length; int min;int max; for(int i=0,j=len-1;i<j;i++,j--){ min = i;//標記最小值的下標 max = j; for(int k=i;k<=j;k++){ if(nums[k] < nums[min]) min = k; if(nums[k] > nums[max]) max = k; } if(min != i){ int tmp = nums[i]; nums[i] = nums[min]; nums[min] = tmp; } if(max != j){ int tmp = nums[j]; nums[j] = nums[max]; nums[max] = tmp; } } }
- 插入排序
- 基本思想:撲克牌,拿起一張牌插入左手中已排序的部分,可以從右向左和手中的牌依次比較,直到找到一個小於他的(升序排列)。
- 複雜度分析:長度爲N的數組,最壞情況比較次數:N^2/2 交換次數:N^2/2 時間複雜度:介於N和N^2之間
- 特點:運行時間和輸入有關,若輸入是一個已排序的數組,能很快發現,運行時間線性,對於所有元素都相等的數組,時間也是線性的;特別適合部分有序數組的排序以及小規模數組,所以在歸併和快速排序中,數組變小後可以使用插入排序提高效率;是穩定的;
- 代碼:
public void Insertion_sort(int[] nums){ int len = nums.length; for(int i=1;i<len;i++){//i剛拿起的未排序的撲克牌 int tmp = nums[i]; int j = i-1; while(j >= 0 && tmp < nums[j]){//j從右向左將手中的牌依次和i比較 nums[j+1] = nums[j]; j--; } nums[j+1] = tmp; } }
- 冒泡排序
- 基本思想:(升序排列)最大值向下沉,相鄰的元素依次比較。
- 複雜度分析:長度爲N的數組,比較次數:N^2/2 交換次數:N^2/2 時間複雜度:N^2
- 特點:運行時間和輸入無關;
- 代碼:
public void Bubble_sort(int[] nums){ int len = nums.length; for(int i=len-1;i>0;i--){//i最大值存放的位置 for(int j=0;j<i;j++){ if(nums[j] >= nums[j+1]){ int tmp = nums[j]; nums[j] = nums[j+1]; nums[j+1] = tmp; } } } }
- 改進:對冒泡排序常見的改進方法是加入一標誌性變量exchange,用於標誌某一趟排序過程中是否有數據交換,如果進行某一趟排序時並沒有進行數據交換,則說明數據已經按要求排列好,可立即結束排序,避免不必要的比較過程。所以可以:設置一標誌性變量pos,用於記錄每趟排序中最後一次進行交換的位置。由於pos位置之後的記錄均已交換到位,故在進行下一趟排序時只要掃描到pos位置即可。
public void Bubble_sort_next(int[] nums){ int len = nums.length; for(int i=len-1;i>0;){ int tmp_pos = 0; for(int j=0;j<i;j++){ if(nums[j] >= nums[j+1]){ tmp_pos = j; int tmp = nums[j]; nums[j] = nums[j+1]; nums[j+1] = tmp; }//pos之後都是排好序的了 } i = tmp_pos; } }
- 歸併排序
- 基本思想:將兩個已排序的數組歸併爲一個有序數組。
- 複雜度分析:長度爲N的數組,時間複雜度:NlogN
- 特點:穩定排序,需要額外的空間,空間複雜度O(N)
- 代碼:
public void merge(int[] nums,int start,int end,int mid){ //額外的空 int[] arr = new int[end-start+1]; int i = start; int j = mid+1; int k = 0; while(i<=mid && j<=end){ if(nums[i] < nums[j]) arr[k++] = nums[i++]; else arr[k++] = nums[j++]; } while(i<=mid) arr[k++] = nums[i++]; while(j<=end) arr[k++] = nums[j++]; for(i=start;i<=end;i++) nums[i] = arr[i-start]; } //遞歸版本 public void Merge_sort(int[] nums,int start,int end){ if(start >= end) return; int mid = start + (end-start)/2; Merge_sort(nums,start,mid); Merge_sort(nums,mid+1,end); merge(nums,start,end,mid); }
- 快速排序
- 基本思想:將數組切分爲兩部分,兩部分整體有序,內部無序,之後遞歸切分即可。
- 複雜度分析:長度爲N的數組,時間複雜度:NlogN
- 特點:簡單、高效、適用於各種不同的輸入數據
- 代碼:
public int partition(int[] nums,int start,int end){ int x = nums[start];//哨兵節點 int i = start+1; int j = end; while(i <= j){ while(i <= j && nums[i] < x) i++; while(i <= j && nums[j] >= x) j--; if(i > j) break; int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } nums[start] = nums[j]; nums[j] = x; return j; } //遞歸版本 public void Quick_sort(int[] nums,int start,int end){ if(start >= end) return; int index = partition(nums,start,end); Quick_sort(nums,start,index-1); Quick_sort(nums,index+1,end); }
- 改進:當數組比較小時,就可以使用插入排序:代碼如下
public void Quick_sort_next(int[] nums,int start,int end,int k){ if(end <= start+k){ Insertion(nums,start,end); return; } int index = partition(nums,start,end); Quick_sort(nums,start,index-1); Quick_sort(nums,index+1,end); }
- 改進:當數組中包含大量重複元素時,可以將數組切分爲三部分:小於、等於和大於x,從而避免對元素全部重複的子數組的遞歸排序;
public void Quick_sort_improve(int[] nums,int start,int end){ //遞歸結束的條件 if(start >= end) return; int x = nums[start]; int i = start +1; int lt = start;//存放下一個小於x的值,目前指向等於x的值,lt與i之間的元素等於x int gt = end;//存放下一個大於x的值,i與gt之間的是未排序的 while(i <= gt){ if(nums[i] < x) exchange(nums,i++,lt++);//lt原來指向等於x的值,i小於x的值,交換後均向後移動 else if(nums[i] > x) exchange(nums,i,gt--);//gt原來指向的值大小不明確,所以交換後i不變 else i++;//等於x,i++即可 } //此時,start--lt-1,小於x,lt--gt等於x,gt+1--end大於x,遞歸對兩部分排序即可 Quick_sort_improve(nums,start,lt-1); Quick_sort_improve(nums,gt+1,end); } public static void main(String[] args){ int[] nums = {1,9,11,-5,-2,0,-5,7,8,1,20,11,-11,32,-9,56,1,1,1,1,1,-11}; //int[] nums = {1,9,11,14,20}; new Test().Quick_sort_improve(nums,0,nums.length-1); for(int i=0;i<nums.length;i++) System.out.print(nums[i]+" "); }
/*基本思想: *當數組中包含大量重複元素時,利用此改進的快速排序,可達到線性的時間複雜度 *數組分爲三部分,小於x,等於x,大於x*/ public void exchange(int[] nums,int i,int j){ int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; }
- 還有一種改進就是:使用子數組的一小部分元素的中位數來切分數組,也就是選擇更好的x,使得切分後左右兩部分儘可能均勻,代價是需要計算中位數;
- 參考:《算法》 八大排序算法