基數排序詳解


基數排序詳解


摘要:基數排序是一種代碼量比較復雜,但是時間復雜度比較低的排序,其時間復雜度和數組規模以及使用到的桶的個數相關,基數排序和計數排序、桶排序有很大的相關性,我們在學習排序的時候一般會成套的學習這三種排序,基數排序是這三種使用到“桶”的排序中時間復雜度比較高的一個,但是它的最好情況與最壞情況也比較穩定。接下來我們學習基數排序

1.基數排序算法圖解

​ 基數排序需要根據數組中所有元素的具體情況構建一個桶,並不斷地重復將數組中元素放入桶中並不斷拿出來的這個過程,根據其內部相關機制,最后這個數組就會變得有序,接下來我們使用圖解的方式對這個算法過程進行深入理解。

​ 基數排序的應用場景一般都是數組中元素存在多位數,否則其將發生退化的現象,這個問題在我們了解了基數排序算法之后,就會有更深刻的理解,首先我們得到了一個待排序的數組:

​ 現在我們創建“”,什么是桶?桶就是一個根據某種分類規則分類存放數據元素的結構,也就是根據事物的某種特征對一堆混在一起的事物進行分類,並根據分類規則及結果將元素們分別放在相互隔離的存儲空間里,這就叫桶。打個比方,我們現在有籃球,足球,網球,棒球,乒乓球,排球幾種運動器材,它們現在混在一起堆成一堆,我們拿來了六個大桶,根據它們外形的不同,或者直接就是根據它們球種類的不同,分門別類的放進這六個大桶中,讓這些球同類的放在一個桶中,不同類型的球被自己的桶相互隔離,這就是排序時用到的桶的作用。簡而言之:桶就是分類存放某種數據的一個數據結構。接下來我們看看排序用到的桶具體是什么樣子的,這樣能夠進一步加深理解:

​ 在這里我們使用到的桶是這樣的,為什么是這樣呢?基數排序中的桶的分類規則又是什么呢?且聽我細細道來:在基數排序中,數字會被多次放入到桶中,每次數字被放入到桶中時,都是按照其某一個數位進行分類的,簡而言之,基數排序中的桶分類數據的規則是按照數字中某一位的值為多少進行分類的,也就是按照數字們的某個位如個位,十位,百位...上的具體數字進行分類,數字相同的將被分在一個桶中,如:

147
68
6
1376
167
139
27
這個桶會按照這些數字的個位,十位,百位進行分類,它是按照數字們同量級位上的數字進行分類的,在這里,這個桶會將這些數組按照個位分成:{6,1376},{147,167,27},{68},{139}四組。

​ 那么這個桶的具體構造又怎么解釋呢?按照這里的桶的分類規則,也就是按照十位數字的每一個位上邊的數字進行分類這個規則,我們可以推斷出,這個桶的數量,一定超不過10,這是因為在十進制中,每一個數位上的值只有0~9這10個數字,因此在基數排序中的所有桶的列數,都是10,而行數,我們則需要考慮到數組元素中的值的個數,如果我們桶結構的行數只有5行,數組元素有10個,那么桶結構能夠存儲的數字就是5*10,也就是50個,看上去存儲數組這10個值完全是綽綽有余的,然而,當數組中的這十個值的個位或者十位(反正是有這么一位)都相同的話,那么在某一個時刻,這十個值一定會聚集到這一排桶中的其中一個桶中去,而每一個桶的上限就是行數,也就是5,那么這時就會產生棧溢出,因此,我們為了保險起見,將桶的行數要設定為數組中的元素值的個數,以防出現這種情況,因此在這里我們的數組又十個值,這個桶結構就是有十行十列的,十列代表桶結構有十個桶,十行代表每個桶的容量都是10。

​ 我們在基數排序的過程中,要不斷地將數組中的元素放進桶中並拿出來,我們將元素放進桶中的話,那必須要有一個變量來記住每個桶中已經放入元素的數量,這樣我們在往外取的時候才能正常取出,否則就會兩眼一抹黑,取不到正確的數量,因此我們還需要額外的設計一個新的數組結構,來記錄每個桶中的元素數量,因為有十個桶,所以我們要聲明一個長度為十的數組,這個數組中的每一位都會和每一個桶相對應,這個對應關系時基於數學原理的,因為桶的列下標就等於桶中存放的位數值,並等於記錄數組的下標,它們三個是完全自然對應的。每當一個桶中新加入了一個值,記錄數組的相應元素就要自增1,如圖:

​ 至此我們講解了基數排序中通的基本定義與分類原則,至於是如何按照位數分類的,具體怎樣操作,我們即將以一個從小到大的基數排序例子來詳細解說基數排序的過程:

​ 我們首先要根據個位上的數字,把這個數組中的元素全部放入到桶結構中,進而進行一次分類,首先我們遍歷整個數組,將數組中的值放入桶中,在放入的時候要根據每個數字的個位進行放置:

​ 首先我們獲取到了第一個元素,並且知道了它的個位數字為4,因此我們將其放到下標為4的桶中去,並將下標為4的桶的長度記錄變量加1,這個長度記錄變量的另一層含義也是這個桶的頭指針,如當前這個桶的長度記錄變量為1,既說明現在桶中有一個元素,也讓其指向了桶中下一個為空的位置,下一次放入時直接放入這個位置再讓它自增1,表示當前有兩個元素,並且它又指向了下一個新的空位置,這里的數學思想非常好。現在我們跳過整個放數字的繁瑣過程,直接展現數字全都放好之后的狀態:

​ 如圖所示,當前是所有數組元素全部放入桶中后的桶的狀態,現在我們需要按照順序,將桶中元素依次再放入數組中,這個順序當然就是根據下標(注意下標和數位值是相等的)從小到大的順序放回,在放回過程中,我們要遍歷桶的記錄變量數組,也就是藍色填充的數組,我們遍歷這個數組,如果發現其值不等於0,那么說明它對應的桶里是存在數字的,我們就要將這個數字取出來,放到數組中,現在我們詳細進行這個過程:

​ 首先我們檢測到了1位置,里邊只有一個數字21,我們將其放到數組的0下標出,之后繼續遍歷,並將原數組的位置指針向后移動一個單位。

​ 之后是個位為2的數字,我們也將其放入到了原數組中,之后我們又檢測到了個位為3的位置,也存在一個數值,我們將其取回並放入到原數組中:

​ 之后我們有檢測到了個位為4的數字,有兩個,我們依次將其取回,如圖:

​ 之后我們又檢測到了個位為6的數字,我們將其取出並放在原數組中:

​ 之后我們又檢測到了個位為7的數字三個,我們依次將它們取出並放在原數組中:

​ 最后我們發現個位為9的數字也有一個,我們將其取出並放到數組中去,至此第一輪循環宣告完成:

​ 現在我們按照所有元素的個位進行了第一輪排序,可見現在的數組仍然是一個無序狀態,但是現在我們可以發現它們的個位上的數字實際上已經按照從小到大的順序排好了。之后,我們還要再進行一輪排序,此時我們就不會再需要使用個位上的數字進行排序了,而是使用十位上的數字,那么問題來了,既然我們將要使用十位上的數字,那么個位數怎么辦呢?在代碼中,我們使用的是除以10^n之后再對10求余的方式獲取每一位的,例如:

對於9和98,我們在取得它的個位數時,是先除以1*10^0之后,在對10求余,這樣一來9通過運算可以得到9,而98則會得到8
當我們取得它的十位數時,我們要先除以1*10^1之后,在對10求余,此時9在除1*10^1的時候,就已經變成了0,之后再對10求余的結果還是0
而98則不同,它在除以1*10^1之后,變成了9,再對10求余,結果就成了9,這樣一來就成功取到了98的十位上的數字9

​ 因此我們在第二輪循環的時候,之前的個位數會統統被按照之前的順序被依次放置到下標為0的桶上去,也就是十位數為0的桶,因為第一輪循環是按照個位數字進行的排序,因此實際上在第一輪循環中個位數已經被排好序了,因此這樣被依次放置到下標為0的桶上去,實際上是一次對個位數的分類與整合,讓個位數都進到了第一個桶中去,這樣一來在下一次的桶中取數過程中,我們就會首先按照順序的從第一個桶中取到所有的個位數並放到最前邊,這樣實際上就是已經完成了個位數的排序,而其他的十位數百位數則將繼續像第一輪循環中那樣被放在之后的桶中,並繼續按照十位上的數字進行排序,現在我們直接展現第二輪循環的結果:

​ 如圖所示這是我們按照十位上的數字放置到桶中的狀態,之前的個位數因為十位上數字被計算為0,而依次被排布到了下標為0的桶上去,我們現在發現這個桶上的各位數字其實已經被排好序了,之后我們按照之前的規則依次將桶中的數字放回到數組中去,現在我們直接展示這個直接結果:

​ 我們現在可以發現除了個位數已經按照順序的被排布在了最前邊,其余數字也是按照十位數從小到大的順序被排布好了,與此同時我們發現了一個神奇的規律,這是百位數雖然穿插在不符合位置的地方,但是整個數組中的十位數的大小順序,實際上已經符合從小到大的規律了,如果你細心一點可以發現,早在第一輪循環結束后,個位數的從前到后順序就已經是正確的了,盡管十位數和百位數穿插在它們期間,但是在第二輪循環中,它們就會因為將要研究十位,而它們的十位上是0這一共同點,被有序的收集到了下標為0的桶中去,進而連接在了一起,成為了一個有序的子數組。

​ 這時這個待遇將輪到十位數享受了,現在我們即將開啟第三輪循環,第三輪循環我們研究的是百位上的數字,當我們研究百位上的數字的時候,根據上文中提到的位數獲取算法,我們就會發現,在第三輪循環中,十位數字也會被有序的存放在下標為0的桶中,因為這個存放順序是從前到后的,因此肯定是個位數字在前邊,十位數字在后邊,在第三輪循環中放入桶中操作結束之后,我們發現當前桶的結構是這樣的:

​ 我們發現個位數和十位數已經被有序的存放到下標為零的桶中去了,在下標為1的桶中有157,下標為4的桶中有457,接下來我們進行取數放入桶中的操作,在按照順序取數並放回桶中之后,我們發現了現在數組變成了這樣:

​ 這個數組已經排好序了。

2.基數排序的解讀

​ 在我們進行排序時,肯定是個位數小於十位數小於百位數,基數排序的思想就是先對個位數進行排序,然后對十位數進行排序,最后對百位數進行排序。整體思想上是使用了桶的思想。整個桶的思想,我們可以理解為,排序+收集。先排個位,再排十位,再排百位...總體這樣下來,一輪輪就有序了。

​ 細化來講,在第一輪基數排序的時候,我們會先研究所有數字的個位,並按照個位上的數組排好序,在經過第一輪的排序之后,所有數字便已經按照個位排好序了,而之后我們再按照十位數進行排序,而這時數組中的擁有十位數字的數肯定是比只擁有個位數字的個位數大,不管它們怎么排,我們都知道它們必定會比各位數字大,因此這時我們完全可以心安理得的將個位數按照第一輪的順序排在數組的最前邊,然后在后邊對擁有十位數值的大數們進行排序,同理在研究百位的時候,我們也會這樣對待之前的僅有十位和個位的十位數們,這樣依次排序,我們就能將這些數字排好序。

3.代碼

    public static void ridix(int[] arr) {// 基數排序
        int max = arr[0];
        for (int i = 1; i < arr.length; i++) {
            if (max < arr[i])
                max = arr[i];
        }
        int maxSize = (max + "").length();
        for (int i = 1, n = 1; i <= maxSize; i++, n *= 10) {
            int[][] bucket = new int[10][arr.length];
            int[] bucketElement = new int[10];
            for (int j = 0; j < arr.length; j++) {
                int element = arr[j] / n % 10;
                bucket[element][bucketElement[element]] = arr[j];
                bucketElement[element]++;
            }

            for (int x = 0, index = 0; x < bucketElement.length; x++) {
                if (bucketElement[x] != 0) {
                    for (int z = 0; z < bucketElement[x]; z++) {
                        arr[index] = bucket[x][z];
                        index++;
                    }
                }
                bucketElement[x] = 0;
            }

        }
        System.out.println(Arrays.toString(arr));
    }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM