先來看看8種排序之間的關係:javascript
下圖是各類排序的比較:java
1, 直接插入排序算法
(1)基本思想:在要排序的一組數中,假設前面(n-1) [n>=2] 個數已是排shell
好順序的,如今要把第n個數插到前面的有序數中,使得這n個數數組
也是排好順序的。如此反覆循環,直到所有排好順序。數據結構
在插入算法中,若是有一個最小的數在數組的最後面,用插入算法就會從最後一個app
位置移動到第一個。dom
(2)實例函數
package cglib;性能
public class StringNumber {
public static void insertSort(int[] a) {
if (a == null || a.length < 2) {
return;
}
int length=a.length; //數組長度
int j; //當前值的位置
int i; //指向j前的位置
int key; //當前要進行插入排序的值
//從數組的第二個位置開始遍歷值
for(j=1;j<length;j++){
key=a[j];
i=j-1;
System.out.println(" 將i="+i);
//a[i]比當前值大時,a[i]後移一位,空出i的位置,好讓下一次循環的值後移
while(i>=0 && a[i]>key){
System.out.println("進 i="+i);
a[i+1]=a[i]; //將a[i]值後移
i--; //i前移
System.out.println(" i="+i);
}//跳出循環(找到要插入的中間位置或已遍歷到0下標)
System.out.println(" 退出while");
System.out.println(" i="+i);
a[i+1]=key; //將當前值插入
}
}
public static void main(String[] args) {
int[] array = { 3, -1, 0, -8, 2, 1 };
ArrayUtils.printArray(array);
insertSort(array);
ArrayUtils.printArray(array);
}
}
class ArrayUtils {
public static void printArray(int[] array) {
System.out.print("{");
for (int i = 0; i < array.length; i++) {
System.out.print(array[i]);
if (i < array.length - 1) {
System.out.print(", ");
}
}
System.out.println("}");
}
}
輸出:
{3, -1, 0, -8, 2, 1}
將i=0
進 i=0
i=-1
退出while
i=-1
將i=1
進 i=1
i=0
退出while
i=0
將i=2
進 i=2
i=1
進 i=1
i=0
進 i=0
i=-1
退出while
i=-1
將i=3
進 i=3
i=2
退出while
i=2
將i=4
進 i=4
i=3
進 i=3
i=2
退出while
i=2
{-8, -1, 0, 1, 2, 3}
希爾排序(最小增量排序)
基本算法:
先將整個待排元素序列分割成若干個子序列(由相隔某個「增量」的元素組成的)分別進行直接插入排序,而後依次縮減增量再進行排序,待整個序列中的元 素基本有序(增量足夠小)時,再對全體元素進行一次直接插入排序。由於直接插入排序在元素基本有序的狀況下(接近最好狀況),效率是很高的,所以希爾排序在時間效率上比前兩種方法有較大提升。步長的選擇是希爾排序的重要部分。只要最終步長爲1任何步長序列均可以工做。
算法最開始以必定的步長進行排序,而後會繼續以必定步長進行排序,最終算法以步長爲1進行排序。當步長爲1時,算法變爲插入排序,這就保證了數據一 定會被排序。Donald Shell 最初建議步長選擇爲\frac{n}{2}而且對步長取半直到步長達到 1。雖然這樣取能夠比\mathcal{O}(n^2)類的算法(插入排序)更好,但這樣仍然有減小平均時間和最差時間的餘地。
希爾排序示例:n=10的一個數組 58 27 32 93 65 87 58 46 9 65,步長爲n/2。
第一次排序 步長爲 10/2 = 5
58 27 32 93 65 87 58 46 9 65
1A 1B
2A 2B
3A 3B
4A 4B
5A 5B
首先將待排序元素序列分組,以5爲步長,(1A,1B), (2A,2B),(3A,3B)等爲分組標記,大寫字母表示是該組的第幾個元素,數字相同的表示在同一組,這樣就分紅5組,即(58,87), (27,58),(32,46),(93,9),(65,65),而後分別對各分組進行直接插入排序,排序後5組爲(58,87),(27,58), (32,46),(9,93),(65,65),分組排序只是變得各個分組內的下表,下同。
第二次排序 步長爲 5/2 = 2
58 27 32 9 65 87 58 46 93 65
1A 1B
2A 2B
3A 3B
.............................................
第三次排序 步長爲 2/2 = 1
32 9 58 27 58 46 65 65 93 87
1A 1B 1C 1D 1E 1F 1G 1H 1I 1J
第四次排序 步長爲 1/2 = 0 獲得有序元素序列
9 27 32 46 58 58 65 65 87 93
希爾排序的時間性能優於直接插入排序的緣由:
①當文件初態基本有序時直接插入排序所需的比較和移動次數均較少。
②當n值較小時,n和n2的差異也較小,即直接插入排序的最好時間複雜度O(n)和最壞時間複雜度0(n2)差異不大。
③在希爾排序開始時增量較大,分組較多,每組的記錄數目少,故各組內直接插入較快,後來增量di逐漸縮小,分組數逐漸減小,而各組的記錄數目逐漸增多,但因爲已經按di-1做爲距離排過序,使文件較接近於有序狀態,因此新的一趟排序過程也較快。
增量序列的選擇:Shell排序的執行時間依賴於增量序列。
好的增量序列的共同特徵(查到的資料都這麼講):
① 最後一個增量必須爲1;
② 應該儘可能避免序列中的值(尤爲是相鄰的值)互爲倍數的狀況。
package cglib;
public class StringNumber {
public static void main(String[] args) {
int[] arr = new int[]{44,33,99,10,30,20,59,78,23,48};
System.out.print("排序前:");
for(int o: arr) {
System.out.print(o+" ");
}
System.out.println();
shellSort(arr);
System.out.print("排序後:");
for(int o: arr) {
System.out.print(o+" ");
}
System.out.println();
}
private static void shellSort(int[] arr) {
int j;
int len = arr.length;
for(int val=len>>1; val>0; val>>=1) {
//下面是對本次的全部分組作直接插入排序
for(int i=val; i<len; i++) {
System.out.println("for:i="+i);
System.out.println("for:arr[i]="+arr[i]);
System.out.println("for:val="+val);
int temp = arr[i];
/*
* 爲何每次都用temp比較呢?
* 由於直接插入就是找到temp的合適位置。
* 爲何temp<arr[j-val]這個條件能夠放在for內呢?
* 由於原來的組內數據已經有序,找到位置就中止即是。
*
*/
for(j=i; j>=val&&temp<arr[j-val]; j-=val) {
System.out.println("er:j="+j);
System.out.println("er:arr[j]="+arr[j]);
System.out.println("er:j-val="+(j-val));
System.out.println("er:arr[j-val]="+arr[j-val]);
/*
* 爲何是arr[j-val]不是arr[j]呢?
* 由於j=i開始的,並且條件是j>=val&&temp<arr[j-val]
*/
arr[j] = arr[j-val];
System.out.println("賦值er:arr[j]="+arr[j]);
}
/*
* 注意不是arr[i] = temp
* 直接插入排序也是這樣的。
* 爲何呢?
* 由於j是位置,i是待插入元素
*/
arr[j] = temp;
}
}
}
}
輸出:
排序前:44 33 99 10 30 20 59 78 23 48
for:i=5
for:arr[i]=20
for:val=5
er:j=5
er:arr[j]=20
er:j-val=0
er:arr[j-val]=44
賦值er:arr[j]=44
for:i=6
for:arr[i]=59
for:val=5
for:i=7
for:arr[i]=78
for:val=5
er:j=7
er:arr[j]=78
er:j-val=2
er:arr[j-val]=99
賦值er:arr[j]=99
for:i=8
for:arr[i]=23
for:val=5
for:i=9
for:arr[i]=48
for:val=5
for:i=2
for:arr[i]=78
for:val=2
for:i=3
for:arr[i]=10
for:val=2
er:j=3
er:arr[j]=10
er:j-val=1
er:arr[j-val]=33
賦值er:arr[j]=33
for:i=4
for:arr[i]=30
for:val=2
er:j=4
er:arr[j]=30
er:j-val=2
er:arr[j-val]=78
賦值er:arr[j]=78
for:i=5
for:arr[i]=44
for:val=2
for:i=6
for:arr[i]=59
for:val=2
er:j=6
er:arr[j]=59
er:j-val=4
er:arr[j-val]=78
賦值er:arr[j]=78
for:i=7
for:arr[i]=99
for:val=2
for:i=8
for:arr[i]=23
for:val=2
er:j=8
er:arr[j]=23
er:j-val=6
er:arr[j-val]=78
賦值er:arr[j]=78
er:j=6
er:arr[j]=78
er:j-val=4
er:arr[j-val]=59
賦值er:arr[j]=59
er:j=4
er:arr[j]=59
er:j-val=2
er:arr[j-val]=30
賦值er:arr[j]=30
for:i=9
for:arr[i]=48
for:val=2
er:j=9
er:arr[j]=48
er:j-val=7
er:arr[j-val]=99
賦值er:arr[j]=99
for:i=1
for:arr[i]=10
for:val=1
er:j=1
er:arr[j]=10
er:j-val=0
er:arr[j-val]=20
賦值er:arr[j]=20
for:i=2
for:arr[i]=23
for:val=1
for:i=3
for:arr[i]=33
for:val=1
for:i=4
for:arr[i]=30
for:val=1
er:j=4
er:arr[j]=30
er:j-val=3
er:arr[j-val]=33
賦值er:arr[j]=33
for:i=5
for:arr[i]=44
for:val=1
for:i=6
for:arr[i]=59
for:val=1
for:i=7
for:arr[i]=48
for:val=1
er:j=7
er:arr[j]=48
er:j-val=6
er:arr[j-val]=59
賦值er:arr[j]=59
for:i=8
for:arr[i]=78
for:val=1
for:i=9
for:arr[i]=99
for:val=1
排序後:10 20 23 30 33 44 48 59 78 99
選擇排序
每一趟從待排序的數據元素中選出最小(或最大)的一個元素,順序放在已排好序的數列的最後,直到所有待排序的數據元素排完。
package cglib;
import java.util.Arrays;
import java.util.Date;
import java.util.Random;
public class StringNumber {
public static void main(String[] args){
Random random = new Random();
int[] array = new int[2000];
for (int j = 0; j < 2000; j++) {
array[j] = random.nextInt(100000);
}
System.out.println(Arrays.toString(array));
selectSortTest(array);
System.out.println(Arrays.toString(array));
}
public static void selectSortTest(int a[]) {
Date dateStart = new Date();
selectSort(a);
Date dateEnd = new Date();
System.out.println("選擇排序耗費時間:"
+ (dateEnd.getTime() - dateStart.getTime()));
}
public static void selectSort(int a[]){
int n = a.length;
for(int k=0; k<n-1; k++) {
int min = k;
for(int i=k+1; i<n; i++) {//找出最小值
if(a[i] < a[min]) {
min = i;
}
}
if(k != min) {
int temp = a[k];
a[k] = a[min];
a[min] = temp;
}
}
}
}
堆排序和合並排序同樣,是一種時間複雜度爲O(nlgn)的算法,同時和插入排序同樣,是一種就地排序算法(不須要額外的存儲空間)。堆排序須要用到一種 被稱爲最大堆的數據結構,與java或者lisp的gc不同,這裏的堆是一種數據結構,他能夠被視爲一種徹底二叉樹,即樹裏面除了最後一層其餘層都是填 滿的。也正是由於這樣,樹裏面每一個節點的子女和雙親節點的序號均可以根據當前節點的序號直接求出。
Parent(i)=i/2
Left(i)=2*i
Right(i)=2*i+1
如 上圖所示,1位置的子女節點分別爲2,3 2節點的子女節點爲4,5 2的雙親節點爲1 考察其餘節點也很容易發現上述關係。最大堆是一種特殊的堆,其特色是每一個雙親節點的值都比子女節點大。他的這一特色使得他能夠實現nlgn的就地排序。現 在咱們先來看看怎麼構建和保持一個最大堆。
咱們如今有一個數組A,大小是n,假設其中元素按照徹底二叉樹的方式排列。如何將其構形成一個最大堆?首先咱們知道最大堆的每一個子樹都符合最大堆的性質 (根節點值大於全部子節點)。同時咱們知道序號爲(n/2+1)~n的元素都是葉子節點(由於其子女節點的序號都大於n,即說明沒有子女節點),所以咱們 構建最大堆的操做就在序號爲1~n/2的元素內進行(其餘元素已知足最大堆性質)。咱們定義以下操做maxify(i):將以i位置節點爲根的子樹改形成 最大堆。其操做內容以下:對於每一個節點i,咱們考察他與子女節點的大小,若是他比某個子女節點小,則將他與子女節點中最大的那個互換位置,而後在相應的子 女節點位置重複操做,直到到達堆的葉子節點或者考察的位置比子女節點的值都要大爲止。由此可知咱們構造最大堆buildmaxheap的過程就是在每一個內 部節點上調用maxify過程,依次到樹的根部,此時其左右子樹都是最大堆,如今
步驟&實例
實現堆排序需解決兩個問題:
(1)如何將n 個待排序的數建成堆;
(2)輸出堆頂元素後,怎樣調整剩餘n-1 個元素,使其成爲一個新堆。
建堆方法(小頂堆):
對初始序列建堆的過程,就是一個反覆進行篩選的過程。
n 個結點的徹底二叉樹,則最後一個結點是第n/2個結點的子樹。
篩選從第n/2個結點爲根的子樹開始(n/2是最後一個有子樹的結點),使該子樹成爲堆。
以後向前依次對各結點爲根的子樹進行篩選,使之成爲堆,直到根結點。
如圖建堆初始過程
無序序列:(49, 38, 65, 97, 76, 13, 27, 49)
(a) 無序序列,初始二叉樹,97(第8/2=4個結點)爲最後一個結點(49)的父結點。
(b) 97>=49,替換位置,接下來對n/2的上一個結點65進行篩選。
(c) 13<=27且65>=13,替換65和13的位置,接下來對38進行替換(都大於它,不需操做),對49進行篩選。
(d) 13<=38且49>=13,替換49和13的位置,49>=27,替換49和27的位置。
(e) 最終獲得一個堆,13是咱們獲得的最小數。
調整堆的方法(小頂堆):
設有m 個元素的堆,輸出堆頂元素後,剩下m-1 個元素。將堆底元素送入堆頂,堆被破壞,其緣由僅是根結點不知足堆的性質。
將根結點與左、右子樹中較小元素的進行交換。
若與左子樹交換:若是左子樹堆被破壞,則重複方法(2).
若與右子樹交換,若是右子樹堆被破壞,則重複方法(2).
繼續對不知足堆性質的子樹進行上述交換操做,直到葉子結點,堆被建成。
調整堆只需考慮被破壞的結點,其餘的結點不需調整。
package cglib;
public class StringNumber {
public static void main(String[] args) {
int[] array = { 49, 38, 65, 97, 76, 13, 27, 49 };
heapSort(array, true);
System.out.print("\n"+"排完:");
for (int i : array) {
System.out.print(i + " ");
}
}
/**
* 調整爲小頂堆(排序後結果爲從大到小)
*
* @param array是待調整的堆數組
* @param s是待調整的數組元素的位置
* @param length是數組的長度
*
*/
public static void heapAdjustS(int[] array, int s, int length) {
int tmp = array[s];
int child = 2 * s + 1;// 左孩子結點的位置
System.out.println("待調整結點爲:array[" + s + "] = " + tmp);
while (child < length) {
// child + 1 是當前調整結點的右孩子
// 若是有右孩子且小於左孩子,使用右孩子與結點進行比較,不然使用左孩子
if (child + 1 < length && array[child] > array[child + 1]) {
child++;
}
System.out.println("將與子孩子 array[" + child + "] = " + array[child] + " 進行比較");
// 若是較小的子孩子比此結點小
if (array[s] > array[child]) {
System.out.println("子孩子比其小,交換位置");
array[s] = array[child];// 把較小的子孩子向上移動,替換當前待調整結點
s = child;// 待調整結點移動到較小子孩子原來的位置
array[child] = tmp;
child = 2 * s + 1;// 繼續判斷待調整結點是否須要繼續調整
if (child >= length) {
System.out.println("沒有子孩子了,調整結束");
} else {
System.out.println("繼續與新的子孩子進行比較");
}
// continue;
} else {
System.out.println("子孩子均比其大,調整結束");
break;// 當前待調整結點小於它的左右孩子,不需調整,直接退出
}
}
}
/**
* 調整爲大頂堆(排序後結果爲從小到大)
*
* @param array是待調整的堆數組
* @param s是待調整的數組元素的位置
* @param length是數組的長度
*
*/
public static void heapAdjustB(int[] array, int s, int length) {
int tmp = array[s];
int child = 2 * s + 1;// 左孩子結點的位置
System.out.println("待調整結點爲:array[" + s + "] = " + tmp);
while (child < length) {
// child + 1 是當前調整結點的右孩子
// 若是有右孩子且大於左孩子,使用右孩子與結點進行比較,不然使用左孩子
if (child + 1 < length && array[child] < array[child + 1]) {
child++;
}
System.out.println("將與子孩子 array[" + child + "] = " + array[child] + " 進行比較");
// 若是較大的子孩子比此結點大
if (array[s] < array[child]) {
System.out.println("子孩子比其大,交換位置");
array[s] = array[child];// 把較大的子孩子向上移動,替換當前待調整結點
s = child;// 待調整結點移動到較大子孩子原來的位置
array[child] = tmp;
child = 2 * s + 1;// 繼續判斷待調整結點是否須要繼續調整
if (child >= length) {
System.out.println("沒有子孩子了,調整結束");
} else {
System.out.println("繼續與新的子孩子進行比較");
}
// continue;
} else {
System.out.println("子孩子均比其小,調整結束");
break;// 當前待調整結點大於它的左右孩子,不需調整,直接退出
}
}
}
/**
* 堆排序算法
*
* @param array
* @param inverse true 爲倒序排列,false 爲正序排列
*/
public static void heapSort(int[] array, boolean inverse) {
// 初始堆
// 最後一個有孩子的結點位置 i = (length - 1) / 2, 以此向上調整各結點使其符合堆
System.out.println("初始堆開始");
for (int i = (array.length - 1) / 2; i >= 0; i--) {
if (inverse) {
heapAdjustS(array, i, array.length);
} else {
heapAdjustB(array, i, array.length);
}
}
System.out.println("初始堆結束");
for (int i = array.length - 1; i > 0; i--) {
// 交換堆頂元素H[0]和堆中最後一個元素
int tmp = array[i];
array[i] = array[0];
array[0] = tmp;
// 每次交換堆頂元素和堆中最後一個元素以後,都要對堆進行調整
if (inverse) {
heapAdjustS(array, 0, i);
} else {
heapAdjustB(array, 0, i);
}
}
}
}
輸出:
初始堆開始
待調整結點爲:array[3] = 97
將與子孩子 array[7] = 49 進行比較
子孩子比其小,交換位置
沒有子孩子了,調整結束
待調整結點爲:array[2] = 65
將與子孩子 array[5] = 13 進行比較
子孩子比其小,交換位置
沒有子孩子了,調整結束
待調整結點爲:array[1] = 38
將與子孩子 array[3] = 49 進行比較
子孩子均比其大,調整結束
待調整結點爲:array[0] = 49
將與子孩子 array[2] = 13 進行比較
子孩子比其小,交換位置
繼續與新的子孩子進行比較
將與子孩子 array[6] = 27 進行比較
子孩子比其小,交換位置
沒有子孩子了,調整結束
初始堆結束
待調整結點爲:array[0] = 97
將與子孩子 array[2] = 27 進行比較
子孩子比其小,交換位置
繼續與新的子孩子進行比較
將與子孩子 array[6] = 49 進行比較
子孩子比其小,交換位置
沒有子孩子了,調整結束
待調整結點爲:array[0] = 97
將與子孩子 array[1] = 38 進行比較
子孩子比其小,交換位置
繼續與新的子孩子進行比較
將與子孩子 array[3] = 49 進行比較
子孩子比其小,交換位置
沒有子孩子了,調整結束
待調整結點爲:array[0] = 65
將與子孩子 array[1] = 49 進行比較
子孩子比其小,交換位置
繼續與新的子孩子進行比較
將與子孩子 array[4] = 76 進行比較
子孩子均比其大,調整結束
待調整結點爲:array[0] = 76
將與子孩子 array[2] = 49 進行比較
子孩子比其小,交換位置
沒有子孩子了,調整結束
待調整結點爲:array[0] = 97
將與子孩子 array[1] = 65 進行比較
子孩子比其小,交換位置
沒有子孩子了,調整結束
待調整結點爲:array[0] = 76
將與子孩子 array[1] = 97 進行比較
子孩子均比其大,調整結束
待調整結點爲:array[0] = 97
排完:97 76 65 49 49 38 27 13
5.冒泡排序
(1)基本思想:在要排序的一組數中,對當前還未排好序的範圍內的所有數,自上而下對相鄰的兩個數依次進行比較和調整,讓較大的數往下沉,較小的往上冒。即:每當兩相鄰的數比較後發現它們的排序與排序要求相反時,就將它們互換。
(2)實例:
package cglib;
public class StringNumber {
public static void main(String[] args){
int score[] = {67, 69, 75, 87, 89, 90, 99, 100};
for (int i = 0; i < score.length -1; i++){ //最多作n-1趟排序
for(int j = 0 ;j < score.length - i - 1; j++){ //對當前無序區間score[0......length-i-1]進行排序(j的範圍很關鍵,這個範圍是在逐步縮小的)
if(score[j] < score[j + 1]){ //把小的值交換到後面
int temp = score[j];
score[j] = score[j + 1];
score[j + 1] = temp;
}
}
System.out.print("第" + (i + 1) + "次排序結果:");
for(int a = 0; a < score.length; a++){
System.out.print(score[a] + "\t");
}
System.out.println("");
}
System.out.print("最終排序結果:");
for(int a = 0; a < score.length; a++){
System.out.print(score[a] + "\t");
}
}
}
輸出:
第1次排序結果:69 75 87 89 90 99 100 67
第2次排序結果:75 87 89 90 99 100 69 67
第3次排序結果:87 89 90 99 100 75 69 67
第4次排序結果:89 90 99 100 87 75 69 67
第5次排序結果:90 99 100 89 87 75 69 67
第6次排序結果:99 100 90 89 87 75 69 67
第7次排序結果:100 99 90 89 87 75 69 67
最終排序結果: 100 99 90 89 87 75 69 67
快速排序
算法思想:基於分治的思想,是冒泡排序的改進 型。首先在數組中選擇一個基準點(該基準點的選取可能影響快速排序的效率,後面講解選取的方法),而後分別從數組的兩端掃描數組,設兩個指示標誌(lo指 向起始位置,hi指向末尾),首先從後半部分開始,若是發現有元素比該基準點的值小,就交換lo和hi位置的值,而後從前半部分開始掃秒,發現有元素大於 基準點的值,就交換lo和hi位置的值,如此往復循環,直到lo>=hi,而後把基準點的值放到hi這個位置。一次排序就完成了。之後採用遞歸的方 式分別對前半部分和後半部分排序,當前半部分和後半部分均有序時該數組就天然有序了。
排序過程:
public static int partition(int []array,int lo,int hi){ //固定的切分方式 int key=array[lo]; while(lo<hi){ while(array[hi]>=key&&hi>lo){//從後半部分向前掃描 hi--; } array[lo]=array[hi]; while(array[lo]<=key&&hi>lo){從前半部分向後掃描 lo++; } array[hi]=array[lo]; } array[hi]=key; return hi; } public static void sort(int[] array,int lo ,int hi){ if(lo>=hi){ return ; } int index=partition(array,lo,hi); sort(array,lo,index-1); sort(array,index+1,hi); }
快速排序的優化
對於基準位置的選取通常有三種方法:固定切分,隨機切分和三取樣切分。固定切分的效率並非太好,隨機切分是經常使用的一種切分,效率比較高,最壞狀況下時間複雜度有可能爲O(N2).對於三數取中選擇基準點是最理想的一種。
三數取中切分:
快速排序的優化
對於基準位置的選取通常有三種方法:固定切分,隨機切分和三取樣切分。固定切分的效率並非太好,隨機切分是經常使用的一種切分,效率比較高,最壞狀況下時間複雜度有可能爲O(N2).對於三數取中選擇基準點是最理想的一種。
三數取中切分:
public static int partition(int []array,int lo,int hi){ //三數取中 int mid=lo+(hi-lo)/2; if(array[mid]>array[hi]){//7,5,2 swap(array[mid],array[hi]);//7,2,5 } if(array[lo]>array[hi]){ swap(array[lo],array[hi]);//5,2,7 } if(array[mid]>array[lo]){ swap(array[mid],array[lo]); } int key=array[lo];//取第二大的 while(lo<hi){ while(array[hi]>=key&&hi>lo){ hi--; } array[lo]=array[hi]; while(array[lo]<=key&&hi>lo){ lo++; } array[hi]=array[lo]; } array[hi]=key; return hi; } public static void swap(int a,int b){ int temp=a; a=b; b=temp; } public static void sort(int[] array,int lo ,int hi){ if(lo>=hi){ return ; } int index=partition(array,lo,hi); sort(array,lo,index-1); sort(array,index+1,hi); }
快速排序在序列中元素不多時,效率將比較低,否則插入排序,所以通常在序列中元素不多時使用插入排序,這樣能夠提升總體效率。
public static void quick(int []array ,int lo,int hi){ if(hi-lo+1<10){ insertSort(array); }else{ quickSort(array,lo,hi); } }
將待排序序列R[0...n-1]當作是n個長度爲1的有序序列,將相鄰的有序表成對歸併,獲得n/2個長度爲2的有序表;將這些有序序列再次歸併,獲得n/4個長度爲4的有序序列;如此反覆進行下去,最後獲得一個長度爲n的有序序列。
綜上可知:
歸併排序其實要作兩件事:
(1)「分解」——將序列每次折半劃分。
(2)「合併」——將劃分後的序列段兩兩合併後排序。
咱們先來考慮第二步,如何合併?
在每次合併過程當中,都是對兩個有序的序列段進行合併,而後排序。
這兩個有序序列段分別爲 R[low, mid] 和 R[mid+1, high]。
先將他們合併到一個局部的暫存數組R2中,帶合併完成後再將R2複製回R中。
爲了方便描述,咱們稱 R[low, mid] 第一段,R[mid+1, high] 爲第二段。
每次從兩個段中取出一個記錄進行關鍵字的比較,將較小者放入R2中。最後將各段中餘下的部分直接複製到R2中。
通過這樣的過程,R2已是一個有序的序列,再將其複製回R中,一次合併排序就完成了。
基本排序:歸併(Merge)排序法是將兩個(或兩個以上)有序表合併成一個新的有序表,即把待排序序列分爲若干個子序列,每一個子序列是有序的。而後再把有序子序列合併爲總體有序序列。
(2)實例:
核心代碼
public void Merge(int[] array, int low, int mid, int high) {
int i = low; // i是第一段序列的下標
int j = mid + 1; // j是第二段序列的下標
int k = 0; // k是臨時存放合併序列的下標
int[] array2 = new int[high - low + 1]; // array2是臨時合併序列
// 掃描第一段和第二段序列,直到有一個掃描結束
while (i <= mid && j <= high) {
// 判斷第一段和第二段取出的數哪一個更小,將其存入合併序列,並繼續向下掃描
if (array[i] <= array[j]) {
array2[k] = array[i];
i++;
k++;
} else {
array2[k] = array[j];
j++;
k++;
}
}
// 若第一段序列還沒掃描完,將其所有複製到合併序列
while (i <= mid) {
array2[k] = array[i];
i++;
k++;
}
// 若第二段序列還沒掃描完,將其所有複製到合併序列
while (j <= high) {
array2[k] = array[j];
j++;
k++;
}
// 將合併序列複製到原始序列中
for (k = 0, i = low; i <= high; i++, k++) {
array[i] = array2[k];
}
}
掌握了合併的方法,接下來,讓咱們來了解 如何分解。
在某趟歸併中,設各子表的長度爲gap,則歸併前R[0...n-1]中共有n/gap個有序的子表:R[0...gap-1], R[gap...2*gap-1], ... , R[(n/gap)*gap ... n-1]。
調用Merge將相鄰的子表歸併時,必須對錶的特殊狀況進行特殊處理。
若子表個數爲奇數,則最後一個子表無須和其餘子表歸併(即本趟處理輪空):若子表個數爲偶數,則要注意到最後一對子表中後一個子表區間的上限爲n-1。
核心代碼
public void MergePass(int[] array, int gap, int length) {
int i = 0;
// 歸併gap長度的兩個相鄰子表
for (i = 0; i + 2 * gap - 1 < length; i = i + 2 * gap) {
Merge(array, i, i + gap - 1, i + 2 * gap - 1);
}
// 餘下兩個子表,後者長度小於gap
if (i + gap - 1 < length) {
Merge(array, i, i + gap - 1, length - 1);
}
}
public int[] sort(int[] list) {
for (int gap = 1; gap < list.length; gap = 2 * gap) {
MergePass(list, gap, list.length);
System.out.print("gap = " + gap + ":\t");
this.printAll(list);
}
return list;
}
package cglib;
import java.util.Arrays;
public class StringNumber {
/**
* 歸併排序
* 簡介:將兩個(或兩個以上)有序表合併成一個新的有序表 即把待排序序列分爲若干個子序列,每一個子序列是有序的。而後再把有序子序列合併爲總體有序序列
* 時間複雜度爲O(nlogn)
* 穩定排序方式
* @param nums 待排序數組
* @return 輸出有序數組
*/
public static int[] sort(int[] nums, int low, int high) {
int mid = (low + high) / 2;
if (low < high) {
// 左邊
sort(nums, low, mid);
// 右邊
sort(nums, mid + 1, high);
// 左右歸併
merge(nums, low, mid, high);
}
return nums;
}
public static void merge(int[] nums, int low, int mid, int high) {
int[] temp = new int[high - low + 1];
int i = low;// 左指針
int j = mid + 1;// 右指針
int k = 0;
// 把較小的數先移到新數組中
while (i <= mid && j <= high) {
if (nums[i] < nums[j]) {
temp[k++] = nums[i++];
} else {
temp[k++] = nums[j++];
}
}
// 把左邊剩餘的數移入數組
while (i <= mid) {
temp[k++] = nums[i++];
}
// 把右邊邊剩餘的數移入數組
while (j <= high) {
temp[k++] = nums[j++];
}
// 把新數組中的數覆蓋nums數組
for (int k2 = 0; k2 < temp.length; k2++) {
nums[k2 + low] = temp[k2];
}
}
// 歸併排序的實現
public static void main(String[] args) {
int[] nums = { 2, 7, 8, 3, 1, 6, 9, 0, 5, 4 };
StringNumber.sort(nums, 0, nums.length-1);
System.out.println(Arrays.toString(nums));
}
}
1 package notes.javase.algorithm.sort;
2
3 public class MergeSort {
4 public void Merge(int[] array, int low, int mid, int high) {
5 int i = low; // i是第一段序列的下標
6 int j = mid + 1; // j是第二段序列的下標
7 int k = 0; // k是臨時存放合併序列的下標
8 int[] array2 = new int[high - low + 1]; // array2是臨時合併序列
9
10 // 掃描第一段和第二段序列,直到有一個掃描結束
11 while (i <= mid && j <= high) {
12 // 判斷第一段和第二段取出的數哪一個更小,將其存入合併序列,並繼續向下掃描
13 if (array[i] <= array[j]) {
14 array2[k] = array[i];
15 i++;
16 k++;
17 } else {
18 array2[k] = array[j];
19 j++;
20 k++;
21 }
22 }
23
24 // 若第一段序列還沒掃描完,將其所有複製到合併序列
25 while (i <= mid) {
26 array2[k] = array[i];
27 i++;
28 k++;
29 }
30
31 // 若第二段序列還沒掃描完,將其所有複製到合併序列
32 while (j <= high) {
33 array2[k] = array[j];
34 j++;
35 k++;
36 }
37
38 // 將合併序列複製到原始序列中
39 for (k = 0, i = low; i <= high; i++, k++) {
40 array[i] = array2[k];
41 }
42 }
43
44 public void MergePass(int[] array, int gap, int length) {
45 int i = 0;
46
47 // 歸併gap長度的兩個相鄰子表
48 for (i = 0; i + 2 * gap - 1 < length; i = i + 2 * gap) {
49 Merge(array, i, i + gap - 1, i + 2 * gap - 1);
50 }
51
52 // 餘下兩個子表,後者長度小於gap
53 if (i + gap - 1 < length) {
54 Merge(array, i, i + gap - 1, length - 1);
55 }
56 }
57
58 public int[] sort(int[] list) {
59 for (int gap = 1; gap < list.length; gap = 2 * gap) {
60 MergePass(list, gap, list.length);
61 System.out.print("gap = " + gap + ":\t");
62 this.printAll(list);
63 }
64 return list;
65 }
66
67 // 打印完整序列
68 public void printAll(int[] list) {
69 for (int value : list) {
70 System.out.print(value + "\t");
71 }
72 System.out.println();
73 }
74
75 public static void main(String[] args) {
76 int[] array = {
77 9, 1, 5, 3, 4, 2, 6, 8, 7
78 };
79
80 MergeSort merge = new MergeSort();
81 System.out.print("排序前:\t\t");
82 merge.printAll(array);
83 merge.sort(array);
84 System.out.print("排序後:\t\t");
85 merge.printAll(array);
86 }
87 }
運行結果
排序前: 9 1 5 3 4 2 6 8 7
gap = 1: 1 9 3 5 2 4 6 8 7
gap = 2: 1 3 5 9 2 4 6 8 7
gap = 4: 1 2 3 4 5 6 8 9 7
gap = 8: 1 2 3 4 5 6 7 8 9
排序後: 1 2 3 4 5 6 7 8 9
基數排序
(1)基本思想:將全部待比較數值(正整數)統一爲一樣的數位長度,數位較短的數前面補零。而後,從最低位開始,依次進行一次排序。這樣從最低位排序一直到最高位排序完成之後,數列就變成一個有序序列。
(2)實例:
桶式排序
一、概念:有限個數字m,每一個數字的大小都在1與n之間,則咱們能夠假設有n個桶,遍歷m個數字,將其存入對應的桶中(如數字的值爲3,就存入3號桶,桶的值對應存入數字的個數)
二、例子
有數字3,3,5,1,2,大小均在0-5之間,因此咱們假設有5個桶,分別標號1,2,3,4,5,遍歷全部數字,將其存入桶中,則存儲以後,全部桶的計數以下:
桶號 | 1 | 2 | 3 | 4 | 5 |
計數 | 1 | 1 | 2 | 0 | 1 |
咱們按照桶的序號將數字倒出來,以下:
桶的倒出順序 | 數字隊列 |
5號桶倒出1個5 | 5 |
4號桶倒出0個4 | 5 |
3號桶倒出2個3 | 5,3,3 |
2號桶倒出1個2 | 5,3,3,2 |
1號桶倒出1個1 | 5,3,3,2,1 |
如上所示,咱們成功將所給數列按照從大到小的排序,反之,若是從1號桶開始倒出,咱們會獲得從小到大排列的數字
三、代碼實現(JAVA)
//桶式排序 public class BucketSort{ public static void main(String[] args){ int[] a = {2,4,15,11,6,3,7,19,8,5,4}; sort(a,19); } //桶式排序函數 //a是要排序的數組 //max是最大數字(這裏咱們默認數字最小爲0) public static void sort(int[] a,int max){ //聲明一個數組,這就是桶,編號從0到max的桶,一共max+1個 int[] count = new int[max + 1]; //遍歷數組,用桶計數 for(int i = 0;i < a.length;i++){ count[a[i]]++; } //將桶裏面的數字倒出 for(int i = max;i > 0;i--){ while(count[i] > 0){ System.out.print(i + " "); count[i]--; } } } }
四、弊端:若是咱們的數字波動範圍很是大,好比1到10000,那麼咱們須要一個10000元素數組的空間開銷,並且在倒出數字的時候須要遍歷10000個桶,這樣效率是很是低的,因而咱們有了基於桶式排序的基數排序
2、基數排序
一、基於桶式排序,將要排序的數字一位一位的比較,經歷屢次桶式排序,得出最終的序列
若是要排序的元素能夠分紅多位,而且每一位都在一個固定的範圍內,則能夠用這種排序方法,如對10進制數字的排序
二、例子
有數字23,35,9,73,3,314,11,1234,5,能夠看出來,每一位數字的取值範圍都是0到9,因此咱們能夠用10個桶來進行排序,分別編號0到9。
如今有數組:278,109,63,930,589,184,505,269,8,83
第一次根據各位數將數組劃分爲10個隊列(固然其中的某些隊列可能不含有元素)
0:930
1:
2:
3:63,83
4:184
5:505
6:
7:
8:278,8
9:109,589,269
而後收集成序列:
930,63,83,184,505,278,8,109,589,269
在進行第二次分組:
0:505,8,109
1:
2:
3:930
4:
5:
6:63,269
7:278
8:83,184,589
9:
第二次收集:
505,8,109,930,63,269,278,83,184,589
第三次分配:
0:8,63,83
1:109,184
2:278,269
3:
4:
5:505,589
6:
7:
8:
9:930
最後獲得序列:
8,63,83,109,184,269,278,505,589,930
完成排序!
基數排序實際上是利用多關鍵字先達到局部有序,再調整達到全局有序。
Java代碼
若是待排序列的關鍵字不是天然數,咱們固然能夠對其進行轉化,而後利用相似的方式排序。
代碼實現(JAVA)
注: 正常狀況下,咱們是要告訴咱們的排序方法,咱們最高位的數字是幾位的,這樣在最高位也排序完成後就會中止排序,可是咱們在這段代碼中用了一個叫 hasNum的boolean型變量,用來表徵咱們的數組中是否還存在更高的位數,(PS:雖然省事了,方法更 通用了,可是也產生了額外的開銷)
public class RadixSort{ public static void main(String[] args){ //聲明要排序的數組 int[] data = {73,22,93,867494,43,55,123,8978,10000,14,28,65,39,81,33,100,567}; //調用基數排序函數 sort(data,10); //輸出排序後的數組 for(int i = 0;i < data.length;i++){ System.out.print(data[i] + " "); } } ///基數排序函數 //a表示要排序的數組 //d表示每一位數字的範圍(這裏是10進制數,有0~9一共10種狀況) public static void sort(int[] a,int d){ //n用來表示當前排序的是第幾位 int n = 1; //hasNum用來表示數組中是否有至少一個數字存在第n位 boolean hasNum = false; //二維數組temp用來保存當前排序的數字 //第一維d表示一共有d個桶 //第二維a.length表示每一個桶最多可能存放a.length個數字 int[][] temp = new int[d][a.length]; int[] order = new int[d]; while(true){ //判斷是否全部元素均無比更高位,由於第一遍必定要先排序一次,因此有n!=1的判斷 if(n != 1 && !hasNum){ break; } hasNum = false; //遍歷要排序的數組,將其存入temp數組中(按照第n位上的數字將數字放入桶中) for(int i = 0;i < a.length;i++){ int x = a[i]/(n*10); if(x != 0){ hasNum = true; } int lsd = (x%10); temp[lsd][order[lsd]] = a[i]; order[lsd]++; } //k用來將排序好的temp數組存入data數組(將桶中的數字倒出) int k = 0; for(int i = 0;i < d;i++){ if(order[i] != 0){ for(int j = 0;j < order[i];j++){ a[k] = temp[i][j]; k++; } } order[i] = 0; } n++; } } }
3、基數排序(給英文字符串排序)
一、排序規則
①字符串更長的在前
②z在最前,a在最後
二、代碼實現(JAVA)
public class RadixSort_Letter{ public static void main(String[] args){ //聲明要排序的數組 String[] a = {"ac","ee","ef","b","z","f","ep","gaaa","azh","az","r"}; //調用基數排序函數 sort(a,4); //輸出排序後的數組 for(int i = a.length - 1;i >= 0;i--){ System.out.print(a[i] + " "); } } ///基數排序函數 //a是要排序的數組 //m表示數組元素最高位數,如咱們排序的數組中位數最高的元素爲gaaa,有4位 public static void sort(String[] a,int m){ int n = 0; //27表示每一位字符分紅27類,其中1~26對應'a'~'z' //第0位專門用來存放沒有高位字符的數組元素,如在比較第二位字符時,b,z,f等沒有第二位字符的元素就存在temp[0]中 //相對應的,ac存在temp[1]中,ef存在temp[5]中 String[][] temp = new String[27][a.length]; int[] order = new int[27]; while(n < m){ //這裏聲明String類型的數組b,將數組a中的每一個元素倒序,而後放入數組b //如a[0]="abc",則b[0]="cba" //之因此這樣作,是爲了解決下面調用charAt方法時索引的問題,腦子太笨,沒想到更好的方法 String[] b = new String[a.length]; for(int i = 0;i < a.length;i++){ if(a[i].length() > 1){ StringBuffer sb = new StringBuffer(a[i]); sb.reverse(); b[i] = new String(sb); }else{ b[i] = a[i]; } } for(int i = 0;i < a.length;i++){ if(a[i].length() > n){ int lsd = b[i].charAt(n) - 'a' + 1; temp[lsd][order[lsd]] = a[i]; order[lsd]++; }else{ temp[0][order[0]] = a[i]; order[0]++; } } int k = 0; for(int i = 0;i < 27;i++){ for(int j = 0;j < order[i];j++){ a[k] = temp[i][j]; k++; } order[i] = 0; } n++; } } }
字典排序