1. 程式人生 > >算法筆記-桶排序、計數排序、基數排序

算法筆記-桶排序、計數排序、基數排序

.... img tex continue += 序列 nlogn $max part

三種時間復雜度是 O(n) 的排序算法:桶排序、計數排序、基數排序。因為這些排序算法的時間復雜度是線性的,所以我們把這類排序算法叫作線性排序(Linear sort)。 桶排序(Bucket sort) 將要排序的數據分到幾個有序的桶裏,每個桶裏的數據再單獨進行排序。桶內排完序之後,再把每個桶裏的數據按照順序依次取出,組成的序列就是有序的了。 桶排序對要排序數據的要求是非常苛刻的。首先,要排序的數據需要很容易就能劃分成 m 個桶,並且,桶與桶之間有著天然的大小順序。這樣每個桶內的數據都排序完之後,桶與桶之間的數據不需要再進行排序。其次,數據在各個桶之間的分布是比較均勻的。如果數據經過桶的劃分之後,有些桶裏的數據非常多,有些非常少,很不平均,那桶內數據排序的時間復雜度就不是常量級了。在極端情況下,如果數據都被劃分到一個桶裏,那就退化為 O(nlogn) 的排序算法了。 桶排序比較適合用在外部排序中。所謂的外部排序就是數據存儲在外部磁盤中,數據量比較大,內存有限,無法將數據全部加載到內存中。
//
先引用之前的 快排的代碼 function quickSort(array &$a) { $n = count($a); quickSortInternally($a, 0, $n - 1); } function quickSortInternally(array &$a, int $l, int $r) { if ($l >= $r) return; $q = partition($a, $l, $r); quickSortInternally($a, $l, $q - 1); quickSortInternally($a, $q
+ 1, $r); } function partition(&$a, $l, $r): int { $pivot = $a[$r]; $i = $l; for ($j = $l; $j < $r; ++$j) { if ($a[$j] < $pivot) { [$a[$j], $a[$i]] = [$a[$i], $a[$j]]; ++$i; } } [$a[$r], $a[$i]] = [$a[$i], $a[$r]]; return $i; }
/** * 桶排序 * 假設一個桶只能放置10個元素 * 當一個桶內元素過多,需要繼續分桶 * @param array $numbers * @param [type] $size * * @return void * @date 2018/11/25 * @author yuanliandu */ function bucketSort(array $numbers) { $min = min($numbers); $max = max($numbers); $length = count($numbers); $bucketNumber = ceil(($max-$min)/$length) + 1; $buckets = []; foreach($numbers as $key => $value) { $index = ceil(($value-$min)/$length); $buckets[$index][] = $value; } $result = []; for($i=0;$i<$bucketNumber;$i++) { $bucket = $buckets[$i]; $length = count($bucket); //如果桶內元素為空,跳過這個桶 if($length == 0) { continue; } if( $length > 10) { $bucket = bucketSort($bucket,$length); } quickSort($bucket,0,count($bucket)-1); $result = array_merge($result,$bucket); } return $result; } $numbers = [11, 23, 45, 67, 88, 99, 22, 34, 56, 78, 90, 12, 34, 5, 6, 91, 92, 93, 93, 94, 95, 94, 95, 96, 97, 98, 99, 100]; $size = 10; print_r(bucketSort($numbers, 10));

計數排序(Counting sort)—— 其實是桶排序的一種特殊情況 當要排序的 n 個數據,所處的範圍並不大的時候,比如最大值是 k,我們就可以把數據劃分成 k 個桶。每個桶內的數據值都是相同的,省掉了桶內排序的時間 計數排序只能用在數據範圍不大的場景中,如果數據範圍 k 比要排序的數據 n 大很多,就不適合用計數排序了。而且,計數排序只能給非負整數排序,如果要排序的數據是其他類型的,要將其在不改變相對大小的情況下,轉化為非負整數。 問題:如何根據年齡給100萬用戶數據排序? 我們假設年齡的範圍最小 1 歲,最大不超過 120 歲。我們可以遍歷這 100 萬用戶,根據年齡將其劃分到這 120個桶裏,然後依次順序遍歷這 120 個桶中的元素。這樣就得到了按照年齡排序的 100 萬用戶數據。
<?php

/**
 * 計數排序
 * 五分制
 * 13個人
 */
$score = [0, 1, 5, 3, 2, 4, 1, 2, 4, 2, 1, 4, 4];
print_r(countingSort($score));

function countingSort(array $score)
{

    $length = count($score);
    if ($length <= 1) {
        return $score;
    }

    /**
     * 統計每個分數的人數
     */
    $temp = [];
    $countScore = [];
    foreach ($score as $key => $value) {
        @$countScore[$value]++;
    }

    /**
     * 順序求和
     */
    for ($i = 1; $i <= 5; $i++) {
        $countScore[$i] += $countScore[$i - 1];
    }
    /**
     * 排序
     */
    foreach ($score as $key => $value) {
        $countScore[$value]--;
        $temp[$countScore[$value]] = $value;
    }
    //copy
    for ($i = 0; $i < $length; $i++) {
        $score[$i] = $temp[$i];
    }
    return $score;
}
基數排序(Radix sort) 假設有 10 萬個手機號碼,希望將這 10 萬個手機號碼從小到大排序,你有什麽比較快速的排序方法呢? 有這樣的規律:假設要比較兩個手機號碼 a,b 的大小,如果在前面幾位中,a手機號碼已經比 b 手機號碼大了,那後面的幾位就不用看了。 基數排序對要排序的數據是有要求的,需要可以分割出獨立的“位”來比較,而且位之間有遞進的關系,如果 a 數據的高位比 b 數據大,那剩下的低位就不用比較了。除此之外,每一位的數據範圍不能太大,要可以用線性排序算法來排序,否則,基數排序的時間復雜度就無法做到 O(n) 了。
<?php

/**
 * 基數排序
 * 先根據個位排序、百位、千位........
 */
$numbers = [
    1234,
    4321,
    12,
    31,
    412,
];
$max = (string)max($numbers);//求出最大數字
$loop = strlen($max);//計算最大數字的長度,決定循環次數

for ($i = 0; $i < $loop; $i++) {
    radixSort($numbers, $i);
}
print_r($numbers);

/**
 * 基數排序
 * @param array $numbers
 * @param [type] $loop
 */
function radixSort(array &$numbers, $loop)
{

    $divisor = pow(10, $loop);//除數  主要決定比較個位數、百位.....
    $buckets = (new \SplFixedArray(10))->toArray();
    foreach ($numbers as $key => $value) {
        $index = ($value / $divisor) % 10;//計算該數字在哪個桶中
        $buckets[$index][] = $value;
    }
    /**
     * 從桶中取出數字
     */
    $k = 0;
    for ($i = 0; $i < 10; $i++) {
        while (count($buckets[$i]) > 0) {
            $numbers[$k++] = array_shift($buckets[$i]);
        }
    }
}


如何實現一個通用的、高性能的排序函數? 技術分享圖片 快速排序比較適合來實現排序函數,如何優化快速排序?最理想的分區點是:被分區點分開的兩個分區中,數據的數量差不多。為了提高排序算法的性能,要盡可能地讓每次分區都比較平均。
  • 1. 三數取中法
①從區間的首、中、尾分別取一個數,然後比較大小,取中間值作為分區點。 ②如果要排序的數組比較大,那“三數取中”可能就不夠用了,可能要“5數取中”或者“10數取中”。
  • 2.隨機法:每次從要排序的區間中,隨機選擇一個元素作為分區點。
  • 3.警惕快排的遞歸發生堆棧溢出,有2種解決方法,如下:
①限制遞歸深度,一旦遞歸超過了設置的閾值就停止遞歸。 ②在堆上模擬實現一個函數調用棧,手動模擬遞歸壓棧、出棧過程,這樣就沒有系統棧大小的限制。 通用排序函數實現技巧 1.數據量不大時,可以采取用時間換空間的思路 2.數據量大時,優化快排分區點的選擇 3.防止堆棧溢出,可以選擇在堆上手動模擬調用棧解決 4.在排序區間中,當元素個數小於某個常數是,可以考慮使用O(n^2)級別的插入排序 5.用哨兵簡化代碼,每次排序都減少一次判斷,盡可能把性能優化到極致

算法筆記-桶排序、計數排序、基數排序