排序演算法(二)
4、希爾排序 原理:希爾排序是直接插入排序的增強,希爾排序是將整個序列分成若干個組,然後進行分組插入排序,分組的增量gap從大到小,最終必須為1,當增量為1時,也就是插入排序(gap一般是gap = gap/2不斷縮小到1或者gap = (gap/3+1),逐步縮小至1)。 舉例如下: 程式碼實現如下:
public class ShellSort { public void shellSort(int[] arr) { int gap = arr.length;//增量 int i,j,temp; while (gap > 0) { gap = gap/2;//逐步縮小到1 for (i = gap; i < arr.length; i++) { if (arr[i] < arr[i-gap]){ temp = arr[i]; for(j = i-gap;j >= 0;j -= gap){//將大於temp的元素放到後面 if(arr[j] > temp) { arr[j + gap] = arr[j]; }else{ break; } } arr[j+gap] = temp; } } } } public static void main(String[] args){ ShellSort m = new ShellSort(); int[] arr = new int[]{3,6,2,8,5,7,3,8,5,8,3,7,0}; m.shellSort(arr); System.out.println(Arrays.toString(arr)); } }
結果如下: 5、快速排序 快速排序是排序演算法中非常重要的一種,也是經常會考到的一種,同時它有多種改進演算法,需要細細研究。 原理:快速排序是選定一個基準,將大於基準的放在右邊,將小於基準的放在左邊,依次遞迴,最終達到整個序列的有序,一般改進的演算法主要是在選定基準時進行改進。 步驟: (1) 選定基準值後,從後往前進行比較,找到第一個小於基準值的進行交換; (2) 然後,再從前往後找,找到第一個大於基準的進行交換; (3) 直到從前往後的比較陣列下標大於從後往前的比較陣列下標,完成一次迴圈; (4) 接著按著上述迴圈分別比較左右兩側的序列,最終有序。 程式碼實現:
public void swap(int[] arr,int i,int j){ int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } public int partition(int[] arr,int low,int high){ int base = arr[low];//基準 while(low < high){ while(low < high && arr[high] >= base){//右側掃描,找到第一個小於base的元素 high--; } arr[low] = arr[high]; while(low < high && arr[low] <= base){//左側掃描,找到第一個大於base的元素 low++; } arr[high] = arr[low]; } arr[low] = base; return low; } public void quickSort(int[] arr,int low,int high){ if(low < high){ int p = partition(arr,low,high); quickSort(arr,low,p-1); quickSort(arr,p+1,high); } }
快速排序演算法的優化: (1)隨機選定基準 當序列幾乎有序時,基準每次選定為第一個值時,快排的效能不好,因此,將基準選擇改為隨機的。(但當整個序列是亂序的,排序效能可能會下降,有些許的運氣成分) 程式碼實現:
public void swap(int[] arr,int i,int j){ int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } public int partition(int[] arr,int low,int high){ //排序前將base與隨機的數進行交換,此時,基準值具有隨機性了 swap(arr,low,(int)(Math.random()*(high-low+1)+low)); int base = arr[low]; int j = low; for(int i = low+1;i <= high;i++){ if(base > arr[i]){ j++; swap(arr,i,j); } } swap(arr,low,j); return j; } public void quickSort(int[] arr,int low,int high){ if(low < high){ int p = partition(arr,low,high); quickSort(arr,low,p-1); quickSort(arr,p+1,high); } }
(2)兩路快排 快速排序時,將大於基準的排在基準右邊,將小於等於基準的排在基準左邊。 程式碼實現:
public void swap(int[] arr,int i,int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public int partition(int[] arr,int low,int high){
swap(arr,low,(int)(Math.random()*(high-low+1)+low));
int base = arr[low];//每次將基準丟擲,相當於對[low+1,...,high]的排序
int i = low+1;//i表示對[low+1,...,i)的比base小(等)的部分排序
int j = high;//j表示對(j,...,high]的比base大(等)的部分排序
while(true){
while (i <= high && arr[i] < base){//左側掃描,找到第一個比base大的元素
i++;
}
while(j >= low && arr[j] > base){//右側掃描,找到第一個比base小的元素
j--;
}
if(i > j){
break;
}
swap(arr,i++,j--);
}
swap(arr,low,j);
return j;
}
public void quickSort(int[] arr,int low,int high){
if(low < high){
int p = partition(arr,low,high);
quickSort(arr,low,p-1);
quickSort(arr,p+1,high);
}
}
(3)配合插入排序使用 快排是採用分治思想,也就是遞迴將問題不斷縮小規模進而解決,但當資料規模小,整個序列近乎有序時,採用“遞迴+不穩定”的方式,效率不一定好,此時利用插入排序,效果會更好一些。 程式碼如下:
public void swap(int[] arr,int i,int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public void insertSort(int[] arr,int m,int n){
int i,j,temp = 0;
for(i = m+1;i <= n;i++){
temp = arr[i];
for(j = i-1;j >= 0 && arr[j] > temp;j--){//將大於基準的後移
arr[j+1] = arr[j];
}
arr[j+1] = temp;
}
}
public int partition(int[] arr,int low,int high){
swap(arr,low,(int)(Math.random()*(high-low+1)+low));
int base = arr[low];
int j = low;
for(int i = low+1;i <= high;i++) {
if (base > arr[i]) {
j++;
swap(arr, i, j);
}
}
swap(arr,low,j);
return j;
}
public void quickSort(int[] arr,int low,int high,int k){
if(low >= high) return;
if(high-low < k){
insertSort(arr,low,high);
return;
}
int p = partition(arr,low,high);
quickSort(arr,low,p-1,k);
quickSort(arr,p+1,high,k);
}
(4)三路快排 當整個序列中有大量的重複資料時,將整個序列分成小於基準,等於基準,大於基準三部分進行排序,也就是所稱的三路快排。用指標從前到後掃描,如果cur指向的數小於base,那麼交換arr[cur]和arr[i]的值,然後i++,cur++;cur指向的數等於base, 那麼cur++;cur指向的數大於base,那麼交換arr[cur]和arr[j]的值,然後j–。當cur > j的時候說明三路都已經完成。 程式碼實現如下:
public static void swap(int[] arr,int i,int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public int[] partition(int[] arr,int low,int high){
swap(arr,low,(int)(Math.random()*(high-low+1)+low));
int temp = arr[low];
int i = low;
int j = high;
int cur = i;
while(cur <= j){
if(arr[cur] < temp){
swap(arr,cur++,i++);
}else if(arr[cur] == temp){
cur++;
}else{
swap(arr,cur,j--);
}
}
return new int[]{i-1,j+1};//[i...j]都等於base,子問題就只需要解決i左邊和j右邊就行了
}
public void quickSort(int[] arr,int low,int high){
if(low < high){
int[] brr = partition(arr,low,high);
quickSort(arr,low,brr[0]);
quickSort(arr,brr[1],high);
}
}
}
三路快排可以避免很多重複元素再次參與遞迴,對於大量重複的待排序列,效率有所提升。