面試中經常使用排序算法實現(Java)

     當咱們進行數據處理的時候,每每須要對數據進行查找操做,一個有序的數據集每每可以在高效的查找算法下快速獲得結果。因此排序的效率就會顯的十分重要,本篇咱們將着重的介紹幾個常見的排序算法,涉及以下內容:算法

  • 排序相關的概念
  • 插入類排序
  • 交換類排序
  • 選擇類排序
  • 歸併排序算法實現

1、排序相關的基本概念
     排序實際上是一個至關大的概念,主要分爲兩類:內部排序和外部排序。而咱們一般所說的各類排序算法其實指的是內部排序算法。內部排序是基於內存的,整個排序過程都是在內存中完成的,而外部排序指的是因爲數據量太大,內存不能徹底容納,排序的時候須要藉助外存才能完成(經常是算計着某一部分已經計算過的數據移出內存讓另外一部分未被計算的數據進入內存)。而咱們本篇文章將主要介紹內排序中的幾種經常使用排序算法:數組

這裏寫圖片描述

還有一個概念問題,排序的穩定性問題。若是Ai = Aj,排序前Ai在Aj以前,排序後Ai還在Aj以前,則稱這種排序算法是穩定的,不然說明該算法不穩定。函數

2、插入類排序算法
     插入類排序算法的核心思想是,在一個有序的集合中,咱們將當前值插入到適合位置上,使得插入結束以後整個集合依然是有序的。那咱們接下來就學習下這幾種同一類別的不一樣實現。學習

     一、直接插入排序
     直接插入排序算法的核心思想是,將第 i 個記錄插入到前面 i-1 個已經有序的集合中。下圖是一個完整的直接插入排序過程:指針

這裏寫圖片描述

由於一個元素確定是有序的,i 等於 2 的時候,將第二個元素插入到前 i-1個有序集合中,當 i 等於3的時候,將第三個元素插入到前 i-1(2)集合中,等等。直到咱們去插入最後一個元素的時候,前面的 i-1 個元素構成的集合已是有序的了,因而咱們找到第 i 個元素的合適位置插入便可,整個插入排序完成。下面是具體的實現代碼:code

public static void InsertSort(int[] array){
    int i=0,j=0,key;
    for (i=1;i<10;i++){
        key = array[i];
        j = i-1;
        while(j>=0&&key<array[j]){
            //須要移動位置,將較大的值array[j]向後移動一個位置
            array[j+1] = array[j];
            j--;
        }
        //循環結束說明找到適當的位置了,是時候插入值了
        array[j+1] = key;
    }
    //輸出排序後的數組內容
    for (int value  : array){
        System.out.print(value+",");
    }
}

//主函數中對其進行調用
int[] array = {1,13,72,9,22,4,6,781,29,2};
InsertSort(array);

輸出結果以下:排序

這裏寫圖片描述

整個程序的邏輯是從數組的第二個元素開始,每一個元素都以其前面全部的元素爲基本,找到合適的位置進行插入。對於這種按照從小到大的排序原則,程序使用一個臨時變量temp保存當前須要插入的元素的值,從前面的子序列的最後一個元素開始,循環的與temp進行比較,一旦發現有大於temp的元素,讓它順序的日後移動一個位置,直到找到一個元素小於temp,那麼就找到合適的插入位置了。遞歸

由於咱們使用的判斷條件是,key>array[j]。因此來講,插入排序算法也是穩定的算法。對於值相同的元素並不會更改他們原來的位置順序。至於該算法的效率,最好的狀況是全部元素都已有序,比較次數爲n-1,最壞的狀況是全部元素都是逆序的,比較次數爲(n+2)(n-1)/2,因此該算法的時間複雜度爲O(n*n)。索引

     二、二分折半插入排序
     既然咱們每次要插入的序列是有序的,咱們徹底可使用二分查找到合適位置再進行插入,這顯然要比直接插入的效率要高一些。代碼比較相似,再也不作解釋。隊列

public static void halfInsertSort(int[] array){
    for(int k=1;k<array.length;k++){
        int key = array[k];
        //找到合適的位置
        int low,high,mid;
        low = 0;high = k-1;
        while(low <= high){
            mid = (low+high)/2;
            if(key == array[mid])break;
            else if(key > array[mid]){
                low = mid+1;
            }else{
                high = mid-1;
            }
        }
        //low的索引位置就是即將插入的位置
        //移動low索引位置後面的全部元素
        for(int x=k-1;x>=low;x--){
            array[x+1] = array[x];
        }
        array[low] = key;
    }
    //遍歷輸出有序隊列內容
    for(int key:array){
        System.out.print(key + ",");
    }
}

雖然,折半插入改善了查找插入位置的比較次數,可是移動的時間耗費並無獲得改善,因此效率上優秀的量可觀,時間複雜度仍然爲O(n*n)。

     二、希爾排序
     直接插入排序在整個待排序序列基本有序的狀況下,效率最佳,但咱們每每不能保證每次待排序的序列都是基本有序的。希爾排序就是基於這樣的情形,它將待排序序列拆分紅多個子序列,保證每一個子序列的組成元素相對較少,而後經過對子序列使用直接排序。對於本就容量不大的子序列來講,直接排序的效率是至關優秀的。

希爾排序算法使用一個距離增量來切分子序列,例如:

這裏寫圖片描述

如圖,咱們初始有一個序列,按照距離增量爲4來拆分的話,能夠將整個序列拆分紅四個子序列,咱們對四個子序列內部進行直接插入排序獲得結果以下:

這裏寫圖片描述

修改距離增量從新劃分子序列:

這裏寫圖片描述

很顯然,當距離增量變小的時候,序列的個數也會變少,可是這些子序列的內部都基本有序,當對他們進行直接插入排序的時候會使得效率變高。一旦距離增量減小爲1,那麼子序列的個數也將減小爲1,也就是咱們的原序列,而此時的序列內部基本有序,最後執行一次直接插入排序完成整個排序操做。

下面咱們看算法是的具體實現:

/*漸減delete的值*/
public static void ShellSort(){
    int[] array = {46,55,13,42,94,17,5,70};
    int[] delets = {4,2,1};
    for (int i=0;i<delets.length;i++){
        oneShellSort(array,delets[i]);
    }
    //遍歷輸出數組內容
    for(int value : array){
        System.out.print(value + ",");
    }
}
/*根據距離增量的值劃分子序列並對子序列內部進行直接插入排序*/
public static void oneShellSort(int[] array,int delet){
    int temp;
    for(int i=delet;i<array.length;i++){
        //從第二個子序列開始交替進行直接的插入排序
        //將當前元素插入到前面的有序隊列中
        if(array[i-delet] > array[i]){
            temp = array[i];
            int j=i-delet;
            while(j>=0 && array[j] > temp){
                array[j+delet] = array[j];
                j -= delet;
            }
            array[j + delet] = temp;
        }
    }
}

方法比較簡單,具體的實現和直接插入排序算法相近,此處再也不作解釋。

3、交換類排序
     交換類的排序算法通常是利用兩個元素之間的值的大小進行比較運算,而後移動外置實現的,這類排序算法主要有兩種:
     一、冒泡排序
     冒泡排序經過兩兩比較,每次將最大或者最小的元素移動到整個序列的一端。這種排序至關常見,也比較簡單,直接上代碼:

public static void bubbleSort(int[] array){
    int temp = 0;
    for(int i=0;i<array.length-1;i++){
        for(int j =0;j<array.length-1-i;j++){
            if(array[j]>array[j+1]){
                //交換兩個數組元素的值
                temp = array[j];
                array[j] = array[j+1];
                array[j+1] = temp;
            }
        }
    }
    //遍歷輸出數組元素
    for(int value : array){
        System.out.print(value + ",");
    }
}

     二、快速排序
     快速排序的基本思想是,從序列中任選一個元素,但一般會直接選擇序列的第一個元素做爲一個標準,全部比該元素值小的元素所有移動到他的左邊,比他大的都移動到他的右邊。咱們稱這叫作一趟快速排序,位於該元素兩邊的子表繼續進行快速排序算法直到整個序列都有序。該排序算法是目前爲止,內部排序中效率最高的排序算法。具體它是怎麼作的呢?

這裏寫圖片描述

首先他定義了兩個頭尾指針,分別指向序列的頭部和尾部。從high指針位置開始掃描整個序列,若是high指針所指向的元素值大於等於臨界值,指針前移。若是high指針所指向的元素的值小於臨界值的話:

這裏寫圖片描述

將high指針所指向的較小的值交換給low指針所指向的元素值,而後low指針前移。

這裏寫圖片描述

而後從low指針開始,逐個與臨界值進行比較,若是low指向的元素的值小於臨界值,那麼low指針前移,不然將low指針所指向的當前元素的值交換給high指針所指向的當前元素的值,而後把high指針前移。

這裏寫圖片描述

按照這樣的算法,

這裏寫圖片描述

當low和high會和的時候,就表明着本次快速排序結束,臨界值的左邊和右邊都已歸類。這樣的話,咱們再使用遞歸去快速排序其兩邊的子表,直到最後,整張表必然是有序的。下面咱們看代碼實現:

/*快速排序的遞歸定義*/
public static void FastSort(int[] array,int low,int high){
    if(low<high){
        int pos = OneFastSort(array,low,high);
        FastSort(array,low,pos-1);
        FastSort(array,pos+1,high);
    }
}
public static int OneFastSort(int[] array,int low,int high){
    //實現一次快速排序
    int key = array[low];
    int flag = 0;
    while (low != high) {
        if (flag == 0) {
            //flag爲0表示指針從high的一端開始移動
            if (array[high] < key) {
                array[low] = array[high];
                low++;
                flag = 1;
            } else {
                high--;
            }
        } else {
            //指針從low的一端開始移動
            if (array[low] > key) {
                array[high] = array[low];
                high--;
                flag = 0;
            } else {
                low++;
            }
        }
    }
    array[low] = key;
    return low;
}

若是上述介紹的快速排序的算法核心思想理解的話,這段代碼的實現也就比較容易理解了。

4、選擇類排序
     選擇類排序的基本思想是,每一趟會在n個元素中比較n-1次,選擇出最大或者最小的一個元素放在整個序列的端點處。選擇類排序有基於樹的也有基於線性表的,有關樹結構的各類排序算法,咱們將在後續文章中進行描述,此處咱們實現簡單的選擇排序算法。

public static void ChooseSort(int[] array){
    for (int i=0;i<array.length;i++){
        for (int j=i+1;j<array.length;j++){
            if(array[i]>array[j]){
                //發現比本身小的元素,則交換位置
                int temp = array[j];
                array[j]=array[i];
                array[i] = temp;
            }
        }
    }
    //輸出排序後的數組內容
    for (int key  : array){
        System.out.print(key+",");
    }
}

代碼堪比冒泡排序的算法實現,比較簡單直接,此處再也不贅述。

5、歸併類排序算法
     這裏的歸併類排序算法指的就是歸併排序。歸併排序的核心思想是,對於一個初始的序列不斷遞歸,直到子序列中的元素足夠少時,對他們進行直接排序。而後遞歸返回繼續對兩個分別有序的序列進行直接排序,最終遞歸結束的時候,整個序列必然是有序的。

這裏寫圖片描述

對於一個初始序列,咱們遞歸拆分的結果如上圖。最小的子序列只有兩個元素,咱們能夠輕易的對他們進行直接的排序。簡單的排序結果以下:

這裏寫圖片描述

而後咱們遞歸返回:

這裏寫圖片描述

初看起來和咱們的希爾排序的基本思想有點像,希爾排序經過對初始序列的稀疏化,使得每一個子序列在內部上都是有序的,最終在對整個序列進行排序的時候,序列的內部基本有序,整體上能提升效率。可是咱們的歸併排序的和核心思想是,經過不斷的遞歸,直到子序列元素足夠少,在內部對他們進行直接的排序操做,當遞歸返回的時候,對返回的兩個子表再次進行歸併排序,使得合成的新序列是有序的,一直到遞歸返回調用結束時候,整個序列就是有序的。

/*歸併排序的遞歸調用*/
public static void MergeSort(int[] array,int low,int high){
    if(low == high){
        //說明子數組長度爲1,無需分解,直接返回便可
    }else{
        int p = (low+high)/2;
        MergeSort(array,low,p);
        MergeSort(array,p+1,high);
        //完成相鄰兩個子集合的歸併
        MergeTwoData(array,low,high);
    }
}
/*用於排序兩個子序列的歸併排序算法實現*/
public static void MergeTwoData(int[] array,int low,int high){ 
    int[] arrCopy = new int[high-low+1];
    int i,j;
    i = low;j= (low+high)/2+1;
    for (int key=0;key<=high-low;key++){
        //若是左邊子數組長度小於右邊數組長度,當左數組所有入庫以後,右側數組不用作比較直接入庫
        if(i==(low+high)/2+1){
            arrCopy[key] = array[j];
            j++;
        }
        //若是右側數組長度小於左側數組長度,當右側數組所有入庫以後,左側數組不用作比較直接入庫
        else if(j==high+1){
            arrCopy[key]=array[i];
            i++;
        }else if(array[i]<array[j]){
            arrCopy[key]=array[i];
            i++;
        }else{
            arrCopy[key] = array[j];
            j++;
        }
    }
    j = 0;
    //按順序寫回原數組
    for(int x=low;x<=high;x++) {
        array[x] = arrCopy[j];
        j++;
    }
}

咱們的遞歸調用方法仍是比較簡單的,首先從一個序列的中間位置開始遞歸分紅兩個子序列並傳入該序列的頭尾索引,當頭尾索引相等的時候,說明序列中只有一個元素,因而無需調用歸併排序,直接返回。等待兩邊的子序列都返回的時候,咱們調用歸併排序對這兩個子序列進行排序,繼續向上返回直到遞歸結束,最終的序列必然是有序的。咱們主要看看用於歸併兩個子序列的排序算法的實現。

假設咱們遞歸返回了這樣的兩個子序列,由於這些子序列都是遞歸返回的,因此他們在內部都是有序的,因而咱們須要對兩個子序列排序和併成新序列並向上遞歸返回。

這裏寫圖片描述

排序的算法以下:

分別取出兩個序列的棧頂元素,進行比較,把小的一方的元素出棧並存入新序列中,值較大的元素依然位於棧頂。這樣,當有一方的元素所有出棧以後,另外一方的元素順序填入新序列中便可。具體的算法實現已經在上述代碼中給出了。

至此,線性的基本排序算法都已經介紹完成了,有些算法介紹的比較粗糙,待後續深刻理解後再回來補充,總結不到之處,望指出!

相關文章
相關標籤/搜索