最近繼續重溫下數據結構中的排序,其實之前在三月份得時候也有複習過了一遍,其實前人已經總結得很不錯了,這次重新再過一遍,重新手寫一下代碼:
以下代碼的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中的時候只有一個指針,如果排序的數組重複元素很多的情況下,還是會使得劃分極其的不均勻。
改進版快速排序(推薦):(注意區分與上面的不同)
- 寫一個切分方法partition
- 然後每一次切分完成後遞歸調用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~