算法和數據結構 --- 八種必須掌握的排序

轉載算法和數據結構 — 八種必須掌握的排序java

排序方法能夠分爲五種∶插入排序、選擇排序、交換排序、分配排序和歸併排序。
在排序過程當中,所有記錄存放在內存,則稱爲內排序,若是排序過程當中須要使用外存,則稱爲外排序。
首先來看一下八種排序之間的關係圖
8種排序關係圖git

一、 直接插入排序

(1)基本思想:

在要排序的一組數中,假設前面(n-1) [n>=2] 個數已是排好順序的,如今要把第n個數插到前面的有序數中,使得這n個數也是排好順序的。如此反覆循環,直到所有排好順序。web

(2)理解圖:

已知待序的一組記錄的初始排列爲:21, 25, 49, 25*, 16, 08
排序理解圖算法

開始排序
開始排序1shell

開始排序2

(3)代碼實現

public void insertSort(int[] a) {
        int i, j, temp;
        for (i = 1; i < a.length; i++) {
            temp = a[i]; // 把當前待比較項付給中間量
            for (j = i; j > 0 && temp < a[j - 1]; j--) {
                // 若是待比較項小 
                a[j] = a[j - 1]; // 向後移
                // 直到找到沒有比比較項大的就退出當前循環
            }
            a[j] = temp;// 49
        }
        for (i = 0; i < a.length; i++) {
            System.out.print(a[i] + "\t");
        }
    }

直接插入排序最大的優勢是簡單,在記錄數較少時,是比較好的辦法。數組

二、希爾排序(最小增量排序)

(1)基本思想:

算法先將要排序的一組數按某個增量d(n/2,n爲要排序數的個數)分紅若干組,每組中記錄的下標相差d.對每組中所有元素進行直 接插入排序,而後再用一個較小的增量(d/2)對它進行分組,在每組中再進行直接插入排序。當增量減到1時,進行直接插入排序後,排序完成。數據結構

(2)理解圖

希爾排序理解圖

(3)代碼實現

public void shellSort(int[] a) {
        int temp = 0;
        double d1 = a.length;
        while (true) {
            d1 = Math.ceil(d1 / 2);
            int d = (int) d1;

            for (int x = 0; x < d; x++) {
                for (int i = x + d; i < a.length; i += d) {
                    int j = i - d;
                    temp = a[i];
                    for (; j >= 0 && temp < a[j]; j -= d) {
                        a[j + d] = a[j];
                    }
                    a[j + d] = temp;
                }
            }
            if (d == 1) {
                break;
            }
        }
        for (int i = 0; i < a.length; i++)
            System.out.print(a[i] + "\t");
    }

三、簡單選擇排序

(1)基本思想:

在要排序的一組數中,選出最小的一個數與第一個位置的數交換; 而後在剩下的數當中再找最小的與第二個位置的數交換,如此循環到倒數第二個數和最後一個數比較爲止。ide

(2)理解圖

簡單選擇排序1
簡單選擇排序2

第一次 : 08最小 和21交換位置
第二次: 除第一個位置的08外 16最小 和25交換位置
以此類推svg

(3)代碼實現

public static void selectSort(int[] a) {
        int position = 0;
        for (int i = 0; i < a.length; i++) {
            int j = i + 1;
            position = i;
            int temp = a[i];
            for (; j < a.length; j++) {
                if (a[j] < temp) {
                    temp = a[j];
                    position = j;
                }
            }
            a[position] = a[i];
            a[i] = temp;
        }
        for (int i = 0; i < a.length; i++)
            System.out.print(a[i] + "\t");
    }

四、堆排序

(1)基本思想:

堆排序是一種樹形選擇排序,是對直接選擇排序的有效改進。
堆的定義以下:具備n個元素的序列(h1,h2,…,hn),當且僅當知足(hi>=h2i,hi>=2i+1)或(hi& lt;=h2i,hi<=2i+1)(i=1,2,…,n/2)時稱之爲堆。在這裏只討論知足前者條件的堆。
由堆的定義能夠看出,堆頂元素(即第一 個元素)必爲最大項(大頂堆)。徹底二叉樹能夠很直觀地表示堆的結構。
堆頂爲根,其它爲左子樹、右子樹。初始時把要排序的數的序列看做是一棵順序存儲的二 叉樹,調整它們的存儲序,使之成爲一個堆,這時堆的根節點的數最大。而後將根節點與堆的最後一個節點交換。而後對前面(n-1)個數從新調整使之成爲堆。 依此類推,直到只有兩個節點的堆,並對它們做交換,最後獲得有n個節點的有序序列。
從算法描述來看,堆排序須要兩個過程,一是創建堆,二是堆頂與堆的最後 一個元素交換位置。因此堆排序有兩個函數組成。一是建堆的滲透函數,二是反覆調用滲透函數實現排序的函數。函數

(2)實例:

初始序列:46,79,56,38,40,84

建堆:
堆排序-建堆

交換,從堆中踢出最大數
堆排序-交換

剩餘結點再建堆,再交換踢出最大數
堆排序-剩餘節點建堆交換

依次類推:最後堆中剩餘的最後兩個結點交換,踢出一個,排序完成。

(3)代碼:

/** * 選擇排序之堆排序: * * 1. 基本思想: 堆排序是一樹形選擇排序,在排序過程當中,將R[1..N]當作是一顆徹底二叉樹的順序存儲結構, * 利用徹底二叉樹中雙親結點和孩子結點之間的內在關係來選擇最小的元素。 * * 2. 堆的定義: N個元素的序列K1,K2,K3,...,Kn.稱爲堆,當且僅當該序列知足特性: Ki≤K2i Ki ≤K2i+1(1≤ I≤[N/2]) * 堆實質上是知足以下性質的徹底二叉樹:樹中任一非葉子結點的關鍵字均大於等於其孩子結點的關鍵字。例如序列10,15,56,25,30,70就是一個堆, * 它對應的徹底二叉樹如上圖所示。這種堆中根結點(稱爲堆頂)的關鍵字最小,咱們把它稱爲小根堆。 * 反之,若徹底二叉樹中任一非葉子結點的關鍵字均大於等於其孩子的關鍵字,則稱之爲大根堆。 * * 3.排序過程: 堆排序正是利用小根堆(或大根堆)來選取當前無序區中關鍵字小(或最大)的記錄實現排序的。咱們不妨利用大根堆來排序。每一趟排序的基本操做是: * 將當前無序區調整爲一個大根堆 * ,選取關鍵字最大的堆頂記錄,將它和無序區中的最後一個記錄交換。這樣,正好和直接選擇排序相反,有序區是在原記錄區的尾部造成並逐步向前擴大到整個記錄區。 */
public class HeapSort {

    /** * 排序算法的實現,對數組中指定的元素進行排序 * * @param array * 待排序的數組 * @param c * 比較器 */
    public void sort(Integer[] arr) {
        // 建立初始堆
        initialHeap(arr);

        /* * 對初始堆進行循環,且從最後一個節點開始,直到樹只有兩個節點止 每輪循環後丟棄最後一個葉子節點,再看做一個新的樹 */
        for (int i = arr.length; i >= 2; i--) {
            // 根節點與最後一個葉子節點交換位置,即數組中的第一個元素與最後一個元素互換
            swap(arr, 0, i - 1);
            // 交換後須要從新調整堆
            adjustNote(arr, 1, i - 1);
        }

    }

    /** * @param arr * 排序數組 * @param c * 比較器 */
    private void initialHeap(Integer[] arr) {
        int lastBranchIndex = arr.length  / 2;// 最後一個非葉子節點
        // 對全部的非葉子節點進行循環 ,且從最後一個非葉子節點開始
        for (int i = lastBranchIndex; i >= 1; i--) {
            adjustNote(arr, i,  arr.length );
        }
    }

    /** * 調整節點順序,從父、左右子節點三個節點中選擇一個最大節點與父節點轉換 * * @param arr * 待排序數組 * @param parentNodeIndex * 要調整的節點,與它的子節點一塊兒進行調整 * @param len * 樹的節點數 * @param c * 比較器 */
    private void adjustNote(Integer[] arr, int parentNodeIndex, int len) {
        int maxValueIndex = parentNodeIndex;
        // 若是有左子樹,i * 2爲左子樹節點索引
        if (parentNodeIndex * 2 <= len) {
            // 若是父節點小於左子樹時
            if ((arr[parentNodeIndex - 1]
                    .compareTo(arr[parentNodeIndex * 2 - 1])) < 0) {
                maxValueIndex = parentNodeIndex * 2;// 記錄最大索引爲左子節點索引
            }
            // 只有在有左子樹的前提下才可能有右子樹,再進一步斷判是否有右子樹
            if (parentNodeIndex * 2 + 1 <= len) {
                // 若是右子樹比最大節點更大
                if ((arr[maxValueIndex - 1]
                        .compareTo(arr[(parentNodeIndex * 2 + 1) - 1])) < 0) {
                    maxValueIndex = parentNodeIndex * 2 + 1;// 記錄最大索引爲右子節點索引
                }
            }
        }

        // 若是在父節點、左、右子節點三者中,最大節點不是父節點時須要交換,把最大的與父節點交換,建立大頂堆
        if (maxValueIndex != parentNodeIndex) {
            swap(arr, parentNodeIndex - 1, maxValueIndex - 1);
            // 交換後可能須要重建堆,原父節點可能須要繼續下沉 由於交換後 maxValueIndex位置的值就不必定是三個節點中最大的了! 
            if (maxValueIndex * 2 <= len) {// 是否有子節點,注,只需判斷是否有左子樹便可知道
                adjustNote(arr, maxValueIndex, len);
            }
        }
    }

    /** * 交換數組中的兩個元素的位置 * * @param array * 待交換的數組 * @param i * 第一個元素 * @param j * 第二個元素 */
    public void swap(Integer[] array, int i, int j) {
        if (i != j) {// 只有不是同一位置時才需交換
            Integer tmp = array[i];
            array[i] = array[j];
            array[j] = tmp;
        }
    }

    /** * 測試 * * @param args */
    public static void main(String[] args) {
        Integer[] a = { 6,9,0,4,5, 9, 1, 4, 2, 6, 3, 8, 0, 7, 0, -7, -1, 34 };
        HeapSort heapsort = new HeapSort();
        heapsort.sort(a);
        for (Integer arrValue : a) {
            System.out.print(arrValue + " ");
        }
    }

}

五、冒泡排序

(1)基本思想:

在要排序的一組數中,對當前還未排好序的範圍內的所有數,自上而下對相鄰的兩個數依次進行比較和調整,讓較大的數往下沉,較小的往上冒。即:每當兩相鄰的數比較後發現它們的排序與排序要求相反時,就將它們互換。

(2)理解圖

冒泡排序1
冒泡排序2
冒泡排序3

(3)代碼實現

/** * 冒泡排序是一種簡單的排序算法。它重複地走訪過要排序的數列,一次比較兩個元素, * 若是他們的順序錯誤就把他們交換過來。走訪數列的工做是重複地進行直到沒有再須要交換, * 也就是說該數列已經排序完成。 * 這個算法的名字由來是由於越小的元素會經由交換慢慢「浮」到數列的頂端。 * 步驟: * 一、比較相鄰的元素。若是第一個比第二個大,就交換他們兩個。 二、對每一對相鄰元素做一樣的工做,從開始第一對到結尾的最後一對。在這一點,最後的元素應該會是最大的數。 三、針對全部的元素重複以上的步驟,除了最後一個。 四、持續每次對愈來愈少的元素重複上面的步驟,直到沒有任何一對數字須要比較。 * @author Administrator * */
public class bubbleSort {
    public bubbleSort() {
        int a[] = { 49, 38, 65, 97, 76, 13, 27, 49, 78, 34, 12, 64, 5, 4, 62, 99, 98, 54, 56, 17, 18, 23, 34, 15, 35,
                25, 53, 51 };
        int temp = 0;
        for (int i = 0; i < a.length - 1; i++) {
            for (int j = 0; j < a.length - 1 - i; j++) {
                if (a[j] > a[j + 1]) {
                    temp = a[j];
                    a[j] = a[j + 1];
                    a[j + 1] = temp;
                }
            }
        }
        for (int i = 0; i < a.length; i++)
            System.out.println(a[i]);
    }
}

六、快速排序

(1)基本思想:

選擇一個基準元素,一般選擇第一個元素或者最後一個元素,經過一趟掃描,將待排序列分紅兩部分,一部分比基準元素小,一部分大於等於基準元素,此時基準元素在其排好序後的正確位置,而後再用一樣的方法遞歸地排序劃分的兩部分。

(2)理解圖

快速排序1
快速排序2

(3)代碼實現

/** * 快速排序是其基本思想是基本思想是,經過一趟排序將待排記錄分隔成獨立的兩部分, * 其中一部分記錄的關鍵字均比另外一部分的關鍵字小, * 則可分別對這兩部分記錄繼續進行排序,以達到整個序列有序。 * 步驟: * 一、從數列中挑出一個元素,稱爲 "基準"(pivot), 二、從新排序數列,全部元素比基準值小的擺放在基準前面, 全部元素比基準值大的擺在基準的後面(相同的數能夠到任一邊)。在這個分區退出以後, 該基準就處於數列的中間位置。這個稱爲分區(partition)操做。 三、遞歸地(recursive)把小於基準值元素的子數列和大於基準值元素的子數列排序。 * @author Administrator * */
public class QuickSort {

    public  void sort(){
        int a[] = { 11, 33, 44, 2, 0, 1, 49, 38, 65, 97, 76, 13, 27, 49, 78, 34, 12, 64, 5, 4, 62, 99, 98, 54, 56, 17,
                18, 23, 34, 15, 35, 25, 53, 51, 90 };

        quickSort(a,0,a.length-1);
        for (int i = 0; i < a.length; i++) {
            System.out.print(a[i]+" ");
        }

    }
    /** * * @param a 待排序數組 * @param low 能夠看作低位助手 * @param high 能夠看作高位助手 * 低助手是用來找比基準位大的數 * 高助手是用來找比基準位小的數 這樣就能夠看作兩個助手在活動 */
    private  void quickSort(int[] a, int low, int high) {
        int start=low;//起始位置 0;
        int end=high; //結束位置
        int base=a[low]; //基準數 :通常是第一位
        int tempIndex=low; //找到的符合要求的位置:由於要把它的值付給基準數所在位置 因此要記錄該位置 能夠看作是助手移動到的位置
        while(low<high){
            //高助手從右向左找一個比基準位小的數 找到後給到低助手當前所在位置
            //沒有找到的話 高助手向前移動一位
            while(low<high&&base<=a[high]){
                high--;
            }
            //找到時 把找到的數賦值給低助手所在位置
            a[low]=a[high];
            tempIndex=high;//記錄當前助手位置

            //而後低助手從左向右找一個比基準數大的數 ,找到後給到高助手當前所在位置
            //沒有找到的話 低助手向後移動一位
            while(low<high&&base>=a[low]){
                low++;
            }
            //找到後賦值給高助手當前所在位置
            a[high]=a[low];
            tempIndex=low;//記錄當前助手位置

            //直到循環結束 -->低助手和高助手重疊 就把基準數賦到當前中軸重疊位置
            a[tempIndex]=base;

        }
        //以上第一次排序結束 把數列分紅了先後兩個部分
        //最後在對上面先後兩個部分數列 分別遞歸
        if(low-start>1){//前部分至少有兩個數據
            quickSort(a,0,low-1);
        }
        if(end-high>1){
            quickSort(a,high+1,end);
        }
    }

    public static void main(String[] args) {
        QuickSort q=new QuickSort();
        q.sort();
    }

}

七、歸併排序

(1)基本排序:

歸併(Merge)排序法是將兩個(或兩個以上)有序表合併成一個新的有序表,即把待排序序列分爲若干個子序列,每一個子序列是有序的。而後再把有序子序列合併爲總體有序序列。

(2)理解圖:

遞歸分解:
遞歸分解

下面的圖理解合併
合併理解

(3)、實現代碼

import java.util.Arrays;
/** * 歸併排序是創建在歸併操做上的一種有效的排序算法。 * 該算法是採用分治法(Divide and Conquer)的一個很是典型的應用。 * 歸併排序是一種穩定的排 * 步驟: 一、Divide: 把長度爲n的輸入序列分紅兩個長度爲n/2的子序列。 二、Conquer: 對這兩個子序列分別採用歸併排序。 三、Combine: 將兩個排序好的子序列合併成一個最終的排序序列。 * @author Administrator * */
public class mergineSort {
    int a[] = { 49, 38, 65, 97, 76, 13, 27, 49, 78, 34, 12, 64, 5, 4, 62, 99, 98, 54, 56, 17, 18, 23, 34, 15, 35, 25,
            53, 51 };

    public mergineSort() {
        sort(a, 0, a.length - 1);
        for (int i = 0; i < a.length; i++)
            System.out.println(a[i]);
    }
    /** * * @param data 待排序數組 * @param left 數組起始位置 * @param right 數組結束位置 */
    public void sort(int[] data, int left, int right) {
        if (left < right) {//代表能夠繼續拆分
            // 找出中間索引
            int center = (left + right) / 2;
            // 對左邊數組進行遞歸
            sort(data, left, center);
            // 對右邊數組進行遞歸
            sort(data, center + 1, right);
            // 合併
            merge(data, left, center, right);

        }
    }
    /** * * @param data排序完的原數組 * @param left 起始位置 * @param center 中間位置 * @param right 結束位置 */
    public void merge(int[] data, int left, int center, int right) {
        int[] tmpArr = new int[data.length];//中間臨時數組
        int mid = center + 1;
        // temp記錄中間數組的索引 -->就是合併這兩個數組的大數組的索引
        int temp = left;
        while (left <= center && mid <= right) {
            // 從兩個數組中取出最小的放入中間數組
            if (data[left] <= data[mid]) {
                tmpArr[temp] = data[left];
                left++;
                temp++;
            } else {
                tmpArr[temp] = data[mid];
                mid++;
                temp++;
            }
        }
        // 剩餘部分依次放入中間數組(見上面的合併圖解)
        while (mid <= right) {
            tmpArr[temp] = data[mid];
            mid++;
            temp++;
        }
        while (left <= center) {
            tmpArr[temp] = data[left];
            left++;
            temp++;
        }
        // 將中間數組中的內容複製回原數組
        for(int i=0;i<=right;i++){
            data[i]=tmpArr[i];
        }
    }
}

八、基數排序

(1)基本思想:

將全部待比較數值(正整數)統一爲一樣的數位長度,數位較短的數前面補零。而後,從最低位開始,依次進行一次排序。這樣從最低位排序一直到最高位排序完成之後,數列就變成一個有序序列。

(2)理解圖

基數排序理解

(3)實現代碼

/** * 基數排序是一種非比較型整數排序算法,其原理是將整數按位數切割成不一樣的數字,而後按每一個位數分別比較。 * 因爲整數也能夠表達字符串(好比名字或日期)和特定格式的浮點數, * 因此基數排序也不是隻能使用於整數。 * 步驟: * 一、將全部待比較數值(正整數)統一爲一樣的數位長度,數位較短的數前面補零。 二、從最低位開始,依次進行一次排序。 三、這樣從最低位排序一直到最高位排序完成之後, 數列就變成一個有序序列。 * @author Administrator * */
public class RadixSort {

    public static int[] radixSortAsc(int[] arr) {
        // 從低位往高位循環
        for (int d = 1; d <= getMax(arr); d++) {
            // 臨時數組,用來存放排序過程當中的數據
            int[] tmpArray = new int[arr.length];
            // 位記數器,從第0個元素到第9個元素依次用來記錄當前比較位是0的有多少個...是9的有多少個數
            int[] count = new int[10];
            // 開始統計0有多少個,並存儲在第0位,再統計1有多少個,並存儲在第1位..依次統計到9有多少個
            // { 73, 22, 93, 43, 55, 14, 28, 65, 39, 81, 33, 10 };
            for (int i = 0; i < arr.length; i++) {
                count[digit(arr[i], d)] += 1;// 統計該位上有多少個數字 好比第一位上0有多少個
            }
            /* * 好比某次通過上面統計後結果爲:[0, 2, 3, 3, 0, 0, 0, 0, 0, 0]則通過下面計算後 結果爲: [0, 2, * 5, 8, 8, 8, 8, 8, 8, 8]但實質上只有以下[0, 2, 5, 8, 0, 0, 0, 0, 0, 0]中 * 非零數纔用到,由於其餘位不存在,它們分別表示以下:2表示比較位爲1的元素能夠存放在索引爲一、0的 * 位置,5表示比較位爲2的元素能夠存放在四、三、2三個(5-2=3)位置,8表示比較位爲3的元素能夠存放在 * 七、六、5三個(8-5=3)位置 */
            for (int i = 1; i < 10; i++) {
                count[i] += count[i - 1];
            }

            /* * 注,這裏只能從數組後往前循環,由於排序時還需保持之前的已排序好的 順序,不該該打 * 亂原來已排好的序,若是從前日後處理,則會把原來在前面會擺到後面去,由於在處理某個 * 元素的位置時,位記數器是從大到到小(count[digit(arr[i], d)]--)的方式來處 * 理的,即先存放索引大的元素,再存放索引小的元素,因此需從最後一個元素開始處理。 * 若有這樣的一個序列[212,213,312],若是按照從第一個元素開始循環的話,通過第一輪 * 後(個位)排序後,獲得這樣一個序列[312,212,213],第一次好像沒什麼問題,但問題會 * 從第二輪開始出現,第二輪排序後,會獲得[213,212,312],這樣個位爲3的元素本應該 * 放在最後,但通過第二輪後卻排在了前面了,因此出現了問題 */
            for (int i = arr.length - 1; i >= 0; i--) {// 只能從最後一個元素往前處理
                // for (int i = 0; i < arr.length; i++) {//不能從第一個元素開始循環
                tmpArray[count[digit(arr[i], d)] - 1] = arr[i];
                count[digit(arr[i], d)]--;
            }
            //System.arraycopy(tmpArray, 0, arr, 0, tmpArray.length);
            for(int i=0;i<arr.length;i++){
                arr[i]=tmpArray[i];
            }
        }
        return arr;
    }
    //求出最大數的位數的函數
    public static int getMax(int[] array) {
        // 取出最大數而後求出最大的位數
        int max = array[0];
        for (int j = 1; j < array.length; j++) {
            if (array[j] > max) {
                max = array[j];
            }
        }
        int time = 0;
        // 判斷位數;
        while (max > 0) {
            max /= 10;
            time++;
        }
        return time;
        // return String.valueOf(max).length();也能夠根據字符串長度返回
    }

    /** * 取數xxx上的第d位數字 * * @param x * 數字 * @param d * 第幾位,從低位到高位 * @return */
    public static int digit(int num, int d) {
        int pow = 1;
        while (--d > 0) {
            pow *= 10;
        }
        return num / pow % 10;
    }

    public static void main(String[] args) {
        int[] data = { 73, 22, 93, 43, 55, 14, 28, 65, 39, 81, 33, 10 };
        System.out.println(radixSortAsc(data));
        for (int i = 0; i < data.length; i++) {
            System.out.print(data[i] + " ");
        }
    }
}