簡介
本篇文章總結一下最近學習的排序算法,提煉出其思想及不同之處。有歸併排序,快速排序,堆排序以及冒泡排序
歸併排序(Merging Sort)
- 歸併是指將兩個或兩個以上的有序表組合成一個新的有序表。
- 歸併排序是指把無序的的待排序序列分解成若干個有序子序列,並把有序子序列合併爲整體有序序列的過程。
- 長度爲1的序列是有序的。
採用兩兩分解和歸併的策略簡單易行,這樣的歸併排序稱爲2-路歸併排序。
public static void sort(int[] arr) {
if (arr == null || arr.length == 0)
return;
int len = arr.length;
int[] temp = new int[len];
mergeSort(arr, 0, len - 1, temp);
}
public static void mergeSort(int[] arr, int first, int last, int[] temp) {
// 條件
if (first < last) {
int mid = (first + last) / 2;
mergeSort(arr, first, mid, temp);
mergeSort(arr, mid + 1, last, temp);
mergeArray(arr, first, mid, last, temp);
}
}
public static void mergeArray(int[] arr, int first, int mid, int last, int[] temp) {
int i = first, j = mid + 1;
int index = first;
while (i <= mid && j <= last) {
// 排序條件
if (arr[i] > arr[j])
temp[index++] = arr[i++];
else
temp[index++] = arr[j++];
}
while (i <= mid)
temp[index++] = arr[i++];
while (j <= last)
temp[index++] = arr[j++];
for (int k = first; k <= last; k++) {
arr[k] = temp[k];
}
}
另一種寫法:
// 將相鄰有序的a[i..m]和a[m+1..n] 歸併到b[i..n] 包含 n
void Merge(int a[], int i,int m, int n, int b[]){
int k,j;
for(k = i, j = m+1; k <= m && j <= n;k++){
if(a[i] < a[j]){
b[k] = a[i++];
}else{
b[k] = a[j++];
}
}
while(i<=m) b[k++] = a[i++];
while(j<=n) b[k++] = a[j++];
}
// i:遞歸的層次(第一層爲0)-- 用於指示在MSort當前調用層次中a,b數組的具體作用:
// 當i爲奇數時,則從a[s..t]歸併至b[s..t]
// 否則從b[s..t]歸併至a[s..t]
void MSort(int a[], int b[], int i,int s,int t){
int m;
if( s == t ){
if( 1 == i%2 ) b[s] = a[s];
}else{
m = (s+t)/2;
MSort(a,b,i+1,s,m);
MSort(a,b,i+1,m+1,t);
if( 1 == i%2 ) Merge(a,s,m,t,b);
else Merge(b,s,m,t,a);
}
}
void MergeSort(int a[],int len){
int b[len];
MSort(a,b,0,0,len-1);
free(b); // 釋放資源
}
簡單分析:
整個歸併排序過程需要進行
歸併排序是穩定排序
快速排序(Quick Sort)
最簡單的交換排序是冒泡排序,而快速排序是對冒泡排序的改進。
首先從待排序列中選定一個關鍵字,稱爲樞軸,通過關鍵字與樞軸的比較將待排序的序列劃分爲位於樞軸前後的兩個子序列,其中樞軸之前的子序列的所有關鍵字都不大於樞軸,樞軸之後的子序列都不小於樞軸,此時樞軸已經到位,再按同樣的方法對這兩個子序列分別遞歸進行快速排序,最終使得整個序列有序
快速排序的一次劃分具體過程:
- 將位標low指向待排記錄的第一個記錄(同時是樞紐記錄),high指向最後一個記錄,並將樞軸記錄複製至指向的0號閒置單元。
- 首先將high所指位置向前移動,直至找到第一個比樞軸記錄關鍵字小的記錄並複製至pivotkey所指位置。
- 然後將low向後移動,找到第一個比樞軸記錄關鍵字大的記錄並將其複製至high所指位置。
- 直至low與high相等時,將樞軸記錄複製至low所指的位置
private static void qSort(int[] arr) {
if (arr == null || arr.length == 0)
return;
qSort(arr, 0, arr.length - 1);
}
public static void qSort(int[] arr, int first, int last) {
// 長度大於一
if (first < last) {
int pKey = partition(arr, first, last);
// 記得pKey位置已經不用再參與了,所以pKey - 1 和 pKey + 1
qSort(arr, first, pKey - 1);
qSort(arr, pKey + 1, last);
System.out.println("key : " + pKey);
} else {
System.out.println("first > last");
}
}
public static int partition(int[] arr, int first, int last) {
int key = arr[first];
while (first < last) {
// 注意這裏的>= 和 <=
while (first < last && arr[last] >= key)
last--;
arr[first] = arr[last];
while (first < last && arr[first] <= key)
first++;
arr[last] = arr[first];
}
arr[first] = key;
return first;
}
爲避免樞軸關鍵字爲最大或者最小的情況,可採用“三者取中”的方法,即以a[s]、a[t]、a[(s+t)/2]三者中關鍵字的大小居中的記錄爲樞軸,並與a[s]互換
堆排序之堆的基本操作
堆是一類完全二叉樹,若堆中所有非葉子結點均不大於其左右子節點,則稱爲小頂堆;若堆中所有非葉子結點均不小於其左右子節點,則稱爲大頂堆
堆的結構類型
typedef struct{
int *rcd; // 堆基址,0號但願不可用
int n; // 堆長度
int size; // 堆容器
int tag; // 小頂堆和大頂堆的標誌
int (*prior)(int a,int b); // 函數變量,用於關鍵字優先級比較
} Heap;
int greatPrior(int x,int y) { return x>=y; }
int lessPrior(int x,int y) { return x<=y; }
- 篩選
- 堆的篩選操作,對堆中指定的pos結點爲根的子樹進行堆的特性的維護,其前提是pos結點的左右子樹均滿足堆特性
- 過程:將pos結點與左右孩子優先者比較,若pos結點優先則結束;否則pos結點與左右孩子優先者交換位置,pos位標下移,重複以上步驟。
int SwapHeapElem(Heap &H,int i, int j){
if( i < 0 || i > H.n || j < 0 || j > H.n ) return 0;
int t;
t = H.rcd[i];
H.rcd[i] = H.rcd[j];
H.rcd[j] = t;
return 1;
}
// 堆的篩選操作,對堆中指定的pos結點爲根的子樹進行堆的特性的維護,其前提是pos結點的左右子樹均滿足堆特性
void ShiftDown(Heap &H,int pos){
int c, rc;
while(pos<=H.n/2){
c = pos*2;
rc = pos*2+1;
if(rc<=H.n && H.prior(H.rcd[rc],H.rcd[c])) c = rc; // 這裏注意函數的比較元素位置
if(H.prior(H.rcd[pos],H.rcd[c]))
return ;
SwapHeapElem(H,pos,c);
pos = c;
}
}
- 刪除堆頂結點
- 過程:1、取出堆頂結點;2、將堆頂結點與堆位結點交換位置,並將堆長度減1;3、對堆頂結點進行篩選
int RemoveFirsHeap(Heap &H, int &e){
if(H.n<=0) return 0;
e = H.rcd[1];
SwapHeapElem(H,1,H.n);
if(H.n>1) ShiftDown(H,1);
return 1;
}
- 建堆操作
void MakeHeap(Heap &H,int a[],int n,int size,int tag,int (*prior)(int a,int b)){
H.rcd = a;
H.n = n;
H.size = size;
H.tag = tag;
H.prior = prior;
int i;
// 葉子結點滿足堆特性,故不需要篩選
for( i = H.n/2; i > 0; i -- ){
ShiftDown(H,i);
}
}
堆排序(Heap Sort)
- 利用堆的特性進行排序,大頂堆可以進行升序排序,小頂堆可以進行降序排序。首先將待排序序列建成一個大頂堆,使得堆頂結點最大,將堆頂結點與堆尾交換位置,堆長度減1,調整剩餘結點爲堆,得到次大值結點。重複這一過程,即可得到一個升序序列
void HeapSort(int a[],int n){
Heap H;
MakeHeap(H,a,n,n+2,1,lessPrior);
int e;
for( int i = H.n; i > 0; i -- ){
if(RemoveFirsHeap(H,e) )
printf("%d ",e);
}
}
冒泡排序(Bubble Sort)
- 在第一趟冒泡過程中,首先將比較第一和第二個記錄,如果爲逆序,則交換位置。然後在比較第二和第三記錄,依次操作,直至比較第n-1和第n個記錄爲止,此時第n個記錄最大。第二趟冒泡將次大的記錄交換至n-1位置,直至所有記錄有序。
void BubbleSort(int a[], int len){
int t;
int i, j;
// i表示已經排序的個數
for( i = 0; i < len; i ++ ){
// 開始冒泡到len-i,len-i之後表示已經達到最值
for( j = 1; j < len-i; j ++ ){
if( a[j-1] < a[j] ){
t = a[j-1];
a[j-1] = a[j];
a[j] = t;
}
}
}
}
改進:
void BubbleSort(int a[], int len){
int t;
int i, j, flag;
flag = -1;
// i表示已經排到序的個數,(及排到最尾端的個數)
// flag表示a[flag-1,flag..len-1]已經爲有序序列,所以flag之後的爲不用再排序序列
for( i = 0; i < len; i ++ ){
if( flag != -1 ){
i = len - flag; // 表示 已經排到序的個數
flag = -1;
}
for( j = 1; j < len-i; j ++ ){
if( a[j-1] > a[j] ){
flag = j;
t = a[j-1];
a[j-1] = a[j];
a[j] = t;
}
}
}
}
其他學習鏈接冒泡排序的三種實現