這裏的排序都以升序爲例
1.插入排序
public static void insertSort(int[] array) {
for (int bound = 1;bound<array.length;bound++) {
int tmp = array[bound];
int cur = bound-1;
for (;cur>=0;cur--) {
if (array[cur]>tmp) {
array[cur+1] = array[cur];
}else {
break;
}
}
array[cur+1] = tmp;
}
}
從bound號下標元素開始,用一個值tmp記住該元素值,因爲自己不需要和自己比較,就從下標爲1開始,用bound元素和前面元素進行比較,發現比bound元素大的,就把該元素向後移一位,直到找到比bound元素小的,就把tmp插入到該元素的後面。這要前面就是排好序的區間,後面就是待排區間。
時間複雜度:O(n^2)
空間複雜度:O(1)
穩定性:穩定
2.希爾排序
public static void shellSort(int[] array) {
int gap = (array.length)/2;
while (gap>1) {
insertSortGap(array,gap);
gap = gap/2;
}
insertSortGap(array,1);
}
public static void insertSortGap(int[] array,int gap) {
for (int bound = gap;bound<array.length;bound++) {
int tmp = array[bound];
int cur = bound-gap;
for (;cur>=0;cur-=gap) {
if (array[cur]>tmp) {
array[cur+gap] = array[cur];
}else {
break;
}
}
array[cur+gap] = tmp;
}
}
希爾排序是插入排序的優化,就是分組後然後分別對每個組進行插入排序。分組呢就是指定一個數,每隔這個數取一個元素,作爲一組,一般的,這個gap取length/2,length/4,length/8.。。。直到gap =1在處理一波。根據插入排序,每次都要從下標爲1的元素開始,所以每組的下標爲1的元素是原數組下標爲gap的元素,然後原本對於插入排序要後移一位的,這裏都要移gap位,其他基本都一樣。
時間複雜度:O(n^1.3)
空間複雜度:O(1)
穩定性:不穩定
3.選擇排序
public static void selectSort(int[] array) {
for (int bound = 0; bound<array.length;bound++) {
for (int cur = bound;cur<array.length;cur++) {
if (array[cur]<array[bound]) {
swap(array,cur,bound);
}
}
}
}
private static void swap(int[] array, int cur, int bound) {
int tmp = array[cur];
array[cur] = array[bound];
array[bound] = tmp;
}
選擇排序是從0號下標開始,每次都從後面的元素中選出一個比當前元素小的進行交換,這樣最小的就排再了最前面。前面是已排序區間,後面是待排序區間。
時間複雜度:O(n^2)
空間複雜度:O(1)
穩定性:不穩定
4.堆排序
public static void heapSort(int[] array) {
createHeap(array);
int heapSize = array.length;
for (int i = 0;i<array.length-1;i++) {
swap(array,0,heapSize-1);
heapSize--;
shiftDown(array,heapSize,0);
}
}
private static void createHeap(int[] array) {
for (int i = (array.length-1-1)/2;i>=0;i--) {
shiftDown(array,array.length,i);
}
}
private static void swap(int[] array, int cur, int bound) {
int tmp = array[cur];
array[cur] = array[bound];
array[bound] = tmp;
}
private static void shiftDown(int[] array, int heapSize, int i) {
int parent = i;
int child = 2*parent+1;
while (child<heapSize) {
if (child+1<heapSize && array[child+1]>array[child]) {
child = child+1;
}
if (array[child]>array[parent]) {
swap(array,child,parent);
}else {
break;
}
parent = child;
child = 2*parent+1;
}
}
先用向下調整構造大堆,用一個數表示當前堆得大小,然後進去循環,每次把堆頂元素和最後一個元素進行交換,然後把除最後一個元素外(就是對堆的大小進行減減操作)的堆進行向下調整使其重新變爲大堆,這裏只要循環length-1就行了,因爲當堆剩下最後一個元素時就不需要進行任何操作了。
關於建堆操作,就從最後一個非葉子節點開始向下調整就行了。向下調整,從當前指定下標元素開始,爲父節點,左孩子下標爲乘以2加一,循環條件爲左孩子小標小於當前堆的大小,如果右孩子也小於堆的大小並且比左孩子大,就把右孩子下標給左孩子,保證左孩子一定是最大的,然後和父節點比大小進行適當交換,如果符合大堆要求,直接break就行了,然後把孩子節點下標賦給父節點,新的孩子節點再再新的父節點基礎上乘以2加一,進行下次循環。
時間複雜度:O(nlogn)
空間複雜度:O(1)
穩定性:不穩定
5.冒泡排序
public static void bubbleSort(int[] array) {
for (int bound = 0;bound<array.length;bound++) {
for (int cur = array.length-1;cur>bound;cur--) {
if (array[cur-1]>array[cur]) {
swap(array,cur-1,cur);
}
}
}
}
冒泡排序,從最後兩個元素開始,每次不符合升序,就交換兩個值,然後都往前移一位,再比較兩個值,這樣最小的就到最前面了。,注意cur的循環終止條件,是>,沒有等於號。這樣前面就是已排序區間,後面是待排序區間。
時間複雜度:O(n^2)
空間複雜度:O(1)
穩定性:穩定
6.快速排序
public static void quickSort(int[] array) {
quickSortHelper(array,0,array.length-1);
}
private static void quickSortHelper(int[] array, int left, int right) {
if (left>=right) {
return;
}
int index = partition(array,left,right);
quickSortHelper(array,left,index-1);
quickSortHelper(array,index+1,right);
}
private static int partition(int[] array, int left, int right) {
int baseValue = array[right];
int i = left;
int j = right;
while (i<j) {
while (i<j && array[i]<=baseValue) {
i++;
}
while (i<j&&array[j]>=baseValue) {
j--;
}
if (i<j) {
swap(array,i,j);
}
}
swap(array,i,right);
return i;
}
快速排序的核心操作是:partition
先找一個基準值,一般是最後一個或者第一個,如果找中間的,需要交換到最前面或者最後面。
找最後一個爲基準值的話,先從第一個開始從左往右找比基準值大的元素,再從最後一個開始從右往左找比基準值小的元素,然後交換兩個值,重複上述操作,知道前面的指針和後面的指針重合,這個數一定是大於基準值的,和基準值進行交換,然後再對重合位置的左右兩邊遞歸進行上述操作,完成整個排序。需要注意的是,當你基準值找的是第一個時,就需要先從右往左找,再從左往右找。
我們可以用一個輔助方法,去完成快速排序,傳入他要排序的區間,如果區間只有一個或者沒有元素,就不用進行處理直接返回,然後進行partition 操作完成第一次排序,並返回新的基準值的位置,接下來對左右區間進行遞歸處理。
對於partition 方法,我們找最後一個爲基準值,然後用i,j分別指向第一個和最後一個元素,判斷i,j是否重合,不重合就進入循環,從前找比基準值大的,從後找比基準值小的,進行交換,出循環後,此時,i,j重合,直接和基準值進行交換,最後返回此時基準值的位置。
時間複雜度:最壞-》O(n^2)
平均-》O(nlogn)
空間複雜度:最壞-》O(n)
平均-》O(logn)
穩定性:不穩定
快速排序非遞歸:
public static void quickSortByLoop(int[] array) {
Stack<Integer> stack = new Stack<>();
stack.push(0);
stack.push(array.length-1);
while (!stack.empty()) {
int right = stack.pop();
int left = stack.pop();
if (left >= right) {
continue;
}
int index = partition(array,left,right);
stack.push(index+1);
stack.push(right);
stack.push(left);
stack.push(index-1);
}
}
private static int partition(int[] array, int left, int right) {
int baseValue = array[right];
int i = left;
int j = right;
while (i < j) {
while (i<j && array[i] <= baseValue) {
i++;
}
while (i<j && array[j] >= baseValue) {
j--;
}
if (i < j) {
swap(array,i,j);
}
}
swap(array,i,right);
return i;
}
private static void swap(int[] array, int i, int j) {
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
非遞歸也是參考遞歸代碼進行編寫的,依舊需要用到棧,先把需要進行排序的區間進行入棧,如果棧非空進入循環,先後進行兩次出棧,如果區間裏只有一個元素或者沒有,進去下次循環,否則就進行partition操作,再把兩邊的區間分別入棧。
7.歸併排序
public static void mergeSort(int[] array) {
mergeSortHelper(array,0,array.length);
}
private static void mergeSortHelper(int[] array, int left, int right) {
if (right-left <= 1) { //前閉後開區間
return;
}
int mid = (left+right)/2;
mergeSortHelper(array,left,mid);
mergeSortHelper(array,mid,right);
merge(array,left,mid,right);
}
private static void merge(int[] array, int left, int mid, int right) {
int cur1 = left;
int cur2 = mid;
int[] output = new int[right-left];
int outputIndex = 0;
while (cur1 < mid && cur2 < right) {
if (array[cur1] <= array[cur2]) { // <=保證穩定性
output[outputIndex] = array[cur1];
cur1++;
outputIndex++;
}else {
output[outputIndex] = array[cur2];
cur2++;
outputIndex++;
}
}
while (cur1<mid) {
output[outputIndex] = array[cur1];
cur1++;
outputIndex++;
}
while (cur2<right) {
output[outputIndex] = array[cur2];
cur2++;
outputIndex++;
}
for (int i = 0;i<right-left;i++) {
array[left+i] = output[i];
}
}
歸併排序,任然需要一個輔助方法去完成排序,傳入要排序的區間的參數,此處的區間是前閉後開的,所以判斷是否只有一個元素或者沒有元素的時候需要right-left<=1去判斷,然後依靠中間下標分爲兩組,在進行後序遍歷遞歸處理,最後的“訪問節點操作”就是排序操作再用一個方法去進行實現。
需要傳入前中後三個指針參數,left和mid就是分成的兩組的首元素,歸併需要額外的空間去進行存儲,所以需要一個可以足夠存儲傳進來數組的空間,創建一個right-left大小的數組,用一個變量表示當前數組裏有幾個元素,當兩個數組都不爲空時,判斷left和mid指向的當前元素的大小,把小的傳進新建的數組,然後指針向後移動一位,進行比較,最後出循環判斷兩個數組的其中一個是否還有元素剩餘,把剩餘的全部加入新建數組裏,最後還需要注意把新建數組裏的元素全部按順序再傳回到原數組裏。這樣整個代碼就完成了。
時間複雜度:O(nlogn)
空間複雜度:O(n)
穩定性:穩定