1. 程式人生 > >常用的排序方法整理

常用的排序方法整理

常用的排序方法

排序方法

平均時間複雜度 時間複雜度(最好) 時間複雜度(最壞) 空間複雜度 穩定性
氣泡排序(BubbleSort) O(n²) O(n) O(n²) O(1) 穩定
直接選擇排序(SectionSort) O(n²) O(n²) O(n²) O(1) 不穩定
直接插入排序(InsertSort) O(n²) O(n) O(n²) O(1) 穩定
希爾排序(ShellSort) O(nlog₂n) O(n) O(n²) O(1) 不穩定
快速排序(QuickSort) O(nlog₂n) O(nlog₂n) O(n²) O(nlog₂n) 不穩定
歸併排序(MergeSort) O(nlog₂n) O(nlog₂n) O(nlog₂n) O(n) 穩定
堆排序(HeapSort) O(nlog₂n) O(nlog₂n) O(nlog₂n) O(1) 不穩定

桶排序(BucketSort)

/基數排序(RadixSort)/

計數排序(CountSort)

O(d(n+m))

O(d(n+m)) O(d(n+m)) O(n+m) 穩定

Tips: d(n+m)=n+n*(logN-logM);

其中,演算法的穩定性是指,當陣列中存在兩個相同的資料,經過排序之後兩個資料的位置是否發生相對改變。

1、氣泡排序(BubbleSort):

1:將每個數與下一個數進行比較,如果大於,則兩者交換位置;(經過1次迴圈之後,最大數位於陣列末端)

2:對剩餘的 n - 1個數,重複第一步操作,經過n次迴圈之後,陣列達到有序。

 Tips:1. 可以將整個過程理解成泡泡的上升,最大的泡泡浮得最快,位於陣列末端,

         2.最好的情況是陣列基本有序,最壞的情況是陣列基本逆序。

public class BubbleSort {
	public static void bubbleSort(int[] arr){
		if(arr == null || arr.length <2){
			return;
		}
		for(int i = arr.length - 1; i > 0; i--){//迴圈N次
			for(int j = 0; j < i; j++){
				if(arr[j] > arr[j+1]){
					SwapArr.swap(arr, j, j+1);
				}
			}
		}
	}
	
}

2、直接選擇排序(SectionSort)

1:遍歷整個陣列,找到其中最小的數,並與陣列最前端的數交換位置(初始為0,經過一次遍歷,最小數位於最前端);

2:從1位置繼續遍歷,重複第1步操作,迴圈n次,最終陣列達到有序。

Tips: 每次都在數組裡找最小的數放到前面(已經排好序的陣列後面)

public class SectionSort {
	public static void sectionSort(int[] arr){
		if(arr == null || arr.length <2){
			return;
		}
		for(int i = 0; i < arr.length - 1; i++){
			int minIndex = i;
			for(int j = i + 1; j < arr.length; j++){
				if(arr[minIndex] > arr[j]){
					minIndex = j;					
				}
			}			
			SwapArr.swap(arr, i, minIndex);
		}	
	}
	
}

3、直接插入排序(InsertSort)

1:從1位置開始,與前面的數進行比較,如果小於前面的數則交換位置,直到不再小於前面的數;

2:從2位置開始,重複第1步,直到達到最後一個位置。

Tips:1.從第二個數開始,做插入操作(插入的位置在小於自己的數之後,大於自己的數之前),最終達到有序,

          2.最好的情況是陣列基本有序,最壞的情況是陣列基本逆序。

public class InsertionSort {
	public static void insertionSort(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		for (int i = 1; i < arr.length; i++) {
			for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
				SwapArr.swap(arr, j, j+1);
			}
		}
	}
}

4、希爾排序(ShellSort)

1:首先確定一個步長 k,根據步長把陣列分為(n/k)個部分進行排序;

2:縮短步長,繼續細分待排序的陣列;

3:直到步長縮短為1,此時陣列達到有序。

Tips:1.過程類似於插入排序,但是通過將待排陣列分為若干個子序列,減少移動次數,是插入排序的一種優化,

          2.常用的初始步長是 (n / 2)。

          3.最好的情況是資料基本有序。

public class ShellSort {
	public static void shellSort(int[] arr) {
		if (arr == null || arr.length < 2)
			return;
		int gap = arr.length / 2;
		for (; gap > 0; gap /= 2) {
			for (int i = gap; i < arr.length; i++) {
				for (int j = i - gap; j >= 0 && arr[j] > arr[j + gap]; j -= gap) {
					SwapArr.swap(arr, j, j+gap);
				}
			}
		}
	}

	public static void shellSort(int[] arr, int n) {
		if (arr == null || arr.length < 2)
			return;
		int gap = n / 2;
		for (; gap > 0; gap /= 2) {
			for (int i = gap; i < n; i++) {
				for (int j = i - gap; j >= 0 && arr[j] > arr[j + gap]; j -= gap) {
					SwapArr.swap(arr, j, j+gap);
				}
			}
		}
	}
}

5、快速排序(QuickSort)

1:隨機選出一個哨兵partition(樞軸 pivot),把大於partition的值放在右邊,小於partition的值放在左邊;

         (partition一般取最左邊的數,也可隨機生成)

2:從partition左右兩邊分割,遞迴呼叫自身;

3:當達到遞迴結束條件是,陣列達到有序狀態。

Tips:1.為防止出現最壞情況(即所取的哨兵(樞軸)為最大數或最小數),可以隨機生成哨兵(樞軸),但會增加開銷

           2.當陣列基本有序時,快排將退化為氣泡排序。

public class QuickSort {
	public static void quickSort(int[] arr){
		if(arr.length>0)
			quickSort(arr, 0, arr.length -1);
	}
	
	public static void quickSort(int[] arr, int left,int right){
		if(left > right){
			return;
		}		
		int i = left;
		int j = right;
		int partition = arr[left];
		while(i < j){			
			while(j > i && arr[j] > partition){
				j--;
			}
			while(j > i && arr[i] <= partition){
				i++;
			}
			if(i<j){
				SwapArr.swap(arr, i, j);
			}
		}
		SwapArr.swap(arr, i, left);
		quickSort(arr, left, i-1);
		quickSort(arr,i+1, right);
	}
}

6、歸併排序(MergeSort)

1:把資料分為兩個部分,分別進行排序,最後合併;

2:遞迴呼叫自身,以實現排序。

Tips:1.將待排陣列分為若干個有序的子序列,再進行合併操作,使其達到有序狀態。

public class MergeSort {
	public static void mergeSort(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		mergeSort(arr, 0, arr.length - 1);
	}

	public static void mergeSort(int[] arr, int left, int right) {
		if (left == right) {
			return;
		}
		int mid = left + ((right - left) / 2);
		mergeSort(arr, left, mid);
		mergeSort(arr, mid + 1, right);

		merge(arr, left, mid, right);
	}

	private static void merge(int[] arr, int left, int mid, int right) {
		int[] temp = new int [right - left + 1];
		int l = left;
		int r = mid + 1;
		int index = 0;
		while(l <= mid && r <= right){
			temp[index++] = arr[l] < arr[r] ? arr[l++] :arr[r++];			
		}
		while(l <= mid){
			temp[index++] = arr[l++];
		}
		while(r <= right){
			temp[index++] = arr[r++];		
		}
		for(int i = 0; i < temp.length; i++){
			arr[left+i] = temp[i];
		}
		
	}	
}

7、堆排序(HeapSort)

1:將陣列構造成一個大頂堆;

2:將堆頂的資料與最後一個數進行交換,然後調整剩下的(n - 1)個數據,使其仍為大頂堆;

3:最後實現排序。

Tips:1.大頂堆指根結點大於葉子結點的完全二叉樹,整個演算法的實現在於如何構建大頂堆和如何調整,

2.完全二叉樹中,{(n - 1)/ 2 } 可以找到葉子結點的根結點在陣列中的索引,(n * 2 + 1)為根結點的左子樹結點在陣列中的結點位置,再加 1 則為右結點。

public class HeapSort {
	private static void insertHeap(int[] arr, int i) {//構建大頂堆
		while (arr[i] > arr[(i - 1) / 2]) {
			SwapArr.swap(arr, i, (i - 1) / 2);
			i = (i - 1) / 2;
		}
	}
	
	private static void heapAdjust(int[] arr,int index, int size){
		int left = index * 2 + 1;
		while(left < size){
			int largest = (left+1) < size && arr[left] < arr[left+1] ? left+1: left;
			if(arr[index] > arr[largest]){
				return;
			}
			SwapArr.swap(arr, index, largest);
			index = largest;
			left = index * 2 +1;
		}
	}
	
	public static void heapSort(int[] arr){
		if(arr == null || arr.length < 2){
			return ;
		}
		for(int i = 1; i < arr.length; i++){ //將陣列整理為大頂堆
			insertHeap(arr, i);
		}
		int size = arr.length - 1;
		while (size > 0){
			SwapArr.swap(arr, 0, size);//交換堆頂和最後一位
			heapAdjust(arr, 0, size--);//重新整理陣列形成大頂堆
		}
		
	}
}

8、桶排序(BucketSort)/基數排序(RadixSort)/計數排序(CountSort)

1:桶排序的實現方法為:找到陣列中的最大數max,建立(max + 1) 個“桶”(陣列),遍歷一遍待排序的陣列,將資料放入對應的“桶”,最後按順序遍歷一遍所有“桶”,將有資料的桶的編號按順序放入陣列中,最後陣列達到有序。

2:基數排序的實現方法有兩種:

MSD(最高位優先 Most Significant Digit first),找到陣列中資料最高位,對最高位進行排序,依次進行到個位數。

LSD(最低位優先 Least Significant Digit first),從個位數開始排序,直到最高位結束。

桶排序:

public class BucketSort {
	public static void bucketSort(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		int max = Integer.MIN_VALUE;
		for (int i : arr) {
			max = Math.max(max, i);
		}

		int[] bucket = new int[max + 1];
		for(int i : arr){
			bucket[i] ++;		
		}
		int i = 0;
		for(int j = 0; j < bucket.length;j++){
			while(bucket[j]-- >0){
				arr[i++] = j;
			}
		}
		
	}
}

基數排序:

public class RadixSort {
	public static void radixSort(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		radixSort(arr, 0, arr.length - 1, maxbit(arr));
	}

	private static void radixSort(int[] arr, int begin, int end, int digit) {
		int[] bucket = new int[end - begin + 1];
		final int radix = 10;
		int i, j = 0;
		int[] count = new int[radix];
		for (int d = 1; d <= digit; d++) {
			for(i = 0; i < radix ; i++){
				count[i] = 0;
			}
			for (i = begin; i <= end; i++) {
				j = getDigit(arr[i], d);
				count[j]++;
			}		
			for (i = 1; i < radix; i++) {//count用來歸位陣列,此處加上前一個是往後增加
				count[i] += count[i - 1];
			}		
			for (i = end; i >= begin; i--) {
				j = getDigit(arr[i], d);
				bucket[count[j] - 1] = arr[i];
				count[j] -- ;
			}
			for(i = begin ,j = 0; i<= end;i++,j++){
				arr[i] = bucket[j];
			}
		}
	}

	private static int maxbit(int[] arr) {
		int max = Integer.MIN_VALUE;
		int res = 0;
		for (int i : arr) {
			max = Math.max(max, i);
		}
		while (max != 0) {
			res++;
			max /= 10;
		}
		return res;
	}

	private static int getDigit(int num, int d) {
		return (num / (int) Math.pow(10, d - 1) % 10);
	}

}