日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

时间复杂度为O(n)的排序(JAVA)

發(fā)布時(shí)間:2023/12/31 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 时间复杂度为O(n)的排序(JAVA) 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

時(shí)間復(fù)雜度為O(n)的排序

以下排序算法都是針對(duì)特定場(chǎng)景才有優(yōu)勢(shì)的排序算法

桶排序

特定場(chǎng)景描述

  • 為20XX年全國(guó)X0萬(wàn)考生的高考分?jǐn)?shù)排序
  • 為截止20XX年全國(guó)1X億公民的身高排序
  • 為截止20XX年全國(guó)1X億公民的體重排序
  • 為20XX年天貓雙十一所有訂單的金額排序
  • 為20XX年滴滴打車所有訂單的下單時(shí)間排序
  • 為20XX年微信所有后臺(tái)日志的時(shí)間排序
  • 以上場(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
    下邊是偽代碼

    public void bucketSort(int[] array, int length) {int bucketCount = 3;int[] bucket1 = new int[5];int bucket1Min = 150;int bucket1Max = 160;int[] bucket2 = new int[5];int bucket2Min = 161;int bucket2Max = 170;int[] bucket3 = new int[5];int bucket3Min = 171;int bucket3Max = 181;int bucket1Index = 0;int bucket2Index = 0;int bucket3Index = 0;for(int i = 0; i < array.length; i++) {int item = array[i];if(item < bucket2Min) {bucket1[bucket1Index++] = item;} else if (item < bucket3Min) {bucket2[bucket2Index++] = item;} else {bucket3[bucket3Index++] = item;}}quickSort(bucket1, bucket1.length);quickSort(bucket2, bucket2.length);quickSort(bucket3, bucket3.length);for(int i = 0; i < bucket1Index; i++) {array[i] = bucket1[i];}for(int i = 0; i < bucket2Index; i++) {array[bucket1Index + i] = bucket2[i];}for(int i = 0; i < bucket3Index; i++) {array[bucket2Index + i] = bucket3[i];} }

    計(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è)桶

    int[] temp = new int[max+1];

    接著遍歷原數(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

    index = 0; item = 3; temp[3] = temp[3] + 1; temp = {0,0,0,1,0,0,0,0}; index = 1; item = 5; temp[5] = temp[5] + 1; temp = {0,0,0,1,0,1,0,0}; index = 2; item = 7; temp[7] = temp[7] + 1; temp = {0,0,0,1,0,1,0,1}; index = 3; item = 2; temp[2] = temp[2] + 1; temp = {0,0,1,1,0,1,0,1}; index = 4; item = 4; temp[4] = temp[4] + 1; temp = {0,0,1,1,1,1,0,1}; index = 5; item = 2; temp[2] = temp[2] + 1; temp = {0,0,2,1,1,1,0,1}; index = 6; item = 7; temp[7] = temp[7] + 1; temp = {0,0,2,1,1,1,0,2}; index = 7; item = 3; temp[3] = temp[3] + 1; temp = {0,0,2,2,1,1,0,2}; index = 8; item = 1; temp[1] = temp[1] + 1; temp = {0,1,2,2,1,1,0,2};

    最終得到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要做如下的元素疊加處理

    for(int i = 1; i < temp.length; i++) {temp[i] = temp[0] + temp[i]; }

    最終得到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ù)排序,就踩到坑了
    坑的位置在

    // 創(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; }

    針對(duì)重復(fù)元素,因?yàn)閠emp[i]要減一,所以后插入的元素是在先插入的前邊
    而這段代碼中是按array從前向后遍歷的,這和上述很巧妙的方式正好反向,導(dǎo)致這里的排序不是穩(wěn)定的排序
    修改方案就是改為逆向遍歷

    // 創(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; }

    完整代碼如下:

    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)景

  • 將北京市所有常駐人口按11位手機(jī)號(hào)排序
  • 將全國(guó)人民按身份證號(hào)排序
  • 將牛津詞典中所有單詞排序
  • 這里涉及到的都是字符串排序,而且滿足如下條件:
    第一個(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)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。