Java編程中快速排序算法的實(shí)現(xiàn)及相關(guān)算法優(yōu)化
時(shí)間復(fù)雜度
平均情況:O(nlgn)
最壞情況:O(n*n),發(fā)生在當(dāng)數(shù)據(jù)已經(jīng)是排序狀態(tài)時(shí)
快排算法的基本原理
1、從數(shù)據(jù)中選取一個(gè)值a[i]作為參考
2、以a[i] 為參考,將數(shù)據(jù)分成2部分:P1、P2,P1中的數(shù)據(jù)全部≤a[i],P2中的數(shù)據(jù)全部>a[i],數(shù)據(jù)變?yōu)椋鸓1}{a[i]}{P2}}
3、將P1、P2重復(fù)上述步驟,直到各部分中只剩1個(gè)數(shù)據(jù)
4、數(shù)據(jù)完成升序排列
基本示例:
原始數(shù)據(jù):
{3,9,8,5,2,1,6}
第1步:選取第1個(gè)數(shù)據(jù):3
第2步:將數(shù)據(jù)分成2部分,左邊≤3,右邊大于>3:
{2,1} {3} {9,8,5,6}
第3步:將各部分重復(fù)以上步驟,直到每部分只剩1個(gè)數(shù)據(jù):
{2,1} => {1} {2} {9,8,5,6} => {8,5,6} {9}=> {5,6} {8} {9}=> {5} {6} {8} {9}
第4步:數(shù)據(jù)完成升序排列:
{1} {2} {3} {5} {6} {8} {9}
程序中數(shù)據(jù)通常保存在數(shù)組中,以int類型的數(shù)組為例,可以將上面的步驟寫成一個(gè)quickSort函數(shù)原型:
quickSort(int begin, int end) { //begin為數(shù)組的第一個(gè)數(shù)據(jù)的索引值,end為數(shù)組的最后一個(gè)數(shù)據(jù)的索引值+1 //如果只有1個(gè)數(shù)據(jù)或0個(gè)數(shù)據(jù),則程序返回 if( begin == end || begin == (end-1) ) return; int p = in[begin];//p為選擇的參考數(shù)據(jù),選擇第一個(gè)數(shù)據(jù) int a = begin +1; //a作為2部分?jǐn)?shù)據(jù)分界線的索引值 int b = a;//b為待比較的數(shù)據(jù)的索引值 for( ; b < end; b++) {//將數(shù)組中的各個(gè)數(shù)據(jù)依次與參考數(shù)據(jù)進(jìn)行比較 if( in[b] < p) { //如果該數(shù)據(jù)<參考數(shù)據(jù)則將其移動(dòng)到左邊 if(a == b){a++; continue;} //如果該數(shù)據(jù)已經(jīng)在左邊則不動(dòng) int temp = in[a];//將數(shù)據(jù)移動(dòng)到左邊 in[a] = in[b]; in[b] = temp; a++;//將分界線右移 } } in[begin] = in[a-1];//講參考值移動(dòng)到2組數(shù)據(jù)中間 in[a-1] = p; if( a-1 > begin){ // 如果左邊有數(shù)據(jù)則將其重復(fù)上述步驟 quickSort(begin, a); } if( end-1 > a ) {// 如果右邊有數(shù)據(jù)則將其重復(fù)上述步驟 quickSort(a, end); } return; // 如果無數(shù)據(jù)返回 }
算法優(yōu)化
上面這個(gè)快速排序算法可以說是最基本的快速排序,因?yàn)樗]有考慮任何輸入數(shù)據(jù)。但是,我們很容易發(fā)現(xiàn)這個(gè)算法的缺陷:這就是在我們輸入數(shù)據(jù)基本有序甚至完全有序的時(shí)候,這算法退化為冒泡排序,不再是O(n㏒n),而是O(n^2)了。
究其根源,在于我們的代碼實(shí)現(xiàn)中,每次只從數(shù)組第一個(gè)開始取。如果我們采用“三者取中”,即arr[low],arr[high],arr[(low+high)/2]三者的中值作為樞軸記錄,則可以大大提高快速排序在最壞情況下的性能。但是,我們?nèi)匀粺o法將它在數(shù)組有序情形下的性能提高到O(n)。還有一些方法可以不同程度地提高快速排序在最壞情況下的時(shí)間性能。
此外,快速排序需要一個(gè)遞歸棧,通常情況下這個(gè)棧不會(huì)很深,為log(n)級(jí)別。但是,如果每次劃分的兩個(gè)數(shù)組長(zhǎng)度嚴(yán)重失衡,則為最壞情況,棧的深度將增加到O(n)。此時(shí),由??臻g帶來的空間復(fù)雜度不可忽略。如果加上額外變量的開銷,這里甚至可能達(dá)到恐怖的O(n^2)空間復(fù)雜度。所以,快速排序的最差空間復(fù)雜度不是一個(gè)定值,甚至可能不在一個(gè)級(jí)別。
為了解決這個(gè)問題,我們可以在每次劃分后比較兩端的長(zhǎng)度,并先對(duì)短的序列進(jìn)行排序(目的是先結(jié)束這些棧以釋放空間),可以將最大深度降回到O(㏒n)級(jí)別。
這里提出對(duì)快速排序的3點(diǎn)優(yōu)化思路:
對(duì)于小數(shù)組,可以采用插入排序,避免遞歸調(diào)用。例如,當(dāng)if(hi <= lo + M)時(shí),就可以轉(zhuǎn)到插入排序。
采用子數(shù)組的一部分元素的中位數(shù)來切分?jǐn)?shù)組。這樣做得到的切分更好,但代價(jià)是需要計(jì)算中位數(shù)。
如果數(shù)組中含有大量的重復(fù)元素,可以采用三向切分。將數(shù)組切分為三部分,分別對(duì)應(yīng)于小于、等于和大于切分元素的數(shù)組元素。代碼實(shí)現(xiàn)如下:
private static void sort1(int[] a, int lo, int hi) { if (hi <= lo) return; int lt = lo, i = lo + 1, gt = hi; int v = a[lo]; while (i <= gt) { if (a[i] < v) { swap(a, lt++, i++); } else if (a[i] > v) { swap(a, i, gt--); } else { i++; } sort(a, lo, lt - 1); sort(a, gt + 1, hi); } }
相關(guān)文章
Java變量的初始化及靜態(tài)方法的實(shí)現(xiàn)
這篇文章主要介紹了Java變量的初始化及靜態(tài)方法的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10idea配置maven環(huán)境時(shí)maven下載速度慢的解決方法
我們?cè)趇dea配置maven環(huán)境的時(shí)候會(huì)發(fā)現(xiàn)maven更新慢的現(xiàn)象,解決辦法就是下載國內(nèi)的鏡像包,完美解決下載速度慢的問題,文中有詳細(xì)的具體操作方法,并通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2024-02-02Java 動(dòng)態(tài)代理你真的懂了嗎(動(dòng)態(tài)和代理)
動(dòng)態(tài)代理分兩部分,動(dòng)態(tài)和代理,今天通過本文給大家普及代碼模式及動(dòng)態(tài)代理的概念及示例代碼,感興趣的朋友跟隨小編一起看看吧2021-07-07使用spring整合Quartz實(shí)現(xiàn)—定時(shí)器功能
這篇文章主要介紹了使用spring整合Quartz實(shí)現(xiàn)—定時(shí)器功能,不基于特定的基類的方法,需要的朋友可以參考下2018-04-04java反射遍歷實(shí)體類屬性和類型,并賦值和獲取值的簡(jiǎn)單方法
下面小編就為大家?guī)硪黄猨ava反射遍歷實(shí)體類屬性和類型,并賦值和獲取值的簡(jiǎn)單方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-04-04springboot集成mybatis-plus遇到的問題及解決方法
這篇文章主要介紹了springboot集成mybatis-plus遇到的問題及解決方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11SpringCloud微服務(wù)續(xù)約實(shí)現(xiàn)源碼分析詳解
這篇文章主要介紹了SpringCloud微服務(wù)續(xù)約實(shí)現(xiàn)源碼分析,服務(wù)續(xù)期和服務(wù)注冊(cè)非常相似,服務(wù)注冊(cè)在Eureka?Client程序啟動(dòng)之后開啟,并同時(shí)開啟服務(wù)續(xù)期的定時(shí)任務(wù)2022-11-11基于java實(shí)現(xiàn)簡(jiǎn)單的圖片類別識(shí)別
這篇文章主要為大家詳細(xì)介紹了如何基于java實(shí)現(xiàn)簡(jiǎn)單的圖片類別識(shí)別功能,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-12-12Java基于線程實(shí)現(xiàn)帶有滾動(dòng)效果的Label標(biāo)簽實(shí)例
這篇文章主要介紹了Java基于線程實(shí)現(xiàn)帶有滾動(dòng)效果的Label標(biāo)簽,實(shí)例分析了java線程的使用技巧及l(fā)abel標(biāo)簽的實(shí)現(xiàn)方法,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-07-07