1. 程式人生 > >算法系列(四)排序演算法中篇--歸併排序和快速排序

算法系列(四)排序演算法中篇--歸併排序和快速排序

算法系列(三)排序演算法上篇 一文中,介紹了氣泡排序,插入排序和選擇排序演算法。這篇文章繼續講解排序演算法。

概述

氣泡排序,插入排序和選擇排序演算法這些演算法的時間複雜度都是O(N^2),是否有更高效的排序演算法呢?當然有了,堆排序,歸併排序,快速排序,它們的時間複雜度都是O(nlogn)。堆排序使用了樹結構,到目我們前還沒有介紹樹相關的演算法,這裡先分析歸併排序跟快速排序。

歸併排序

基本原理

歸併排序使用了一個被稱為分治法的通用模式,在分治法中,我們將問題分解為類似於原子問題的問題,遞迴的求解這些子問題,然後再合併這些自問題的解來得出原問題的解。 分治法的一般步驟 1、分解:把一個問題分解為多個子問題,這些子問題是更小例項上的原問題。
2、解決:遞迴求解自問題。當子問題足夠小時,按照基礎情況求解。 3、合併:把自問題的解合併為原問題的解。 歸併操作的工作原理如下: 第一步:申請空間,使其大小為兩個已經排序序列之和,該空間用來存放合併後的序列
第二步:設定兩個指標,最初位置分別為兩個已經排序序列的起始位置
第三步:比較兩個指標所指向的元素,選擇相對小的元素放入到合併空間,並移動指標到下一位置
重複步驟3直到某一指標超出序列尾
將另一序列剩下的所有元素直接複製到合併序列尾

具體過程

對於歸併排序的實現,我們看一下《演算法基礎》中給的一個歸併的例子

程式碼實現

(比較容易理解,但是寫的不是很好,合併方法可以優化)

package com.algorithm.sort;

/**
 * 歸併排序
 * 
 * @author chao
 *
 */
public class MergeSort {
	/**
	 * 歸併排序
	 * 
	 * @param num
	 * @param start
	 * @param end
	 */
	public static void sort(int num[], int start, int end) {
		if (start >= end) {
			return;
		}
		int mid = (start + end) / 2;
		sort(num, start, mid);
		sort(num, mid + 1, end);
		merge(num, start, mid, end);

	}

	/**
	 * num[start]到num[mid]是有序的,num[mid+1]到num[end]是有序的,
	 * 重新合併陣列,使陣列num[start]到num[end]有序
	 * 
	 * @param num
	 * @param start
	 * @param mid
	 * @param end
	 */
	public static void merge(int[] num, int start, int mid, int end) {
		int[] num1 = new int[mid - start + 1];
		int[] num2 = new int[end - mid];
		int i, j, k;
		for (i = start; i <= end; i++) {
			if (i - start < num1.length) {
				num1[i - start] = num[i];
			} else {
				num2[i - start - num1.length] = num[i];
			}
		}
		i = j = k = 0;
		while (i < num1.length && j < num2.length) {
			if (num1[i] <= num2[j]) {
				num[start + k++] = num1[i++];
			} else {
				num[start + k++] = num2[j++];
			}
		}

		while (i < num1.length) {
			num[start + k++] = num1[i++];
		}
		while (j < num2.length) {
			num[start + k++] = num2[j++];
		}
	}

	public static void main(String[] args) {
		int[] num = { 1, 5, 3, 2 };
		sort(num, 0, num.length - 1);
		for (int i = 0; i < num.length; i++)
			System.out.print(num[i] + " ");
	}
}

時間複雜度

最好最的時間複雜度都是O(nlogn)
儘管歸併排序效率很高,但是還是有一些缺點。歸併排序並不是原址的,它必須將整個陣列進行完全拷貝,如果空間非常寶貴,不適合使用歸併排序。

快速排序

基本原理

和歸併排序一樣,快速排序也使用分治模式 假設要排序的陣列是A[0]……A[N-1],首先任意選取一個數據(通常選用第一個資料)作為關鍵資料,然後將所有比它的數都放到它前面,所有比它大的數都放到它後面,這個過程稱為一躺快速排序。一躺快速排序的演算法是:
1、設定兩個變數I、J,排序開始的時候I:=1,J:=N-1;
2、以第一個陣列元素作為關鍵資料,賦值給X,即X=A[1];
3、從J開始向前搜尋,即由後開始向前搜尋(J=J-1),找到第一個小於X的值,兩者交換;
4、從I開始向後搜尋,即由前開始向後搜尋(I=I+1),找到第一個大於X的值,兩者交換;
5、重複第3、4步,直到I=J;

具體過程
待排序的陣列A的值分別是:(初始關鍵資料X=49)
                   A[0]     A[1]     A[2]     A[3]     A[4]      A[5]     A[6]:
                     49        38       65       97       76       13        27
進行第一次交換後:   27        38       65       97       76       13        49
                   ( 按照演算法的第三步從後面開始找)
進行第二次交換後:   27        38       49       97       76       13        65
                  ( 按照演算法的第四步從前面開始找>X的值,65>49,兩者交換,此時I:=3 )
進行第三次交換後:   27        38       13       97       76       49        65
( 按照演算法的第五步將又一次執行演算法的第三步從後開始找)
進行第四次交換後:   27        38       13       49       76       97        65
( 按照演算法的第四步從前面開始找大於X的值,97>49,兩者交換,此時J=4 )
此時再執行第三不的時候就發現I=J,從而結束一躺快速排序,那麼經過一躺快速排序之後的結果是:
27        38       13       49       76       97        65,
即所有大於49的數全部在49的後面,所有小於49的數全部在49的前面。

程式碼實現

package com.algorithm.sort;

/**
 * 快速排序
 * 
 * @author chao
 *
 */
public class QuickSort {
	/**
	 * 快速排序
	 * 
	 * @param num
	 * @param left
	 * @param right
	 */
	public static void sort(int[] num, int left, int right) {
		if (left < right) {
			int dp = partition(num, left, right);
			sort(num, left, dp - 1);
			sort(num, dp + 1, right);
		}
	}

	/**
	 * 資料分組
	 * 
	 * @param num
	 * @param left
	 * @param right
	 */
	public static int partition(int[] num, int left, int right) {
		int pivot = num[left];
		while (left < right) {
			while (left < right && num[right] >= pivot)
				right--;
			if (left < right)
				num[left++] = num[right];
			while (left < right && num[left] <= pivot)
				left++;
			if (left < right)
				num[right--] = num[left];
		}
		num[left] = pivot;
		return left;
	}

	public static void main(String[] args) {
		int[] num = { 1, 5, 3, 2 };
		sort(num, 0, num.length - 1);
		for (int i = 0; i < num.length; i++)
			System.out.print(num[i] + " ");

	}

}

複雜度分析

最好的情況是樞紐元選取得當,每次都能均勻的劃分序列。 時間複雜度O(nlogn)
最壞情況是樞紐元為最大或者最小數字,那麼所有數都劃分到一個序列去了 時間複雜度為O(n^2)

演算法實現程式碼github地址為我的github

後續會不斷補充,有些地方寫的可能有問題,請多指教。

歡迎掃描二維碼,關注公眾賬號


相關推薦

系列排序演算法中篇--歸併排序快速排序

在算法系列(三)排序演算法上篇 一文中,介紹了氣泡排序,插入排序和選擇排序演算法。這篇文章繼續講解排序演算法。 概述 氣泡排序,插入排序和選擇排序演算法這些演算法的時間複雜度都是O(N^2),是否有更

系列查詢演算法--基本查詢二分查詢

在 算法系列(一)基本概念 一文中,簡單介紹了演算法基本概念,演算法複雜度評估,常用演算法證明方式。這篇文章介紹一下查詢演算法,主要是二分查詢演算法。 從n個元素中A0,A1....An-1中,找到要

系列插入排序的兩種改進:規避邊界檢測取消交換Java實現

前言:演算法第四版習題2.1.24插入排序的哨兵和習題2.1.25不需要交換的插入排序 規避邊界檢測: 在插入排序的實現中先找到最小的元素並將其置於陣列的第一個位置,可以省掉內迴圈的判斷條件 j>0 。能夠省略判斷條件的元素稱為哨兵。 public class Ex

C#系列6——歸併排序

       本文主要描述了歸併排序的兩種實現方式,遞迴方式和非遞迴方式,以及二者的優缺點比較。下面首先介紹一下歸併排序的原理。 一、理解歸併排序        歸併排序的本質:通過兩兩合併排序再合併,最終獲得了一個有序的陣列。通過在紙上演示,就會發現它

導論 1

-1 int 計數 track clas -a spa namespace ++ #include <iostream> using namespace std; int main() { int a[5]; int b[5];

Andrew Ng機器學習筆記+Weka相關實現SVM原始對偶問題

優化問題 坐標 出了 變量 addclass fun ber 找到 線性 這篇博客主要解說了Ng的課第六、七個視頻,涉及到的內容包含,函數間隔和幾何間隔、最優間隔分類器 ( Optimal Margin Classifier)、原始/對偶問題 ( Pr

學習

ket ets amp 出棧 使用 append {} elif 算法 1.Bit Count(位計算) 說明:計算機中的所有值都以二進制系統表示。在這個簡單的任務中,您要編寫一個程序,該程序計算給定值中的非零位數。我們使用的是32位整數值,所以應該從0到32個非零位。

隊列

很多 棧和隊列 雙端 指針 size 實現 font 操作 鏈表 (一)棧和隊列的基本性質 棧是先進後出的 隊列是先進先出的 棧和隊列在實現結構上可以有數組和鏈表兩種形式 數組結構實現較容易 用鏈表結構較復雜,因為牽扯很多指針操作 (二)隊列和棧的基本操作 po

C#系列2——線索二叉樹

       首先在這裡宣告一下,本篇部落格參考另外一位大神的部落格,部落格連結如下:http://blog.csdn.net/UncleMing5371/article/details/54176252。由於寫的很好理解,所以就拿來借鑑一下,主要目的也是出於學

機器學習十大系列——邏輯迴歸

  本系列博文整理了常見的機器學習演算法,大部分資料問題都可以通過它們解決: 1.線性迴歸 (Linear Regression) 2.邏輯迴歸 (Logistic Regression) 3.決策樹 (Decision Tree) 4.支援向量機(SV

每天學習一系列4 (輸入一個整形陣列,數組裡有正數也有負數,陣列中連續的一個或多個整陣列成一個子陣列,每個子陣列都有一個)

題目: 輸入一個整形陣列,數組裡有正數也有負數,陣列中連續的一個或多個整陣列成一個子陣列,每個子陣列都有一個和。 求所有子陣列的和的最大值。要求時間複雜度為O(n)。 例如輸入的陣列為1, -2, 3, 10, -4, 7, 2, -5,和最大的子陣列為3, 10, -4,

測試開發人員需瞭解的基礎系列

背景介紹 演算法 這個近兩年由於大資料人工智慧的興起而被提及最多的關鍵詞之一,從而在IT這個猿類圈中有了基因變異-演算法工程師,被其他老猿類羨慕嫉妒恨,其實我覺得也沒必要去羨慕,做好我們自己在本職的崗位做到優秀也不會比別人差的,有的測試猿說測試不需要了解或者研

資料結構與系列陣列實現

## 資料結構與算法系列(一)陣列實現 注:`這是一個新的系列,主要是由於資料結構與演算法是程式設計師以後立身的根本,我以前在大學也學過,但是很快就忘記了,現在想把它撿起來,通過寫一個系列文章,加深自己的理解,其實我寫這個系列主要是想先通過預熱,然後去刷leetcode。刷演算法本身是想鍛鍊自己寫程式的思維,

C#資料結構與系列:逆波蘭計算器——逆波蘭表示式字尾表示式

1.介紹 字尾表示式又稱逆波蘭表示式,與字首表示式相似,只是運算子位於運算元之後 2.舉例說明 (3+4)*5-6對應的字尾表示式就是3 4 +5 * 6 - 3.示例 輸入一個逆波蘭表示式(字尾表示式),使用棧(Stack),計算其結果 思路分析: 從左至右掃描表示式,遇到數字時,將數字壓入堆疊,遇到運算

前端程式設計師學好系列陣列

前端程式設計師怎麼才能學好演算法呢?目前演算法優秀的視訊集中在c++,java,python,本人通過幾個月專心看c++的視訊掌握了演算法的基本思路,都翻譯成前端程式碼一一寫出來,從真題到思維全面提升演算法思維面對演算法面試,不畏懼 二分查詢法O(logn)尋找陣列中的最大/最小值O(N)歸併排序演算法 O(

學習插入排序

-1 color pri 最小 inpu range col pre arr 1.Maximum of array 說明:給定一組數組,進行排序,得到最大值和最小值。 1 input data: 2 1 3 5 7 9 11 ... 295 297 299 300 298

八大排序總結1

n-1 冒泡排序 int 排序算法 length != 位置 倒數 選擇 冒泡排序: 第一輪:從下標0到n-1(n 是數組長度),如果前一個元素比後一個元素大,那麽,相鄰的兩兩交換,最後數組中最大的元素放在最後一個位置上。 第二輪:從標0到n-2,重復上過程,這樣第二大的元

筆記c++--桶排序題目

ostream namespace out 數字 str pre div 排序 code               算法筆記(c++)--桶排序 記得題目是排序,輸入n個1-1000的數字然後去重然後排序。 桶排序沒毛病 #include<iostream>

筆記:計數排序基數排序

性能 相同 ngs 余數 得出 其他 大牛 .com 針對 (一)說明 這裏我是按自己的理解去實現的,時間復雜度和空間復雜度和算法導論上的可能不一樣,感興趣的話參考下就行,感覺最重要的還是算法思想。根據算法性能去實現算法以後再研究。 (二)計數排序

牛頓與擬牛頓學習筆記BFGS 演算法

機器學習演算法中經常碰到非線性優化問題,如 Sparse Filtering 演算法,其主要工作在於求解一個非線性極小化問題。在具體實現中,大多呼叫的是成熟的軟體包做支撐,其中最常用的