java 八大排序演算法
文章目錄
常用的排序演算法主要包括:- 插入排序 直接插入排序 希爾排序
- 交換排序 氣泡排序 快速排序
- 選擇排序 簡單選擇排序 堆排序
- 歸併排序
- 基數排序
交換排序
氣泡排序
氣泡排序是最簡單的一種排序演算法。
- 思想 在要排序的一組數中,對當前還未排好序的範圍內的全部數,自上而下對相鄰的兩個數依次進行比較和調整,讓較大的數往下沉,較小的往上冒。即:每當兩相鄰的數比較後發現它們的排序與排序要求相反時,就將它們互換。 一句話概括:每遍歷一次,就把最大的元素放在最後。
- 演算法實現
package sort;
/**
* 氣泡排序
* 思路:
* 依次比較相鄰的兩個數,將小數放在前面,大數放在後面。即在第一趟:首先比較第1個和第2個數,將小數放前,大數放後。然後比較第2個數和第3個數,將小數放前,大數放後,如此繼續,直至比較最後兩個數,將小數放前,大數放後。重複第一趟步驟,直至全部排序完成。
* 第一趟比較完成後,最後一個數一定是陣列中最大的一個數,所以第二趟比較的時候最後一個數不參與比較;
* 第二趟比較完成後,倒數第二個數也一定是陣列中第二大的數,所以第三趟比較的時候最後兩個數不參與比較;
* 依次類推,每一趟比較次數-1;
* ……
*/
import java.util.Arrays;
public class BubbleSort {
public static void bubbleSort(int[] arr){
int temp=0;
for(int i=0;i<arr.length-1;i++) {
for(int j=0;j<arr.length-1-i;j++) {
if(arr[j]>arr[j+1]) {
temp=arr[j+1];
arr[j+1]=arr[j];
arr[j]=temp;
}
}
System.out. println("第["+(i+1)+"]輪,排序結果:"+Arrays.toString(arr));
}
}
public static void main(String[] args) {
int arr[]= {65,58,95,10,57,62,13,106,78,23,85};
System.out.println("排序前:"+Arrays.toString(arr));
System.out.println("-------------------------------------------------------");
bubbleSort(arr);
System.out.println("-------------------------------------------------------");
System.out.println("排序後:"+Arrays.toString(arr));
}
}
- 排序結果
排序前:[65, 58, 95, 10, 57, 62, 13, 106, 78, 23, 85]
-------------------------------------------------------
第[1]輪,排序結果:[58, 65, 10, 57, 62, 13, 95, 78, 23, 85, 106]
第[2]輪,排序結果:[58, 10, 57, 62, 13, 65, 78, 23, 85, 95, 106]
第[3]輪,排序結果:[10, 57, 58, 13, 62, 65, 23, 78, 85, 95, 106]
第[4]輪,排序結果:[10, 57, 13, 58, 62, 23, 65, 78, 85, 95, 106]
第[5]輪,排序結果:[10, 13, 57, 58, 23, 62, 65, 78, 85, 95, 106]
第[6]輪,排序結果:[10, 13, 57, 23, 58, 62, 65, 78, 85, 95, 106]
第[7]輪,排序結果:[10, 13, 23, 57, 58, 62, 65, 78, 85, 95, 106]
第[8]輪,排序結果:[10, 13, 23, 57, 58, 62, 65, 78, 85, 95, 106]
第[9]輪,排序結果:[10, 13, 23, 57, 58, 62, 65, 78, 85, 95, 106]
第[10]輪,排序結果:[10, 13, 23, 57, 58, 62, 65, 78, 85, 95, 106]
-------------------------------------------------------
排序後:[10, 13, 23, 57, 58, 62, 65, 78, 85, 95, 106]
- 優化思路 加入一標誌性變數exchange,用於標誌某一趟排序過程中是否有資料交換,如果進行某一趟排序時並沒有進行資料交換,則說明資料已經按要求排列好,可立即結束排序,避免不必要的比較過程。
- 優化程式碼
package sort;
import java.util.Arrays;
public class BubbleSort {
public static void bubbleSort(int[] arr){
int temp=0;
int flag=0;
for(int i=0;i<arr.length-1;i++) {
flag=0;//是否進行了交換的識別符號
for(int j=0;j<arr.length-1-i;j++) {
if(arr[j]>arr[j+1]) {
temp=arr[j+1];
arr[j+1]=arr[j];
arr[j]=temp;
flag=1;
}
}
if(flag==0) {
break;
}
System.out.println("第["+(i+1)+"]輪,排序結果:"+Arrays.toString(arr));
}
}
public static void main(String[] args) {
//int arr[]= {65,58,95,10,57,62,13,106,78,23,85};
int arr[]= {11,22,33,44,55,66};
System.out.println("排序前:"+Arrays.toString(arr));
System.out.println("-------------------------------------------------------");
bubbleSort(arr);
System.out.println("-------------------------------------------------------");
System.out.println("排序後:"+Arrays.toString(arr));
}
}
- 排序結果
排序前:[11, 22, 33, 44, 55, 66]
-------------------------------------------------------
-------------------------------------------------------
排序後:[11, 22, 33, 44, 55, 66]
快速排序
本質上來看,快速排序應該算是在氣泡排序基礎上的遞迴分治法。
-
思想 快速排序使用分治策略來把一個序列分為兩個子序列。步驟為:
從數列中挑出一個元素,稱為"基準"。 重新排序數列,所有比基準值小的元素擺放在基準前面,所有比基準值大的元素擺在基準後面(相同的數可以到任一邊)。在這個分割槽結束之後,該基準就處於數列的中間位置。這個稱為分割槽操作。 遞迴地把小於基準值元素的子數列和大於基準值元素的子數列排序。 遞迴到最底部時,數列的大小是0或1,也就是已經排序好了。這個演算法一定會結束,因為在每次的迭代中,它至少會把一個元素擺到它最後的位置去。
-
演算法實現
package sort;
import java.util.Arrays;
/**
* 快速排序
* 思路:
* 選擇一個關鍵值作為基準值。比基準值小的都在左邊序列(一般是無序的),比基準值大的都在右邊(一般是無序的)。一般選擇序列的第一個元素。
* 一次迴圈:從後往前比較,用基準值和最後一個值比較,如果比基準值小的交換位置,如果沒有繼續比較下一個,直到找到第一個比基準值小的值才交換。
* 找到這個值之後,又從前往後開始比較,如果有比基準值大的,交換位置,如果沒有繼續比較下一個,直到找到第一個比基準值大的值才交換。
* 直到從前往後的比較索引>從後往前比較的索引,結束第一次迴圈,此時,對於基準值來說,左右兩邊就是有序的了。
* 接著分別比較左右兩邊的序列,重複上述的迴圈。
*
* @author 54060
*
*/
public class QuickSort {
public static void quickSort(int [] arr,int left,int right) {
int pivot=0;
if(left<right) {
pivot=partition(arr,left,right);
quickSort(arr,left,pivot-1);
quickSort(arr,pivot+1,right);
}
}
private static int partition(int[] arr,int left,int right) {
int key=arr[left];
while(left<right) {
while(left<right && arr[right]>=key) {
right--;
}
arr[left]=arr[right];
while(left<right && arr[left]<=key) {
left++;
}
arr[right]=arr[left];
}
arr[left]=key;
return left;
}
public static void main(String[] args) {
int arr[]= {65,58,95,10,57,62,13,106,78,23,85};
System.out.println("排序前:"+Arrays.toString(arr));
quickSort(arr,0,arr.length-1);
System.out.println("排序後:"+Arrays.toString(arr));
}
}
- 排序結果
排序前:[65, 58, 95, 10, 57, 62, 13, 106, 78, 23, 85]
排序後:[10, 13, 23, 57, 58, 62, 65, 78, 85, 95, 106]
選擇排序
直接選擇排序
- 思路 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然後,再從剩餘未排序元素中繼續尋找最小(大)元素,然後放到已排序序列的末尾。以此類推,直到所有元素均排序完畢。
- 演算法實現
package sort;
import java.util.Arrays;
/**
* 選擇排序
* 1.找到陣列中最小的那個元素
* 2.將最小的這個元素和陣列中第一個元素交換位置
* 3.在剩下的元素中找到最小的的元素,與陣列第二個元素交換位置
* 重複以上步驟,即可以得到有序陣列。
* @author 54060
*
*/
public class SelectionSort {
//{ 5,3,6,2,10 }->{2,3,6,5,10}->{2,3,6,5,10}->{2,3,5,6,10}->{2,3,5,6,10}
public static void selectionSort(int[] arr) {
int n = arr.length;
for (int i = 0; i < n; i++) {
int k = i;
// 找出最小值的小標
for (int j = i + 1; j < n; j++) {
if (arr[j] < arr[k]) {
k = j;
}
}
// 將最小值放到排序序列開頭(調換位置)
if (k > i) {//如果第i個就是最小的,則不用交換了
int tmp = arr[i];
arr[i] = arr[k];
arr[k] = tmp;
}
}
}
public static void main(String[] args) {
int[] arr = {5,3,6,2,10};
System.out.println("排序前:"+Arrays.toString(arr));
selectionSort(arr);
System.out.println("排序後:"+Arrays.toString(arr));
}
}
- 排序結果
排序前:[5, 3, 6, 2, 10]
排序後:[2, 3, 5, 6, 10]
堆排序
- 堆的定義 n個元素的序列{k1,k2,…,kn}當且僅當滿足下列關係之一時,稱之為堆。 情形1:ki <= k2i 且ki <= k2i+1 (最小化堆或小頂堆) 情形2:ki >= k2i 且ki >= k2i+1 (最大化堆或大頂堆) 其中i=1,2,…,n/2向下取整; 若將和此序列對應的一維陣列(即以一維陣列作此序列的儲存結構)看成是一個完全二叉樹,則堆的含義表明,完全二叉樹中所有非終端結點的值均不大於(或不小於)其左、右孩子結點的值。 由此,若序列{k1,k2,…,kn}是堆,則堆頂元素(或完全二叉樹的根)必為序列中n個元素的最小值(或最大值)。
- 堆排序 初始時把要排序的n個數的序列看作是一棵順序儲存的二叉樹(一維陣列儲存二叉樹),調整它們的儲存序,使之成為一個堆,將堆頂元素輸出,得到n 個元素中最小(或最大)的元素,這時堆的根節點的數最小(或者最大)。然後對前面(n-1)個元素重新調整使之成為堆,輸出堆頂元素,得到n 個元素中次小(或次大)的元素。依此類推,直到只有兩個節點的堆,並對它們作交換,最後得到有n個節點的有序序列。稱這個過程為堆排序。
- 堆排序的實現 因此,實現堆排序需解決兩個問題:
- 如何將n 個待排序的數建成堆;
- 輸出堆頂元素後,怎樣調整剩餘n-1 個元素,使其成為一個新堆。
首先討論第2個問題:輸出堆頂元素後,對剩餘n-1元素重新建成堆的調整過程。 調整小頂堆的方法:
1)設有m 個元素的堆,輸出堆頂元素後,剩下m-1 個元素。將堆底元素送入堆頂((最後一個元素與堆頂進行交換),堆被破壞,其原因僅是根結點不滿足堆的性質。
2)將根結點與左、右子樹中較小元素的進行交換。
3)若與左子樹交換:如果左子樹堆被破壞,即左子樹的根結點不滿足堆的性質,則重複方法 (2).
4)若與右子樹交換,如果右子樹堆被破壞,即右子樹的根結點不滿足堆的性質。則重複方法 (2).
5)繼續對不滿足堆性質的子樹進行上述交換操作,直到葉子結點,堆被建成。
稱這個自根結點到葉子結點的調整過程為篩選。如圖: 再討論對第2個問題:n 個元素初始建堆的過程。 建堆方法:對初始序列建堆的過程,就是一個反覆進行篩選的過程。
1)n 個結點的完全二叉樹,則最後一個結點是第個結點的子樹。
2)篩選從第個結點為根的子樹開始,該子樹成為堆。
3)之後向前依次對各結點為根的子樹進行篩選,使之成為堆,直到根結點。
如圖建堆初始過程:無序序列:(49,38,65,97,76,13,27,49)
-
對於堆節點的訪問: 父節點i的左子節點在位置:(2i+1); 父節點i的右子節點在位置:(2i+2); 子節點i的父節點在位置:floor((i-1)/2);
-
演算法實現
package sort;
import java.util.Arrays;
/**
* 堆排序
* @author 54060
*
*/
public class HeapSort {
/*
* 建立小頂堆:雙親節點小於子節點的值。從葉子節點開始,直到根節點。這樣建立的堆定位最小值
*/
private void createLittleHeap(int[] data, int last) {
for (int i = (last- 1) / 2; i >= 0; i--) { //找到最後一個葉子節點的雙親節點
// 儲存當前正在判斷的節點
int parent = i;
// 若當前節點的左子節點存在,即子節點存在
while (2 * parent + 1 <= last) {
// biggerIndex總是記錄較大節點的值,先賦值為當前判斷節點的左子節點
int bigger = 2 * parent + 1;//bigger指向左子節點
if (bigger < last) { //說明存在右子節點
if (data[bigger] > data[bigger+ 1]) { //右子節點>左子節點時
bigger=bigger+1; //bigger取左子節點和右子節點最小的那個。因為父節點要小於這兩個節點的值
}
}
//下-->上-->下
if (data[parent] > data[bigger]) { //若雙親節點值大於子節點中最小的
// 若當前節點值比子節點最小值大,則交換2者的值,交換後將biggerIndex值賦值給k
swap(data, parent, bigger)