交換排序
交換排序的基本思想:兩兩比較待排序元素的關鍵字,發現兩個元素的次序相反時即進行交換,直到沒有反序的元素爲止。冒泡排序是一種典型的交換排序的方法,而快速排序是通過交換方式以基準元素爲分界線將待排數據序列一分爲二的。
冒泡排序
- 基本思想
冒泡排序在衆多排序算法中算比較簡單的一個。在要排序的一組數中,對當前還未排好序的範圍內的全部數,自上而下對相鄰的兩個數依次進行比較和調整,讓較大的數往下沉,較小的往上冒(反之亦可)。即:每當兩相鄰的關鍵字比較後發現它們的排序與排序要求相反時,就將它們互換。 使得經過一次冒泡排序後,關鍵字最小(最大的元素到達最上端)。接着再在剩下的元素中找關鍵字次小(次大)的元素,並把它換在第二個位置上。依次類推,一直到所有元素都有序爲止。 - 例子
{10,6,5,3,8,9,1,4,2,7}
第一次
10,6,5,3,8,9,1,4,2,7——比較關鍵字 2,7
10,6,5,3,8,9,1,2,4,7——比較關鍵字 4,2 交換
10,6,5,3,8,9,1,2,4,7——比較關鍵字 1,2
10,6,5,3,8,1,9,2,4,7——比較關鍵字 9,1 交換
10,6,5,3,1,8,9,2,4,7——比較關鍵字 8,1 交換
10,6,5,1,3,8,9,2,4,7——比較關鍵字 3,1 交換
10,6,1,5,3,8,9,2,4,7——比較關鍵字 5,1 交換
10,1,6,5,3,8,9,2,4,7——比較關鍵字 6,1 交換
1,10,6,5,3,8,9,2,4,7——比較關鍵字 10,1 交換
第二次排序結果: 1,2,10,6,5,3,8,9,4,7
第三次排序結果: 1,2,3,10,6,5,4,8,9,7
第四次排序結果: 1,2,3,4,10,6,5,7,8,9
第五次排序結果: 1,2,3,4,5,10,6,7,8,9
第六次排序結果: 1,2,3,4,5,6,10,7,8,9
第七次排序結果: 1,2,3,4,5,6,7,10,8,9
第八次排序結果: 1,2,3,4,5,6,7,8,10,9
第九次排序結果: 1,2,3,4,5,6,7,8,9,10 - 算法分析
空間複雜度 O(1)
時間複雜度:最好情況,若初始序列爲正序,則一次掃描即可完成排序,比較次數n-1,移動次數0,時間複雜度爲O(n)。
最壞情況 ,初始序列爲逆序,進行n-1趟排序,每趟進行n-i+1次比較,移動次數爲n(n-1)/2 最壞的時間複雜度爲O(n²)。
平均時間複雜度爲O(n²)。
另外,當i>j且arr[i] = arr[j]時,兩者沒有逆序,不會發生交換。所以說冒泡排序是一種穩定的排序方法。 - Java代碼實現
package com.gray;
import java.util.Arrays;
public class BubbleSort {
public static void main(String args[]) {
int a[] = {10, 6, 5, 3, 8, 9, 1, 4, 2, 7};
function(a);
System.out.println("排序後:" + Arrays.toString(a));
}
public static void function(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
for (int j = arr.length - 1; j > i; j--) {
if (arr[j] < arr[j - 1]) {
int temp = arr[j];
arr[j] = arr[j - 1];
arr[j - 1] = temp;
}
}
}
}
}
快速排序
快速排序(Quicksort)是對冒泡排序的一種改進。採用”分而治之“的思想所以也被稱爲分治法。但更好理解的應該是把它看做是挖坑填坑的過程。
- 基本思想
在待排序的n個元素中任取一個元素作爲基準(一般取第一個元素),通過掃描序列被其分成兩部分,一部分比它小的放左邊,一部分比它大的放右邊,這個過程就叫一趟快速排序。以後對所有的兩部分分別重複上述過程(對了。。沒錯遞歸來了),直至每部分內只有一個元素或爲空爲止。 - 例子
{6,10,5,3,8,9,1,4,2,7}
第一次
6拿出來當基準,挖了一個坑,想象一下有兩個指針,i從左邊開始,j從右邊開始
空,10,5,3,8,9,1,4,2,7——j從右邊開始
空,10,5,3,8,9,1,4,2,7——7比6大 繼續 j–
2,10,5,3,8,9,1,4,空,7——2比6小,填坑挖新坑 i從左邊開始
2,空,5,3,8,9,1,4,10,7——10比6大,填坑挖新坑 j–
2,4,5,3,8,9,1,空,10,7——4比6小,填坑挖新坑 i++
2,4,5,3,8,9,1,空,10,7——5比6小 i++
2,4,5,3,8,9,1,空,10,7——3比6小 i++
2,4,5,3,空,9,1,8,10,7——8比6大 填坑挖新坑 j–
2,4,5,3,1,9,空,8,10,7——1比6小 填坑挖新坑 i++
2,4,5,3,1,空,9,8,10,7——9比6大 填坑挖新坑 i=j結束 6填會到坑裏面
[2,4,5,3,1]6[9,8,10,7]
第一次排序結束後形成被基準6分割的局面,左邊比6小,右邊比6大。
之後分別對兩邊的無序序列進行同樣的操作。 - 算法分析
快速排序算法的時間主要耗費在劃分操作上,對長度爲n的區間進行劃分,共需要n-1次關鍵字的比較。最壞的情況是每次劃分的基準都是當前無序區中關鍵字最小(或最大)的元素,劃分的結果是基準左邊的子區間爲空(或右邊的子區間爲空),而劃分所得的另一個非空的子區間中元素的數目,僅僅比劃分前的無序區中元素個數減少一個,因此,快速排序必須做n-1次劃分,第i次劃分開始時區間長度爲n-i+1,所需的比較次數爲n-i,故總的比較次數達到最大值n(n-1)/2 = O(n²)。
最好的情況下,每次劃分所取得基準都是當前無序區的“中值”元素,劃分的結果是基準的左、右兩個無序子區間的長度大致相等。設C(n)表示對長度爲n的表進行快速排序所需的比較次數,比較次數爲n-1,然後遞歸進行排序的比較次數爲2C(n/2),假設表長度爲n=2^k,C(n)=n1+2C(n/2),展開此遞歸關係式即可求得
C(n)<=n+2C(n/2)<=n+2(n/2+2C(n/2²))
<=…<=kn+2^kC(n/2^k)
=nlog2n+nC(1)=O(nlog2n)
所以快速排序算法的最好時間複雜度爲O(nlog2n)。
快速排序算法的平均時間複雜度爲O(nlog2n)。
若每一趟排序都將元素序列均勻地分割成兩個長度接近的子序列時,其深度是O(log2n),所需棧空間爲O(log2n)。但是在最壞的情況下棧空間爲O(n),平均所需棧空間爲O(log2n),所以快速排序的空間複雜度O(log2n)。
在快速排序挖坑填坑的過程中,如上例子所示,10會跑到7的前面,本來10是應該在7的後面的,現在關鍵字的相對位置改變了,所以說快速排序是一種不穩定的排序方法。 - Java代碼實現
1.遞歸實現
package com.gray;
import java.util.Arrays;
import java.util.Stack;
public class Quicksort {
public static void main(String args[]) {
int a[] = {6, 10, 5, 3, 8, 9, 1, 4, 2, 7};
function(a);
System.out.println("排序後:" + Arrays.toString(a));
}
public static void function(int[] arr) {
quickSort(arr, 0, arr.length - 1);
}
private static void quickSort(int[] arr, int s, int t) {
int i = s;
int j = t;
int temp;
if (s < t) {
temp = arr[s];
while (i != j) {
while (j > i && arr[j] > temp)
j--;
arr[i] = arr[j];
while (i < j && arr[i] < temp)
i++;
arr[j] = arr[i];
}
arr[i] = temp;
quickSort(arr, s, i - 1);
quickSort(arr, i + 1, t);
}
}
}
2.非遞歸實現(沒錯,就是棧啦)
package com.gray;
import java.util.Arrays;
import java.util.Stack;
public class Quicksort {
public static void main(String args[]) {
int a[] = {6, 10, 5, 3, 8, 9, 1, 4, 2, 7};
function(a);
System.out.println("排序後:" + Arrays.toString(a));
}
private static void function(int[] arr) {
Stack<Integer> stack = new Stack<Integer>();
stack.push(0);
stack.push(arr.length - 1);
while (!stack.empty()) {
int j = stack.pop();
int i = stack.pop();
if (i < j) {
int k = quickSortNoRec(arr, i, j);
if (i < k - 1) {
stack.push(i);
stack.push(k - 1);
}
if (j > k + 1) {
stack.push(k + 1);
stack.push(j);
}
}
}
}
private static int quickSortNoRec(int[] arr, int s, int t) {
int i = s;
int j = t;
int temp;
if (s < t) {
temp = arr[s];
while (i != j) {
while (j > i && arr[j] > temp)
j--;
arr[i] = arr[j];
while (i < j && arr[i] < temp)
i++;
arr[j] = arr[i];
}
arr[i] = temp;
}
return i;
}
}