數據結構-常見排序算法總結

Sort Algorithm(ASC)

[TOC] //怎么生成目錄,糾結ing

插入排序

每一趟排序都將待排元素插入到已有序的序列中,但不能保證該元素的插入位置是全局有序的。

Insertion Sort

直接插入排序

  • 最好情況:$O(n)$
  • 最壞情況:$O(n^2)$
  • 平均情況:$O(n^2)$
  • 輔助空間:O(1)
  • 穩(wěn)定性:穩(wěn)定
  • 每一趟排序只能保證當前有序,并不能保證全局有序
  1. 核心思想
    數組中的每一個待排元素與已部分有序的元素依次比較,如果待排元素小于當前已有序的元素,則交換兩者位置,并繼續(xù)向前比較。

  2. 程序示例

    public static void insertionSort(int[] arr){
        for (int i=1;i<arr.length;i++){
            for (int j=i-1;j>=0;j--){
                if (arr[j]>arr[j+1]){
                    swap(arr,j,j+1);
                }
            }
        }
    }
    public static void  swap(int[] arr,int i,int j){
        int tmp=arr[i];
        arr[i]=arr[j];
        arr[j]=tmp;
    }
    
  3. 程序分析
    直接插入排序采用兩層循環(huán):

    • 外層循環(huán)用來跟蹤每一個待排元素,從1到 n-1,剛開始arr[0]是默認有序的。
    • 內層循環(huán)負責當前待排元素與已部分有序的元素進行逐一比較,確定當前待排元素的最終插入位置。
  4. 優(yōu)化分析*
    在內層循環(huán)中,每當逆序(已有序元素大于待排元素時)的情況出現時,就執(zhí)行一次 swap 操作,交換兩元素的位置,這樣會導致交換次數較多。可以考慮通過比較確定最后的插入位置,然后將交換操作推遲到最后進行。這樣只需要進行一次交換操作,但已有序元素的移動次數是不會改變的。

Shell Sort

希爾排序(縮小增量排序)

  • 步長+直接插入排序
  • 最好情況:$O(n)$
  • 最壞情況:$O(n^2)$
  • 平均情況:$O(n^{1.3})$
  • 輔助空間:O(1)
  • 穩(wěn)定性:不穩(wěn)定
  • 每一趟排序只能保證當前有序,并不能保證全局有序
  1. 核心思想
    希爾排序,又稱縮小增量排序,使用步長來確定每一次的待排子序列,然后使用直接插入排序對當前待排子序列進行排序。在完成步長為 h 的排序后,通過證明可以保證,對于每一個 i 與 i+h 元素都是有序的。然后縮小步長,再次使用直接插入排序。直到最后步長為1,最后一次序列已經基本有序,最后采用一次直接插入排序后就可以得到最終的有序數組。

  2. 程序示例

    public static void shellSort(int[] arr){
        //第一層循環(huán)控制步長
        for (int gap=arr.length/2;gap>0;gap=gap/2){
            //第二層循環(huán)控制滿足步長的所有子序列
            for (int i=gap;i<arr.length;i++){
                //第三層循環(huán)控制當前滿足步長的子序列的排序,采用直接插入排序的方式進行排序
                for (int j=i;j<arr.length;j+=gap){
                    if (arr[j]<arr[j-gap]){
                        swap(arr,j,j-gap);
                    }
                }
            }
        }
    }
    
    public static void  swap(int[] arr,int i,int j){
        int tmp=arr[i];
        arr[i]=arr[j];
        arr[j]=tmp;
    }
    
  3. 程序分析
    希爾排序采用三層循環(huán):

    • 第一層循環(huán)負責確定每一趟的步長,并按一定的規(guī)則(這里采用/2折半的規(guī)則),循環(huán)減小步長到1。
    • 第二層循環(huán)針對當前步長,依次確定滿足該步長的待排子序列。
    • 第三層循環(huán)針對當前確定的待排子序列,采用直接插入排序的方法進行排序。

選擇排序

基本思想:比較+交換

Selection Sort

簡單選擇排序

  • 最好情況:$O(n^2)$
  • 最壞情況:$O(n^2)$
  • 平均情況:$O(n^2)$
  • 輔助空間:O(1)
  • 穩(wěn)定性:不穩(wěn)定
  • 每一趟排序保證全局有序
  1. 核心思想
    簡單選擇排序,是選擇排序中的一種,遵循其基本思想:比較+交換。即通過依次比較確定當前趟最小元素,并根據情況與當前待排元素交換。具體做法是在每趟排序中,通過將當前待排元素與與剩余所有元素比較,找到最小的元素并與當前元素交換(如果當前待排元素就是最小,則不用交換)。

  2. 程序示例

    public static void selectionSort(int[] arr){
        for (int i=0;i<arr.length;i++){
            //這里采用 min 來標記最小元素的下標,方便 swap 的時候通過下標進行交換
            int min=i;
            //第二層循環(huán)負責將當前元素與剩余所有元素比較,找到這些元素中的最小值,如果該最小值不是當前元素,則在退出循環(huán)后將當前元素與該最小元素交換
            //因為每一次尋找當前趟最小元素都是與所有待排元素比較,因此,簡單選擇排序是全局有序的
            for (int j=i+1;j<arr.length;j++){
                if (arr[j]<arr[min]){
                    min=j;
                }
            }
            if (min!=i){
                swap(arr,i,min);
            }
        }
    }
    
    public static void  swap(int[] arr,int i,int j){
        int tmp=arr[i];
        arr[i]=arr[j];
        arr[j]=tmp;
    }
    
  3. 程序分析
    簡單選擇排序,采用兩層循環(huán):

    • 外層循環(huán)用于控制當前待排元素及其待排位置。
    • 內存循環(huán)用于基于當前待排位置和待排元素,循環(huán)依次與序列中剩余其它元素比較,如果存在元素小于當前待排元素,則采用一個臨時變量始終標記符合該條件的元素。在退出內層循環(huán)后,判斷經過一趟完整比較后,最小元素是否為當前待排元素,如果是,說明當前待排元素是當前趟的最小值,當前位置即全局有序位置,不交換;如果不是,則采用 swap 交換。

Heap Sort

堆排序

  • 最好情況:$O(nlog_2n)$
  • 最壞情況:$O(nlog_2n)$
  • 平均情況:$O(nlog_2n)$
  • 輔助空間:O(1)
  • 穩(wěn)定性:不穩(wěn)定
  • 每一趟排序保證全局有序
  1. 核心思想
    堆排序,先根據父節(jié)點與子節(jié)點下標關系(i,2i+1,2i+2)將待排序列構造為大頂堆(實際上還是在數組中存儲)。然后執(zhí)行循環(huán)操作:每次將頂點元素與最后一個元素swap,交換后,當前最大元素已經位于數組的末尾,因為原來的末尾元素到了頂點位置,有可能破壞堆的性質,則通過堆調整來修正堆性質。循環(huán)執(zhí)行這一組操作,直到堆中只有一個元素的時候,該待排序列已然有序。

  2. 程序示例

    public static void heapSort(int[] arr){
        buildHeap(arr);
        int length=arr.length;
        while (length>0){
            swap(arr,0,length-1);
            length--;
            adjustToHeap(arr,length,0);
        }
    
    }
    
    public static void buildHeap(int[] arr){
        for (int i=0;i<arr.length/2;i++){
            adjustToHeap(arr,arr.length,i);
        }
    }
    
    public static void  adjustToHeap(int[] arr,int length,int index){
        int indexOfMax=index;
        while (true){
            int leftIndex=getLeftChild(index);
            int rightIndex=getRightChild(index);
            if (leftIndex<length&&arr[leftIndex]>arr[indexOfMax]){
                indexOfMax=leftIndex;
            }
            if (rightIndex<length&&arr[rightIndex]>arr[indexOfMax]){
                indexOfMax=rightIndex;
            }
            if (indexOfMax!=index){
                swap(arr,index,indexOfMax);
                index=indexOfMax;
                continue;
            }
            else {
                break;
            }
        }
    }
    
    public static int getLeftChild(int i){
        return 2*i+1;
    }
    
    public static int getRightChild(int i){
        return 2*i+2;
    }
    
    public static void  swap(int[] arr,int i,int j){
        int tmp=arr[i];
        arr[i]=arr[j];
        arr[j]=tmp;
    }
    
  3. 程序分析
    在采用堆排序前,需要對待排序列構造大頂堆。具體構造方式,稍后詳述

    • 在生成大頂堆后,滿足大頂堆的性質,第一個元素 arr[0]是當前0~arr.length-1中的最大值。接下來對該大頂堆進行 N-1 次的交換和調整。
    • 具體做法是,在每次交換和調整中,將堆頂的最大值與堆尾元素互換,這樣,該最大值就位于正確順序位置上。因為互換后有可能破壞大頂堆順序,因此必須在互換后調用 adjustToHeap 進行堆性質修正。

交換排序

Bubble Sort

冒泡排序

  • 最好情況:$O(n^2)$
  • 最壞情況:$O(n^2)$
  • 平均情況:$O(n^2)$
  • 輔助空間:O(1)
  • 穩(wěn)定性:穩(wěn)定
  • 每一趟排序保證全局有序
  1. 核心思想
    冒泡排序,在每一趟排序中,都依次比較左右兩個元素,如果不符合大小關系,則交換位置,并繼續(xù)比較下一個坐標位置。在一趟比較結束后,當前趟最大的元素會沉底。

  2. 程序示例

    public static void bubbleSort(int[] arr){
        //對于 N 個元素的序列,需要 N-1 趟排序
        for (int i=1;i<arr.length;i++){
            //在每一趟排序后,下標為 arr.length-i的元素已經位于全局有序位置,因此下一趟比較的最后待排位置為 arr.length-i-1
            //每一趟比較中,當前下標的左右兩個元素如果順序不當,則互相交換,較大的向后調整,如果滿足大小要求,則不交換。這樣依次對剩余元素進行比較,直到 arr.length-i
            for (int j=0;j<arr.length-i;j++){
                if (arr[j]>arr[j+1]){
                    swap(arr,j,j+1);
                }
            }
        }
    }
    
    public static void  swap(int[] arr,int i,int j){
        int tmp=arr[i];
        arr[i]=arr[j];
        arr[j]=tmp;
    }
    
  3. 程序分析
    冒泡排序采用兩層循環(huán),對于 N 個元素的冒泡排序,需要進行 N-1 趟排序,第 i 趟排序需要比較 N-i 次。

    • 第一層循環(huán)控制冒泡排序的趟數,從1到 N-1趟冒泡排序。
    • 第二層循環(huán)控制每一趟的冒泡排序,因為經過第 i-1 趟的排序,第 N-i+1 個位置已經有序,所以在第 i 趟的冒泡排序中,只需要從第一個元素一直比較到第 N-i 個元素。
  4. 優(yōu)化分析
    針對最好情況為$O(n)$的情形,可以在外層循環(huán)中設置一個變量用來檢查上一趟是否存在交換操作,在內層循環(huán)中,發(fā)生 swap 操作則置該變量為 true,否則默認為 false。在退出內層循環(huán)后,外層邏輯會判斷如果變量為 true 說明上一趟待排序列不滿足順序條件,繼續(xù)本趟排序;如果為 false 說明在上一趟中待排序列所有元素已經有序。因此在本趟排序可以直接 break,已經提前完成了序列排序。

Quick Sort

快速排序

  • 最好情況:$O(nlog_2n)$
  • 最壞情況:$O(n^2)$
  • 平均情況:$O(nlog_2n)$
  • 輔助空間:$O(nlog_2n)$
  • 穩(wěn)定性:不穩(wěn)定
  • 每一趟排序保證軸 key 全局有序
  1. 核心思想
    循環(huán)比較交換+分而治之的思想。在每一趟的排序操作中,當 i不等于j 的時候,將大于 key 的元素都交換在 key 的右邊,小于 key 的元素都交換在 key 的左邊。當 i等于j 的時候,就是 key 的最終有序位置。但是經過一趟排序操作,僅保證了當前 key 位于正確有序的位置 i,但對于start~i-1和 i+1~end 是無序的,因此需要在這兩段上遞歸調用剛剛的排序操作。

  2. 程序示例

    public static void quickSort(int[] arr,int start,int end){
        if (start<end){
            int i=start;
            int j=end;
            int key=arr[start];
            while (i!=j){
                //從后向前循環(huán)找到第一個小于 key 的元素
                while (i<j&&arr[j]>=key){
                    j--;
                }
                //將該元素替換到 key 的左邊
                if (i<j){
                    arr[i]=arr[j];
                    i++;
                }
                //從前向后循環(huán)找第一個大于 key 的元素
                while (i<j&&arr[i]<key){
                    i++;
                }
                //將該元素替換到 key 的右邊
                if (i<j){
                    arr[j]=arr[i];
                    j--;
                }
            }
            //當 i=j 的時候,說明在這一趟交換過后,所有大于 key 的元素都位于 key 的右邊,所有小于 key 的元素都位于 key 的左邊
            //而此時 i==j 即表示 key 應該插入的位置,即原先的 arr[start]元素的最終位置
            arr[i]=key;
            quickSort(arr,start,i-1);
            quickSort(arr,i+1,end);
        }
    }
    
  3. 程序分析
    快速排序在每一趟排序中,先選定一個軸,通過交換來確定軸的最終位置,在一趟排序后,大于或小于該軸的元素會位于該軸的兩側。

    • 外層循環(huán)用來控制這趟排序是否結束,當 i 等于 j 的時候,說明待排序列的比較已經結束,該位置即為軸 key 的最終有序位置。
    • 在第一個內層循環(huán)中,先從后向前循環(huán)查找第一個小于 key 的元素,如果存在則退出循環(huán)并將該元素交換至 key 的 i 位置,然后再進入第二個內層循環(huán)。如果沒有存在一個小于 key 的元素,說明在從后向前的查找中,key 后面的元素都大于 key,因此,說明 key 就是在正確的有序位置。
    • 在第二個內層循環(huán)中,從前向后循環(huán)查找第一個大于 key 的元素,如果存在則退出循環(huán)并交換至當前 j 位置,然后退出該循環(huán),判斷 i 是否等于 j,不等于則說明該趟還沒比較結束,繼續(xù)從第一個內層循環(huán)開始,繼續(xù)排序操作。直至 i 等于 j。
  4. 優(yōu)化分析
    快速排序對于越無序的序列效果越好,但對于基本有序序列,在選定 key 后,有可能剩余的 N-1個元素均大于或小于 key,導致分而治之的效果不明顯,沒有明顯的均分待排序列,使得效率接近于插入排序$O(n^2)$的效果。改善這樣的情況,可以嘗試采用隨機 key 的方式,每一趟排序,均在當前 start~end 中隨機選取 key 作為軸,而不是始終設定為 arr[start]為軸,盡可能通過隨機化減少有序影響,實現或接近分治效果。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容