常見排序代碼總結(Java語言實現)

最近繼續重溫下數據結構中的排序,其實之前在三月份得時候也有複習過了一遍,其實前人已經總結得很不錯了,這次重新再過一遍,重新手寫一下代碼:
在這裏插入圖片描述
以下代碼的swap代碼都是基於如下代碼:
使用位運算,更快更有逼格~

private static swap(int [] arr, int i, int j) {
	arr[i] = arr[i] ^ arr[j];
	arr[j] = arr[i] ^ arr[j];
	arr[i] = arr[i] ^ arr[j];
}

冒泡排序:

  • 比較相鄰的元素。如果第一個比第二個大,就交換他們兩個。
  • 對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最後一對。這步做完後,最後的元素會是最大的數。
  • 針對所有的元素重複以上的步驟,除了最後一個。
  • 持續每次對越來越少的元素重複上面的步驟,直到沒有任何一對數字需要比較。
public static void bubbleSort (int [] array) {
	for (int i = array.length - 1; i > 0; i--) {
		boolean flag = true;
		for (int j = 0; j < i; j++) {
			if (array[j] > array[j+1]) {
				swap(arr,j,j+1);
				flag = false;
			}
		}
		if (flag) break;
	}
}

選擇排序:

  • 在序列中找到最小(大)元素,放到序列的起始位置作爲已排序序列;
  • 再從剩餘未排序元素中繼續尋找最小(大)元素,放到已排序序列的末尾。
public void selectSort(int [] arr) {
	for (int i = 0; i < arr.length; i++) {
		int minIndex = i;
		for (int j = i + 1; j < arr.length; j++) {
			minIndex = arr[j] < arr[minIndex] ? j : minIndex;
		}
		swap(arr,i,minIdex);
	}
}

插入排序

  • 將第一待排序序列第一個元素看做一個有序序列,把第二個元素到最後一個元素當成是未排序序列。
  • 從頭到尾依次掃描未排序序列,將掃描到的每個元素插入有序序列的適當位置。(如果待插入的元素與有序序列中的某個元素相等,則將待插入元素插入到相等元素的後面。)
public static void insertSort(int [] arr) {
	for (int i = 1; i < arr.length; i++) {
		int tmp = arr[i];
		for (int j = i - 1; j >= 0 && tmp < arr[j]; j--) {
			arr[j+1] = arr[j];
		}
		arr[j+1] = tmp;
	}
}

希爾排序

希爾排序是更高效的插入排序,它把數組分成幾塊,每一塊進行一個插入排序;
而分塊的依據在於增量的選擇分好塊之後,從gap開始到n,每一組和自己組內的元素進行插入排序;
這裏以gap爲2爲例。

public void shellSort(int [] arr) {
	//增量序列,這裏爲2
	for (gap = arr.length; gap > 0; gap /= 2){
		//從數組第gap個元素開始
		for (int i = gap; i < gap; i++) {
			int tmp = arr[i],j;
			//每個元素與自己組內的數據進行直接插入排序
			for (j = i - gap; j >= 0 && tmp < arr[j]; j-= gap) {
				arr[j+gap] = arr[j];
			}
			arr[j] = tmp;
		}
	}
}

快速排序

原始快速排序:

static void quickSort(int [] arr) {
	if (arr == null || arr.length <= 1){
		return;
	}
	quick(arr,0,arr.length - 1);
}

static void quick(int [] arr, int L, int R){
	if (L >= R) return;
	int p = partition(arr,L,R);
	quick(arr,L,p - 1);
	quick(arr,p + 1,R);
	
}
// 返回pivot,使得左邊都小於切分數,右邊都大於切分數
static int partition(int [] arr, int L, int R) {
	//獲取第一個爲切分元素
	int num = arr[L];
	int pivot = L;
	for (int i = L + 1; i <= R; i++) {
		if (arr[i] < num) {
			swap(arr,i,++pivot);
		}
	}
	swap(arr,L,pivot);
	return pivot;
	
}

這種情況的快速排序有兩種弊端,一是每次選第一個爲切分元素,如果是最小值或者最大值,這樣比較不好,還有就是說上面那種在partition中的時候只有一個指針,如果排序的數組重複元素很多的情況下,還是會使得劃分極其的不均勻。

改進版快速排序(推薦):(注意區分與上面的不同)

  1. 寫一個切分方法partition
  2. 然後每一次切分完成後遞歸調用quick方法,縮小區間。
static void quickSort(int [] arr) {
	if (arr.length <= 1) return;
	quick(arr,0,arr.length - 1);
}
static void quick(int [] arr,int L, int R) {
	if (L >= R) return;
	// 隨機選取
	swap(arr,L,L+(int)(Math.random()*(R - L + 1)));
	int p = partition(arr,L,R);
	quick(arr, L, p - 1);
	quick(arr, p + 1, R);
}
static int partition(int [] arr,int L, int R) {
	int num = arr[L];
	//兩個節點
	int less = L + 1, more = R;
	while (true) {
		while (less < R && arr[less] < num) less++;
		while (more > L) && arr[more] > num) more--;
		if (less >= more) break;
		swap(arr, less++, more--);
	}
	//最後跟L交換的是more
	swap(arr,L,more);
	return more;
}

堆排序

堆排序的過程是一個反覆調整堆的過程:

  • 利用數組建立一個大根堆(父親比孩子的值大);
  • 把堆頂元素和堆尾元素互換;
  • 把堆(無序區)的尺寸縮小1,並下沉;
  • 重複步驟,直到堆的大小爲1;
private void HeapSort(int [] arr) {
	int N = arr.length - 1;
	for (int i = N / 2; i >= 1; i--) {
		sink(arr,i,N);
	}
	while (N > 1){
		swap(arr,1,N--);
		sink(arr,1,N);
	}
	
}
//下沉
private void sink(int [] arr,int k,int N) {
	while (2 * k <= N) {
		int j = 2 * k;
		//與兩個子節點中最大的節點進行交換
		if (j < N && arr[j] < arr[j+1]) j++;
		if (arr[k] >= arr[j]) {
			break;
		}
		// 下沉
		swap(arr,k,j)
		
		k = j;	
	}
}

歸併排序

歸併排序採用分治的思想,這裏用遞歸實現,首先先遞歸拆分子序列,然後將兩個已經有序的子序列合併成一個有序序列,需要準備一個額外的數組(temp),使其大小爲原來數組長度,該空間用來存放合併後的序列。
關鍵代碼是 merge那一部分

private void mergeSort(int [] arr) {
	//創建臨時數組
	int temp[] = new int[arr.length];
	sort(arr,0,arr.length - 1,temp);
}
private void sort(int []arr,int l,int r,int [] temp) {
	if (l < r){
		int mid = (l + r) / 2;
		//遞歸,左邊歸併排序,使得左子序列有序
		sort(arr,l,mid,temp);
		//右邊歸併排序,使得右子序列有序
		sort(arr,mid+1,r,temp);
		//將兩個有序子數組合並操作
		merge(arr,l,mid,r,temp);
	}
}
//關鍵代碼
private void merge(int []arr,int l,int mid,int r,int [] temp) {
	int i = l;
	int j = mid + 1;
	// 臨時變量
	int t = 0;
	while (i <= mid && j<= r) {
		if (arr[i] <= arr[j]) {
			temp[t++] = arr[i++];
		} else {
			temp[t++] = arr[j++];
		}
	}
	//將左邊剩餘元素填充進temp中
	while (i <= mid) {
		temp[t++] = arr[i++];
	}
	//將右邊剩餘元素填充進temp中
	while (j <= r) {
		temp[t++] = arr[j++];
	}
	
	//將temp中的元素全部拷貝到原數組中
	t = 0;
	while (l <= r) {
		arr[l++] = temp[t++];
	}
}

這裏七個排序算是比較常見的排序方式了,要好好熟悉,尤其是快排、堆排,平時刷題偶爾也可以用到,比如說快排的partition可以用來求無序數組中的第K大的數字,堆排序用來做優先隊列可以用來求最大值最小值等等。
end~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章