一、 穩定性
穩定性是指待排序的序列中有兩個或者兩個以上相同的項,排序前和排序後,看這些相同的項的相對位置有沒有沒發生變化,如果沒有發生變化,就是穩定的;如果發生變化,就是不穩定的。
二、 排序分類以及複雜度
2.1、 插入類排序
2.1.1、直接插入排序
最壞時間複雜度:O(n^2)
最好時間複雜度:O(n)
平均時間複雜度:O(n^2)
空間複雜度:O(1)
2.1.2、折半插入排序
最壞時間複雜度:O(n^2)
最好時間複雜度:O(n)
平均時間複雜度:O(n^2)
空間複雜度:O(1)
2.1.3、希爾排序
平均時間複雜度:O(nlogn)
空間複雜度:O(1)
2.2、 交換類排序
2.2.1、冒泡排序
最壞時間複雜度:O(n^2)
最好時間複雜度:O(n)
平均時間複雜度:O(n^2)
空間複雜度:O(1)
2.2.2、快速排序
最壞時間複雜度:O(n^2)
最好時間複雜度:O(nlogn)
平均時間複雜度:O(nlogn)
空間複雜度:O(logn)
2.3、 選擇類排序
2.3.1、簡單選擇排序
最壞時間複雜度:O(n^2)
最好時間複雜度:O(n^2)
平均時間複雜度:O(n^2)
空間複雜度:O(1)
2.3.2、堆排序
最壞時間複雜度:O(nlogn)
最好時間複雜度:0(nlogn)
平均時間複雜度:O(nlogn)
空間複雜度:O(1)
2.4、 歸併類排序
2.4.1、二路歸併排序
最壞時間複雜度:O(nlogn)
最好時間複雜度:O(nlogn)
平均時間複雜度:O(nlogn)
空間複雜度:O(n)
2.5、 基數類排序
最壞時間複雜度:O(d(n+rd))
最好時間複雜度:
平均時間複雜度:O(d(n+rd))
空間複雜度:O(rd)
三、 各類排序算法Java代碼
3.1、 直接插入排序
介紹和思想
介紹:插入排序基本操作就是將一個數據插入到已經排好序的有序數據中,從而得到一個新的、個數加一的有序數據,算法適用於少量數據的排序,時間複雜度爲O(n^2)。是穩定的排序方法。
思想:每步將一個待排序的紀錄,按其關鍵碼值的大小插入前面已經排序的文件中適當位置上,直到全部插入完爲止。
算法代碼
public static <T extends Comparable<? super T>> void insertionSort(T[] a) {
int j;
for (int i = 1; i < a.length; i++) {
T tmp = a[i];
for (j = i; j > 0 && tmp.compareTo(a[j-1])<0; j--) {
a[j] = a[j-1];
}
a[j] = tmp;
}
}
3.2、 折半插入排序
介紹和思想
折半插入排序的基本思想和直接插入排序一樣,區別在於尋找插入位置的方法不同,折半插入排序是採用折半查找方法類尋找插入位置的。
算法代碼
public static void binaryInsertSort(int[] a) {
for(int i = 1;i < a.length;i++) {
int temp = a[i];
int low = 0;
int high = i-1;
while(low<=high) {
int mid = (low+high)/2;
if(temp < a[mid]) {
high = mid-1;
}else {
low = mid+1;
}
}
for(int j = i;j>=low+1;j--) {
a[j] = a[j-1];
}
a[low] = temp;
}
}
3.3、 冒泡排序
算法介紹和思想
冒泡排序是通過一系列的“交換”動作完成的,是交換類排序的一種。
首先第一個記錄和第二個記錄比較,如果第一個大,則兩者交換,否則不交換;
然後第二個記錄和第三個記錄比較,如果第二個大,則兩者交換,否則不交換……一直按這種方式進行下去,最終最大的那個記錄被交換到了最後,一趟冒泡排序完成。
然後進行第二趟冒泡排序,對前n-1個記錄進行同樣的操作,使關鍵字次大的記錄被安置到第n-1個記錄的位置上……一直這樣進行下去,直到“在一趟排序過程中沒有進行過交換記錄的操作”,這也是冒泡排序算法的結束條件。
算法代碼public static <T extends Comparable<? super T>> void bubbleSort(T[] a) {
int i,j,flag;
T temp;
for(i=a.length-1;i>0;i--) {//最多進行a.length-1趟排序
flag = 0;
for(j=0;j<i;j++) {////對當前無序區間a[0......i]進行排序
if(a[j].compareTo(a[j+1])>0) {//大值交換到後面
temp = a[j+1];
a[j+1] = a[j];
a[j] = temp;
flag = 1;
}
}
if(flag == 0) {//結束條件:某一趟排序是否有交換
return;
}
}
}
3.4、 快速排序
算法介紹和思想
快速排序是對冒泡排序的一種改進,它也是“交換”類排序的一種。它的基本思想是,通過一趟排序將待排記錄分割成獨立的兩部分,其中一部分記錄的關鍵字均比另一部分記錄的關鍵字小,則可分別對這兩部分記錄繼續進行排序,以達到整個序列有序。
算法步驟和過程
一趟快速排序的具體做法是:附設兩個指針low和high,它們的初值分別爲low和high,設樞軸記錄的關鍵字爲pivotkey,則首先從high所指位置起向前搜索找到第一個關鍵字小於pivotkey的記錄和樞軸記錄互相交換,然後從low所指位置起向後搜索,找到第一個關鍵字大於pivotkey的記錄和樞軸記錄互相交換,重複這兩步直至low=high爲止。
實質上,快速排序就三個步驟:
第一步:將待排序序列一分爲二
第二步:對小於pivotkey的低子表遞歸排序
第三步:對大於pivotkey的高子表遞歸排序
算法代碼/**
* 第一步:將待排序序列一分爲二
* 第二步: 對低子表遞歸排序
* 第三步:對高子表遞歸排序
* @author 卡羅-晨
*/
public class QuickSort {
/**
* 將序列一分爲二,並返回樞軸位置
*/
private static <T extends Comparable<? super T>> int partition(T[] a,int low,int high) {
T pivotkey = a[low];
while(low<high) {
while(low<high && a[high].compareTo(pivotkey)>=0) --high;
a[low] = a[high];
while(low<high && a[low].compareTo(pivotkey)<=0) ++low;
a[high] = a[low];
}
a[low] = pivotkey;
return low;
}
/**
* 快速排序三步走
*/
private static <T extends Comparable<? super T>> void qSort(T[] a,int low,int high) {
if(low<high) {
int pivotloc = partition(a, low, high);
qSort(a, low, pivotloc-1);
qSort(a, pivotloc+1, high);
}
}
public static <T extends Comparable<? super T>> void quickSort(T[] a) {
qSort(a, 0, a.length-1);
}
}
一個方法實現快速排序
public static void quickSort(int[] a,int low,int high) {
int temp;
int i = low,j = high;
if(low < high) {
temp = a[low];
while(i != j) {
while(j>i&&a[j]>=temp) --j;
if(i<j) {
a[i] = a[j];
++i;
}
while(i<j&&a[i]<=temp) ++i;
if(i<j) {
a[j] = a[i];
--j;
}
a[i] = temp;
quickSort(a, low, i-1);
quickSort(a, i+1, high);
}
}
}
3.5、 簡單選擇排序
算法介紹和思想
選擇類排序的主要動作是“選擇”,簡單選擇排序採用最簡單的選擇方式,從頭至尾掃描序列,找出最小的一個記錄,和第一個記錄交換,接着從剩下的記錄中繼續這種選擇和交換嗎,最終使序列有序。
算法代碼
public static void selectSort(int[] a) {
int minIndex;
int temp;
for(int i = 0;i<a.length;i++) {
minIndex = i;
for(int j = i+1;j<a.length;j++) {
if(a[j]<a[minIndex]) {
minIndex = j;
}
}
if(minIndex != i) {
temp = a[i];
a[i] = a[minIndex];
a[minIndex] = temp;
}
}
}
3.6、 堆排序
算法介紹
堆是一種數據結構,可以把堆看成一棵完全二叉樹,這棵完全二叉樹滿足:任何一個非葉結點的值都不大於(或不小於)其左右孩子結點的值。若父親大孩子小,叫大頂堆;若父親小孩子大,則這樣的堆叫做小頂堆。
二叉樹:每個結點最多隻有兩棵子樹,即二叉樹中結點的度只能爲0、1、2。子樹有左右之分,不能顛倒。
滿二叉樹:在一棵二叉樹中,如果所有的分支結點都有左孩子和右孩子結點,並且葉子結點都集中在二叉樹的最下一層。另一種定義:一棵深度爲k且有 個結點的二叉樹稱爲滿二叉樹。
完全二叉樹:深度爲k,有n個結點的二叉樹,當且僅當其每一個結點都與深度爲k的滿二叉樹中編號從1至n的結點一一對應時,稱之爲完全二叉樹。通俗來講,一棵完全二叉樹是由一棵滿二叉樹從右至左從下而上,挨個刪除結點所得到的。
堆排序的思想:將一個無序序列調整爲一個堆,就可以找出這個序列的最大(或最小)值,然後將找出的這個值交換到序列的最後(或最前),這樣有序序列元素增加·個,無序序列中元素減少1個,對新的無序序列重複這樣的操作,就實現了排序。
堆排序的過程(自述):
(1)首先,建堆:原始序列對應一個還不是堆的完全二叉樹。對非葉子節點從右至左,從下至上,進行下濾操作,得到一個大頂堆。
待下濾操作的第一個非葉子節點計算方法:a.length/2-1
下濾操作思想:首先判斷非葉子節點是否爲兩個孩子,兩個孩子誰更大,指針指向大孩子;接着,判斷非葉子節點和指針指向的大孩子誰更大,孩子大則交換。
(2)接着,排序:將大頂堆堆頂和無序序列最後一個記錄交換,有序+1記錄,無序-1記錄。
(3)然後,將交換後的完全二叉樹,從堆頂下濾,將其調整爲一個大頂堆,重複上述過程。直到樹中只剩下1個節點時排序完成。
堆排序執行過程
l 將原始序列化爲一個完全二叉樹。
l 從無序序列所確定的完全二叉樹的第一個非葉子結點開始,從右至左,從下至上,對每一個結點進行調整,最終將得到一個大頂堆。
對結點的調整方法:將當前結點(假設爲a)的值與其孩子結點進行比較,如果存在大於a值的孩子結點,則從中選出最大的一個與a交換。當a來到下一層的時候重複上述過程,直到a的孩子結點值都小於a的值爲止。
l 將當前無序序列中第一個元素,反映在樹中是根節點(假設爲a)與無序序列中最後一個元素交換(假設爲b)。a進入有序序列,到達最終位置。無序序列中元素減少1個,有序序列中元素增加1個。此時只有結點b可能不滿足堆的定義,對其進行調整。
l 重複3)中的過程,知道無序序列中的元素剩下1個時排序結束
算法代碼(Java)/**
* 堆排序
* @author 卡羅-晨
*/
public class HeapSort {
public static int leftChild(int i) {
return 2*i+1;
}
private static <T extends Comparable<? super T>> void percDown(T[] a,int i,int n) {
int child;
T tmp;
for (tmp = a[i];leftChild(i)<n;i=child) {
child = leftChild(i);
if(child != n-1 && a[child].compareTo(a[child+1])<0) {
child++;
}
if(tmp.compareTo(a[child])<0) {
a[i] = a[child];
}else
break;
}
a[i] = tmp;
}
/**
* 標準堆排序
* @param a可比較的集合
*/
public static <T extends Comparable<? super T>> void heapsort(T[] a) {
//建立堆
for(int i=a.length/2-1;i>=0;i--) {
percDown(a, i, a.length);
}
for(int i=a.length-1;i>0;i--) {
T tmp = a[0];
a[0] = a[i];
a[i] = tmp;
percDown(a, 0, i);
}
}
}
3.7、 二路歸併排序
算法描述
歸併排序(MERGE-SORT)是建立在歸併操作上的一種有效的排序算法,該算法是採用分治法(Divide and Conquer)的一個非常典型的應用。將已有序的子序列合併,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。
若將兩個有序表合併成一個有序表,稱爲二路歸併。
算法思想
1、 劃分:將待排序序列劃分爲兩個長度相等的子序列。
2、 求解子問題:分別對這兩個子序列進行歸併排序,得到兩個有序子序列。
3、 合併:將兩個有序子序列合併成一個有序序列。
算法核心步驟
核心步驟就一下三步:
l 1、歸併排序前半個子序列
l 2、歸併排序後半個子序列
l 3、合併兩個已排序的子序列
其中,前兩個步驟的是一樣的方法mergeSort()進行遞歸,第三步是另一個方法merge()。這個算法中基本的操作就是第三步合併兩個已排序的表。因爲這兩個表是已排序的,所以若將輸出放到第三個表中,則該算法可以通過對輸入數據一趟排序來完成。基本的合併算法是取兩個輸入數組A和B,一個輸出數組C,以及3個計數器Actr、Bctr、Cctr,它們初始置於對應數組的開始端。A[Actr]和B[Bctr]中較小者被拷貝到C中的下一個位置,相關的計數器向前推進一步。當兩個輸入表有一個用完的時候,則將另一個表中剩餘部分拷貝到C中。
實現代碼public class MergeSortExample {
public static void mergeSort(int[] a) {
int[] tempArray = new int[a.length];
mergeSort(a, tempArray, 0, a.length - 1);
}
private static void mergeSort(int[] a, int[] tempArray, int left, int right) {
if (left < right) {
int center = (left + right) / 2;
mergeSort(a, tempArray, left, center);
mergeSort(a, tempArray, center + 1, right);
merge(a, tempArray, left, center + 1, right);
}
}
private static void merge(int[] a, int[] tempArray, int leftPos, int rightPos, int rightEnd) {
int leftEnd = rightPos-1;
int temPos = leftPos;
int numElements = rightEnd-leftPos+1;
while(leftPos<=leftEnd && rightPos<=rightEnd) {
if(a[leftPos] <= a[rightPos]) {
tempArray[temPos++] = a[leftPos++];
}else {
tempArray[temPos++] = a[rightPos++];
}
}
while(leftPos<=leftEnd) {
tempArray[temPos++] = a[leftPos++];
}
while(rightPos <= rightEnd) {
tempArray[temPos++] = a[rightPos++];
}
for(int i=0;i<numElements;i++,rightEnd--) {
a[rightEnd] = tempArray[rightEnd];
}
}
}
merge()方法很精巧。如果對merge的每個遞歸調用均局部聲明一個臨時數組,那麼在任一時刻就可能有個臨時數組處在活動期。精密的考察表明,由於merge是mergeSort的最後一行,因此在任一時刻只需要一個臨時數組在活動,而且這個臨時數組可以在public型的mergeSort驅動程序中建立。