十大經典排序算法
排序算法日常使用的最基本的算法之一,大體可以分爲外部排序和內部排序。
- 內部排序指的是待排序列完全存放在內存中所進行的排序過程,只適合較短的元素序列,不適合大數據排序。
- 外部排序指的是大文件排序,待排序列存儲在外存儲器上,體積過大無法一次裝入內存,需要在內存和外存儲器之間進行多次數據交換。
一、內部排序的比較
排序算法 | 平均時間複雜度 | 最好情況 | 最壞情況 | 空間複雜度 | 穩定性 |
---|---|---|---|---|---|
冒泡排序 | O(n^2) | O(n) | O(n^2) | O(1) | 穩定 |
選擇排序 | O(n^2) | O(n^2) | O(n^2) | O(1) | 不穩定 |
插入排序 | O(n^2) | O(n) | O(n^2) | O(1) | 穩定 |
希爾排序 | O(nlogn) | O(n) | O(n ^ s) 1<s<2 | O(1) | 不穩定 |
歸併排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 穩定 |
快速排序 | O(nlogn) | O(nlogn) | O(n ^ 2) | O(nlogn) | 不穩定 |
堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(1) | 不穩定 |
計數排序 | O(n + k) | O(n + k) | O(n + k) | O(k) | 穩定 |
桶排序 | O(n + k) | O(n + k) | O(n^2) | O(n + k) | 穩定 |
基數排序 | O(n * k) | O(n * k) | O(n * k) | O(n + k) | 穩定 |
在基本有序情況下,高效地算法是:直接插入排序,冒泡排序
在無序情況下:高效的算法是:堆排序,快速排序,歸併排序。
關於時間複雜度:
- 平方階排序(On^2):插入、選擇和冒泡排序
- 線性對數階(O(nlog2n))排序:快速排序、堆排序、歸併排序
- O(n1+§)) 排序,§ 是介於 0 和 1 之間的常數。 希爾排序
- 線性階 (O(n)) 排序 基數排序,此外還有桶、箱排序。
關於穩定性:
- 穩定的排序算法:冒泡排序、插入排序、歸併排序和基數排序
- 不穩定的排序算法:選擇排序、快速排序、希爾排序、堆排序
1.1 冒泡排序
1.1.1 算法步驟:
依次比較相鄰的兩個數,把小數放在前面,大數放在後面。首先第一趟,比較第一個和第二個數,小數在前,大數在後,然後比較第二和第三兩個數,小數在前,大數在後,如此依次比較,直至到數組末尾第一趟結束。針對除了最後一個元素的所有元素重複以上步驟,直至沒有任何一對數字需要比較。
規律:N個數字排序,總共進行N-1趟排序,每趟將一個最大值移至末尾。
示例:以3,1,0,2,4,9,8,6,7,5
爲例:
- 第1趟:[1, 0, 2, 3, 4, 8, 6, 7, 5, 9]
- 第2趟:[0, 1, 2, 3, 4, 6, 7, 5, 8, 9]
- 第3趟:[0, 1, 2, 3, 4, 6, 5, 7, 8, 9]
- 第4趟:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
- 第5趟:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
結果 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
1.1.2 參考代碼:
/**
* 冒泡排序
* */
public void bubbleSort(int[] nums) {
int n = nums.length;
for (int i = 0;i < n-1; i ++) {
boolean flag = true;
for (int j = 0;j < n - i-1; j++) {
if (nums[j] > nums[j + 1]) {
int tmp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = tmp;
flag = false;
}
}
if (flag) break;
}
}
1.2 選擇排序
1.2.1 算法步驟:
首先在待排序列中選擇最小(大)元素,存放到待排序列的起始(結束)位置。然後從剩餘待排序列中繼續選擇最小(大元素),存放到已排序列的末尾。重複以上步驟,直至整個序列排序完成。
示例:以3,1,0,2,4,9,8,6,7,5
爲例:
- 第1趟:[0, 1, 3, 2, 4, 9, 8, 6, 7, 5]
- 第2趟:[0, 1, 3, 2, 4, 9, 8, 6, 7, 5]
- 第3趟:[0, 1, 2, 3, 4, 9, 8, 6, 7, 5]
- 第4趟:[0, 1, 2, 3, 4, 9, 8, 6, 7, 5]
- 第5趟:[0, 1, 2, 3, 4, 9, 8, 6, 7, 5]
- 第6趟:[0, 1, 2, 3, 4, 5, 8, 6, 7, 9]
- 第7趟:[0, 1, 2, 3, 4, 5, 6, 8, 7, 9]
- 第8趟:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
- 第9趟:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
- 第10趟:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
結果 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
1.2.2 參考代碼:
/**
* 簡單選擇排序
* */
public void selectionSort(int[] nums) {
for (int i = 0;i < nums.length; i++) {
int small_index = i;
for (int j = i + 1; j < nums.length; j++) {
if (nums[j] < nums[small_index]) {
small_index = j;
}
}
if (i < small_index) {
int tmp = nums[i];
nums[i] = nums[small_index];
nums[small_index] = tmp;
}
System.out.println("* 第" + (i+1) + "趟:" + Arrays.toString(nums));
}
}
1.3 插入排序
1.3.1 算法步驟:
把初始n個待排序的元素,看成有序表+無序表。初始狀態,有序表只有待排序序列的第一個元素,無序表中有n-1個元素。每次排序,從無序表中取出第i個原則,插入都有序表中對應合適的位置。重複這個步驟,直至所有元素都插入有序表,代表整個序列排序完成。
示例:以3,1,0,2,4,9,8,6,7,5
爲例:
- 第1趟:[1, 3, 0, 2, 4, 9, 8, 6, 7, 5]
- 第2趟:[0, 1, 3, 2, 4, 9, 8, 6, 7, 5]
- 第3趟:[0, 1, 2, 3, 4, 9, 8, 6, 7, 5]
- 第4趟:[0, 1, 2, 3, 4, 9, 8, 6, 7, 5]
- 第5趟:[0, 1, 2, 3, 4, 9, 8, 6, 7, 5]
- 第6趟:[0, 1, 2, 3, 4, 8, 9, 6, 7, 5]
- 第7趟:[0, 1, 2, 3, 4, 6, 8, 9, 7, 5]
- 第8趟:[0, 1, 2, 3, 4, 6, 7, 8, 9, 5]
- 第9趟:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
結果:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
1.3.2 參考代碼:
/**
* 直接插入排序
* */
public void insertSort(int[] nums) {
if(nums==null||nums.length==0)
return;
for (int i = 1;i < nums.length; i++) {
int tmp = nums[i] , j = i;
if (nums[j - 1] > tmp) {
while (j >= 1 && nums[j - 1] > tmp) {
nums[j] = nums[j - 1];
j--;
}
}
nums[j] = tmp;
System.out.println("* 第" + (i) + "趟:" + Arrays.toString(nums));
}
}
1.4 希爾排序
1.4.1 算法步驟:
首先選擇一個增量序列 t1 , t2, … , tk , 其中ti > tj , tk = 1; 然後按照增量分別將待排數組分成多個子序列,使每個子序列元素個數相對較少,然後對各個子序列分別進行直接插入排序,代整個排序序列基本有序後,對所有元組再進行一次直接插入排序。
示例:以3,1,0,2,4,9,8,6,7,5
爲例:
- 增量爲5的時:[3, 1, 0, 2, 4, 9, 8, 6, 7, 5]
- 增量爲2的時:[0, 1, 3, 2, 4, 5, 7, 6, 8, 9]
- 增量爲1的時:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
1.4.2 參考代碼:
/**
* 希爾排序
* */
public static void hillSort(int[] nums){
if(nums == null || nums.length <= 1){
return;
}
int i,j;
int increment;
int temp;
for(increment=nums.length/2;increment>0;increment/=2) {
for(i=increment;i<nums.length;i++) {
temp=nums[i];
for(j=i-increment;j>=0;j-=increment) {
if(temp<nums[j]) {
nums[j+increment]=nums[j];
}else
break;
}
nums[j+increment]=temp;
}
}
}
1.5 歸併排序
1.5.1 算法步驟
歸併排序是利用歸併的思想實現的排序方法,該方法採用的經典的分治策略(把問題分解成若干個小問題然後遞歸求解)。首先我們需要申請空間,使其大小爲兩個已排序序列之和,該空間用來存放合併後的序列。然後我們設定兩個指針,最初位置分別是兩個已經排序序列的起始位置。比較兩個比較兩個指針所指向的元素,選擇相對小的元素放入到合併空間,並移動指針到下一位置;重複這個步驟 直到某一指針達到序列尾;然後將將另一序列剩下的所有元素直接複製到合併序列尾。
示例:以3,1,0,2,4,9,8,6,7,5
爲例:
- [3, 1, 0, 2, 4, 9, 8, 6, 7, 5]
- [3, 1, 0, 2, 4, 9, 8, 6, 7, 5]
- [3, 1, 0, 2, 4, 9, 8, 6, 7, 5]
- [3, 1, 0, 2, 4, 9, 8, 6, 7, 5]
- [0, 1, 3, 2, 4, 9, 8, 6, 7, 5]
- [0, 1, 2, 3, 4, 9, 8, 6, 7, 5]
- [0, 1, 2, 3, 4, 9, 8, 6, 7, 5]
- [0, 1, 2, 3, 4, 9, 8, 6, 7, 5]
- [0, 1, 2, 3, 4, 6, 8, 9, 7, 5]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
1.5.2 參考代碼
/**
* 歸併排序
* */
public static void mergeSort(int []arr){
int []temp = new int[arr.length];//在排序前,先建好一個長度等於原數組長度的臨時數組,避免遞歸中頻繁開闢空間
sort(arr,0,arr.length-1,temp);
}
private static void merge(int[] arr,int left,int mid,int right,int[] temp){
int i = left;//左序列指針
int j = mid+1;//右序列指針
int t = 0;//臨時數組指針
while (i<=mid && j<=right){
if(arr[i]<=arr[j]){
temp[t++] = arr[i++];
}else {
temp[t++] = arr[j++];
}
}
while(i<=mid){//將左邊剩餘元素填充進temp中
temp[t++] = arr[i++];
}
while(j<=right){//將右序列剩餘元素填充進temp中
temp[t++] = arr[j++];
}
t = 0;//因爲需要拷貝所以要把臨時數組的指針置0(即指向第一個元素)
//將temp中的元素全部拷貝到原數組中
while(left <= right){
arr[left++] = temp[t++];
}
}
private static void sort(int[] arr,int left,int right,int []temp){
if(left<right){
int mid = (left+right)/2;
sort(arr,left,mid,temp);//左邊歸併排序,使得左子序列有序
sort(arr,mid+1,right,temp);//右邊歸併排序,使得右子序列有序
merge(arr,left,mid,right,temp);//將兩個有序子數組合並操作
}
}
1.6 快速排序
1.6.1 算法步驟
每次挑選出一個元素爲基準
,設兩個指針,一個left
一個right
,分別指向序列的頭和尾。用兩個指針掃描數組,把所有比基準大的,都擺放在基準的後面,所有比基準小的,都擺放在基準的後面。這一次掃描之後,基準就位於它的最終位置。然後分別在基準左側和基準右側再次執行這個操作。知道整個序列排列完成。
示例:以3,1,0,2,4,9,8,6,7,5
爲例:
- [3, 1, 0, 2, 4, 9, 8, 6, 7, 5]
- [2, 1, 0, 3, 4, 9, 8, 6, 7, 5]
- [0, 1, 2, 3, 4, 9, 8, 6, 7, 5]
- [0, 1, 2, 3, 4, 9, 8, 6, 7, 5]
- [0, 1, 2, 3, 4, 9, 8, 6, 7, 5]
- [0, 1, 2, 3, 4, 5, 8, 6, 7, 9]
- [0, 1, 2, 3, 4, 5, 8, 6, 7, 9]
- [0, 1, 2, 3, 4, 5, 7, 6, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
1.6.2 參考代碼
/**
* 快速排序
* */
private void quickSort(int nums[], int left , int right) {
if (left < right) {
int i = left , j = right,temp = nums[left];
System.out.println(" * "+Arrays.toString(nums));
while (i < j) {
while (nums[j] > temp && i < j) j--;
if (i < j){
nums[i] = nums[j]; i++;
}
while (nums[i] < temp && i < j) i++;
if (i < j) {
nums[j] = nums[i]; j--;
}
}
nums[i] = temp;
quickSort(nums , left , i -1);
quickSort(nums , i+1 , right);
}
}
1.7 堆排序(重要!!!)
1.7.1 算法步驟
首先,將待排序列構造成一個大頂堆。我們用數組+限定規則構造大頂堆,下標爲i
的數組元素是下標爲2*i+1
與2*i+2
元素的父節點。且i
的數組元素的父節點下標爲(i-1)/2
此時堆頂元素就是序列的最大值。然後我們把最後一個位置的數和堆頂位置的數做交換,把最大值放到數組最後的位置。此時讓堆大小減1,最大值就被固定在了末尾。
把除最後一個元素的序列重新調整成大頂堆,然後依次執行上面的步驟,輸出倒數第二大的數。依次類推,把所有元素都輸出之後,整個序列就變成了有序序列。
示例:以3,1,0,2,4,9,8,6,7,5
爲例:
- [2, 7, 8, 6, 5, 0, 4, 1, 3, 9]
- [3, 7, 4, 6, 5, 0, 2, 1, 8, 9]
- [1, 6, 4, 3, 5, 0, 2, 7, 8, 9]
- [2, 5, 4, 3, 1, 0, 6, 7, 8, 9]
- [0, 3, 4, 2, 1, 5, 6, 7, 8, 9]
- [1, 3, 0, 2, 4, 5, 6, 7, 8, 9]
- [1, 2, 0, 3, 4, 5, 6, 7, 8, 9]
- [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
- [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
1.7.2 參考代碼
/**
* 堆排序
* */
public void heapSort(int [] arr) {
if(arr==null||arr.length<2) {
return;
}
for(int i=0;i<arr.length;i++) {
heapInsert(arr,i);//依次將數組中i位置上的數加進來,讓它0~i之間的數形成大頂堆
}
int heapSize=arr.length;//堆大小heapSize一開始等於數組的全部
swap(arr,0,--heapSize);//最後一個位置上的數與第一個位置上的數(堆頂元素)交換,堆大小減1,即最後一個位置上的數不動了
while(heapSize>0) {
heapify(arr,0,heapSize);//從0位置開始,將當前形成的堆繼續調整爲一個大頂堆
swap(arr,0,--heapSize);//最後一個位置上的數與第一個位置上的數(堆頂元素)交換,堆大小減1,即最後一個位置上的數不動了
}
}
//建立大頂堆的過程
public void heapInsert(int[] arr,int index) {
while(arr[index] > arr[(index-1)/2]) {//當前index位置上的數若比其父結點上的數大,則交換他倆的位置
swap(arr,index,(index-1)/2);
index=(index-1)/2;//然後index來到了它的父節點位置,繼續上面的while
}
}
//若有一個節點的值變小了,則需要往下沉(與其左右孩子中較大的數進行交換位置的)的操作
public void heapify(int[] arr,int index,int heapSize) {
int left=index*2+1;//左孩子下標
while(left<heapSize) {//未越界,即該節點並非葉子節點,存在左孩子
//該節點有右孩子,讓largest作爲左右孩子較大值的下標
int largest=(left+1 < heapSize) && arr[left+1] > arr[left] ? left+1 : left;
//讓largest成爲該節點與該節點左右孩子中較大值的下標
largest=arr[largest] > arr[index] ? largest : index;
if(largest==index) {
break;
}
swap(arr,largest,index);//largest!=index
index=largest;//該節點下標變成了較大孩子的下標
left=index*2+1;//繼續往下走,重複上面的while
}
}
public void swap(int[] arr,int i,int j) {
int temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
代碼已上傳至本人Github倉庫 , 歡迎大家star,follow