1. 程式人生 > >面試常用排序演算法java實現

面試常用排序演算法java實現

概要

本文總結面試常見的排序演算法,及基本的實現java
話不多說,先上乾貨。

一、演算法的複雜度及穩定性

sort
穩定的排序演算法是:氣泡排序,直接插入排序,歸併排序,基數排序,二叉樹排序,計數排序。
不穩定的排序演算法:選擇排序,快速排序,堆排序,希爾排序。

二、演算法的實現

面試中常見的演算法: 快速排序>歸併排序>堆排序>冒泡>插入>選擇。下面依次實現各排序演算法。

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。儲存結構如圖所示:

這裡寫圖片描述

  • 演算法原理
    1. 先將初始資料R[1..n]建成一個最大堆,此堆為初始的無序區
    2. 再將關鍵字最大的記錄R[1](即堆頂)和無序區的最後一個記錄
    3. R[n]交換,由此得到新的無序區R[1..n-1]和有序區R[n],且滿足R[1..n-1].keys≤R[n].key
    4. 由於交換後新的根R[1]可能違反堆性質,故應將當前無序區R[1..n-1]調整為堆。
    5. 重複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; 
          }
      }
  }