八大常用排序算法詳細分析 包括複雜度,原理和實現

本文是轉載文章,文章的來源:csdn博客
博主:厚積_薄發
文章: 八大常用排序算法詳細分析 包括複雜度,原理和實現
博文地址:https://blog.csdn.net/yuxin6866/article/details/52771739

八大常用排序算法詳細分析 包括複雜度,原理和實現如下:

1. 冒泡排序

1.1 算法原理:

S1:從待排序序列的起始位置開始,從前往後依次比較各個位置和其後一位置的大小並執行S2。 
S2:如果當前位置的值大於其後一位置的值,就把他倆的值交換(完成一次全序列比較後,序列最後位置的值即此序列最大值,所以其不需要再參與冒泡)。 
S3:將序列的最後位置從待排序序列中移除。若移除後的待排序序列不爲空則繼續執行S1,否則冒泡結束。

1.2 算法實現(Java):

1.2.1 基礎實現:

public static void bubbleSort(int[] array) {
    int len = array.length;
    for (int i = 0; i < len; i++) {
        for (int j = 0; j < len - i - 1; j++) {
            if (array[j] > array[j + 1]) {
                int temp = array[j + 1];
                array[j + 1] = array[j];
                array[j] = temp;
            }
        }
    }
}

1.2.2 算法優化:

若某一趟排序中未進行一次交換,則排序結束

public static void bubbleSort(int[] array) {
    int len = array.length;
    boolean flag = true;
    while (flag) {
        flag = false;
        for (int i = 0; i < len - 1; i++) {
            if (array[i] > array[i + 1]) {
                int temp = array[i + 1];
                array[i + 1] = array[j];
                array[i] = temp;
                flag = true;
            }
        }
        len--;
    }
}

2. 快速排序

2.1 算法原理:

快速排序是對冒泡排序的一種改進。基本思想是:通過一趟排序將要排序的數據分割成獨立的兩部分,其中一部分的所有數據都比另外一部分的所有數據都要小,然後再按此方法對這兩部分數據分別進行快速排序,整個排序過程可以遞歸進行,以此實現整個數據變成有序序列。

2.2 算法實現(Java):

public static void quickSort(int[] array, int left, int right) {
    if (left < right) {
        int pivot = array[left];
        int low = left;
        int high = right;
        while (low < high) {
            while (low < high && array[high] >= pivot) {
                high--;
            }
            array[low] = array[high];
            while (low < high && array[low] <= pivot) {
                low++;
            }
            array[high] = array[low];
        }
        array[low] = pivot;
        quickSort(array, left, low - 1);
        quickSort(array, low + 1, right);
    }
}

3. 直接插入排序

3.1 算法原理:

插入排序的基本方法是:每步將一個待排序序列按數據大小插到前面已經排序的序列中的適當位置,直到全部數據插入完畢爲止。 
假設有一組無序序列 , , … , : 
(1) 將這個序列的第一個元素R0視爲一個有序序列; 
(2) 依次把 , , … ,  插入到這個有序序列中; 
(3) 將插入到有序序列中時,前 i-1 個數是有序的,將和 ~ 從後往前進行比較,確定要插入的位置。

3.2 算法實現(Java):

public static void insertSort(int[] array) {
    for (int i = 1, len = array.length; i < len; i++) {
        if (array[i] < array[i - 1]) {
            int temp = array[i];
            int j;
            for (j = i - 1; j >= 0 && temp < array[j]; j--) {
                array[j + 1] = array[j];
            }
            array[j + 1] = temp;
        }

    }
}

4. Shell排序

4.1 算法原理:

希爾排序是一種插入排序算法,又稱作縮小增量排序。是對直接插入排序算法的改進。其基本思想是: 
先取一個小於n的整數作爲第一個增量,把全部數據分成個組。所有距離爲的倍數的記錄放在同一個組中。先在各組內進行直接插入排序;然後,取第二個增量重複上述的分組和排序,直至所取的增量,即所有記錄放在同一組中進行直接插入排序爲止。該方法實質上是一種分組插入方法。

4.2 算法實現(Java):

public static void shellSort(int[] array) {
    int n = array.length;
    int h;
    for (h = n / 2; h > 0; h /= 2) {
        for (int i = h; i < n; i++) {
            for (int j = i - h; j >= 0; j -= h) {
                if (array[j] > array[j + h]) {
                    int temp = array[j];
                    array[j] = array[j + h];
                    array[j + h] = temp;
                }
            }
        }
    }
}

5. 直接選擇排序

5.1 算法原理:

直接選擇排序是一種簡單的排序方法,它的基本思想是: 
第一次從R[0]~R[n-1]中選取最小值,與R[0]交換, 
第二次從R[1]~R[n-1]中選取最小值,與R[1]交換, 
…., 
第i次從R[i-1]~R[n-1]中選取最小值,與R[i-1]交換, 
….., 
第n-1次從R[n-2]~R[n-1]中選取最小值,與R[n-2]交換, 
共通過n-1次,得到一個從小到大排列的有序序列。

5.2 算法實現(Java):

public static void selectSort(int[] array) {
    int n = array.length;
    for (int i = 0; i < n; i++) {
        int minIndex = i;
        for (int j = i + 1; j < n; j++) {
            if (array[minIndex] > array[j]) {
                minIndex = j;
            }
        }
        if (i != minIndex) {
            int temp = array[i];
            array[i] = array[minIndex];
            array[minIndex] = temp;
        }
    }
}

6. 堆排序

6.1 算法原理:

6.1.1 二叉堆定義:

二叉堆是完全二叉樹或近似完全二叉樹。二叉堆滿足兩個特性: 
  1)父結點的鍵值總是大於或等於(小於或等於)任何一個子節點的鍵值。 
  2)每個結點的左子樹和右子樹都是一個二叉堆。 
當父結點的鍵值總是大於或等於任何一個子節點的鍵值時爲大根堆。當父結點的鍵值總是小於或等於任何一個子節點的鍵值時爲小根堆。下面展示一個小根堆: 
這裏寫圖片描述 
由於其它幾種堆(二項式堆,斐波納契堆等)用的較少,一般將二叉堆就簡稱爲堆。

6.1.2 堆的存儲:

一般都用數組來表示堆,i結點的父結點下標就爲(i – 1) / 2。它的左右子結點下標分別爲2 * i + 1和2 * i + 2。如第0個結點左右子結點下標分別爲1和2。 
這裏寫圖片描述

6.1.3 堆的插入:

每次插入都是將新數據放在數組最後。可以發現從這個新數據的父結點到根結點必然爲一個有序的數列,然後將這個新數據插入到這個有序數據中。

6.1.4 堆排序:

堆排序(Heapsort)是指利用堆積樹(堆)這種數據結構所設計的一種排序算法,它是選擇排序的一種。可以利用數組的特點快速定位指定索引的元素。堆分爲大根堆和小根堆,是完全二叉樹。大根堆的要求是每個節點的值都不大於其父節點的值,即A[PARENT[i]] >= A[i],小根堆則相反。 
堆排序利用了大根堆(或小根堆)堆頂記錄的關鍵字最大(或最小)這一特徵,使得在當前無序區中選取最大(或最小)關鍵字的記錄變得簡單。 
(1)用大根堆排序的基本思想 
  ① 先將初始數組建成一個大根堆,此堆爲初始的無序區 
  ② 再將最大的元素(即堆頂)和無序區的最後一個記錄交換,由此得到新的無序區和有序區,且滿足的值<=的值。 
  ③由於交換後新的根可能違反堆性質,故應將當前無序區調整爲堆。然後再次將中最大的元素和該區間的最後一個記錄交換,由此得到新的無序區和有序區,且仍滿足關係的值<=的值,同樣要將調整爲堆。 
…… 
直到無序區只有一個元素爲止。 
(2)大根堆排序算法的基本操作: 
  ①建堆,建堆是不斷調整堆的過程,從len/2處開始調整,一直到第一個節點,此處len是堆中元素的個數。建堆的過程是線性的過程,從len/2到0處一直調用調整堆的過程,相當於o(h1)+o(h2)…+o(hlen/2) 其中h表示節點的深度,len/2表示節點的個數,這是一個求和的過程,結果是線性的O(n)。 
  ②調整堆:調整堆在構建堆的過程中會用到,而且在堆排序過程中也會用到。利用的思想是比較節點i和它的孩子節點left(i),right(i),選出三者最大(或者最小)者,如果最大(小)值不是節點i而是它的一個孩子節點,那邊交互節點i和該節點,然後再調用調整堆過程,這是一個遞歸的過程。調整堆的過程時間複雜度與堆的深度有關係,是lgn的操作,因爲是沿着深度方向進行調整的。 
  ③堆排序:堆排序是利用上面的兩個過程來進行的。首先是根據元素構建堆。然後將堆的根節點取出(一般是與最後一個節點進行交換),將前面len-1個節點繼續進行堆調整的過程,然後再將根節點取出,這樣一直到所有節點都取出。

6.2 算法實現(Java):

public static void heapSort(int[] array) {
    // 1. 創建最大堆:從最後一個節點的父節點開始
    int lastIndex = array.length - 1;
    int startIndex = (lastIndex - 1) / 2;
    for (int i = startIndex; i >= 0; i--) {
        maxHeap(sort, sort.length, i);
    }
    // 2. 排序:末尾與頭交換,逐一找出最大值,最終形成一個遞增的有序序列
    for (int i = array.length - 1; i > 0; i--) {
        int temp = array[0];
        array[0] = array[i];
        array[i] = temp;
        maxHeap(array, i, 0);
    }
}

private static void maxHeap(int[] data, int heapSize, int index) {
    // 左子節點
    int leftChild = 2 * index + 1;
    // 右子節點
    int rightChild = 2 * index + 2;
    // 最大元素下標
    int largestIndex = index;
    // 分別比較當前節點和左右子節點,找出最大值
    if (leftChild < heapSize && data[leftChild] > data[largestIndex]) {
        largestIndex = leftChild;
    }
    if (rightChild < heapSize && data[rightChild] > data[largestIndex]) {
        largestIndex = rightChild;
    }
    // 如果最大值是子節點,則進行交換
    if (largestIndex != index) {
        int temp = data[index];
        data[index] = data[largestIndex];
        data[largestIndex] = temp;
        // 交換後,其子節點可能就不是最大堆了,需要對交換的子節點重新調整
        maxHeap(data, heapSize, largestIndex);
    }
}

7. 歸併排序

7.1 算法原理:

歸併排序是建立在歸併操作上的一種有效的排序算法,該算法是採用分治法的一個非常典型的應用。將已有序的子序列合併,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。若將兩個有序表合併成一個有序表,稱爲二路歸併。 
歸併過程爲: 
  比較a[i]和a[j]的大小,若a[i]≤a[j],則將第一個有序表中的元素a[i]複製到r[k]中,並令i和k分別加上1;否則將第二個有序表中的元素a[j]複製到r[k]中,並令j和k分別加上1,如此循環下去,直到其中一個有序表取完,然後再將另一個有序表中剩餘的元素複製到r中從下標k到下標t的單元。 
歸併排序的算法我們通常用遞歸實現,先把待排序區間[s,t]以中點二分,接着把左邊子區間排序,再把右邊子區間排序,最後把左區間和右區間用一次歸併操作合併成有序的區間[s,t]。 
歸併操作的工作原理如下: 
  S1: 申請空間,使其大小爲兩個已經排序序列之和,該空間用來存放合併後的序列 
  S2: 設定兩個指針,最初位置分別爲兩個已經排序序列的起始位置 
  S3: 比較兩個指針所指向的元素,選擇相對小的元素放入到合併空間,並移動指針到下一位置 
  S4: 重複S3,直到某一指針超出序列尾 
  S5: 將另一序列剩下的所有元素直接複製到合併序列尾

7.2 算法實現(Java):

public static void mergeSort(int[] array, int low, int high) {
    int middle = (low + high) / 2;
    if (low < high) {
        mergeSort(array, low, middle);
        mergeSort(array, middle + 1, high);
        merge(array, low, middle, high);
    }
}

public static void merge(int[] array, int low, int middle, int high) {
    int[] temp = new int[high - low + 1];
    int i = low;
    int j = middle + 1;
    int k = 0;
    while (i <= middle && j <= high) {
        if (array[i] < array[j]) {
            temp[k++] = array[i++];
        } else {
            temp[k++] = array[j++];
        }
    }
    while (i <= middle) {
        temp[k++] = array[i++];
    }
    while (j <= high) {
        temp[k++] = array[j++];
    }
    for (int m = 0; m < temp.length; m++) {
        array[m + low] = temp[m];
    }
}

8. 基數排序

8.1 算法原理:

基數排序的原理如下:將所有待比較數值(正整數)統一爲同樣的數位長度,數位較短的數前面補零。然後,從最低位開始,依次進行一次排序。這樣從最低位排序一直到最高位排序完成以後, 數列就變成一個有序序列。 
基數排序的方式有以下兩種: 
  最高位優先(Most Significant Digit first)法,簡稱MSD法:先按排序分組,同一組中記錄,關鍵碼相等,再對各組按排序分成子組,之後,對後面的關鍵碼繼續這樣的排序分組,直到按最次位關鍵碼對各子組排序後。再將各組連接起來,便得到一個有序序列。 
  最低位優先(Least Significant Digit first)法,簡稱LSD法:先從開始排序,再對進行排序,依次重複,直到對排序後便得到一個有序序列。

8.2 算法實現(Java):

/**
 * 基數排序(LSD)
 *
 * @param array 待排序數組
 * @param d     表示最大的元素的位數
 */
public static void radixSort(int[] array, int d) {
    int n = 1;
    int times = 1; // 排序次數,由位數最多的元素決定
    int[][] temp = new int[10][array.length]; //數組的第一維表示可能的餘數0-9
    int[] order = new int[10]; //數組order用來表示該位是i的元素個數
    while (times <= d) {
        for (int i = 0; i < array.length; i++) {
            int lsd = ((array[i] / n) % 10);
            temp[lsd][order[lsd]] = array[i];
            order[lsd]++;
        }
        int k = 0;
        for (int i = 0; i < 10; i++) {
            if (order[i] != 0) {
                for (int j = 0; j < order[i]; j++) {
                    array[k] = temp[i][j];
                    k++;
                }
                order[i] = 0;
            }
        }
        n *= 10;
        times++;
    }
}
轉載請註明:http://blog.csdn.net/yuxin6866/article/details/52771739

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章