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

http://blog.csdn.net/gane_cheng/article/details/52652705html

http://www.ganecheng.tech/blog/52652705.html (瀏覽效果更好)java

排序算法通過了很長時間的演變,產生了不少種不一樣的方法。對於初學者來講,對它們進行整理便於理解記憶顯得很重要。每種算法都有它特定的使用場合,很難通用。所以,咱們頗有必要對全部常見的排序算法進行概括。算法

排序大的分類能夠分爲兩種:內排序和外排序。在排序過程當中,所有記錄存放在內存,則稱爲內排序,若是排序過程當中須要使用外存,則稱爲外排序。下面講的排序都是屬於內排序。數組

內排序有能夠分爲如下幾類:函數

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

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

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

  (4)、歸併排序spa

  (5)、基數排序.net

表格版

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

圖片版

這裏寫圖片描述


① 插入排序

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

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

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

二、實例 
這裏寫圖片描述

三、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)二分法插入排序(按二分法找到合適位置插入)

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

二、實例

這裏寫圖片描述

三、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)希爾排序

一、基本思想:先取一個小於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)直接選擇排序

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

二、實例

這裏寫圖片描述

三、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)堆排序

一、基本思想:

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

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

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

二、實例

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

建堆:

這裏寫圖片描述

交換,從堆中踢出最大數

這裏寫圖片描述

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

三、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)冒泡排序

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

二、實例

這裏寫圖片描述

三、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)快速排序

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

二、實例

這裏寫圖片描述

三、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; } } 

 

④ 歸併排序

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

二、實例

這裏寫圖片描述

三、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)); } } 

 

⑤ 基數排序

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

二、實例

這裏寫圖片描述

三、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++; } } } } }

測試總結:

logn < n < nlogn < n^2 < n^3

根據上面結果,我繪製瞭如下表格:
第一列爲時間複雜度,第一行爲數據個數,中間數據爲時間(秒爲單位)。

 

100

1000

10000

100000

1000000

O(n)

0.035

0.034

0.041

0.049

0.339

O(nlogn)

0.028

0.023

0.032

0.069

0.580

O(n^2)

0.025

0.026

0.357

--------

----------

O(n^3)

0.023

0.655

------

--------

---------

相關文章
相關標籤/搜索