常見排序演算法及對應的時間複雜度和空間複雜度
轉載請註明出處:
排序演算法經過了很長時間的演變,產生了很多種不同的方法。對於初學者來說,對它們進行整理便於理解記憶顯得很重要。每種演算法都有它特定的使用場合,很難通用。因此,我們很有必要對所有常見的排序演算法進行歸納。
排序大的分類可以分為兩種:內排序和外排序。在排序過程中,全部記錄存放在記憶體,則稱為內排序,如果排序過程中需要使用外存,則稱為外排序。下面講的排序都是屬於內排序。
內排序有可以分為以下幾類:
(1)、插入排序:直接插入排序、二分法插入排序、希爾排序。
(2)、選擇排序:直接選擇排序、堆排序。
(3)、交換排序:氣泡排序、快速排序。
(4)、歸併排序
(5)、基數排序
表格版
排序方法 | 時間複雜度(平均) | 時間複雜度(最壞) | 時間複雜度(最好) | 空間複雜度 | 穩定性 | 複雜性 |
---|---|---|---|---|---|---|
直接插入排序 | 穩定 | 簡單 | ||||
希爾排序 | 不穩定 | 較複雜 | ||||
直接選擇排序 | 不穩定 | 簡單 | ||||
堆排序 | 不穩定 | 較複雜 | ||||
氣泡排序 | 穩定 | 簡單 | ||||
快速排序 | 不穩定 | 較複雜 | ||||
歸併排序 | 穩定 | 較複雜 | ||||
基數排序 | 穩定 | 較複雜 |
圖片版
① 插入排序
•思想:每步將一個待排序的記錄,按其順序碼大小插入到前面已經排序的字序列的合適位置,直到全部插入排序完為止。
•關鍵問題:在前面已經排好序的序列中找到合適的插入位置。
•方法:
–直接插入排序
–二分插入排序
–希爾排序
(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)氣泡排序