1. 程式人生 > 程式設計 >分治演演算法-歸併,快排,快速選擇

分治演演算法-歸併,快排,快速選擇

前言

大家好,感謝大家對前兩篇部落格的支援。今天我準備提供歸併,快排,快速選擇的筆記,這三個是分治演演算法的典型例子。這次有利用疊縮證明演演算法的時間界限哦!,另外程式碼已經放到碼雲上啦。

分治演演算法

分治演演算法的基本思想是將一個規模為N的問題分解為K個規模較小的子問題,這些子問題相互獨立且與原問題性質相同。求出子問題的解,就可得到原問題的解。即一種分目標完成程式演演算法,簡單問題可用二分法完成。
歸併,快排,快速選擇是分治思想的三個典型例子。

Java中對應的演演算法

一般排序使用Java提供的歸併演演算法,即Collections.sort(..)。快速選擇一般用於解決TopN問題, 下面分別介紹三個演演算法。

1歸併排序

簡介

歸併排序(MERGE-SORT)是建立在歸併操作上的一種有效的排序演演算法。

演演算法介紹

該演演算法的基本操作是合併兩個已排序的表,下面舉例說明合併過程:

輸入圖片說明
在這裡輸入圖片標題

演演算法描述詳細描述:

  • 如果N=1,只有一個元素,直接返回
  • 否則,前半部分和後半部分各自進行歸併排序,得到排序過的兩部分,之後按照上面的演演算法進行合併。

核心程式碼示例

private static <AnyType extends Comparable<? super AnyType>> 
	void mergeSort(AnyType[] a,AnyType[] tmpArray,int left,int right) {
 if
(left < right) { int center = (left + right) / 2; // 如下方式排除了只有3個元素的情況,只有三個元素是 center=1 mergeSort(a,tmpArray,left,center); mergeSort(a,center + 1,right); merge(a,right); } } /** * 合併函式,歸併排序的基本步驟 * @param a 原始資料陣列 * @param tmpArray 歸併使用的第三個臨時陣列 * @param leftPos 左邊部分開始,對應圖上Actr初始位置 * @param rightPos 右邊開始 ,對應Bctr初始位置 * @param rightEnd 右邊結束 ,對應Bctr結束位置 */ private static <AnyType extends Comparable<? super AnyType>> void merge(AnyType[] a,int leftPos,int rightPos,int rightEnd) { // 一定範圍內合併 //左邊結束,對應圖上Actr結束位置 int leftEnd = rightPos - 1; int tmpPos = leftPos; //本次合併總共包含的元素數量 int numElements = rightEnd - leftPos + 1; //進行歸併 while
(leftPos <= leftEnd && rightPos <= rightEnd) { if (a[leftPos].compareTo(a[rightPos]) <= 0) { tmpArray[tmpPos++] = a[leftPos++]; } else { tmpArray[tmpPos++] = a[rightPos++]; } } while (leftPos <= leftEnd) { tmpArray[tmpPos++] = a[leftPos++]; } while (rightPos <= rightEnd) { tmpArray[tmpPos++] = a[rightPos++]; } // 將排序過的資料拷貝會原始陣列,【只有rightEnd沒有變化】。 for (int i = 0; i < numElements; i++,rightEnd--) { a[rightEnd] = tmpArray[rightEnd]; } } 複製程式碼

分析

根據前面的描述可以得出如下通項公式:

歸併排序通項公式
歸併排序通項公式

使用疊縮求和,進行推導,兩邊除以N

歸併排序疊縮求和
歸併排序疊縮求和

之後進行求和兩邊減去公項後結果為:
歸併排序推導

之後兩邊同乘以N,得到時間界:

歸併排序時間界
歸併排序時間界

2快速排序

簡介

快速排序(Quicksort) 的基本思想是:通過一趟排序將要排序的資料分割成獨立的兩部分,其中一部分的所有資料都比另外一部分的所有資料都要小,然後再按此方法對這兩部分資料分別進行快速排序,整個排序過程可以遞迴進行,以此達到整個資料變成有序序列

演演算法介紹

  • 簡單4步
  1. 如果S中元素個數為0或1,則返回,或者S中元素小於CUTOFF,則將S排序
  2. 以三數中值取樞元v
  3. 將S-{v}劃分為兩個不相交集合,並確定樞元v的位置:
    快排劃分說明
    快排劃分說明
  4. 按如下順序分治其餘部分處理
    分治其餘部分
    分治其餘部分
  • 舉例說明
    • 三數中值取樞元
      即左端,右端和中心的中值作為樞元,(實際能夠減少14%的比較次數). 將最小值放到左端, 最大值放到右端, 中值放到次右端, 原中值處放次右端值。
      三數中值取樞元
      三數中值取樞元
    • 分割示意
      分割示意
      分割示意

核心程式碼示意

/**
* 三數中值分割法
* @param a 原始陣列
* @param left 左邊界
* @param right 右邊界
* @return 返回樞元
*/
private static <AnyType extends Comparable<? super AnyType>> 
	AnyType median3(AnyType[] a,int right) {
	int center=(left+right)/2;
	if(a[center].compareTo(a[left])<0){
		swapReferences(a,center);
	}
	if(a[right].compareTo(a[left])<0){
		swapReferences(a,right);
	}
	if(a[right].compareTo(a[center])<0){
		swapReferences(a,center,right);
	}
		swapReferences(a,right-1);
	return a[right-1];
}

/**
 * 快排核心
 * @param a 原始陣列
 * @param left 左邊界
 * @param right 右邊界
 */
private static <AnyType extends Comparable<? super AnyType>>
  void quickSort(AnyType[] a,int right) {
	if(left+CUTOFF<=right){
		//三數中值分割法,取樞元
		AnyType pivot=median3(a,right);
		int i=left;
		int j=right-1;
		while(true){
			//i找大於樞元的元素
			while(a[++i].compareTo(pivot)<0);
			//j找小於樞元的元素
			while(a[--j].compareTo(pivot)>0);
			if(i<j){
				swapReferences(a,i,j);
			}else{//i>j,此輪分割結束
				break;
			}
		}
	//交換i,與樞元
	swapReferences(a,right-1);
	//分治進行,快排
	quickSort(a,i-1);
	quickSort(a,i+1,right);
	}else{
		insertSort(a,right);
	}
}
複製程式碼

時間複雜度

  • 最壞情況

最壞情況
最壞情況

  • 平均情況和最好情況

平均情況和最好情況
平均情況和最好情況

  • 平均情況分析

假設S1,每個大小都是等可能的。
由於該假設,可知

平均分析1
平均分析1


平均分析2
平均分析2

的平均值為

平均分析3
在這裡輸入圖片標題

可以得到通項為:
通項
通項

兩邊乘以N得到式子1如下:
通項兩邊*N
通項兩邊*N

由N通項得出N-1通項如下為式子2:
N-1通項
N-1通項

式子1-式子2,得到如下:

式子1-式子2
式子1-式子2

除去無關係-c,進行疊縮:
快排時間界
在這裡輸入圖片標題

進行求和:

快排疊縮求和
快排疊縮求和

該和大概為O(logN),於是得到平均時間界限:
快排時間界
快排時間界

3快速選擇

問題描述

亂序集合中找到第K個最小元。

演演算法介紹

依照快排思路進行處理:

  1. 如果S=1,K=1將S中元素直接返回,若

快速選擇圖1
在這裡輸入圖片標題

則將S排序返回第k個最小元。 2. 以三數中值取樞元v 3. 將S-{v}劃分為兩個不相交集合,並確定樞元v的位置:

快速選擇兩個不相交集
快速選擇兩個不相交集

4. 如果
輸入圖片說明
在這裡輸入圖片標題

第K個元素在
輸入圖片說明
在這裡輸入圖片標題

中,返回
輸入圖片說明
在這裡輸入圖片標題

否則返回:
輸入圖片說明
在這裡輸入圖片標題

程式碼

/**
* 快速選擇核心
* @param a 原始陣列
* @param left 左邊界
* @param right 右邊界
* @param k  需要選擇的位
*/
private static<AnyType extends Comparable<? super AnyType>> void 
quickSelect(AnyType[] a,int right,int k) {	
if(left+CUTOFF<=right){
	AnyType pivot=median3(a,right);
	int i=left;
	int j=right-1;
	while(true){
		while(a[++i].compareTo(pivot)<0);
		while(a[--j].compareTo(pivot)>0);
		if(i<j){
			swapReferences(a,j);
		}else{
			break;
		}
	}
	swapReferences(a,right-1);
	if(k<=i){
		quickSelect(a,i-1,k);
	}else if(k>i+1){
		quickSelect(a,right,k);
	}
}else{
 insertSort(a,right);
 }
}
複製程式碼

總結

  1. 快排和歸併排序演演算法的平均時間界限是NlogN,Java預設使用歸併演演算法。
  2. 快速選擇是TopN演演算法的經典解法,但是本人工程中用到較少

完整程式碼地址:

碼雲: 歸併&快排: 點選檢視 快速選擇 : 點選檢視
github: 歸併&快排 快速選擇