面試常用排序演算法java實現
阿新 • • 發佈:2019-02-16
概要
本文總結面試常見的排序演算法,及基本的實現java
話不多說,先上乾貨。
一、演算法的複雜度及穩定性
穩定的排序演算法是:氣泡排序,直接插入排序,歸併排序,基數排序,二叉樹排序,計數排序。
不穩定的排序演算法:選擇排序,快速排序,堆排序,希爾排序。
二、演算法的實現
面試中常見的演算法: 快速排序>歸併排序>堆排序>冒泡>插入>選擇。下面依次實現各排序演算法。
1. 快速排序
- 演算法原理
快速排序是目前在實踐中非常高效的一種排序演算法,它不是穩定的排序演算法,平均時間複雜度為O(nlogn),最差情況下複雜度為O(n^2)。它的基本思想是:通過一趟排序將要排序的資料分割成獨立的兩部分,其中一部分的所有資料都比另外一部分的所有資料都要小,然後再按此方法對這兩部分資料分別進行快速排序,整個排序過程可以遞迴進行,以此達到整個資料變成有序序列。 - 實現
public static void QuickSort(int[] a, int l, int r ) {
if(l < r) {
int p = partition(a, l, r);
QuickSort(a, l, p-1);
QuickSort(a, p+1, r);
}
}
private static int partition(int[] a, int l, int r) {
int tmp = a[r];
int p = l - 1;
for(int i=l; i<r; i++) {
if (a[i]< tmp) {
p++;
swap(a, i, p);
}
}
swap(a, r, p+1);
return p+1;
}
2. 歸併排序
演算法原理
歸併排序具體工作原理如下(假設序列共有n個元素):將序列每相鄰兩個數字進行歸併操作(merge),形成floor(n/2)個序列,排序後每個序列包含兩個元素
將上述序列再次歸併,形成floor(n/4)個序列,每個序列包含四個元素
重複步驟2,直到所有元素排序完畢
歸併排序是穩定的排序演算法,其時間複雜度為O(nlogn),如果是使用連結串列的實現的話,空間複雜度可以達到O(1),但如果是使用陣列來儲存資料的話,在歸併的過程中,需要臨時空間來儲存歸併好的資料,所以空間複雜度為O(n)
public void mergeSort(int[] A, int l, int r) {
if(l>=r) return;
int m = l + (r-l)/2;
mergeSort(A,l,m);
mergeSort(A,m+1,r);
merge(A,l,m,r);
}
public void merge(int[] A, int l, int mid, int r) {
int[] B = new int[A.length];
int s = l;
int m = mid+1;
int k = l;//陣列標誌位
while(s<=mid && m<=r) {
if(A[s]<=A[m]) {
B[k++] = A[s++];
} else{
B[k++] = A[m++];
}
}
while(s<=mid) {
B[k++] = A[s++];
}
while(m<=r) {
B[k++] = A[m++];
}
for(int i=l;i<=r;i++) {
A[i] = B[i];
}
}
3. 堆排序
首先講一下二叉堆
二叉堆是完全二叉樹或者近似完全二叉樹,滿足兩個特性
1. 父結點的鍵值總是大於或等於(小於或等於)任何一個子節點的鍵值
2. 每個結點的左子樹和右子樹都是一個二叉堆
當父結點的鍵值總是大於或等於任何一個子節點的鍵值時為最大堆。當父結點的鍵值總是小於或等於任何一個子節點的鍵值時為最小堆。一般二叉樹簡稱為堆。
一般都是陣列來儲存堆,i結點的父結點下標就為(i – 1) / 2。它的左右子結點下標分別為2 * i + 1和2 * i + 2。如第0個結點左右子結點下標分別為1和2。儲存結構如圖所示:
- 演算法原理
- 先將初始資料R[1..n]建成一個最大堆,此堆為初始的無序區
- 再將關鍵字最大的記錄R[1](即堆頂)和無序區的最後一個記錄
- R[n]交換,由此得到新的無序區R[1..n-1]和有序區R[n],且滿足R[1..n-1].keys≤R[n].key
- 由於交換後新的根R[1]可能違反堆性質,故應將當前無序區R[1..n-1]調整為堆。
- 重複2、3步驟,直到無序區只有一個元素為止。
- 實現
/**
* 將陣列arr構建大根堆
* @param arr 待調整的陣列
* @param i 待調整的陣列元素的下標
* @param len 陣列的長度
*/
public void heap_adjust(int arr[], int i, int len)
{
int child;
int temp;
for (; 2 * i + 1 < len; i = child)
{
child = 2 * i + 1; // 子結點的位置 = 2 * 父結點的位置 + 1
// 得到子結點中鍵值較大的結點
if (child < len - 1 && arr[child + 1] > arr[child])
child ++;
// 如果較大的子結點大於父結點那麼把較大的子結點往上移動,替換它的父結點
if (arr[i] < arr[child])
{
temp = arr[i];
arr[i] = arr[child];
arr[child] = temp;
}
else
break;
}
}
/**
* 堆排序演算法
*/
public void heap_sort(int arr[], int len)
{
int i;
// 調整序列的前半部分元素,調整完之後第一個元素是序列的最大的元素
for (int i = len / 2 - 1; i >= 0; i--)
{
heap_adjust(arr, i, len);
}
for (i = len - 1; i > 0; i--)
{
// 將第1個元素與當前最後一個元素交換,保證當前的最後一個位置的元素都是現在的這個序列中最大的
int temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
// 不斷縮小調整heap的範圍,每一次調整完畢保證第一個元素是當前序列的最大值
heap_adjust(arr, 0, i);
}
}
4. 氣泡排序
演算法原理
相鄰的資料進行兩兩比較,小數放在前面,大數放在後面,這樣一趟下來,最小的數就被排在了第一位,第二趟也是如此,如此類推,直到所有的資料排序完成實現
public void bubbleSort(int[] A) {
int len = A.length;
for(int i=0; i<len; i++) {
for(int j=len-1; j>i; j--) {
if(A[i]>A[j]){
int tmp = A[i];
A[i] = A[j];
A[j] = tmp;
}
}
}
}
5. 插入排序
- 演算法原理
將資料分為兩部分,有序部分與無序部分,一開始有序部分包含第1個元素,依次將無序的元素插入到有序部分,直到所有元素有序。插入排序又分為直接插入排序、二分插入排序、連結串列插入等,這裡只討論直接插入排序。它是穩定的排序演算法,時間複雜度為O(n^2)
public void insertSort(int[] A) {
int len = A.length;
int j;
for(int i=0;i<len;i++){
int tmp = A[i];
for(j = i; j > 0 && tmp < A[j-1]; j--)
A[j] = A[j-1];
A[j] = tmp;
}
}
6. 選擇排序
- 演算法原理
先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然後,再從剩餘未排序元素中繼續尋找最小(大)元素,然後放到已排序序列的末尾。以此類推,直到所有元素均排序完畢。 - 實現
void select_sort(int arr[], int len)
{
for (int i = 0; i < len; i++)
{
int index = i;
for (int j = i + 1; j < len; j++)
{
if (arr[j] < arr[index])
index = j;
}
if (index != i)
{
int temp = arr[i];
arr[i] = arr[index];
arr[index] = temp;
}
}
}