常見排序算法及對應的時間複雜度和空間複雜度

轉載自:https://blog.csdn.net/gane_cheng/article/details/52652705

排序算法經過了很長時間的演變,產生了很多種不同的方法。對於初學者來說,對它們進行整理便於理解記憶顯得很重要。每種算法都有它特定的使用場合,很難通用。因此,我們很有必要對所有常見的排序算法進行歸納。

排序大的分類可以分爲兩種:內排序和外排序。在排序過程中,全部記錄存放在內存,則稱爲內排序,如果排序過程中需要使用外存,則稱爲外排序。下面講的排序都是屬於內排序。

內排序有可以分爲以下幾類:

  (1)、插入排序:直接插入排序、二分法插入排序、希爾排序。

  (2)、選擇排序:直接選擇排序、堆排序。

  (3)、交換排序:冒泡排序、快速排序。

  (4)、歸併排序

  (5)、基數排序

表格版

排序方法 時間複雜度(平均) 時間複雜度(最壞) 時間複雜度(最好) 空間複雜度 穩定性 複雜性
直接插入排序 O(n2) O(n2) O(n) O(1) 穩定 簡單
希爾排序 O(nlog2n) O(n2) O(n) O(1) 不穩定 較複雜
直接選擇排序 O(n2) O(n2) O(n2) O(1) 不穩定 簡單
堆排序 O(nlog2n) O(nlog2n) O(nlog2n) O(1) 不穩定 較複雜
冒泡排序 O(n2) O(n2) O(n) O(1) 穩定 簡單
快速排序 O(nlog2n) O(n2) O(nlog2n) O(nlog2n) 不穩定 較複雜
歸併排序 O(nlog2n) O(nlog2n) O(nlog2n) O(n) 穩定 較複雜
基數排序 O(d(n+r)) O(d(n+r)) O(d(n+r)) O(n+r)O(n+r) 穩定 較複雜

 

圖片版

① 插入排序

•思想:每步將一個待排序的記錄,按其順序碼大小插入到前面已經排序的字序列的合適位置,直到全部插入排序完爲止。 
•關鍵問題:在前面已經排好序的序列中找到合適的插入位置。 
•方法: 
–直接插入排序 
–二分插入排序 
–希爾排序

(1)直接插入排序(從後向前找到合適位置後插入)

1、基本思想:每步將一個待排序的記錄,按其順序碼大小插入到前面已經排序的字序列的合適位置(從後向前找到合適位置後),直到全部插入排序完爲止。

2、實例 
這裏寫圖片描述

3、java實現

package DirectInsertSort;

public class DirectInsertSort
{

    public static void main(String[] args)
    {
        int[] a = { 49, 38, 65, 97, 76, 13, 27, 49, 78, 34, 12, 64, 1 };
        System.out.println("排序之前:");
        for (int i = 0; i < a.length; i++)
        {
            System.out.print(a[i] + " ");
        }
        // 直接插入排序
        for (int i = 1; i < a.length; i++)
        {
            // 待插入元素
            int temp = a[i];
            int j;
            for (j = i - 1; j >= 0; j--)
            {
                // 將大於temp的往後移動一位
                if (a[j] > temp)
                {
                    a[j + 1] = a[j];
                }
                else
                {
                    break;
                }
            }
            a[j + 1] = temp;
        }
        System.out.println();
        System.out.println("排序之後:");
        for (int i = 0; i < a.length; i++)
        {
            System.out.print(a[i] + " ");
        }

    }

}

(2)二分法插入排序(按二分法找到合適位置插入)

1、基本思想:二分法插入排序的思想和直接插入一樣,只是找合適的插入位置的方式不同,這裏是按二分法找到合適的位置,可以減少比較的次數。

2、實例

這裏寫圖片描述

3、java實現

package BinaryInsertSort;

public class BinaryInsertSort
{

    public static void main(String[] args)
    {
        int[] a = { 49, 38, 65, 97, 176, 213, 227, 49, 78, 34, 12, 164, 11, 18, 1 };
        System.out.println("排序之前:");
        for (int i = 0; i < a.length; i++)
        {
            System.out.print(a[i] + " ");
        }
        // 二分插入排序
        sort(a);
        System.out.println();
        System.out.println("排序之後:");
        for (int i = 0; i < a.length; i++)
        {
            System.out.print(a[i] + " ");
        }
    }

    private static void sort(int[] a)
    {
        for (int i = 0; i < a.length; i++)
        {
            int temp = a[i];
            int left = 0;
            int right = i - 1;
            int mid = 0;
            while (left <= right)
            {
                mid = (left + right) / 2;
                if (temp < a[mid])
                {
                    right = mid - 1;
                }
                else
                {
                    left = mid + 1;
                }
            }
            for (int j = i - 1; j >= left; j--)
            {
                a[j + 1] = a[j];
            }
            if (left != i)
            {
                a[left] = temp;
            }
        }
    }

}

(3)希爾排序

1、基本思想:先取一個小於n的整數d1作爲第一個增量,把文件的全部記錄分成d1個組。所有距離爲d1的倍數的記錄放在同一個組中。先在各組內進行直接插入排序;然後,取第二個增量d2

package ShellSort;

public class ShellSort
{

    public static void main(String[] args)
    {
        int[] a = { 49, 38, 65, 97, 76, 13, 27, 49, 78, 34, 12, 64, 1 };
        System.out.println("排序之前:");
        for (int i = 0; i < a.length; i++)
        {
            System.out.print(a[i] + " ");
        }
        // 希爾排序
        int d = a.length;
        while (true)
        {
            d = d / 2;
            for (int x = 0; x < d; x++)
            {
                for (int i = x + d; i < a.length; i = i + d)
                {
                    int temp = a[i];
                    int j;
                    for (j = i - d; j >= 0 && a[j] > temp; j = j - d)
                    {
                        a[j + d] = a[j];
                    }
                    a[j + d] = temp;
                }
            }
            if (d == 1)
            {
                break;
            }
        }
        System.out.println();
        System.out.println("排序之後:");
        for (int i = 0; i < a.length; i++)
        {
            System.out.print(a[i] + " ");
        }

    }

}

② 選擇排序

•思想:每趟從待排序的記錄序列中選擇關鍵字最小的記錄放置到已排序表的最前位置,直到全部排完。 
•關鍵問題:在剩餘的待排序記錄序列中找到最小關鍵碼記錄。 
•方法: 
–直接選擇排序 
–堆排序

(1)直接選擇排序

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

2、實例

這裏寫圖片描述

3、java實現

package DirectSelectSort;

public class DirectSelectSort
{

    public static void main(String[] args)
    {
        int[] a = { 49, 38, 65, 97, 76, 13, 27, 49, 78, 34, 12, 64, 1, 8 };
        System.out.println("排序之前:");
        for (int i = 0; i < a.length; i++)
        {
            System.out.print(a[i] + " ");
        }
        // 直接選擇排序
        for (int i = 0; i < a.length; i++)
        {
            int min = a[i];
            int n = i; // 最小數的索引
            for (int j = i + 1; j < a.length; j++)
            {
                if (a[j] < min)
                { // 找出最小的數
                    min = a[j];
                    n = j;
                }
            }
            a[n] = a[i];
            a[i] = min;

        }
        System.out.println();
        System.out.println("排序之後:");
        for (int i = 0; i < a.length; i++)
        {
            System.out.print(a[i] + " ");
        }

    }

}

(2)堆排序

1、基本思想:

  堆排序是一種樹形選擇排序,是對直接選擇排序的有效改進。

  堆的定義下:具有n個元素的序列 (h1,h2,…,hn),當且僅當滿足(hi>=h2i,hi>=2i+1)或(hi<=h2i,hi<=2i+1) (i=1,2,…,n/2)時稱之爲堆。在這裏只討論滿足前者條件的堆。由堆的定義可以看出,堆頂元素(即第一個元素)必爲最大項(大頂堆)。完全二叉樹可以很直觀地表示堆的結構。堆頂爲根,其它爲左子樹、右子樹。

  思想:初始時把要排序的數的序列看作是一棵順序存儲的二叉樹,調整它們的存儲序,使之成爲一個堆,這時堆的根節點的數最大。然後將根節點與堆的最後一個節點交換。然後對前面(n-1)個數重新調整使之成爲堆。依此類推,直到只有兩個節點的堆,並對它們作交換,最後得到有n個節點的有序序列。從算法描述來看,堆排序需要兩個過程,一是建立堆,二是堆頂與堆的最後一個元素交換位置。所以堆排序有兩個函數組成。一是建堆的滲透函數,二是反覆調用滲透函數實現排序的函數。

2、實例

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

建堆:

這裏寫圖片描述

交換,從堆中踢出最大數

這裏寫圖片描述

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

3、java實現

package HeapSort;

import java.util.Arrays;

public class HeapSort
{
    public static void main(String[] args)
    {
        int[] a = { 49, 38, 65, 97, 76, 13, 27, 49, 78, 34, 12, 64 };
        int arrayLength = a.length;
        // 循環建堆
        for (int i = 0; i < arrayLength - 1; i++)
        {
            // 建堆
            buildMaxHeap(a, arrayLength - 1 - i);
            // 交換堆頂和最後一個元素
            swap(a, 0, arrayLength - 1 - i);
            System.out.println(Arrays.toString(a));
        }
    }

    // 對data數組從0到lastIndex建大頂堆
    public static void buildMaxHeap(int[] data, int lastIndex)
    {
        // 從lastIndex處節點(最後一個節點)的父節點開始
        for (int i = (lastIndex - 1) / 2; i >= 0; i--)
        {
            // k保存正在判斷的節點
            int k = i;
            // 如果當前k節點的子節點存在
            while (k * 2 + 1 <= lastIndex)
            {
                // k節點的左子節點的索引
                int biggerIndex = 2 * k + 1;
                // 如果biggerIndex小於lastIndex,即biggerIndex+1代表的k節點的右子節點存在
                if (biggerIndex < lastIndex)
                {
                    // 若果右子節點的值較大
                    if (data[biggerIndex] < data[biggerIndex + 1])
                    {
                        // biggerIndex總是記錄較大子節點的索引
                        biggerIndex++;
                    }
                }
                // 如果k節點的值小於其較大的子節點的值
                if (data[k] < data[biggerIndex])
                {
                    // 交換他們
                    swap(data, k, biggerIndex);
                    // 將biggerIndex賦予k,開始while循環的下一次循環,重新保證k節點的值大於其左右子節點的值
                    k = biggerIndex;
                }
                else
                {
                    break;
                }
            }
        }
    }

    // 交換
    private static void swap(int[] data, int i, int j)
    {
        int tmp = data[i];
        data[i] = data[j];
        data[j] = tmp;
    }
}

③ 交換排序

(1)冒泡排序

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

2、實例

這裏寫圖片描述

3、java實現

package BubbleSort;

public class BubbleSort
{
    public static void main(String[] args)
    {
        int[] a = { 49, 38, 65, 97, 76, 13, 27, 49, 78, 34, 12, 64, 1, 8 };
        System.out.println("排序之前:");
        for (int i = 0; i < a.length; i++)
        {
            System.out.print(a[i] + " ");
        }
        // 冒泡排序
        for (int i = 0; i < a.length; i++)
        {
            for (int j = 0; j < a.length - i - 1; j++)
            {
                // 這裏-i主要是每遍歷一次都把最大的i個數沉到最底下去了,沒有必要再替換了
                if (a[j] > a[j + 1])
                {
                    int temp = a[j];
                    a[j] = a[j + 1];
                    a[j + 1] = temp;
                }
            }
        }
        System.out.println();
        System.out.println("排序之後:");
        for (int i = 0; i < a.length; i++)
        {
            System.out.print(a[i] + " ");
        }

    }

}

(2)快速排序

1、基本思想:選擇一個基準元素,通常選擇第一個元素或者最後一個元素,通過一趟掃描,將待排序列分成兩部分,一部分比基準元素小,一部分大於等於基準元素,此時基準元素在其排好序後的正確位置,然後再用同樣的方法遞歸地排序劃分的兩部分。

2、實例

這裏寫圖片描述

3、java實現

package QuickSort;

public class QuickSort
{
    public static void main(String[] args)
    {
        int[] a = { 49, 38, 65, 97, 76, 13, 27, 49, 78, 34, 12, 64, 1, 8 };
        System.out.println("排序之前:");
        for (int i = 0; i < a.length; i++)
        {
            System.out.print(a[i] + " ");
        }
        // 快速排序
        quick(a);
        System.out.println();
        System.out.println("排序之後:");
        for (int i = 0; i < a.length; i++)
        {
            System.out.print(a[i] + " ");
        }
    }

    private static void quick(int[] a)
    {
        if (a.length > 0)
        {
            quickSort(a, 0, a.length - 1);
        }
    }

    private static void quickSort(int[] a, int low, int high)
    {
        if (low < high)
        { // 如果不加這個判斷遞歸會無法退出導致堆棧溢出異常
            int middle = getMiddle(a, low, high);
            quickSort(a, 0, middle - 1);
            quickSort(a, middle + 1, high);
        }
    }

    private static int getMiddle(int[] a, int low, int high)
    {
        int temp = a[low];// 基準元素
        while (low < high)
        {
            // 找到比基準元素小的元素位置
            while (low < high && a[high] >= temp)
            {
                high--;
            }
            a[low] = a[high];
            while (low < high && a[low] <= temp)
            {
                low++;
            }
            a[high] = a[low];
        }
        a[low] = temp;
        return low;
    }
}

④ 歸併排序

1、基本思想:歸併(Merge)排序法是將兩個(或兩個以上)有序表合併成一個新的有序表,即把待排序序列分爲若干個子序列,每個子序列是有序的。然後再把有序子序列合併爲整體有序序列。

2、實例

這裏寫圖片描述

3、java實現

package MergeSort;

import java.util.Arrays;

public class MergeSort
{
    /**
     * 歸併排序 簡介:將兩個(或兩個以上)有序表合併成一個新的有序表
     * 即把待排序序列分爲若干個子序列,每個子序列是有序的。然後再把有序子序列合併爲整體有序序列 時間複雜度爲O(nlogn) 穩定排序方式
     * 
     * @param nums
     *            待排序數組
     * @return 輸出有序數組
     */
    public static int[] sort(int[] nums, int low, int high)
    {
        int mid = (low + high) / 2;
        if (low < high)
        {
            // 左邊
            sort(nums, low, mid);
            // 右邊
            sort(nums, mid + 1, high);
            // 左右歸併
            merge(nums, low, mid, high);
        }
        return nums;
    }

    public static void merge(int[] nums, int low, int mid, int high)
    {
        int[] temp = new int[high - low + 1];
        int i = low;// 左指針
        int j = mid + 1;// 右指針
        int k = 0;

        // 把較小的數先移到新數組中
        while (i <= mid && j <= high)
        {
            if (nums[i] < nums[j])
            {
                temp[k++] = nums[i++];
            }
            else
            {
                temp[k++] = nums[j++];
            }
        }

        // 把左邊剩餘的數移入數組
        while (i <= mid)
        {
            temp[k++] = nums[i++];
        }

        // 把右邊邊剩餘的數移入數組
        while (j <= high)
        {
            temp[k++] = nums[j++];
        }

        // 把新數組中的數覆蓋nums數組
        for (int k2 = 0; k2 < temp.length; k2++)
        {
            nums[k2 + low] = temp[k2];
        }
    }

    // 歸併排序的實現
    public static void main(String[] args)
    {

        int[] nums = { 2, 7, 8, 3, 1, 6, 9, 0, 5, 4 };

        MergeSort.sort(nums, 0, nums.length - 1);
        System.out.println(Arrays.toString(nums));
    }
}

⑤ 基數排序

1、基本思想:將所有待比較數值(正整數)統一爲同樣的數位長度,數位較短的數前面補零。然後,從最低位開始,依次進行一次排序。這樣從最低位排序一直到最高位排序完成以後,數列就變成一個有序序列。

2、實例

這裏寫圖片描述

3、java實現

package BaseSort;

import java.util.*;

public class BaseSort
{

    public static void main(String[] args)
    {
        int[] a = { 49, 38, 65, 97, 176, 213, 227, 49, 78, 34, 12, 164, 11, 18, 1 };
        System.out.println("排序之前:");
        for (int i = 0; i < a.length; i++)
        {
            System.out.print(a[i] + " ");
        }
        // 基數排序
        sort(a);
        System.out.println();
        System.out.println("排序之後:");
        for (int i = 0; i < a.length; i++)
        {
            System.out.print(a[i] + " ");
        }
    }

    private static void sort(int[] array)
    {
        // 找到最大數,確定要排序幾趟
        int max = 0;
        for (int i = 0; i < array.length; i++)
        {
            if (max < array[i])
            {
                max = array[i];
            }
        }
        // 判斷位數
        int times = 0;
        while (max > 0)
        {
            max = max / 10;
            times++;
        }
        // 建立十個隊列
        List<ArrayList> queue = new ArrayList<ArrayList>();
        for (int i = 0; i < 10; i++)
        {
            ArrayList queue1 = new ArrayList();
            queue.add(queue1);
        }
        // 進行times次分配和收集
        for (int i = 0; i < times; i++)
        {
            // 分配
            for (int j = 0; j < array.length; j++)
            {
                int x = array[j] % (int) Math.pow(10, i + 1) / (int) Math.pow(10, i);
                ArrayList queue2 = queue.get(x);
                queue2.add(array[j]);
                queue.set(x, queue2);
            }
            // 收集
            int count = 0;
            for (int j = 0; j < 10; j++)
            {
                while (queue.get(j).size() > 0)
                {
                    ArrayList<Integer> queue3 = queue.get(j);
                    array[count] = queue3.get(0);
                    queue3.remove(0);
                    count++;
                }
            }
        }
    }

}
發佈了16 篇原創文章 · 獲贊 13 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章