データ構造とアルゴリズムの美しさ | ソート (3)

リニアソート

バケットソート

基本的な考え方:

  • 並べ替えられるデータは、順序付けされたいくつかのバケットに分割され、各バケット内のデータは個別に並べ替えられます。
  • バケットがソートされた後、各バケット内のデータが順番に取り出され、形成された順序が整います。
  • バケット ソートは外部ソート [^1] でよく使用されます。

10 GB の注文データがあり、注文金額で並べ替えたいと考えています (金額がすべて正の整数であると仮定します)。ただし、メモリは数百 MB と限られており、10 GB のデータすべてをロードする方法はありません。一気に記憶の中に。この時どうすればいいでしょうか?

解決策:

  1. まずファイルをスキャンして、注文金額のデータ範囲を確認します。

    スキャンした結果、最低注文金額が 1 元、最高注文金額が 100,000 元であることがわかったとします。

  2. すべての注文を金額ごとに 100 個のバケットに分割します。

    最初のバケットには 1 元から 1,000 元の範囲の金額の注文が保存され、2 番目のバケットには 1,001 元から 2,000 元の範囲の金額の注文が保存されます。各バケットはファイルに対応し、金額範囲の順に番号が付けられます (00、01、02…99)。

  3. 理想的には、注文量が 1 ~ 100,000 の間で均等に配分される場合、注文は 100 個のファイルに均等に分割されます。

    1) それぞれの小さなファイルには約 100 MB の注文データが保存されており、これら 100 個の小さなファイルを 1 つずつメモリに置き、クイック ソートを使用して並べ替えることができます。

    2) すべてのファイルがソートされたら、ファイル番号に従って小さいファイルから大きいファイルまで順番にデータを読み取って、ファイルに書き込むだけで、このファイルに格納されるのは、次の順序でソートされた順序データです。量は少ないものから多いものまで。

  4. 理想的ではない状況では、注文が 1 元から 100,000 元までの金額に応じて必ずしも均等に配分されるわけではありません。

    一定量の範囲で大量のデータがあり、分割後の対応するファイルは非常に大きくなり、一度にメモリに読み込むことができない可能性があります。

  5. これらの分割後もまだ比較的大きいファイルについては、分割を続けることができます。

    たとえば、1 元から 1,000 元までの注文金額が多いため、この範囲を 1 元から 100 元、101 元から 200 元、201 元から 300 元...901 元という 10 個の小さな間隔に分割していきます。 1000人民元まで。分割後も 101 元から 200 元までの注文が多すぎて一度にメモリに読み込むことができない場合は、すべてのファイルがメモリに読み込めるようになるまで分割を続けます。

# bucket sort in Python

import math

def bucket_sort(order_data):
    '''
    使用桶排序算法对订单数据进行排序
    
    参数:
    	order_data(list):待排序列表
    
    返回值:
    	sorted_data(list): 已完成排序列表
    
    '''
    # 找到最大的订单金额
    max_amount = max(order_data)

    # 确定桶的数量
    num_buckets = math.ceil(max_amount / 100)

    # 创建空桶
    buckets = [[] for _ in range(num_buckets)]

    # 将订单数据放入对应的桶中
    for amount in order_data:
        # 划分为100个桶
        bucket_index = amount // 100
        buckets[bucket_index].append(amount)

    # 对每个桶中的数据进行排序
    for bucket in buckets:
        bucket.sort()

    # 合并所有桶的数据
    sorted_data = []
    for bucket in buckets:
        sorted_data.extend(bucket)

    return sorted_data

# 示例订单数据(假设为整数列表)
order_data = [102, 75, 158, 42, 95, 205, 300, 280, 150, 50, 180, 220, 500, 350]

# 使用桶排序进行排序
sorted_orders = bucket_sort(order_data)

# 打印排序结果
print(sorted_orders)

バケットソートアルゴリズムの評価:

  • 実行効率: 最良の場合の時間計算量はO ( n + k ) O(n+k)です。O ( n+k )、最悪の場合の時間計算量はO ( n 2 ) O(n^2)O ( n2 )、ケースの平均時間計算量はO (n) O(n)O ( n )

  • メモリ消費量: O ( n + k ) O(n+k)の空間計算量を持つインプレース ソート アルゴリズムです。O ( n+k )

  • 安定性: 安定した並べ替えアルゴリズムです。


カウントソート

基本的な考え方:

  • カウンティングソートは特別な種類のバケットソートです

  • 配列内の一意の各要素の出現数を数えることによって、配列の要素を並べ替えます。

  • カウントは補助配列に格納され、ソートはカウントを補助配列のインデックスにマッピングすることによって行われます。

大学入学試験のスコアをチェックすると、システムは私たちの成績と私たちの州のランキングを表示します。あなたの州に 500,000 人の候補者がいる場合、結果をすばやく並べ替えてランキングを取得するにはどうすればよいでしょうか?

解決:

  1. 候補者のフルスコアは 900 ポイントで、最小値は 0 ポイントです。このデータの範囲は非常に狭いため、901 個のバケットに分割でき、対応するスコアの範囲は 0 から 900 ポイントです。
  2. 候補者のスコアに基づいて、500,000 人の候補者をこれらの 901 個のバケットに分割しました。バケット内のデータはすべて同じスコアを持つ候補であるため、並べ替える必要はありません。
  3. 各バケットを順番にスキャンし、バケット内の候補を順番に配列に出力するだけで、500,000 個の候補のソートが実現されます。スキャン トラバーサル操作のみが含まれるため、時間計算量は O(n) です。
# count sort in Python

def counting_sort(arr):
    """
    计数排序,arr是数组。
    假设数组中存储的都是非负整数
    
    参数:
    	arr (List[int]): 输入的数组

    返回值:
        None: 原地排序,直接修改输入的数组arr
    """

    n = len(arr)
    if n <= 1:
        return

    # 查找数组中数据的范围
    max_val = arr[0]
    for i in range(1, n):
        if max_val < arr[i]:
            max_val = arr[i]
            
	# 申请一个计数数组count,下标大小[0, max_val]
    count = [0] * (max_val + 1)  

    # 统计每个元素在输入数组中出现的次数
    for i in range(n):
        count[arr[i]] += 1

    # 依次累加
    for i in range(1, max_val + 1):
        # 帮助计算每个元素应该放置在排序结果数组中的正确索引位置
        # count[i] 保存的值表示了输入数组中小于等于 i 的元素的个数
        count[i] = count[i - 1] + count[i]

    # 临时数组result,存储排序之后的结果
    result = [0] * n

    # 从输入数组的最后一个元素开始向前遍历,即从右往左,从后往前遍历。
    for i in range(n - 1, -1, -1):
        index = count[arr[i]] - 1
        result[index] = arr[i]
        count[arr[i]] -= 1

    # 将结果拷贝给arr数组
    for i in range(n):
        arr[i] = result[i]

カウンティングソートアルゴリズムの評価:

  • 実行効率: 最良の場合の時間計算量はO ( n + k ) O(n+k)です。O ( n+k )、最悪の場合の時間計算量はO ( n + k ) O(n+k)O ( n+k )、平均ケース時間計算量はO ( n + k ) O(n+k)O ( n+k )

  • メモリ消費量: 空間複雑度がO ( N ) O(N)のインプレース ソート アルゴリズムです。O ( N )

  • 安定性: 安定した並べ替えアルゴリズムです。

知らせ:

  • カウントソートは、データ範囲が大きくないシナリオでのみ使用できます。データ範囲 K がソート対象のデータ n よりはるかに大きい場合、カウントソートの使用は適していません。

  • カウント ソートでは、非負の整数のみをソートできます。ソートするデータが他の型の場合は、相対サイズを変更せずに非負の整数に変換する必要があります。

基数ソート

基本的な考え方:

  1. 要素は、最初に同じビット値を持つ個々の数値をグループ化することによって並べ替えられます。
  2. 次に、要素は昇順/降順に従って並べ替えられます。
# Radix sort in Python

def countingSort(array, place):
    '''
	使用计数排序根据位数对元素进行排序。
	
	参数:
        array (List[int]): 输入的数组。
        place (int): 当前位数。

	返回值:
    	None
	'''
    size = len(array)
    output = [0] * size
    count = [0] * 10

    # # 统计每个数字出现的次数
    for i in range(0, size):
        index = array[i] // place
        count[index % 10] += 1

    # 计算数字累计出现的次数
    for i in range(1, 10):
        count[i] += count[i - 1]

    # 按照位数顺序将元素放置到输出数组中
    i = size - 1
    while i >= 0:
        index = array[i] // place
        output[count[index % 10] - 1] = array[i]
        count[index % 10] -= 1
        i -= 1
        
	# 将输出数组复制回原始数组
    for i in range(0, size):
        array[i] = output[i]



def radixSort(array):
    """
	实现基数排序的主函数。
	
	参数:
    	array (List[int]): 输入的数组。

	返回值:
    	None
	"""
   
    max_element = max(array)

    # 根据数字的位数进行计数排序
    place = 1
    while max_element // place > 0:
        countingSort(array, place)
        place *= 10


data = [121, 432, 564, 23, 1, 45, 788]
radixSort(data)
print(data)

基数ソートアルゴリズムの評価:

  • 実行効率: 最良の場合の時間計算量はO ( n + k ) O(n+k)です。O ( n+k )、最悪の場合の時間計算量はO ( n + k ) O(n+k)O ( n+k )、平均ケース時間計算量はO ( n + k ) O(n+k)O ( n+k )

  • メモリ消費量: 空間複雑度がO ( N ) O(N)のインプレース ソート アルゴリズムです。O ( N )

  • 安定性: 安定した並べ替えアルゴリズムです。

:

  • 基数ソートには、データをソートするための要件が​​あります。比較のために独立した「ビット」を分離できる必要があり、ビット間には漸進的な関係があります。データ a の上位ビットがデータ b より大きい場合、残りの下位ビットは比較する必要はありません。
  • さらに、各ビットのデータ範囲は大きすぎてはならず、線形ソート アルゴリズムを使用してソートする必要があります。そうしないと、基数ソートの時間計算量を O(n) にすることはできません。

参考文献

おすすめ

転載: blog.csdn.net/YuvalNoah/article/details/131163051