快速排序的分析與優化
一、快速排序的介紹
快速排序是一種排序演算法,對包含n個數的輸入陣列,最壞的情況執行時間為Θ(n2)[Θ 讀作theta]。雖然這個最壞情況的執行時間比較差,但快速排序通常是用於排序的最佳的實用選擇。這是因為其平均情況下的效能相當好:期望的執行時間為 Θ(nlgn),且Θ(nlgn)記號中隱含的常數因子很小。另外,它還能夠進行就地排序,在虛擬記憶體環境中也能很好的工作。
和歸併排序一樣,快速排序也是基於分治法(Divide and conquer):
-
分解:陣列A[p..r]被劃分成兩個(可能為空)的子陣列A[p..q-1]和A[q+1..r],使得A[p..q-1]中的每個元素都小於等於A[q],A[q+1..r]中的每個元素都大於等於A[q]。這樣元素A[q]就位於其最終位置上了。
-
解決:通過遞迴呼叫快速排序,對子陣列A[p..q-1]和A[q+1..r]排序。
-
合併:因為兩個子陣列是就地排序,不需要合併,整個陣列已有序。
虛擬碼:
PARTITION(A, p, r) x = A[p] i = p for j=p+1 to r do if A[j] <= x then i = i+1 exchange(A[i],A[j]) exchange(A[p], A[i]) return i QUICKSORT(A, p, r) if p < r then q = PARTITION(A, p, r) QUICKSORT(A, p, q-1) QUICKSORT(A, q+1, r)
二、效能分析
1、最壞情況
快速排序的最壞情況發生在當陣列已經有序或者逆序排好的情況下。這樣的話劃分過程產生的兩個區域中有一個沒有元素,另一個包含n-1個元素。此時演算法的執行時間可以遞迴地表示為:T(n) = T(n-1)+T(0)+Θ(n)
,遞迴式的解為T(n)=Θ(n^2)
。可以看出,快速排序演算法最壞情況執行時間並不比插入排序的更好。
2、最好情況
如果我們足夠幸運,在每次劃分操作中做到最平衡的劃分,即將陣列劃分為n/2:n/2。此時得到的遞迴式為T(n) = 2T(n/2)+Θ(n)
,根據主定理的情況二可得T(n)=Θ(nlgn)
。
3、平均情況
假設一:快排中的劃分點非常偏斜,比如每次都將陣列劃分為1/10 : 9/10的兩個子區域,這種情況下執行時間是多少呢?執行時間遞迴式為T(n) = T(n/10)+T(9n/10)+Θ(n)
T(n)=Θ(nlgn)
。可以看出,當劃分點非常偏斜的時候,執行時間仍然是Θ(nlgn)。
假設二:Partition所產生的劃分既有“好的”,也有“差的”,它們交替出現。這種平均情況下執行時間又是多少呢?這時的遞迴式為(G表示Good,B表示Bad):
G(n) = 2B(n/2) + Θ(n)
B(n) = G(n-1) + Θ(n)
解:G(n) = 2(G(n/2-1) + Θ(n/2)) + Θ(n) = 2G(n/2-1) + Θ(n) = Θ(nlgn)
可以看出,當好、差劃分交替出現時,快排的執行時間就如全是好的劃分一樣,仍然是Θ(nlgn) 。
三、快排的優化
經過上面的分析可以知道,在輸入有序或逆序時快速排序很慢,在其餘情況則表現良好。如果輸入本身已被排序,那麼就糟了。那麼我們如何確保對於所有輸 入,它均能夠獲得較好的平均情況效能呢?前面的快速排序我們預設使用陣列中第一個元素作為主元。假設隨機選擇陣列中的元素作為主元,則快排的執行時間將不 依賴於輸入序列的順序。我們把隨機選擇主元的快速排序叫做Randomized Quicksort。
在隨機化的快速排序中,我們不是始終選擇第一個元素作為主元,而是從陣列A[p…r]中隨機選擇一個元素,然後將其與第一個元素交換。由於主元元素是隨機選擇的,我們期望在平均情況下,對輸入陣列的劃分能夠比較對稱。
虛擬碼:
RANDOMIZED-PARTITION(A, p, r)
i = RANDOM(p, r)
exchange(A[p], A[i])
return PARTITION(A, p, r)
RANDOMIZED-QUICKSORT(A, p, r)
if p < r
then q = RANDOMIZED-PARTITION(A, p, r)
RANDOMIZED-QUICKSORT(A, p, q-1)
RANDOMIZED-QUICKSORT(A, q+1, r)
我們對3萬個元素的有序序列分別進行傳統的快速排序 和 隨機化的快速排序,並比較它們的執行時間:
/*************************************************************************
> File Name: QuickSort.cpp
> Author: SongLee
> E-mail: [email protected]
> Created Time: 2014年06月21日 星期六 10時11分30秒
> Personal Blog: http://songlee24.github.com
************************************************************************/
#include<iostream>
#include<cstdlib> // srand rand
#include<ctime> // clock_t clock
using namespace std;
void swap(int &a, int &b)
{
int tmp = a;
a = b;
b = tmp;
}
// 傳統劃分操作
int Partition(int A[], int low, int high)
{
int pivot = A[low];
int i = low;
for(int j=low+1; j<=high; ++j)
{
if(A[j] <= pivot)
{
++i;
swap(A[i], A[j]);
}
}
swap(A[i], A[low]);
return i;
}
// 隨機化劃分操作,隨機選擇pivot
int Partition_Random(int A[], int low, int high)
{
srand(time(NULL));
int i = rand() % (high+1);
swap(A[low], A[i]);
return Partition(A, low, high);
}
// 傳統快排
void QuickSort(int A[], int low, int high)
{
if(low < high)
{
int pos = Partition(A, low, high);
QuickSort(A, low, pos-1);
QuickSort(A, pos+1, high);
}
}
// 隨機化快速排序
void QuickSort_Random(int A[], int low, int high)
{
if(low < high)
{
int pos = Partition_Random(A, low, high);
QuickSort_Random(A, low, pos-1);
QuickSort_Random(A, pos+1, high);
}
}
int main()
{
clock_t t1, t2;
// 初始化陣列
int A[30000];
for(int i=0; i<30000; ++i)
A[i] = i+1;
t1 = clock();
QuickSort(A, 0, 30000-1);
t1 = clock() - t1;
cout << "Traditional quicksort took "<< t1 << " clicks(about " << ((float)t1)/CLOCKS_PER_SEC << " seconds)." << endl;
t2 = clock();
QuickSort_Random(A, 0, 30000-1);
t2 = clock() - t2;
cout << "Randomized quicksort took "<< t2 << " clicks(about " << ((float)t2)/CLOCKS_PER_SEC << " seconds)." << endl;
return 0;
}
執行結果:
[[email protected] ~]$ ./QuickSort
Traditional quicksort took 1210309 clicks(about 1.21031 seconds).
Randomized quicksort took 457573 clicks(about 0.457573 seconds).
[[email protected] ~]$ ./QuickSort
Traditional quicksort took 1208038 clicks(about 1.20804 seconds).
Randomized quicksort took 644950 clicks(about 0.64495 seconds).
從執行結果可以看出,對於有序的輸入,隨機化版本的快速排序的效率會高很多。
問題記錄:
我們知道交換兩個變數的值有以下三種方法:
int tmp = a; // 方法一
a = b;
b = tmp
a = a+b; // 方法二
b = a-b;
a = a-b;
a = a^b; // 方法三
b = a^b;
a = a^b;
但是你會發現在本程式中,如果swap函式使用後面兩種方法會出錯。由於方法二和方法三沒有使用中間變數,它們交換值的原理是直接對變數的記憶體單元進行操作。如果兩個變數對應的同一記憶體單元,則經過兩次加減或異或操作,記憶體單元的值已經變為了0,因而不能實現變數值交換。所以當需要交換值的變數可能是同一變數時,必須使用第三變數實現交換,否則會對變數清零。
相關推薦
快速排序的分析與優化
一、快速排序的介紹 快速排序是一種排序演算法,對包含n個數的輸入陣列,最壞的情況執行時間為Θ(n2)[Θ 讀作theta]。雖然這個最壞情況的執行時間比較差,但快速排序通常是用於排序的最佳的實用選擇。這是因為其平均情況下的效能相當好:期望的執行時間為 Θ(nlgn),且Θ(
ExoPlayer Talk 01 緩存策略分析與優化
sca google mes efi allocator method policy 類型 let 操作系統:Windows8.1 顯卡:Nivida GTX965M 開發工具:Android studio 2.3.3 | ExoPlayer r2.5.1 使用 ExoP
MySQL瓶頸分析與優化
MySQL 優化 簡介通過sysbench的oltp_read_write測試來模擬業務壓力、以此來給指定的硬件環境配置一份比較合理的MySQL配置文件。環境介紹硬件配置軟件環境優化層級與指導思想優化層級MySQL數據庫優化可以在多個不同的層級進行,常見的有:SQL優化參數優化 架構優化本文重點關註:
MySQL服務器 IO 100%的分析與優化方案
文件 %u mysq 希望 影響 前言 文章 興趣 排查 前言 壓力測試過程中,如果因為資源使用瓶頸等問題引發最直接性能問題是業務交易響應時間偏大,TPS逐漸降低等。而問題定位分析通常情況下,最優先排查的是監控服務器資源利用率,例如先用TOP 或者nmon等查看CPU、內存
演算法導論 第七章:快速排序 筆記(快速排序的描述、快速排序的效能、快速排序的隨機化版本、快速排序分析)
快速排序的最壞情況時間複雜度為Θ(n^2)。雖然最壞情況時間複雜度很差,但是快速排序通常是實際排序應用中最好的選擇,因為它的平均效能很好。它的期望執行時間複雜度為Θ(n lg n),而且Θ(n lg n)中蘊含的常數因子非常小,而且它還是原址排序的。 快速排序是一種排序演算法,對包含n個數的
iOS作業系統-- App啟動流程分析與優化
背景知識: mach-o檔案為基於Mach核心的作業系統的可執行檔案、目的碼或動態庫,是.out的代替,其提供了更強的擴充套件性並提升了符號表中資訊的訪問速度, 符號表,用於標記原始碼中包括識別符號、宣告資訊、行號、函式名稱等元素的具體資訊,比如說資料型別、作用域以及記憶體地址,iOS符號表在dS
交換排序(氣泡排序/快速排序)及其優化
交換排序基本思想 兩兩比較待排記錄的關鍵字,一旦發現兩個記錄的次序與排序的要求相逆,則交換這兩個記錄的位置,直到表中沒有逆序的記錄存在為止。 分類 氣泡排序 快速排序(對冒泡的改進) <1>氣泡排序 基本思想:序列中相鄰的兩個元素進行比較,如果前一個元素
由淺入深探究mysql索引結構原理、效能分析與優化
第一部分:基礎知識 第二部分:MYISAM和INNODB索引結構 1、 簡單介紹B-tree B+ tree樹 2、 MyisAM索引結構 3、 Annode索引結構 4、 MyisAM索引與InnoDB索引相比較 第三部分:MYSQL優化 1、表資料型別選擇 2、sql語句優化 (1) 最左字首
分享一個基於小米 soar 的開源 sql 分析與優化的 WEB 圖形化工具
tst 自己 file 圖片 pymysql 清除 cfg 線上 python soar-web 基於小米 soar 的開源 sql 分析與優化的 WEB 圖形化工具,支持 soar 配置的添加、修改、復制,多配置切換,配置的導出、導入與導入功能。 環境需求 python3
Active session量持續走高即將故障原因分析與優化建議
SID TY ID1 ID2 LMODE REQUEST ---------- -- ---------- ---------- ---------- ---------- 2887 TX 2490401 43458
排序--堆排序分析與實現
何為堆 一個數組序列我們可以將其用完全二叉樹或近似完全二叉樹(不是滿二叉樹的完全二叉樹)表示出來,當陣列下標為i時,它的父節點為(i-1)/2,左孩子為(2i+1),右孩子為(2i+2),這種對應關係說明陣列下標為0的地方也要儲存資料。(關於完全二叉樹和滿二叉
帶你玩轉Visual Studio——效能分析與優化
上一篇文章帶你玩轉Visual Studio——VC++的多執行緒開發講了VC++中多執行緒的主要用法。多執行緒是提升效能和解決併發問題的有效途經。在商用程式的開發中,效能是一個重要的指標,程式的效能優化也是一個重要的工作。 找到效能瓶頸 二八法則適
實踐App記憶體優化:如何有序地做記憶體分析與優化
由於專案裡之前線上版本出現過一定比例的OOM,雖然比例並不大,但是還是暴露了一定的問題,所以打算對我們App分為幾個步驟進行記憶體分析和優化,當然記憶體的優化是個長期的過程,不是一兩個版本的事,每個版本都需要收集線上記憶體資料進行監控以及分析。 版本迭代
重新學習Mysql資料庫5:根據MySQL索引原理進行分析與優化
本文出自我的公眾號:程式設計師江湖。 滿滿乾貨,關注就送。 一:Mysql原理與慢查詢 MySQL憑藉著出色的效能、低廉的成本、豐富的資源,已經成為絕大多數網際網路公司的首選關係型資料庫。雖然效能出色,但所謂“好馬配好鞍”,如何能夠更好的使用它,已經成為開發工程
Top團隊大牛帶你玩轉Android效能分析與優化
第1章 課程導學與學習指南 效能優化是高階工程師必備的技能,本課程將帶你由表及裡學到國內Top團隊對效能問題的體系化解決方案,滿滿的乾貨讓你輕鬆晉級高階工程師。 1-1 課前必讀(不看會錯過一個億) 1-2 課程導學試看
快速排序演算法及優化
基本思想: 1)選擇一個基準元素,通常選擇第一個元素或者最後一個元素, 2)通過一趟排序講待排序的記錄分割成獨立的兩部分,其中一部分記錄的元素值均比基準元素值小。另一部分記錄的 元素值比基準值大。 3)此時基準元素在其排好序後的正確位置 4)然後分別對這兩部分記錄
探討兩種快速排序寫法與堆溢位的關係
最近在做星圖識別匹配,因為需要對星表進行星等排序,對星等比較小,比較亮的星星進行優先選擇匹配,考慮到速度問題採用快速排序對其進行排序。正常的快速排序應該像方法一寫的所示,但是考慮到程式碼量的精簡,所以改動了原來的演算法,使用演算法二對其進行排序,剛開始資料混
Android效能全面分析與優化方案研究
效能優化是一個持續的過程,要多種手段,一點一點優化,一般是優化影響比較大頭的,再逐步優化小頭的,
四大排序分析與總結
為了便於描述,在這篇部落格裡且將內部排序分為: 1.插入排序(直接插入和希爾) 2.交換排序(冒泡和快排) 3.選擇排序(直接選擇和堆排序) 4.歸併排序 1.插入排序 直接插入排序(Straight Insertion Sort) 基
android應用冷啟動過程分析與優化過程
http://yifeng.studio/2016/11/15/android-optimize-for-cold-start/?utm_source=tuicool&utm_medium=referral 你有沒有發現,點選安卓手機桌面上的App圖示時,有時候應用馬上進入主介面,有時候要經歷好幾秒甚