二维数组排序php array_work,Arrays.Sort()中的那些排序算法
本文基于JDK 1.8.0_211撰寫(xiě),基于java.util.Arrays.sort()方法淺談目前Java所用到的排序算法,僅我的看法和筆記,如有問(wèn)題歡迎指證,著重介紹其中的TimSort排序,其源于Python,并于JDK1.7引入Java以替代原有的歸并排序。java
引入
Arrays.Sort方法所用的排序算法主要涉及如下三種:雙軸快速排序(DualPivotQuicksort)、歸并排序(MergeSort)、TimSort,也同時(shí)包含了一些非基于比較的排序算法:例如計(jì)數(shù)排序。其具體最終使用哪種排序算法一般根據(jù)類型以及輸入長(zhǎng)度來(lái)動(dòng)態(tài)抉擇。python
輸入數(shù)組類型為基礎(chǔ)類型時(shí),采用雙軸快速排序,輔以計(jì)數(shù)排序;
public static void sort(int[] a) {
DualPivotQuicksort.sort(a,0, a.length - 1, null, 0, 0);
}
輸入數(shù)組類型為包裝類型時(shí),采用歸并排序或TimSort(除非通過(guò)特殊的配置,不然默認(rèn)采用TimSort)
public static voidsort(Object[] a) {if(LegacyMergeSort.userRequested)
legacyMergeSort(a);elseComparableTimSort.sort(a,0, a.length, null, 0, 0);
}public static void sort(T[] a, Comparator super T>c) {if (c == null) {
sort(a);
}else{if(LegacyMergeSort.userRequested)
legacyMergeSort(a, c);elseTimSort.sort(a,0, a.length, c, null, 0, 0);
}
}/**To be removed in a future release.*/
private static void legacyMergeSort(T[] a, Comparator super T>c) {
T[] aux=a.clone();if (c==null)
mergeSort(aux, a,0, a.length, 0);elsemergeSort(aux, a,0, a.length, 0, c);
}
排序穩(wěn)定性
假定在待排序的記錄序列中,存在多個(gè)具備相同的關(guān)鍵字的記錄,若通過(guò)排序,這些記錄的相對(duì)次序保持不變,即在原序列中,r[i] = r[j],且 r[i] 在 r[j] 以前,而在排序后的序列中,r[i] 仍在 r[j] 以前,則稱這種排序算法是穩(wěn)定的;不然稱為不穩(wěn)定的。android
穩(wěn)定性的含義:若是咱們須要在二次排序后保持原有排序的意義,就須要使用到穩(wěn)定性的算法git
例子:咱們要對(duì)一組商品排序,商品存在兩個(gè)屬性:價(jià)格和銷量。當(dāng)咱們按照價(jià)格從高到低排序后,要再按照銷量對(duì)其排序,這時(shí),若是要保證銷量相同的商品仍保持價(jià)格從高到低的順序,就必須使用穩(wěn)定性算法。github
快速排序與雙軸快速排序
快速排序簡(jiǎn)介
單軸快速排序?即為咱們最多見(jiàn)的一種快速排序方式,咱們選取一個(gè)基準(zhǔn)值(pivot),將待排序數(shù)組劃分為兩部分:大于pivot 和 小于pivot,再對(duì)這兩部分進(jìn)行單軸快速排序...?可是這種方式對(duì)于輸入數(shù)組中有不少重復(fù)元素時(shí)效率不太理想。算法
單軸三取樣切分快速排序?做為單軸快速排序的優(yōu)化版本,其主要優(yōu)化相同元素過(guò)多狀況下的快排效率,一樣選取一個(gè)基準(zhǔn)值,但將待排序數(shù)組劃分三部分:?大于pivot、等于pivot、大于pivot。api
雙軸快速排序選取兩個(gè) 基準(zhǔn)值pivot1,pivot2,且保證pivot1<=pivot2,其具體實(shí)現(xiàn)與三取樣切分相似,不過(guò)期將待排序數(shù)組劃分如下三部分:小于pivot,介于pivot1與pivot2之間,大于pivot2。數(shù)組
DualPivotQuicksort實(shí)現(xiàn)優(yōu)化
Java在實(shí)現(xiàn)DualPivotQuickSort時(shí),并未盲目使用雙軸快速排序,而是基于輸入長(zhǎng)度選取排序算法。app
(1)針對(duì)int、long、float、double四種類型,跟據(jù)長(zhǎng)度選取的排序算法以下:svn
當(dāng)待排序數(shù)目小于47,采用插入排序
當(dāng)待排序數(shù)目小于286,采用雙軸快排
當(dāng)待排序數(shù)目大于286,采用歸并排序
咱們暫且將其稱之為一個(gè)標(biāo)準(zhǔn)的雙軸快排
static void sort(int[] a, int left, intright,int[] work, int workBase, intworkLen) {//Use Quicksort on small arrays
if (right - left
sort(a, left, right,true);return;
}//Use MergingSort
doMerge();
}private static void sort(int[] a, int left, int right, booleanleftmost) {//Use insertion sort on tiny arrays
if (length
doInertionSort();
}
doDualPivotQuickSort();
}
(2)針對(duì)short、char兩種類型,根據(jù)長(zhǎng)度選取的排序算法以下:
當(dāng)待排序數(shù)目小于3200,采用標(biāo)準(zhǔn)雙軸快排;
當(dāng)待排序數(shù)目大于3200,采用計(jì)數(shù)排序(CountingSort)
(3)針對(duì)byte類型,根據(jù)長(zhǎng)度選取的排序算法以下:
當(dāng)待排序數(shù)目小于29,采用插入排序;
當(dāng)待排序數(shù)目大于29,采用計(jì)數(shù)排序(CountingSort)
非基于比較排序算法-計(jì)數(shù)排序
計(jì)數(shù)排序與傳統(tǒng)的基于比較的排序算法不一樣,其不經(jīng)過(guò)比較來(lái)排序。咱們常見(jiàn)的非基于比較的排序算法有三種:桶排序、計(jì)數(shù)排序(特殊桶排序)、基數(shù)排序,有興趣的能夠逐個(gè)去了解,這里主要講解計(jì)數(shù)排序。
使用前提
使用計(jì)數(shù)排序待排序內(nèi)容須要是一個(gè)有界的序列,且可用整數(shù)表示
引入
計(jì)數(shù)排序顧名思義即須要統(tǒng)計(jì)數(shù)組中的元素個(gè)數(shù),經(jīng)過(guò)另一個(gè)數(shù)組的地址表示輸入元素的值,數(shù)組的值表示元素個(gè)數(shù)。最后再將這個(gè)數(shù)組反向輸出便可完成排序,見(jiàn)下方示例:
假設(shè):一組范圍在 0~10 之間的數(shù)字,9, 3, 5, 4, 9, 1, 2, 7, 8,1,3, 6, 5, 3, 4, 0, 10, 9, 7, 9
第一步:創(chuàng)建一個(gè)數(shù)組來(lái)統(tǒng)計(jì)每一個(gè)元素出現(xiàn)的個(gè)數(shù),由于是0~10,則創(chuàng)建以下數(shù)組?Count:
第二步:遍歷輸入數(shù)組,將獲取到的內(nèi)容放置到Count 數(shù)組對(duì)應(yīng)的位置,使當(dāng)前位置+1,例如當(dāng)前為9:
以此類推,遍歷完整個(gè)輸入數(shù)組,則獲得一個(gè)完整狀態(tài)的Count:
這時(shí)候,Count中記錄了輸入數(shù)組全部的元素出現(xiàn)的次數(shù),將Count數(shù)組按次數(shù)輸出便可獲得最終排序輸出:
0, 1, 1, 2, 3, 3, 3, 4, 4, 5, 5, 6, 7, 7, 8, 9, 9, 9, 9, 10
計(jì)數(shù)排序的穩(wěn)定性與最終實(shí)現(xiàn)
根據(jù)上面穩(wěn)定性的定義,你們不難發(fā)現(xiàn)上面的實(shí)現(xiàn)過(guò)程不能保證基數(shù)排序的穩(wěn)定性,而實(shí)際上計(jì)數(shù)排序是一個(gè)穩(wěn)定的排序算法,下面咱們經(jīng)過(guò)上面那個(gè)例子來(lái)引出計(jì)數(shù)排序的最終實(shí)現(xiàn)。
例子:輸入數(shù)組? [?0,9,5,4,5?],范圍0 ~ 9
第一步:將Count數(shù)組中所記錄的內(nèi)容進(jìn)行更改,由記錄當(dāng)前元素的出現(xiàn)的次數(shù)?修正為??記錄當(dāng)前索引出現(xiàn)的次數(shù)和當(dāng)前索引以前全部元素次數(shù)的和,這樣在索引中存儲(chǔ)的值就是其真實(shí)的排序后排位數(shù),例如9的值為5,則9的排序后的位置就是第5位:
第二步:咱們從后向前遍歷原序列,此時(shí)為5,則在最終排序序列的位置為4(索引為3)的地方放置5,并將Count序列的5索引處的值-1。
以此類推:到第二個(gè)5時(shí),Count數(shù)組中的值為3(索引為2),這樣就保證了插入排序的穩(wěn)定性。
源碼實(shí)現(xiàn)
/**The number of distinct byte values.*/
private static final int NUM_BYTE_VALUES = 1 << 8;static void sort(byte[] a, int left, intright) {int[] count = new int[NUM_BYTE_VALUES];//創(chuàng)建count數(shù)組
for (int i = left - 1; ++i <=right;
count[a[i]- Byte.MIN_VALUE]++);//從尾部開(kāi)始遍歷
for (int i = NUM_BYTE_VALUES, k = right + 1; k >left; ) {while (count[--i] == 0);byte value = (byte) (i +Byte.MIN_VALUE);int s =count[i];do{
a[--k] =value;
}while (--s > 0);
}
}
TimSort
Timsort是工業(yè)級(jí)算法,其混用插入排序與歸并排序,二分搜索等算法,亮點(diǎn)是充分利用待排序數(shù)據(jù)可能部分有序的事實(shí),而且依據(jù)待排序數(shù)據(jù)內(nèi)容動(dòng)態(tài)改變排序策略——選擇性進(jìn)行歸并以及galloping。另外TimSort是一個(gè)穩(wěn)定的算法,其最好的執(zhí)行狀況要優(yōu)于普通歸并排序,最壞狀況與歸并排序相仿:
針對(duì)小數(shù)據(jù)優(yōu)化
針對(duì)輸入長(zhǎng)度較短的數(shù)組排序,不少輕量級(jí)排序便可勝任,故TimSort在基于輸入數(shù)組長(zhǎng)度的條件下,作出以下優(yōu)化:
當(dāng)輸入序列的長(zhǎng)度小于基準(zhǔn)值時(shí),將采用插入排序直接對(duì)輸入序列排序?;鶞?zhǔn)值的選取:(1)Python:64(2)Java:32
此外上面提到的插入排序,Java的實(shí)現(xiàn)中,對(duì)這部分作了優(yōu)化,嚴(yán)格來(lái)講這里使用的是二分插入排序。將插入排序與二分查找進(jìn)行告終合。由于插入排序的索引左側(cè)均是有序序列:
傳統(tǒng)意義上須要單個(gè)依次向前比較,直到找到新插入元素放置的位置;
二分插入排序,則在此處采用二分查找來(lái)確認(rèn)插入元素的放置位置。
TimSort簡(jiǎn)要流程
TimSort is a hybrid sorting algorithm that uses insertion sort and merge sort.
The algorithm reorders the input array from left to right by finding consecutive (disjoint) sorted segments (called “runs” from hereon). If the run is too short, it is extended using insertion sort. ?The lengths of the generated runs are added to an array named?runLen. Whenever a new run is added to?runLen, a method named mergeCollapse merges runs until the last 3 elements in?runLen?satisfy the following two conditions (the “invariant”):
runLen[n-2] >?runLen[n-1] +?runLen[n]
runLen[n-1] >?runLen?[n]
Here n is the index of the last run in runLen. ?The intention is that checking this invariant on the top 3 runs in?runLen?in fact guarantees that?all?runs satisfy it. At the very end, all runs are merged, yielding a sorted version of the input array.
TimSort算法經(jīng)過(guò)找到連續(xù)的(不相交)排序的段(此后稱為“Run”),若是Run太短,則使用插入排序擴(kuò)充。生成的Run的長(zhǎng)度被稱添加到一個(gè)名為runLen數(shù)組中,每當(dāng)將新Run添加到runLen時(shí),名為mergeCollapse的方法就會(huì)嘗試合并Run,直到runLen中的元素元素知足兩個(gè)恒定的不等式。到最后,全部Run都將合并成一個(gè)Run,從而生成輸入數(shù)組的排序版本。
基本概念 - Run
TimSort算法中,將有序子序列(升序序列和嚴(yán)格降序序列)稱之為Run,例如以下將排序序列:1,2,3,4,5,4,3,6,8,10
則上面的序列全部的Run以下:1,2,3,4,5,5,3,2,2,6,8,10
TimSort中會(huì)區(qū)分其序列為升序仍是降序,若是是降序會(huì)強(qiáng)行反轉(zhuǎn)Run,使其成為升序,則上述的序列通過(guò)反轉(zhuǎn)后即為:1,2,3,4,5,5,2,3,2,6,8,10
注意:Run必須是升序(能夠相等)和嚴(yán)格降序(不能相等),嚴(yán)格降序的緣由是保證TimSort穩(wěn)定性,由于降序須要反轉(zhuǎn)。
基本概念 - MinRun
當(dāng)兩個(gè)數(shù)組歸并時(shí),當(dāng)這個(gè)數(shù)組Run的數(shù)目等于或略小于2的乘方時(shí),效率最高
反過(guò)來(lái)講,咱們須要獲得一個(gè)Run的最小長(zhǎng)度,使得其劃分的Run的數(shù)目達(dá)到上述標(biāo)準(zhǔn)準(zhǔn),即:選取32-64(16-32)這個(gè)范圍做為MinRun的范圍,使得原排序數(shù)組能夠被MinRun分割成N份,這個(gè)N等于或略小于2的乘方。
若是當(dāng)前的Run長(zhǎng)度小于MinRun:嘗試把后面的元素經(jīng)過(guò)插入排序放入Run中,以盡可能達(dá)到MinRun的長(zhǎng)度(剩余長(zhǎng)度知足的狀況下);
若是當(dāng)前的Run長(zhǎng)度大于MinRun:不處理。
經(jīng)過(guò)上述的操做咱們就收獲了一系列Run,將其放置到堆棧runLen中,并嘗試對(duì)其進(jìn)行歸并:
/***?A?stack?of?pending?runs?yet?to?be?merged.?Run?i?starts?at
*?address?base[i]?and?extends?for?len[i]?elements.?It's?always
*?true?(so?long?as?the?indices?are?in?bounds)?that:
*
*?runBase[i]?+?runLen[i]?==?runBase[i?+?1]
*
*?so?we?could?cut?the?storage?for?this,?but?it's?a?minor?amount,
*?and?keeping?all?the?info?explicit?simplifies?the?code.*/private?int?stackSize?=?0;?//Number?of?pending?runs?on?stack
private?final?int[]?runBase;private?final?int[]?runLen;/**?初始化部分截取
*?分配runs-to-be-merged堆棧(不能擴(kuò)大)*/
int?stackLen?=?(len?
runBase=?new?int[stackLen];
runLen=?new?int[stackLen];/***?Pushes?the?specified?run?onto?the?pending-run?stack.
*
*@paramrunBase?index?of?the?first?element?in?the?run
*@paramrunLen??the?number?of?elements?in?the?run*/
private?void?pushRun(int?runBase,?intrunLen)?{this.runBase[stackSize]?=runBase;this.runLen[stackSize]?=runLen;
stackSize++;
}
歸并原則 - 不等式
基于以下緣由:
(1)堆棧runLen內(nèi)存占用盡可能減少,則其是線上具備固定大小,不考慮擴(kuò)容(參考上述源碼的注釋);
(2)讓歸并的兩個(gè)Run的數(shù)目都盡可能接近,更接近普通歸并的模式,提升歸并效率。
TimSort在runLen上制定了兩個(gè)不等式以使runLen盡可能貼近上面的條件:
Run[n-1] > Run[n] + Run[n+1]
Run[n] > Run[n+1]
當(dāng)目前runLen知足這兩個(gè)不等式時(shí),則不進(jìn)行歸并,不然進(jìn)行歸并,由于TimSort時(shí)穩(wěn)定的排序算法,則僅容許歸并相鄰的兩個(gè)Run:
若是不知足不等式一:歸并Run[n]與Run[n-1]、Run[n+1]中長(zhǎng)度較短的Run
若是不知足不等式二:歸并Run[n]與Run[n+1]
不變式的含義:
不變式一:從右至左讀取長(zhǎng)度,則待處理的runLen的增加至少與斐波那契額增加的速度同樣快,則更使得兩個(gè)歸并的Run的大小更為接近;
不變式二:待處理的runLen按遞減順序排序。
經(jīng)過(guò)以上兩個(gè)推論能夠推測(cè)runLen中的Run數(shù)目永遠(yuǎn)會(huì)收斂于一個(gè)肯定的數(shù),以此證實(shí)了只用極小的runLen堆棧就能夠排序很長(zhǎng)的輸入數(shù)組,也正是由于如此在實(shí)現(xiàn)上不考慮擴(kuò)容問(wèn)題,若是須要詳細(xì)數(shù)學(xué)例證能夠查看文后Reference。
實(shí)際代碼是線上,Java、Python、Android保證不等式的手段是檢查棧頂三個(gè)元素是否知足,即上述不等式的n取棧頂?shù)诙€(gè),若是不知足則歸并,歸并完成后再繼續(xù)檢查棧頂三個(gè)直到知足為止。
/*** Examines the stack of runs waiting to be merged and merges adjacent runs
* until the stack invariants are reestablished:
*
* 1. runLen[i - 3] > runLen[i - 2] + runLen[i - 1]
* 2. runLen[i - 2] > runLen[i - 1]
*
* This method is called each time a new run is pushed onto the stack,
* so the invariants are guaranteed to hold for i < stackSize upon
* entry to the method.*/
private voidmergeCollapse() {while (stackSize > 1) {int n = stackSize - 2;if (n > 0 && runLen[n-1] <= runLen[n] + runLen[n+1]) {if (runLen[n - 1] < runLen[n + 1])
n--;
mergeAt(n);
}else if (runLen[n] <= runLen[n + 1]) {
mergeAt(n);
}else{break; //Invariant is established
}
}
}
歸并排序
歸并優(yōu)化一:內(nèi)存優(yōu)化
因?yàn)轫氁WCTimSort的穩(wěn)定性,則歸并排序不能采用原地排序,TimSort引入了臨時(shí)數(shù)組來(lái)進(jìn)行歸并,并將參與歸并的兩個(gè)Run中較小的那個(gè)放置到臨時(shí)數(shù)組中,以節(jié)省內(nèi)存占用。同時(shí)區(qū)分從小開(kāi)始?xì)w并和從大開(kāi)始?xì)w并。
歸并優(yōu)化二:縮短歸并Run長(zhǎng)度
兩個(gè)參與歸并的Run,由不少部分其實(shí)時(shí)不用參與歸并的(從Run的某個(gè)位置開(kāi)始的前面或后面的元素均小于或大于另外一個(gè)Run的所有元素,則這部分就能夠不參與歸并),加之歸并兩個(gè)Run是在原輸入數(shù)組中是相鄰,則咱們考慮是否能夠在去掉部分頭尾元素,以達(dá)到縮短歸并長(zhǎng)度的目的:
(1)在RunA查找一個(gè)位置?I?能夠正好放置RunB[0],則?I?以前的數(shù)據(jù)都不用參與歸并,在原地不須要變化;
(2)在RunB查找一個(gè)位置?J?能夠正好放置RunA[Len-1],則?J?以后的數(shù)據(jù)都不用參與歸并,在原地不須要變化;
歸并排序優(yōu)化三:GallopingMode
在歸并時(shí)有可能存在如下的極端狀況:
RunA 的全部元素都小于RunB,若是這個(gè)狀況下采用常規(guī)的歸并效率確定不理想
因而TimSort引入了GallopingMode,用來(lái)解決上述的問(wèn)題,即當(dāng)歸并時(shí),一個(gè)Run連續(xù)n的元素都小于另外一個(gè)Run,則考慮是否有更多的元素都小于,則跳出正常歸并,進(jìn)入GallopingMode,當(dāng)不知足Galloping條件時(shí),再跳回到正常歸并(不知足Galloping條件時(shí)強(qiáng)制使用Galloping效率低下)。若是RunA的許多元素都小于RunB,那么有可能RunA會(huì)繼續(xù)擁有小于RunB的值(反之亦然),這個(gè)時(shí)候TimSort會(huì)跳出常規(guī)的歸并排序進(jìn)入Galloping Mode,這里規(guī)定了一個(gè)閾值MIN_GALLOP,默認(rèn)值為7。
下面模擬一次Galloping,以mergeHi為例(從大開(kāi)始?xì)w并):
(1)例如此時(shí)RunA已經(jīng)連續(xù)贏得7次歸并,而RunB的元素尚未一次被選取,則已經(jīng)達(dá)到閾值,進(jìn)入GallopingMode:
進(jìn)入GallopingMode,說(shuō)明此時(shí)已經(jīng)有RunA已經(jīng)小于RunB末尾的7個(gè)數(shù)字,TimSort猜想會(huì)有更多的RunA的數(shù)字小于RunB,則進(jìn)行如下操做:
以上則完成了一次Galloping,在這一次Galloping中,咱們一次性將全部大于RunB[len-1]的RunA元素一次性移動(dòng)(包括RunB[len-1],放置到此次移動(dòng)的RunA元素后),不須要再依次歸并。
這時(shí)涉及到一個(gè)概念便是否繼續(xù)執(zhí)行Galloping,仍是回到正常的歸并?
咱們判斷這一次移動(dòng)的元素個(gè)數(shù)是否還知足閾值(黃色),若是知足則繼續(xù),在RunA中尋找RunB[len-2]的位置,不然回到正常的歸并。
Java版本實(shí)現(xiàn)中每次進(jìn)入和退出Galloping會(huì)變動(dòng)這個(gè)進(jìn)入GallopingMode的閾值,應(yīng)該是某種獎(jiǎng)懲機(jī)制,這里不作說(shuō)明。
TimSort 的實(shí)現(xiàn)缺陷
在Java8中TimSort的實(shí)現(xiàn)是有缺陷的,在極端復(fù)雜狀況下可能會(huì)觸發(fā)異常,其主要緣由是若是只檢查棧頂三個(gè)Run的不等式關(guān)系,沒(méi)辦法辦證這個(gè)不等式在整個(gè)runLen堆棧上成立,參考如下示例:
不等式被破壞的代價(jià),即為Run的歸并時(shí)機(jī)被破壞,致使在某些極端狀況下,會(huì)致使堆棧中的Run超過(guò)咱們經(jīng)過(guò)不等式推出來(lái)的那個(gè)收斂值致使溢出:ArrayOutOfBoundsException,這個(gè)Bug在后續(xù)版本已經(jīng)修復(fù):
提供的修復(fù)方案即為檢查棧頂四個(gè)Run而非三個(gè):
private voidnewMergeCollapse() {while (stackSize > 1) {int n = stackSize - 2;if ( (n >= 1 && runLen[n-1] <= runLen[n] + runLen[n+1])|| (n >= 2 && runLen[n-2] <= runLen[n] + runLen[n-1])) {if (runLen[n - 1] < runLen[n + 1])
n--;
}else if (runLen[n] > runLen[n + 1]) {break; //Invariant is established
}
mergeAt(n);
}
}
部分源碼注釋
入口方法:
static void sort(T[] a, int lo, int hi, Comparator super T> c, T[] work, int workBase, intworkLen) {assert c != null && a != null && lo >= 0 && lo <= hi && hi <=a.length;int nRemaining = hi -lo;if (nRemaining < 2) {return; //Arrays of size 0 and 1 are always sorted
}//若是小于32位,則不采用TimSort,而是采用二分插入排序?qū)φ麄€(gè)數(shù)組直接排序
if (nRemaining
int initRunLen =countRunAndMakeAscending(a, lo, hi, c);//二分插入排序(從第一個(gè)Run后開(kāi)始向前排序,第一個(gè)Run已是增序了,不用再排)
binarySort(a, lo, hi, lo +initRunLen, c);return;
}
MyTimSort ts = new MyTimSort<>(a, c, work, workBase, workLen);//根據(jù)當(dāng)前排序數(shù)組長(zhǎng)度生成最小的Run長(zhǎng)度
int minRun =minRunLength(nRemaining);do{//識(shí)別下一個(gè)Run序列,返回這個(gè)Run的長(zhǎng)度
int runLen =countRunAndMakeAscending(a, lo, hi, c);//若是當(dāng)前的Run序列長(zhǎng)度小于MinRun長(zhǎng)度,則嘗試擴(kuò)展到MinRun的長(zhǎng)度(從后面選取元素進(jìn)行二分拆入排序)
if (runLen
int force = nRemaining <= minRun ?nRemaining : minRun;
binarySort(a, lo, lo+ force, lo +runLen, c);
runLen=force;
}//記錄當(dāng)前Run的起始Index以及Run長(zhǎng)度
ts.pushRun(lo, runLen);//嘗試合并
ts.mergeCollapse();//開(kāi)始下一輪的Run尋找
lo +=runLen;
nRemaining-=runLen;
}while (nRemaining != 0);//全部的Run都已經(jīng)尋找完畢,必須合并全部Run
assert lo ==hi;
ts.mergeForceCollapse();assert ts.stackSize == 1;
}
歸并方法(優(yōu)化縮短歸并長(zhǎng)度):
/*** 合并i以及i+1兩個(gè)Run. Run i 必定是倒數(shù)第二個(gè)或者倒數(shù)第三個(gè)Run
*
*@parami 合并堆棧的索引*/
private void mergeAt(inti) {//i 必定是倒數(shù)第二個(gè)或者倒數(shù)第三個(gè),換句話說(shuō),i必定是stackSize-2或者stackSize-3
assert stackSize >= 2;assert i >= 0;assert i == stackSize - 2 || i == stackSize - 3;int base1 =runBase[i];int len1 =runLen[i];int base2 = runBase[i + 1];int len2 = runLen[i + 1];assert len1 > 0 && len2 > 0;assert base1 + len1 ==base2;//將i的長(zhǎng)度更新len1+len2,即合并后的長(zhǎng)度,若是是倒數(shù)第三個(gè)Run,則將倒數(shù)第一個(gè)長(zhǎng)度合并到倒數(shù)第二個(gè)Run中(倒數(shù)第二個(gè)和倒數(shù)第三個(gè)Run合并了)//[RUN1,RUN2,RUN3] -> [RUN1+RUN2,RUN3] ; [RUN1,RUN2] -> [RUN1+RUN2]
runLen[i] = len1 +len2;if (i == stackSize - 3) {
runBase[i+ 1] = runBase[i + 2];
runLen[i+ 1] = runLen[i + 2];
}
stackSize--;//縮短歸并長(zhǎng)度:在Run1中尋找Run2的起始節(jié)點(diǎn)位置(Run2的首個(gè)元素應(yīng)該放置Run1中的位置),能夠忽略run1中先前的元素(由于其已經(jīng)有序)
int k = gallopRight(a[base2], a, base1, len1, 0, c);assert k >= 0;//!!! Run1 這個(gè)位置以前的能夠省略再也不排序,由于Run2全部元素都大于這個(gè)位置
base1 +=k;
len1-=k;//若是剩余排序長(zhǎng)度為0,則已經(jīng)有序,不用再排序(Run1全體大于Run2)
if (len1 == 0) {return;
}//縮短歸并長(zhǎng)度:在Run2中尋找Run1的最后一個(gè)元素應(yīng)該放置的位置,!!! Run2的這個(gè)位置后面能夠省略再也不排序,由于后面全部元素都大于Run1
len2 = gallopLeft(a[base1 + len1 - 1], a, base2, len2, len2 - 1, c);assert len2 >= 0;//若是剩余排序長(zhǎng)度為0,則已經(jīng)有序,不用再排序(Run2全體大于Run1)
if (len2 == 0) {return;
}//并歸兩側(cè),選取較短的一方做為臨時(shí)數(shù)組長(zhǎng)度//mergeLo:將len1 放入臨時(shí)數(shù)組,mergeHi:將len2 放入臨時(shí)數(shù)組
if (len1 <=len2) {
mergeLo(base1, len1, base2, len2);
}else{
mergeHi(base1, len1, base2, len2);
}
}
歸并與Gollaping:
/*** 相似于mergeLo,除了這個(gè)方法應(yīng)該只在len1 >= len2;若是len1 <= len2,則應(yīng)該調(diào)用mergeLo。
* (兩個(gè)方法均可以在len1 == len2時(shí)調(diào)用。)
*
*@parambase1 Run1的隊(duì)首元素
*@paramlen1 Run1的長(zhǎng)度
*@parambase2 Run2的隊(duì)首元素(???must be aBase + aLen)
*@paramlen2 Run2的長(zhǎng)度*/
private void mergeHi(int base1, int len1, int base2, intlen2) {assert len1 > 0 && len2 > 0 && base1 + len1 ==base2;
T[] a= this.a; //For performance
T[] tmp =ensureCapacity(len2);int tmpBase = this.tmpBase;
System.arraycopy(a, base2, tmp, tmpBase, len2);int cursor1 = base1 + len1 - 1;int cursor2 = tmpBase + len2 - 1;int dest = base2 + len2 - 1;//Move last element of first run and deal with degenerate cases
a[dest--] = a[cursor1--];if (--len1 == 0) {
System.arraycopy(tmp, tmpBase, a, dest- (len2 - 1), len2);return;
}//簡(jiǎn)化操做,若是Run2要合并的元素只有一個(gè),這個(gè)元素不比Run1的最大值大,也不比當(dāng)前Run1索引的最小值大(base1的位置是大于Run2隊(duì)首元素的位置),故Run2這個(gè)元素應(yīng)該放置到Run1第一個(gè)
if (len2 == 1) {
dest-= len1; //dest = dest - len1 (因前序dest已經(jīng)-1,故是Run1隊(duì)尾坐標(biāo)),dest:Run1應(yīng)該開(kāi)始合并的起始位置:[....{1},6,16,0,27]
cursor1 -= len1; //cursor1 = cursor1 - len1 (因前序cursor1已經(jīng)-1,故是Run1倒數(shù)第二坐標(biāo)),cursor1:dest的前序位置:[...{X},1,6,16,0,27]
System.arraycopy(a, cursor1 + 1, a, dest + 1, len1); //[...{1,6,16},0,27] -> [...1,{1,6,16},27],將Run要排序部分后移一位
a[dest] = tmp[cursor2]; //[....1,1,6,16,27] -> [...0,1,6,16,27],將tmp中的惟一元素放置到隊(duì)首
return;
}
Comparator super T> c = this.c; //Use local variable for performance
int minGallop = this.minGallop; //" " " " "
outer: while (true) {//開(kāi)始正常歸并,并記錄Run一、Run2 贏得選擇的次數(shù),若是大于minGallop則跳出進(jìn)入GallopingMode
int count1 = 0; //Number of times in a row that first run won
int count2 = 0; //Number of times in a row that second run won
/** Do the straightforward thing until (if ever) one run
* appears to win consistently.*/
do{assert len1 > 0 && len2 > 1;if (c.compare(tmp[cursor2], a[cursor1]) < 0) {
a[dest--] = a[cursor1--];
count1++;
count2= 0;if (--len1 == 0) {breakouter;
}
}else{
a[dest--] = tmp[cursor2--];
count2++;
count1= 0;if (--len2 == 1) {breakouter;
}
}
}while ((count1 | count2)
do{assert len1 > 0 && len2 > 1;
count1= len1 - gallopRight(tmp[cursor2], a, base1, len1, len1 - 1, c);if (count1 != 0) {
dest-=count1;
cursor1-=count1;
len1-=count1;
System.arraycopy(a, cursor1+ 1, a, dest + 1, count1);if (len1 == 0) {breakouter;
}
}
a[dest--] = tmp[cursor2--];if (--len2 == 1) {breakouter;
}
count2= len2 - gallopLeft(a[cursor1], tmp, tmpBase, len2, len2 - 1, c);if (count2 != 0) {
dest-=count2;
cursor2-=count2;
len2-=count2;
System.arraycopy(tmp, cursor2+ 1, a, dest + 1, count2);if (len2 <= 1) {breakouter;
}
}
a[dest--] = a[cursor1--];if (--len1 == 0) {breakouter;
}//完成一次Galloping后對(duì)閾值作修改,并判斷是否須要繼續(xù)Galloping
minGallop--;
}while (count1 >= MIN_GALLOP | count2 >=MIN_GALLOP);if (minGallop < 0) {
minGallop= 0;
}
minGallop+= 2; //Penalize for leaving gallop mode
} //End of "outer" loop
this.minGallop = minGallop < 1 ? 1 : minGallop; //Write back to field
if (len2 == 1) {assert len1 > 0;
dest-=len1;
cursor1-=len1;
System.arraycopy(a, cursor1+ 1, a, dest + 1, len1);
a[dest]= tmp[cursor2]; //Move first elt of run2 to front of merge
} else if (len2 == 0) {throw new IllegalArgumentException("Comparison method violates its general contract!");
}else{assert len1 == 0;assert len2 > 0;
System.arraycopy(tmp, tmpBase, a, dest- (len2 - 1), len2);
}
}
Reference
總結(jié)
以上是生活随笔為你收集整理的二维数组排序php array_work,Arrays.Sort()中的那些排序算法的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 城市超级“充电宝”,全球最大容量铁-铬液
- 下一篇: 动态规划算法php,php算法学习之动态