- 八大排序算法
- 一、直接插入
- 1.基本思路
- 2.代碼實現
- 3.時間複雜度和空間複雜度 - 二、希爾排序
- 1.基本思路
- 2.代碼實現
- 3.時間複雜度和空間複雜度 - 三、簡單選擇
- 1.基本思路
- 2.代碼實現
- 3.時間複雜度和空間複雜度 - 四、堆排序
- 1.基本思路
- 2.代碼實現
- 3.時間複雜度和空間複雜度 - 五、冒泡排序
- 1.基本思路
- 2.代碼實現
- 3.時間複雜度和空間複雜度 - 六、快速排序
- 1.基本思路
- 2.代碼實現
- 3.時間複雜度和空間複雜度 - 七、歸併排序
- 1.基本思路
- 2.代碼實現
- 3.時間複雜度和空間複雜度 - 八、基數排序
- 1.基本思路
- 2.代碼實現
- 3.時間複雜度和空間複雜度 - 總結
- 一、直接插入
八大排序算法
一、直接插入
1.基本思路
在要排序的一組數中,假設前面(n-1) [n>=2] 個數已經是排好順序的,現在要把第n個數插到前面的有序數中,使得這n個數也是排好順序的。如此反覆循環,直到全部排好順序。
2.代碼實現
- 1.遍歷數組,每次循環從第二個數字往前插入
- 2.設定插入數和得到已經排好序列的最後一個數的位數。temp和j=i-1。
- 3.從最後一個數開始向前循環,如果插入數小於當前數,就將當前數向後移動一位。
public static void insertSort(int[] data) {
int temp;
for(int i = 1;i < data.length; i++){// 取第i個數,插入前邊的有序的序列
temp = data[i];
int j;
for(j = i - 1; j>=0; j--) {// 從第i-1的位置上開始比較
if(data[j] > temp) {// 若前面的數大,則往後挪一位
data[j+1] = data[j];
} else {
break;// 否則,說明要插入的數比較大
}
}
data[j+1] = temp;// 找到這個位置,插入數據
}
}
3.時間複雜度和空間複雜度
直接插入排序的平均複雜度爲O(n²),最壞時間複雜度:O(n²),空間複雜度:O(1),沒有分配內存。
二、希爾排序
針對直接插入排序下的效率問題,有人對此進行了改進與升級,這就是現在的希爾排序。希爾排序,也稱遞減增量排序算法,是插入排序的一種更高效的改進版本。希爾排序是非穩定排序算法。
1.基本思路
-
1.數的個數爲length,i=length/2,將下標差值爲i的數分爲一組,構成有序序列。
-
2.再取i=i/2 ,將下標差值爲i的數分爲一組,構成有序序列。
-
3.重複第二步,直到k=1執行簡單插入排序。
思路:
- 1.希爾排序(shell sort)這個排序方法又稱爲縮小增量排序,是1959年D·L·Shell提出來的。該方法的基本思想是:設待排序元素序列有n個元素,首先取一個整數increment(小於n)作爲間隔將全部元素分爲increment個子序列,所有距離爲increment的元素放在同一個子序列中,在每一個子序列中分別實行直接插入排序。然後縮小間隔increment,重複上述子序列劃分和排序工作。直到最後取increment=1,將所有元素放在同一個子序列中排序爲止。
- 2.由於開始時,increment的取值較大,每個子序列中的元素較少,排序速度較快,到排序後期increment取值逐漸變小,子序列中元素個數逐漸增多,但由於前面工作的基礎,大多數元素已經基本有序,所以排序速度仍然很快。
希爾排序舉例:
2.代碼實現
- 1.遍歷數組,每次循環從第二個數字往前插入
- 2.設定插入數和得到已經排好序列的最後一個數的位數。temp和j=i-1。
- 3.從最後一個數開始向前循環,如果插入數小於當前數,就將當前數向後移動一位。
(1)首先確定每一組序列的下標的間隔,循環每次需要的間隔:int i = length/2; i >0 ; i /= 2
(2)然後將每一組序列中元素進行插入排序,第二組第一個插入的數字是第一組第一個插入數字之後的那個數組,從i之後每個數字都要進行插入排序,就是插入的序列是各自不同的序列,不是一個一個子序列循環,而是在一個循環中for (int j=i;j<length;j++)完成所有子序列的插入排序。
(3)直到i=0爲止。
public static void shellSort(int[] array) {
int length = array.length;
for (int i = length / 2; i > 0; i /= 2) {//序列的間隔,一直到間隔爲一,這時候就只有一個子序列
for (int j = i; j < length; j++) {//從i之後每個數字都要進行插入排序,就是插入的序列是各自不同的序列
int temp = array[j];//裏面就是直接插入算法
int k;
for (k = j - i; k >= 0; k -= i) {//實現各個數字插入排序到不同的序列中,直到間隔爲1的時候,只有一個序列,就是完全的一個直接插入排序
if (temp < array[k]) {
array[k + i] = array[k];
} else {
break;
}
}
array[k + i] = temp;//把數字插入到位置上
}
}
System.out.println(Arrays.toString(array));
}
3.時間複雜度和空間複雜度
希爾排序的平均時間複雜度爲O(n²),空間複雜度O(1) 。
三、簡單選擇
1.基本思路
基本原理如下:對於給定的一組記錄,經過第一輪比較後得到最小的記錄,然後將該記錄的位置與第一個記錄的位置交換;接着對不包括第一個記錄以外的其他記錄進行第二次比較,得到最小記錄並與第二個位置記錄交換;重複該過程,直到進行比較的記錄只剩下一個爲止。
2.代碼實現
- 1.確定要插入最小值的位置,從0開始到最後int i = 0; i <len ; i++
- 2.將每次開始位置上的數字暫定爲最小值min,從開始數字之後一個個和min比較,再把最小值存放到min
- 3.將最小值所在位置上的數字和開始位置上的數字交換
public static void selectSort(int[] array) {
int len = array.length;
for (int i = 0; i < len; i++) {//確定每次開始的位置
int min = array[i];//設定開始數字爲最小的值最小值
int flag = i;
for (int j = i + 1; j < len; j++) {//把最小值存放到min,從開始數字向後一個個和min比較,再把最小值存放到min
if (min > array[j]) {
min = array[j];
flag = j;
}
}
if (flag != i) {
array[flag] = array[i];
array[i] = min;
}
}
System.out.println(Arrays.toString(array));
}
3.時間複雜度和空間複雜度
簡單選擇排序的時間複雜度爲O(n²)
四、堆排序
1.基本思路
- 1.若array[0,…,n-1]表示一顆完全二叉樹的順序存儲模式,則雙親節點指針和孩子結點指針之間的內在關係如下:
任意一節點指針 i:
父節點:i==0 ? null : (i-1)/2
左孩子:2*i + 1
右孩子:2*i + 2
- 2.堆得定義
n個關鍵字序列array[0,...,n-1],當且僅當滿足下列要求:(0 <= i <= (n-1)/2)
① array[i] <= array[2*i + 1] 且 array[i] <= array[2*i + 2]; 稱爲小根堆;
② array[i] >= array[2*i + 1] 且 array[i] >= array[2*i + 2]; 稱爲大根堆;
- 3.建立大頂堆
n個節點的完全二叉樹array[0,…,n-1],最後一個節點n-1是第(n-1-1)/2個節點的孩子。對第(n-1-1)/2個節點爲根的子樹調整,使該子樹稱爲堆。
對於大根堆,調整方法爲:若【根節點的關鍵字】小於【左右子女中關鍵字較大者】,則交換。
之後向前依次對各節點((n-2)/2 - 1)~ 0爲根的子樹進行調整,看該節點值是否大於其左右子節點的值,若不是,將左右子節點中較大值與之交換,交換後可能會破壞下一級堆,於是繼續採用上述方法構建下一級的堆,直到以該節點爲根的子樹構成堆爲止。
反覆利用上述調整堆的方法建堆,直到根節點。
- 4.堆排序(大頂堆)
①將存放在array[0,...,n-1]中的n個元素建成初始堆;
②將堆頂元素與堆底元素進行交換,則序列的最大值即已放到正確的位置;
③將數組中array[0,...,n-1]前n-1個元素再次形成大根堆,再重複第②③步,直到堆中僅剩下一個元素爲止。
2.代碼實現
/**
* 大頂堆排序
* @param array
*/
public static void maxHeapSort(int[] array) {
int i;
int len = array.length;
// 構建大頂堆
for (i = len / 2 - 1; i >= 0; i--) {
adjustMaxHeap(array, i, len);
}
// 堆頂是最大值,交換堆頂和最後一個數,再重新調整最大堆,下一次循環 i--
for (i = len - 1; i >= 0; i--) {
int temp = array[0];
array[0] = array[i];
array[i] = temp;
adjustMaxHeap(array, 0, i);
}
System.out.println(Arrays.toString(array));
}
private static void adjustMaxHeap(int[] a, int pos, int len) {
int temp;
int child;
for (temp = a[pos]; 2 * pos + 1 < len; pos = child) {
// 數組從0開始,r(i)>=r(2i) r(i)>=r(2i+1) 對應 pos => 2 * pos + 1 和 2 * pos +2
child = 2 * pos + 1;
// 有右孩子,且右孩子數值更大
if (child + 1 < len && a[child] < a[child + 1]) {
child++;
}
// 最大的孩子大於根節點
if (a[child] > temp) {
a[pos] = a[child];
} else {
break;
}
}
a[pos] = temp;
}
3.時間複雜度和空間複雜度
時間複雜度:建堆:o(n),每次調整o(log n),故最好、最壞、平均情況下:o(n*logn);
五、冒泡排序
打個小guang告,搜索拼duoduo店鋪: Boush雜貨鋪
物美價廉,你值得擁有
1.基本思路
一次冒泡將序列中從頭到尾所有元素兩兩比較,將最大的放在最後面。
將剩餘序列中所有元素再次兩兩比較,將最大的放在最後面。
重複第二步,直到只剩下一個數。
2.代碼實現
/**
* @author fupeng
* 冒泡排序優化第二版
* 第一版優化增加flag標記,沒有數字交換直接return,最優時間複雜度O(n)
* 第二版優化,增加tempPostion記錄內循環最後一次交換的位置,來縮減內循環的次數
*/
public static void bubbleSort(int[] array) {
int len = array.length - 1;
int temp; // 開闢一個臨時空間, 存放交換的中間值
int tempPostion = 0; // 記錄最後一次交換的位置
// 要遍歷的次數
for (int i = 0; i < array.length - 1; i++) {
int flag = 1; // 設置一個標誌位
// 依次的比較相鄰兩個數的大小,遍歷一次後,把數組中第i小的數放在第i個位置上
for (int j = 0; j < len; j++) {
// 比較相鄰的元素,如果前面的數大於後面的數,交換
if (array[j] > array[j + 1]) {
temp = array[j + 1];
array[j + 1] = array[j];
array[j] = temp;
flag = 0; // 發生交換,標誌位置0
tempPostion = j; // 記錄交換的位置
}
}
len = tempPostion; // 把最後一次交換的位置給len,來縮減內循環的次數
if (flag == 1) {// 如果沒有交換過元素,則已經有序
System.out.println(Arrays.toString(array));
return;
}
}
System.out.println(Arrays.toString(array));
}
3.時間複雜度和空間複雜度
冒泡排序的最好時間複雜度爲O(n),最壞時間複雜度爲O(n²),平均時間複雜度爲O(n²),空間複雜度爲O(1),它是一種穩定的排序算法。
六、快速排序
1.基本思路
快速排序使用分治策略來把一個序列(list)分爲兩個子序列(sub-lists)。步驟爲:
- 1.從數列中挑出一個元素,稱爲"基準"(pivot)。
- 2.重新排序數列,所有比基準值小的元素擺放在基準前面,所有比基準值大的元素擺在基準後面(相同的數可以到任一邊)。在這個分區結束之後,該基準就處於數列的中間位置。這個稱爲分區(partition)操作。
- 3.遞歸地(recursively)把小於基準值元素的子數列和大於基準值元素的子數列排序。
遞歸到最底部時,數列的大小是零或一,也就是已經排序好了。這個算法一定會結束,因爲在每次的迭代(iteration)中,它至少會把一個元素擺到它最後的位置去。
2.代碼實現
public static void quickSort(int[] array) {
sort(array, 0, array.length - 1);
System.out.println(Arrays.toString(array));
}
private static void sort(int[] a, int low, int high) {
int i = low;
int j = high;
if (a.length <= 1) {
return;
}
if (i >= j) {
return;
}
int index = a[i];
while (i < j) {
while (i < j && a[j] >= index)
j--;
if (a[j] < index)
a[i++] = a[j];
while (i < j && a[i] <= index)
i++;
if (a[i] > index)
a[j--] = a[i];
}
a[i] = index;
sort(a, low, i - 1);
sort(a, i + 1, high);
}
3.時間複雜度和空間複雜度
雖然 快排的時間複雜度達到了 O(n²),但是在大多數情況下都比平均時間複雜度爲 O(n logn) 的排序算法表現要更好。
七、歸併排序
1.基本思路
歸併排序(MERGE-SORT)是利用歸併的思想實現的排序方法,該算法採用經典的分治(divide-and-conquer)策略(分治法將問題分(divide)成一些小的問題然後遞歸求解,而治(conquer)的階段則將分的階段得到的各答案"修補"在一起,即分而治之)。
- 1.分而治之
可以看到這種結構很像一棵完全二叉樹,本文的歸併排序我們採用遞歸去實現(也可採用迭代的方式去實現)。分階段可以理解爲就是遞歸拆分子序列的過程,遞歸深度爲log2n。
- 2.合併相鄰有序子序列
再來看看治階段,我們需要將兩個已經有序的子序列合併成一個有序序列,比如上圖中的最後一次合併,要將[4,5,7,8]和[1,2,3,6]兩個已經有序的子序列,合併爲最終序列[1,2,3,4,5,6,7,8],來看下實現步驟。
2.代碼實現
public static void mergeSort(int[] array) {
int[] temp = new int[array.length];// 在排序前,先建好一個長度等於原數組長度的臨時數組,避免遞歸中頻繁開闢空間
mergeSort(array, 0, array.length-1, temp);
System.out.println(Arrays.toString(array));
}
private static void mergeSort(int[] arr, int left, int right, int []temp) {
if(left < right) {
int mid = (left+right) / 2;
mergeSort(arr, left, mid, temp);// 左邊歸併排序,使得左子序列有序
mergeSort(arr, mid+1, right, temp);// 右邊歸併排序,使得右子序列有序
merge(arr, left, mid, right, temp);// 將兩個有序子數組合並操作
}
}
private static void merge(int[] arr, int left, int mid, int right, int[] temp) {
int i = left;// 左序列指針
int j = mid+1;// 右序列指針
int t = 0;// 臨時數組指針
while (i <= mid && j <= right) {
if(arr[i] <= arr[j]) {
temp[t++] = arr[i++];
} else {
temp[t++] = arr[j++];
}
}
while(i <= mid) {// 將左邊剩餘元素填充進temp中
temp[t++] = arr[i++];
}
while(j <= right) {// 將右序列剩餘元素填充進temp中
temp[t++] = arr[j++];
}
t = 0;
// 將temp中的元素全部拷貝到原數組中
while(left <= right) {
arr[left++] = temp[t++];
}
}
3.時間複雜度和空間複雜度
歸併排序是穩定排序,它也是一種十分高效的排序,能利用完全二叉樹特性的排序一般性能都不會太差。java中Arrays.sort()採用了一種名爲TimSort的排序算法,就是歸併排序的優化版本。從上文的圖中可看出,每次合併操作的平均時間複雜度爲O(n),而完全二叉樹的深度爲|log2n|。總的平均時間複雜度爲O(nlogn)。而且,歸併排序的最好,最壞,平均時間複雜度均爲O(nlogn)。
八、基數排序
1.基本思路
- 1.基數排序的思想就是先排好各位,然後排好各位的基礎上排十位,以此類推,直到遍歷最高位 次,排序結束(仔細理解最後一句話)
- 2.基數排序不是比較排序,而是通過分配和收集的過程來實現排序
- 3.初始化10個桶(固定的),桶下標爲0-9
- 4.通過得到待排序數字的個十百等位的數字,把這個數字對應的item放到對應的桶中
- 5.基數排序有兩種排序方式:LSD和MSD,最小位優先(從右邊開始)和最大位優先(從左邊開始)
2.代碼實現
public static void radixSort(int[] array) {
ArrayList<ArrayList<Integer>> queue = new ArrayList<>();
for (int i = 0; i <10 ; i++) {
queue.add(new ArrayList<>());// 創建一個基數從0---9 每個數字上都是一個list
}
// 找到最大值,並判斷最大值是幾位數
int max = array[0];
for (int i = 1; i < array.length; i++) {
if (max < array[i]) {
max = array[i];
}
}
int time = 0;
while (max > 0) {
max /= 10;
time++;
}
for (int i = 0; i < time; 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<Integer> queue3 = queue.get(x);
queue3.add(array[j]);
queue.set(x, queue3);
}
int count = 0;
for (int k = 0; k < 10; k++) {
while (queue.get(k).size() > 0) {
ArrayList<Integer> queue4 = queue.get(k);
array[count] = queue4.get(0);
queue4.remove(0);
count++;
}
}
}
}
3.時間複雜度和空間複雜度
歸併排序是穩定排序,它也是一種十分高效的排序,能利用完全二叉樹特性的排序一般性能都不會太差。java中Arrays.sort()採用了一種名爲TimSort的排序算法,就是歸併排序的優化版本。從上文的圖中可看出,每次合併操作的平均時間複雜度爲O(n),而完全二叉樹的深度爲|log2n|。總的平均時間複雜度爲O(nlogn)。而且,歸併排序的最好,最壞,平均時間複雜度均爲O(nlogn)。
總結
引用:
https://www.cnblogs.com/mensan/p/10570050.html
https://www.cnblogs.com/jyroy/p/11248691.html
https://www.cnblogs.com/chengxiao/p/6194356.html
https://www.jianshu.com/p/8340dfaea3af