排序的定義
假設含有 n 個記錄的序列爲 {r1, r2, r3 … rn },其相應的關鍵字分別爲{k1, k2, k3 … kn},需確定 1, 2,… 的一種排列 p1, p2, …, pn, 使其相應的關鍵字滿足 kp1 <= kp2 <= … <= kpn(非遞減或非遞增)關係,即使得序列成爲一個按關鍵字有序的序列{ rp1, rp2, … , rpn },這樣的操作叫作排序。
排序的穩定性:
假設 Ki = Kj (1 <= i <= n, 1 <= j <= n, i 不等於 j),且在排序前的序列中 ri (即 i < j)。如果排序後 ri 仍領先於 rj,則稱所用的排序方法是穩定的;反之,若可能使得排序後的序列中 rj 領先於 ri,則稱所用的排序方法是不穩定的。
穩定性的意義
1、如果只是簡單的進行數字的排序,那麼穩定性將毫無意義。
2、如果排序的內容僅僅是一個複雜對象的某一個數字屬性,那麼穩定性依舊將毫無意義。
3、如果要排序的內容是一個複雜對象的多個數字屬性,但是其原本的初始順序毫無意義,那麼穩定性依舊將毫無意義。
4、除非要排序的內容是一個複雜對象的多個數字屬性,且其原本的初始順序存在意義,那麼我們需要在二次排序的基礎上保持原有排序的意義,才需要使用到穩定性的算法,例如要排序的內容是一組原本按照價格高低排序的對象,如今需要按照銷量高低排序,使用穩定性算法,可以使得想同銷量的對象依舊保持着價格高低的排序展現,只有銷量不同的纔會重新排序。(當然,如果需求不需要保持初始的排序意義,那麼使用穩定性算法依舊將毫無意義)。
內排序
內排序是在排序整個過程中,待排序的所有記錄全部被放置在內存中。
外排序
外排序是由於排序的記錄個數太多,不能同時放置在內存,整個排序過程需要在內外存之間多次交換數據才能進行。
內排序的性能影響因素:
1、時間性能:時間複雜度
2、輔助空間:輔助空間是除了存放排序所佔用的存儲空間之外,執行算法所需要的其他存儲空間
3、算法的複雜性:指算法本身的複雜程度,所需要的步驟是否過多?隨之而來的指令的增多
冒泡排序(Bubble Sort)
一種交換排序,它的基本思想是:兩兩比較相鄰記錄的關鍵字,如果反序則交換,直到沒有反序的記錄爲止。
易混淆的基本交換排序:
//冒牌冒泡排序(交換排序)
public static void bubbleSort0(int[] list){
for(int i = 0; i < list.length; i++){
for(int j = i+1; j < list.length; j++){
if(list[i] > list[j])
swap(list,i,j);
}
}
}
正宗冒泡排序:
冒泡名稱的由來:較小的數字如同氣泡般慢慢浮到上面
//正宗冒泡排序
public static void bubbleSort1(int[] list){
for(int i = 0; i < list.length; i++){
for(int j = list.length - 2; j >= i; j--){
if(list[j] > list[j+1])
swap(list,j,j+1);
}
}
}
交換排序 VS 冒泡排序
後者算法在排序過程中會未後續的排序步驟減少壓力(即減少交換次數)
優化後的冒泡排序:
針對已經排序好的序列,停止判斷操作
//優化版冒泡排序
public static void bubbleSort2(int[] list){
//flag 用來標記
boolean flag = true;
//flag 爲 true 時退出循環
for(int i = 0; i < list.length && flag; i++){
//初始化爲false,如果內循環完成,flag的值不變,則說明後面的序列已經完成排序
flag = false;
for(int j = list.length - 2; j > i; j--){
if(list[j] > list[j+1]){
swap(list,j,j+1);
flag = true;
}
}
}
}
整體代碼:
public static void main(String[] args){
//基礎數據
int[] sortList = {1,4,3,10,9,6,7,8,5,2,20,18,16,11,13,15,14,17,19,12};
System.out.println(Arrays.toString(sortList));
//bubbleSort0(sortList);
//bubbleSort1(sortList);
//bubbleSort2(sortList);
//selectSort(sortList);
System.out.println(Arrays.toString(sortList));
int[] sortList2 = {0,1,4,3,10,9,6,7,8,5,2,20,18,16,11,13,15,14,17,19,12};
System.out.println(Arrays.toString(sortList2));
//insertSort(sortList2);
System.out.println(Arrays.toString(sortList2));
}
//冒牌冒泡排序(交換排序)
public static void bubbleSort0(int[] list){
for(int i = 0; i < list.length; i++){
for(int j = i+1; j < list.length; j++){
if(list[i] > list[j])
swap(list,i,j);
}
}
}
//正宗冒泡排序
public static void bubbleSort1(int[] list){
for(int i = 0; i < list.length; i++){
for(int j = list.length - 2; j >= i; j--){
if(list[j] > list[j+1])
swap(list,j,j+1);
}
}
}
//優化版冒泡排序
public static void bubbleSort2(int[] list){
//flag 用來標記
boolean flag = true;
//flag 爲 true 時退出循環
for(int i = 0; i < list.length && flag; i++){
//初始化爲false,如果內循環完成,flag的值不變,則說明後面的序列已經完成排序
flag = false;
for(int j = list.length - 2; j > i; j--){
if(list[j] > list[j+1]){
swap(list,j,j+1);
flag = true;
}
}
}
}
時間複雜度比較:
最好: O[n ]
最壞: O[n^2)
簡單選擇排序
思想:
在排序時找到適合的關鍵字再做交換,並且只移動一次就完成相應關鍵字的排序定位工作
生活類比:類似於買股票一樣,抓住最關鍵點去買
//選擇排序法
public static void selectSort(int[] list){
int min;
for(int i = 0; i < list.length; i++){
//將當前下標定義爲最小值
min = i;
for(int j = i+1; j < list.length; j++){
//把後續的值與當前最小值比較
if(list[min] > list[j])
min = j;
}
//若 i 不等於 min,說明找到了最小值,進行交換
if( i != min)
swap(list,i,min);
}
}
時間複雜度比較:
最好: O[n ]
最壞: O[n2)
直接插入排序
思想:將一個記錄插入到已經排好序的有序表中,從而得到一個新的,記錄數增 1 的有序表
生活類比:類似於玩撲克牌或者打麻將,選出一張牌兒,直接插入
特點:需要佔用一個輔助空間,就是頭部的空間,並且不作爲最後的排序結果
哨兵的思想 待補充在各種算法的體驗,以及哨兵思想的由來
//插入排序法
public static void insertSort(int[] list){
int i,j;
for( i = 2; i < list.length; i++){
//需要將 i 序號的值插入有序子表
if(list[i] < list[i-1]){
list[0] = list[i];
for( j = i-1; list[j] > list[0]; j--)
//大於哨兵的記錄後移
list[j+1] = list[j];
//插入到有序位置
list[j+1] = list[0];
}
}
}
時間複雜度比較:
最好: O[n ]
最壞: O[n2)