时间复杂度为O(n)的排序(JAVA)
時(shí)間復(fù)雜度為O(n)的排序
以下排序算法都是針對(duì)特定場(chǎng)景才有優(yōu)勢(shì)的排序算法
桶排序
特定場(chǎng)景描述
以上場(chǎng)景一般具有以下特點(diǎn):
-
要排序的數(shù)據(jù)有明顯的范圍。比如高考分?jǐn)?shù)一般介于0~900,公民身高一般介于0~3000,公民體重一般介于0~150,訂單金額一般介于0~max(當(dāng)年),而某一年的時(shí)間,如果把時(shí)間以時(shí)間戳形式計(jì)算,一定是介于某個(gè)固定區(qū)間的
-
既然有了明顯的范圍區(qū)間,就很方便劃分區(qū)間段(類似統(tǒng)計(jì)中的柱狀圖),而且區(qū)間段的數(shù)據(jù)滿足單調(diào)性,比如區(qū)間段f(x)和區(qū)間段f(x+1)一定滿足如下關(guān)系:max(f(x)[i]) < min(f(x+1)[i]),即上一個(gè)區(qū)間段內(nèi)的最大值一定比下一個(gè)區(qū)間段的最小值要小(這里說(shuō)的是升序排列)。比如高考分?jǐn)?shù)可劃分的區(qū)間段是0~100、101~200、201~300、301~400、401~500、501~600、601~700、701~800、801~900,在區(qū)間段201~300中的最大值很明顯會(huì)小于區(qū)間段301~400中的最小值。
-
劃分好了區(qū)間段,還要滿足各區(qū)間段內(nèi)的數(shù)據(jù)量相對(duì)均勻。還拿高考分?jǐn)?shù)為例,意思是區(qū)間段201~300中的考生數(shù)和區(qū)間段301~400中的考生數(shù)和…幾乎相當(dāng)。這個(gè)其實(shí)挺難的。因?yàn)楹芏囝愃频姆植级紩?huì)符合正態(tài)分布的,兩頭少,中間多,當(dāng)然也有特定的情況,這完全是根據(jù)數(shù)據(jù)的業(yè)務(wù)屬性而來(lái)的。但各區(qū)間段內(nèi)的數(shù)據(jù)量相對(duì)均勻是桶排序很關(guān)鍵的一點(diǎn)。
為了滿足各區(qū)間段內(nèi)的數(shù)據(jù)量相對(duì)均勻,就需要根據(jù)實(shí)際業(yè)務(wù)場(chǎng)景,重新劃分區(qū)間段(桶)。
比如考生的分?jǐn)?shù),公民的身高和體重,一般來(lái)說(shuō)符合正態(tài)分布,仍以分?jǐn)?shù)為例,這需要參考以往經(jīng)驗(yàn)了,假設(shè)得到的經(jīng)驗(yàn)是,一般會(huì)在500~800之間扎堆兒,該區(qū)間考生數(shù)占據(jù)了所有的大約80%,那么根據(jù)這條經(jīng)驗(yàn),可以做出新的桶劃分。比如:0~500、501~550、551~600、600~650、651~700、701~750、751~800、801~900,或許500~800還可以分的更細(xì)致些的。
比如微信的后臺(tái)日志時(shí)間戳,以天為單位的話,假設(shè)得到的經(jīng)驗(yàn)是,春節(jié)前后使用量很大,五一、十一也會(huì)很大。相應(yīng)的調(diào)整新的桶劃分。
以上的目的就是盡量滿足各桶中的數(shù)據(jù)量均勻。
思路
以空間換時(shí)間
盡可能減少數(shù)據(jù)間的比較
設(shè)置好若干個(gè)桶之后,首先遍歷待排序數(shù)據(jù),放入特定的桶中,時(shí)間復(fù)雜度是O(n),假設(shè)嚴(yán)絲合縫,也要開(kāi)辟空間復(fù)雜度為O(n)的多個(gè)桶做臨時(shí)存儲(chǔ)
接著將各桶中的數(shù)據(jù)分別排序,如果需要穩(wěn)定性(好像沒(méi)在排序里說(shuō)明穩(wěn)定性的意思,后邊單說(shuō)吧),使用歸并排序,如果不需要穩(wěn)定性,使用快速排序,總之單個(gè)桶的時(shí)間復(fù)雜度是O(klogk)。假設(shè)設(shè)置的桶足夠多,可以讓一個(gè)桶里的元素值相等,那就不需要桶內(nèi)排序了。但這需要開(kāi)辟的桶空間可就更大了(這也算是把“以空間換時(shí)間”發(fā)揮到極致了吧)。
最后按序遍歷各桶以及各桶中元素,依次放入原數(shù)組,完成排序。
實(shí)現(xiàn)代碼
比較糾結(jié)這個(gè)排序算法要怎么寫(xiě)合適,網(wǎng)上也沒(méi)找到一個(gè)很通用的,畢竟設(shè)定幾個(gè)桶,桶的深度都要結(jié)合實(shí)際情況來(lái)看的。這里只能放一個(gè)簡(jiǎn)單的例子,以及代碼實(shí)現(xiàn)。
全班10人,身高分別是[150,163,158,166,170,169,158,175,181,175],按身高升序排序
在執(zhí)行排序前,得知身高最大值max=181,最小值min=150,設(shè)置m=3個(gè)桶,桶區(qū)間依次是150~160,161~170,171~181,每個(gè)桶的深度為5
下邊是偽代碼
計(jì)數(shù)排序
特定場(chǎng)景描述
場(chǎng)景和通排序的場(chǎng)景類似,只不過(guò)有了更苛刻的要求
-
要排序的數(shù)據(jù)不僅有明顯的范圍,而且數(shù)值范圍不大。比如桶排序列出的諸多場(chǎng)景中,分?jǐn)?shù)、身高、體重都是很好的;金額可能有點(diǎn)邪乎,主要是如果土豪太多,雙十一一單花了好幾個(gè)億那種,區(qū)間就太大了,不過(guò)據(jù)說(shuō)單個(gè)訂單有金額上限,如果這樣的話就很好了;按時(shí)間排序,這可能就有點(diǎn)頭大了,但如果不是按納秒,而是粗略一些忽略到毫秒(也難),忽略到秒(一年31,536,000秒,也難),忽略到分鐘(一年525,600分鐘,挺好),忽略到小時(shí)(一年8,760小時(shí),挺好),再忽略估計(jì)這個(gè)排序的意義可能就喪失了。
-
要將被排序的所有元素按照某個(gè)一對(duì)一映射的函數(shù),先計(jì)算成非負(fù)整數(shù)。比如身高和體重,身高169.55可以換算為16955,換算公式是f(x) = 100 * x;而x = f(x) / 100;兩個(gè)函數(shù)的計(jì)算結(jié)果都是唯一的。再比如時(shí)間排序,換算成時(shí)間戳,再減去一個(gè)待排序最小時(shí)間的時(shí)間戳,在允許的情況下忽略納秒、毫秒、甚至秒(這個(gè)逆向的映射有些麻煩,但既然忽略了一些精度,就相當(dāng)于按更粗略的單位來(lái)排序了)。
思路
計(jì)數(shù)排序是特殊的桶排序,實(shí)現(xiàn)思路如下:
因?yàn)閷?duì)數(shù)據(jù)值的范圍做了限定:一方面不大,另一方面都可轉(zhuǎn)為非負(fù)整數(shù),那么就可以進(jìn)行如下操作了。
假設(shè)數(shù)據(jù)值的最大值是max,接著設(shè)定max+1個(gè)桶,而這里的桶不用來(lái)存儲(chǔ)元素的集合,而用來(lái)存儲(chǔ)元素的個(gè)數(shù),所以每個(gè)桶的長(zhǎng)度為1。
所以可創(chuàng)建一個(gè)長(zhǎng)度為max+1的數(shù)組充當(dāng)這max+1個(gè)桶
接著遍歷原數(shù)組array,統(tǒng)計(jì)每個(gè)值的個(gè)數(shù),并將結(jié)果保存到桶中
for(int i = 0; i < array.length; i++) {int item = array[i];temp[item] = temp[item] + 1; }舉個(gè)例子,數(shù)組array = {3,5,7,2,4,2,7,3,1}
其中最大元素是7,那么桶就是temp = new int[8]
遍歷array
最終得到temp = {0,1,2,2,1,1,0,2}
從數(shù)組temp可以看出,數(shù)組array中元素i的個(gè)數(shù)是temp[i]
而如果希望數(shù)組中元素值所表達(dá)如下含義呢?
從數(shù)組temp可以看出,數(shù)組array中<= i的元素的個(gè)數(shù)是temp[i]
數(shù)組temp要做如下的元素疊加處理
最終得到temp = {0,1,3,5,6,7,7,9}
從數(shù)組temp可以看出,數(shù)組array中<= i的元素個(gè)數(shù)是temp[i]
原array = {3,5,7,2,4,2,7,3,1},設(shè)置一個(gè)等長(zhǎng)的臨時(shí)數(shù)組result = new int[array.length]
第一個(gè)元素3,得到temp[3] = 5,說(shuō)明<= 3的元素個(gè)數(shù)是5,那么經(jīng)過(guò)排序后的array中index = 4(第5個(gè)元素)的位置上放置的一定就是3,所以result[4] = 3。既然3的位置已經(jīng)確定,<= 3的元素個(gè)數(shù)就是4了,需要執(zhí)行temp[3] = temp[3] - 1
此時(shí)result = {0,0,0,0,3,0,0,0,0}; temp = {0,1,3,4,6,7,7,9};
第二個(gè)元素5,得到temp[5] = 7,說(shuō)明<= 5的元素個(gè)數(shù)是7,那么經(jīng)過(guò)排序后的array中index = 6(第7個(gè)元素)的位置上放置的一定就是5,所以result[6] = 5。同樣,temp[5] = temp[5] - 1
此時(shí)result = {0,0,0,0,3,0,5,0,0}; temp = {0,1,3,4,6,6,7,9};
第三個(gè)元素7
此時(shí)result = {0,0,0,0,3,0,5,0,7}; temp = {0,1,3,4,6,6,7,8};
第四個(gè)元素2
此時(shí)result = {0,0,2,0,3,0,5,0,7}; temp = {0,1,2,4,6,6,7,8};
第五個(gè)元素4
此時(shí)result = {0,0,2,0,3,4,5,0,7}; temp = {0,1,2,4,5,6,7,8};
第六個(gè)元素2
此時(shí)result = {0,2,2,0,3,4,5,0,7}; temp = {0,1,1,4,5,6,7,8};
第七個(gè)元素7
此時(shí)result = {0,2,2,0,3,4,5,7,7}; temp = {0,1,1,4,5,6,7,7};
第八個(gè)元素3
此時(shí)result = {0,2,2,3,3,4,5,7,7}; temp = {0,1,1,3,5,6,7,7};
第九個(gè)元素1
此時(shí)result = {1,2,2,3,3,4,5,7,7}; temp = {0,0,1,3,5,6,7,7};
最終得到數(shù)組result = {1,2,2,3,3,4,5,7,7}就是排序結(jié)果
再將其逐個(gè)復(fù)制到原數(shù)組array中即可
最終代碼
public void countingSort(int[] array, int length) {// 找最大值int max = array[0];for (int i = 1; i < length; i++) {if (max < array[i]) {max = array[i];}}// 創(chuàng)建統(tǒng)計(jì)元素個(gè)數(shù)的數(shù)組int[] temp = new int[max + 1];for (int i : array) {temp[i] = temp[i] + 1;}// 疊加元素個(gè)數(shù)for (int i = 1; i < temp.length; i++) {temp[i] = temp[i - 1] + temp[i];}// 創(chuàng)建存儲(chǔ)結(jié)果的臨時(shí)數(shù)組int[] result = new int[length];for (int i : array) {result[temp[i] - 1] = i;temp[i] = temp[i] - 1;}// 將result拷貝到array中去for (int i = 0; i < array.length; i++) {array[i] = result[i];} }@Test public void countingSort() {int[] array = {2,6,7,3,1,5,3,2,3};System.out.println(Arrays.toString(array));countingSort(array,array.length);System.out.println(Arrays.toString(array)); }采坑啦!
其實(shí)是在寫(xiě)基數(shù)排序時(shí),使用了上述計(jì)數(shù)排序,就踩到坑了
坑的位置在
針對(duì)重復(fù)元素,因?yàn)閠emp[i]要減一,所以后插入的元素是在先插入的前邊
而這段代碼中是按array從前向后遍歷的,這和上述很巧妙的方式正好反向,導(dǎo)致這里的排序不是穩(wěn)定的排序
修改方案就是改為逆向遍歷
完整代碼如下:
public void countingSort(int[] array, int length) {// 找最大值int max = array[0];for (int i = 1; i < length; i++) {if (max < array[i]) {max = array[i];}}// 創(chuàng)建統(tǒng)計(jì)元素個(gè)數(shù)的數(shù)組int[] temp = new int[max + 1];for (int i : array) {temp[i] = temp[i] + 1;}// 疊加元素個(gè)數(shù)for (int i = 1; i < temp.length; i++) {temp[i] = temp[i - 1] + temp[i];}// 創(chuàng)建存儲(chǔ)結(jié)果的臨時(shí)數(shù)組int[] result = new int[length];for (int i = length - 1; i >= 0; i--) {int item = array[i];result[temp[item] - 1] = item;temp[item] = temp[item] - 1;}// 將result拷貝到array中去for (int i = 0; i < array.length; i++) {array[i] = result[i];} }@Test public void countingSort() {int[] array = {2, 6, 7, 3, 1, 5, 3, 2, 3};System.out.println(Arrays.toString(array));countingSort(array, array.length);System.out.println(Arrays.toString(array)); }基數(shù)排序
特定場(chǎng)景
這里涉及到的都是字符串排序,而且滿足如下條件:
第一個(gè)字符小的排在前
第一個(gè)字符相同時(shí),第二個(gè)字符小的排在前
第一第二個(gè)字符相同時(shí),第三個(gè)字符小的排在前
…
思路
思路其實(shí)是沒(méi)有的,只是覺(jué)得很巧吧
主要是執(zhí)行固定次數(shù)的時(shí)間復(fù)雜度為O(n)的穩(wěn)定排序
比如如下5個(gè)字符串
["113","217","121","212","221"]
首先按第三位排序得
["121","221","212","113","217"]注意這里是穩(wěn)定排序(當(dāng)然,在這里還用不到穩(wěn)定排序的優(yōu)勢(shì)),121和221順序不變
接著按第二位排序得
["212","113","217","121","221"]再次注意這里的穩(wěn)定排序
最后按首位排序得
["113","121","212","217","221"]
最終代碼
private void countingSort(String[] array, int length, int index) {char max = array[0].charAt(index);for (int i = 1; i < length; i++) {char c = array[i].charAt(index);if (max < c) {max = c;}}int maxValue = charToInt(max);int[] temp = new int[maxValue + 1];for (int i = 0; i < length; i++) {int item = charToInt(array[i].charAt(index));temp[item] = temp[item] + 1;}for (int i = 1; i < temp.length; i++) {temp[i] = temp[i - 1] + temp[i];}String[] result = new String[length];for (int i = length - 1; i >= 0; i--) {int item = charToInt(array[i].charAt(index));result[temp[item] - 1] = array[i];temp[item] = temp[item] - 1;}for (int i = 0; i < array.length; i++) {array[i] = result[i];}}private int charToInt(char c) {return c - '0'; }public void radixSort(String[] array, int length) {int stringLength = array[0].length();for (int i = stringLength - 1; i >= 0; i--) {countingSort(array, length, i);} }@Test public void radixSort() {String[] array = {"12345678901", "16789012342", "17890123453", "13456789014", "11234567895","15678901236", "13456789017", "12345678908", "13456789019"};System.out.println(Arrays.toString(array));radixSort(array, array.length);System.out.println(Arrays.toString(array)); }這里附上一段經(jīng)某位同學(xué)點(diǎn)撥后寫(xiě)出的代碼,適用于億級(jí)別int類型整數(shù)的高速排序
public void radixSort(int[] array, int length) {for (int i = 0; i < 32; i++) {radixSort(array, length, i);} }private void radixSort(int[] array, int length, int index) {int[] tempArray = new int[2];for (int i = 0; i < array.length; i++) {int item = (array[i] >> index) & 1;tempArray[item] = tempArray[item] + 1;}for (int i = 1; i < tempArray.length; i++) {tempArray[i] = tempArray[i] + tempArray[i - 1];}int[] result = new int[length];for (int i = length - 1; i >= 0; i--) {int item = (array[i] >> index) & 1;result[tempArray[item] - 1] = array[i];tempArray[item] = tempArray[item] - 1;}for (int i = 0; i < length; i++) {array[i] = result[i];}}說(shuō)明一下:
這相當(dāng)于把一個(gè)int類型值先轉(zhuǎn)為二進(jìn)制,再按二進(jìn)制排序。int最多32位
上萬(wàn)級(jí)別的數(shù)據(jù),某位上的最大值為0的概率極低,所以最大值為1,存儲(chǔ)0和1的個(gè)數(shù)數(shù)組就是int[2]了。
其余操作不變,但對(duì)于取某位上的數(shù)值,可使用如下公式:
取二進(jìn)制數(shù)的倒數(shù)第i位的值 x >> (i - 1) & 1
總結(jié)
以上是生活随笔為你收集整理的时间复杂度为O(n)的排序(JAVA)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: windows7共享打印机无法连接0x0
- 下一篇: SubSonic 安装与使用