【排序算法總結】內排序白話詳解

原理

在這裏插入圖片描述
  每次從最上面的一個元素開始,向下兩兩比較,不符合順序的使之互換位置(注意冒泡排序是逐層篩選,而不是將最小的帶到最末端),直到沒有元素再需要交換,排序完成。

與選擇排序區別:

  冒泡排序通過依次交換相鄰兩個順序不合法的元素位置,從而將當前最小(大)元素放到合適的位置;

  而選擇排序每遍歷一次都記住了當前最小(大)元素的位置,最後僅需一次交換操作即可將其放到合適的位置。

冒泡排序算法的運作如下:

在這裏插入圖片描述
  1.比較相鄰的元素,如果前一個比後一個大,就把它們兩個調換位置。

  2.對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最後一對。這步做完後,最後的元素會是最大的數。

  3.針對所有的元素重複以上的步驟,除了以及排好序的元素。

  4.持續每次對越來越少的元素重複上面的步驟,直到沒有任何一對數字需要比較。

僞代碼描述

在這裏插入圖片描述

代碼

void Swap(int A[], int i, int j)
{
	int temp = A[i];
	A[i] = A[j];
	A[j] = temp;
}
void BubbleSort(int A[], int n)
{
	int flag;
	for (int i = arr.length - 1; i > 0; i--) // 每次最大元素就像氣泡一樣"浮"到數組的最後
	{
		flag=0;
		for (int j = 0; j < i; j++) // 依次比較相鄰的兩個元素,使較大的那個向後移
		{
			if (A[i] > A[i + 1])// 如果條件改成A[i] >= A[i + 1],則變爲不穩定的排序算法
			{
				Swap(A, i, i + 1);
				flag=1;
			}
		}
		if(!flage)break;
}
}

時間複雜度

在這裏插入圖片描述

工作原理:

在這裏插入圖片描述
  1.初始時在序列中找到最小(大)元素,放到序列的起始位置作爲已排序序列;

  2.然後,再從剩餘未排序元素中繼續尋找最小(大)元素,放到已排序序列的末尾。

  3.以此類推,直到所有元素均排序完畢。

與冒泡排序區別:

  冒泡排序通過依次交換相鄰兩個順序不合法的元素位置,從而將當前最小(大)元素放到合適的位置;

  而選擇排序每遍歷一次都記住了當前最小(大)元素的位置,最後僅需一次交換操作即可將其放到合適的位置。

代碼

void Swap(int A[], int i, int j)
{
	int temp = A[i];
	A[i] = A[j];
	A[j] = temp;
}
void SelectionSort(int A[], int n)
{
	for (int i = 0; i < arr.length - 1; i++)         // i爲已排序序列的末尾
	{
		int min = i;// 循環查找最小值
		for (int j = i + 1; j< arr.length; j++) // 未排序序列
		{
			if (arr[min] > arr[j])// 找出未排序序列中的最小值
			{
				min = j;
			}
		}
		if (min != i)
		{
			Swap(A, min, i);// 放到已排序序列的末尾,該操作很有可能把穩定性打亂,所以選擇排序是不穩定的排序算法
		}
	}
}

穩定性

  用數組實現的選擇排序是不穩定的,用鏈表實現的選擇排序是穩定的。

  不過,一般提到排序算法時,大家往往會默認是數組實現,所以選擇排序是不穩定的。

  雞尾酒排序,也叫定向冒泡排序,是對冒泡排序的一種改進。

  此算法與冒泡排序的不同處在於從低到高然後從高到低,而冒泡排序則僅從低到高去比較序列裏的每個元素。

  他可以得到比冒泡排序稍微好一點的效能。

排序過程:

在這裏插入圖片描述
  1.先對數組從左到右進行冒泡排序(升序),則最大的元素去到最右端

  2.再對數組從右到左進行冒泡排序(降序),則最小的元素去到最左端

  3.以此類推,依次改變冒泡的方向,並不斷縮小未排序元素的範圍,直到最後一個元素結束

代碼~

void Swap(int A[], int i, int j)
{
	int temp = A[i];
	A[i] = A[j];
	A[j] = temp;
}
void cocktail_sort(int arr[], int len) {
	int left = 0;// 初始化邊界
	int right = n - 1;
	while (left < right) {
		for (i = left; i < right; i++) {
			if (arr[i] > arr[i + 1]) {
				Swap(A, i, i + 1);
			}
			right--;
		}
		for (i = right; i > left; i--) {
			if (arr[i - 1] > arr[i]) {
 				Swap(A, i - 1, i);
			}
			left++;
		}
	}
}

原理

在這裏插入圖片描述
  1.把待排序的數組分成已排序和未排序兩部分,初始的時候把第一個元素認爲是已排好序的。

  2.從第二個元素開始,在已排好序的子數組中尋找到該元素合適的位置並插入該位置。

  3.重複上述過程直到最後一個元素被插入有序子數組中。

僞代碼

在這裏插入圖片描述

代碼

public static void insertionSort(int[] arr){
	for (int i=1; i<arr.length; ++i){
		int value = arr[i];
		int position=i;
		while (position>0 && arr[position-1]>value){
			arr[position] = arr[position-1];
			position--;
		}
		arr[position] = value;
	}
}

穩定性

  由於只需要找到不大於當前數的位置而並不需要交換,因此,直接插入排序是穩定的排序方法。

複雜度

在這裏插入圖片描述

  對於插入排序,如果比較操作的代價比交換操作大的話,可以採用二分查找法來減少比較操作的次數,我們稱爲二分插入排序

算法描述:

  在直接插入排序的基礎上,利用二分(折半)查找算法決策出當前元素所要插入的位置。

二分查找:

  1.找到中間元素,如果中間元素比當前元素大,則當前元素要插入到中間元素的左側;

  2.否則,中間元素比當前元素小,則當前元素要插入到中間元素的右側。

  3.找到當前元素的插入位置 i 之後,把 i 和 high 之間的元素從後往前依次後移一個位置,然後再把當前元素放入位置 i。

代碼

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

希爾排序是基於插入排序的以下兩點性質而提出改進方法的:

  插入排序在對幾乎已經排好序的數據操作時,效率高,即可以達到線性排序的效率

  但插入排序一般來說是低效的,因爲插入排序每次只能將數據移動一位

  希爾排序通過將比較的全部元素分爲幾個區域來提升插入排序的性能

  可以讓一個元素可以一次性地朝最終位置前進一大步。

  然後算法再取越來越小的步長進行排序,算法的最後一步就是普通的插入排序,但是到了這步,需排序的數據幾乎是已排好的了(此時插入排序較快)。

原理

  希爾排序是將待排序的數組元素 按下標的一定增量分組 ,分成多個子序列,然後對各個子序列進行直接插入排序算法排序;

  然後依次縮減增量再進行排序,直到增量爲1時,進行最後一次直接插入排序,排序結束。

算法描述

在這裏插入圖片描述
  1.先將整個待排序的記錄序列分割成爲若干子序列分別進行直接插入排序,具體算法描述:

  2.選擇一個增量序列t1,t2,…,tk,其中ti>tj,tk=1;

  3.按增量序列個數k,對序列進行 k 趟排序;

  4.每趟排序,根據對應的增量ti,將待排序列分割成若干長度爲m 的子序列,分別對各子表進行直接插入排序。

注意:"tk間隔"排序後的有序的序列,在執行"tk+1間隔"排序後,"Dk間隔"排序後的結果依然是有序的

  5.僅當增量因子爲1 時,整個序列作爲一個表來處理,表長度即爲整個序列的長度。

僞代碼

在這裏插入圖片描述

代碼

Donald Shell增量

  增量元素不互質,則小增量可能根本不起作用

void shellSort(int [] arr){ 
	int temp; 
	for (int delta = arr.length/2;delta>=1;delta/=2){ //對每個增量進行一次排序
		for (int i=delta; i`<arr.length; i++){
			for (int j=i;j>=delta && arr[j]<arr[j-delta]; j-=delta){
			// 注意每個地方增量和差值都是delta
				temp=arr[j-delta];
				arr[j-delta]=arr[j];
				arr[j]=temp;
			}
		} //loop i
 	} //loop delta 
 }

其他增量

在這裏插入圖片描述

void ShellSort( ElementType A[], int N )
{ /* 希爾排序 - 用Sedgewick增量序列 */
     int Si, D, P, i;
     ElementType Tmp;
     /* 這裏只列出一小部分增量 */
     int Sedgewick[] = {929, 505, 209, 109, 41, 19, 5, 1, 0};
      
     for ( Si=0; Sedgewick[Si]>=N; Si++ ) 
         ; /* 初始的增量Sedgewick[Si]不能超過待排序列長度 */
 
     for ( D=Sedgewick[Si]; D>0; D=Sedgewick[++Si] )
         for ( P=D; P<N; P++ ) { /* 插入排序*/
             Tmp = A[P];
             for ( i=P; i>=D && A[i-D]>Tmp; i-=D )
                 A[i] = A[i-D];
             A[i] = Tmp;
         }
}

希爾排序是不穩定的排序算法。

  雖然一次插入排序是穩定的,不會改變相同元素的相對順序,但在不同的插入排序過程中,相同的元素可能在各自的插入排序中移動,最後其穩定性就會被打亂。

複雜度

  最差時間複雜度 ---- 根據步長序列的不同而不同。已知最好的爲O(n(logn)^2)

  最優時間複雜度 ---- O(n)

  堆排序(Heapsort)是指利用堆積樹(堆)這種數據結構所設計的一種排序算法,它是選擇排序的一種。

選擇排序每次掃描出一個最小元素,與指定位置的元素進行互換

  可以利用數組的特點快速定位指定索引的元素。

  堆排序就是把最大堆堆頂的最大數取出,將剩餘的堆繼續調整爲最大堆,再次將堆頂的最大數取出,這個過程持續到剩餘數只有一個時結束。

堆的概念

   堆是一種特殊的完全二叉樹(complete binary tree)。

  完全二叉樹的一個"優秀"的性質是,除了最底層之外,每一層都是滿的,這使得堆可以利用數組來表示(普通的一般的二叉樹通常用鏈表作爲基本容器表示),每一個結點對應數組中的一個元素。
在這裏插入圖片描述
  對於給定的某個結點的下標 i,可以很容易的計算出這個結點的父結點、孩子結點的下標:

  • Parent(i) = floor(i/2),i 的父節點下標

  • Left(i) = 2i,i 的左子節點下標

  • Right(i) = 2i + 1,i 的右子節點下標

最大堆:

在這裏插入圖片描述

最大堆中的最大元素值出現在根結點(堆頂)

堆中每個父節點的元素值都大於等於其孩子結點(如果存在)

最小堆:

在這裏插入圖片描述
  最小堆中的最小元素值出現在根結點(堆頂)

  堆中每個父節點的元素值都小於等於其孩子結點(如果存在)

堆排序原理

在這裏插入圖片描述
  堆排序就是把最大堆堆頂的最大數取出,將剩餘的堆繼續調整爲最大堆,再次將堆頂的最大數取出,這個過程持續到剩餘數只有一個時結束。

  在堆中定義以下幾種操作:

   最大堆調整(Max-Heapify):將堆的末端子節點作調整,使得子節點永遠小於父節點

   創建最大堆(Build-Max-Heap):將堆所有數據重新排序,使其成爲最大堆

   堆排序(Heap-Sort):移除位在第一個數據的根節點,並做最大堆調整的遞歸運算 繼續進行下面的討論前,需要注意的一個問題是:

數組都是 Zero-Based,這就意味着我們的堆數據結構模型要發生改變(堆中0位置存放哨兵元素)

在這裏插入圖片描述
相應的,幾個計算公式也要作出相應調整:

Parent(i) = floor((i-1)/2),i 的父節點下標

Left(i) = 2i + 1,i 的左子節點下標*Right(i) = 2(i + 1),i 的右子節點下標

堆的建立和維護

  (1)將N個元素按輸入順序存入,先滿足完全二義樹的結構特性

  (2)調整各結點位置,以滿足最大堆的有序特性

   選取第一個含有子節點的節點((N-1)/2)開始,將其與子元素比較調整位置,然後再選擇前一個結點重複操作,直到獲取到一個堆爲止

   需要兩個循環,第一個循環遍歷從尾結點的父節點到根節點,第二個循環調整第一個循環選中結點保證該節點下符合堆結構

算法描述

在這裏插入圖片描述

方法一

  我們需要一個升序的序列,可以建立一個最小堆,然後每次輸出根元素。

  但是,這個方法需要額外的空間(否則將造成大量的元素移動,其複雜度會飆升到O(n2) )。

方法二

  我們可以建立最大堆,然後我們倒着輸出,在最後一個位置輸出最大值,次末位置輸出次大值…

  由於每次輸出的最大元素會騰出第一個空間,因此,我們恰好可以放置這樣的元素而不需要額外空間。

每次將堆頂元素與堆尾元素互換,然後再次調整取出輸出元素的數組爲最大堆,重複操作

代碼

void Swap( ElementType *a, ElementType *b )
{
     ElementType t = *a; *a = *b; *b = t;
}
  
void PercDown( ElementType A[], int p, int N )
{ /* 改編代碼4.24的PercDown( MaxHeap H, int p )    */
  /* 將N個元素的數組中以A[p]爲根的子堆調整爲最大堆 */
    int Parent, Child;
    ElementType X;
 
    X = A[p]; /* 取出根結點存放的值 */
    for( Parent=p; (Parent*2+1)<N; Parent=Child ) {
        Child = Parent * 2 + 1;
        if( (Child!=N-1) && (A[Child]<A[Child+1]) )
            Child++;  /* Child指向左右子結點的較大者 */
        if( X >= A[Child] ) break; /* 找到了合適位置 */
        else  /* 下濾X */
            A[Parent] = A[Child];
    }
    A[Parent] = X;
}
 
void HeapSort( ElementType A[], int N ) 
{ /* 堆排序 */
     int i;
       
     for ( i=N/2-1; i>=0; i-- )/* 建立最大堆 */
         PercDown( A, i, N );
      
     for ( i=N-1; i>0; i-- ) {
         /* 刪除最大堆頂 */
         Swap( &A[0], &A[i] ); /* 見代碼7.1 */
         PercDown( A, 0, i );
     }
}

概念

  歸併排序是建立在歸併操作上的一種有效的排序算法。

   該算法是採用分治法的一個非常典型的應用。

   將已有序的子序列合併,得到完全有序的序列;

  即先使每個子序列有序,再使子序列段間有序。

  若將兩個有序表合併成一個有序表,稱爲2-路歸併。

使用前提

  有序子列的歸併

  • 假設有上個表A 、B、C,將有序表A、B插入到C中

  • 設置三個指針(並非必須爲指針,可以是任何可以指定位置的結構)Aptr、Bptr、Cptr分別指向A、B、C表的起始位置

  • 比較Aptr和Bptr將大的將其放置到Cptr的位置,然後將改變的+1

  • 代碼
    在這裏插入圖片描述

  • A爲待排序數組(A、B),TmpA爲排序結果臨時存放數組(C),L爲左邊起始位置(Aptr),R右邊的起始位置(Bptr),REnd(右邊數組的長度,左邊的可以計算)

算法思路

在這裏插入圖片描述
  歸併排序是將兩個或兩個以上的有序表合併成一個新的有序表

  將已有序的子序列合併,得到完全有序的序列;

即先使每個子序列有序,再使子序列段間有序。

適用場景

  歸併排序在數據量比較大的時候也有較爲出色的表現(效率上),但是,其空間複雜度O(n)使得在數據量特別大的時候(例如,1千萬數據)幾乎不可接受。

  而且,考慮到有的機器內存本身就比較小,因此,採用歸併排序一定要注意。

算法描述

遞歸法(Top-down)

分而治之

  分而治之是一種使用遞歸解決問題的算法,主要的技巧是將一個大的複雜的問題劃分爲多個子問題,而這些子問題可以作爲終止條件,或者在一個遞歸步驟中得到解決,所有子問題的解決結合起來就構成了對原問題的解決

原理如下(假設序列共有n個元素):

  申請空間,使其大小爲兩個已經排序序列之和,該空間用來存放合併後的序列

   設定兩個指針,最初位置分別爲兩個已經排序序列的起始位置

  比較兩個指針所指向的元素,選擇相對小的元素放入到合併空間,並移動指針到下一位置

  重複步驟3直到某一指針到達序列尾

  將另一序列剩下的所有元素直接複製到合併序列尾

僞碼描述

  生成臨時存放空間,並向遞歸算法傳入長度
在這裏插入圖片描述
  遞歸算法:每次分半歸併到臨時數組,然後將臨時數組寫回待排數組
在這裏插入圖片描述

迭代法(Bottom-up)

迭代

  將待排序列A進行不斷的歸併,歸併結果存放在A與臨時數組TmpA中(如果每次迭代都開闢臨時數組將會造成巨大的空間佔用),最後將排好序的結果返回在A中即可

原理如下(假設序列共有n個元素):
在這裏插入圖片描述
**  將序列每相鄰兩個數字進行歸併操作,形成ceil(n/2)個序列,排序後每個序列包含兩/一個元素
在這裏插入圖片描述
**  若此時序列數不是1個則將上述序列再次歸併,形成ceil(n/4)個序列,每個序列包含四/三個元素
在這裏插入圖片描述
**  重複步驟2,直到所有元素排序完畢,即序列數爲1
在這裏插入圖片描述
僞碼描述

總控算法:控制迭代算法的執行
在這裏插入圖片描述
迭代算法
在這裏插入圖片描述

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