算法——其餘排序算法

1、希爾排序(Shell Sort)

  希爾排序(Shell Sort)是一種分組插入排序算法。希爾排序也是一種插入排序,它是簡單插入排序通過改進以後的一個更高效的版本,也稱爲縮小增量排序,同時該算法是衝破O(n2)的第一批算法之一。python

一、算法思路

首先取一個整數d1=n/2,將元素分爲d1個組,每組相鄰量元素之間距離爲d1,在各組內進行直接插入排序;git

取第二個整數d2=d1/2,重複上述分組排序過程,直到di=1,即全部元素在同一組算法

希爾排序每趟並不使某些元素有序,而是使總體數據愈來愈接近有序最後一趟排序使得全部數據有序shell

二、算法思路圖解

  例若有一個列表有九個元素以下所示:app

  

(1)第一趟排序

  取d1=9/2獲得d1=4,將整個列表分爲4組,每組相鄰元素間距離爲4:dom

  

  將各個組內進行插入排序:函數

  

  排序完成後,將這些元素返回列表:性能

  

(2)第二趟排序

  取d2=d1/2,獲得d2=2,將整個列表分爲2組,每組相鄰元素間距爲2:測試

  

  再在各個組內進行插入排序:spa

  

  排序完成後,再將這些元素返回列表:

  

(3)最後一趟排序

  此時d3=d2/2,獲得d3=1,此時直接進行插入排序:

  

  如此整個排序過程就完成了。希爾排序每趟並不使某些元素有序,而是使總體數據愈來愈接近有序

三、希爾排序代碼實現

def insert_sort_gap(li, gap):
    """
    希爾排序的分組進行插入排序
    :param li:列表
    :param gap:分組的d,將插入排序全部的1改成gap
    :return:
    """
    for i in range(gap, len(li)):  # i表示摸到牌的下標
        tmp = li[i]  # 摸到的牌
        j = i - gap  # j指得是手裏牌的下標(比摸到的牌小gap)
        while li[j] > tmp and j >= 0:  # 循環條件
            li[j + gap] = li[j]  # 若是手裏的牌大於摸到的牌,將摸到的牌換爲以前手裏的牌
            j -= gap  # 手裏的牌移動到摸到牌的位置

        li[j + gap] = tmp  # 將摸到的牌插入有序區
        # print(li)  # 打印每一趟排序過程


def shell_sort(li):
    """希爾排序"""
    d = len(li) // 2   # 取到第一次循環的d值
    while d >= 1:  # 每次循環d/2,知道d=1的時候結束循環
        insert_sort_gap(li, d)
        d //= 2    # d整除2並賦值給d

li = list(range(100))
import random
random.shuffle(li)
shell_sort(li)
print(li)

  在希爾排序的理解時,咱們傾向於對於每個分組,逐組進行處理,但在代碼實現中,咱們能夠不用這麼循序漸進地處理完一組再調轉回來處理下一組(這樣還得加個for循環去處理分組)好比[5,7,4,6,3,1,2,9,8] ,首次增量設gap=length/2=4,則爲4組[5,3,8] [7,1] [4,2] [6,9],實現時不用循環按組處理,咱們能夠從第gap個元素開始,逐個跨組處理。

四、測試希爾排序性能 

from cal_time import *
import copy, random

def insert_sort_gap(li, gap):...

@cal_time
def shell_sort(li):
    """希爾排序"""
    d = len(li) // 2   # 取到第一次循環的d值
    while d >= 1:  # 每次循環d/2,知道d=1的時候結束循環
        insert_sort_gap(li, d)
        d //= 2    # d整除2並賦值給d

from insert_sort import insert_sort
from heap_sort import *

li = list(range(10000))
random.shuffle(li)

li1 = copy.deepcopy(li)
li2 = copy.deepcopy(li)
li3 = copy.deepcopy(li)

shell_sort(li1)
insert_sort(li2)
"""
shell_sort running time: 0.05884504318237305 secs.
insert_sort running time: 4.995609283447266 secs.
"""
shell_sort(li1)
heap_sort(li3)
"""
shell_sort running time: 0.05833792686462402 secs.
heap_sort running time: 0.04569196701049805 secs.
"""

  因而可知希爾排序的運行效率要遠遠大於插入排序,與堆排序(牛逼三人組中最慢)相比略慢一點。

 五、希爾排序的時間複雜度

  希爾排序的時間複雜度比較複雜,且與選擇的gap(步長)序列有關。

  只要最終步長爲1任何步長序列均可以工做。算法最開始以必定的步長進行排序。而後會繼續以必定步長進行排序,最終算法以步長爲1進行排序。當步長爲1時,算法變爲普通插入排序,這就保證了數據必定會被排序。

  Donald Shell最初建議步長選擇爲n/2而且對步長取半直到步長達到1。雖然這樣取能夠比O(n2)類的算法(插入排序)更好,但這樣仍然有減小平均時間和最差時間的餘地。

  

  更多不一樣gap狀況時間複雜度:https://en.wikipedia.org/wiki/Shellsort#Gap_sequences

2、計數排序 

  對列表進行排序,已知列表中的數範圍都在0到100之間。設計時間複雜度爲O(n)的算法。

  統計每一個數字出現了幾回。

一、計數排序代碼實現

def count_sort(li, max_count=100):
    """
    計數排序
    :param li:
    :param max_count:
    :return:
    """
    count = [0 for _ in range(max_count+1)]   # 生成一個值全爲0長度爲100+1的列表
    for val in li:   # 遍歷li列表的值
        count[val] += 1   # 值對應count列表下標,遍歷到便執行加1
        # 這樣設計也限制了不能用大於100的數字進行排序,由於超出了index下標的範圍
    li.clear()   # 列表清空
    for ind, val in enumerate(count):  # 下標、值
        for i in range(val):    # 遍歷值(對應下標的統計次數)
            li.append(ind)      # 將下標值添加到li列表中(統計了幾回就添加幾回)

import random
li = [random.randint(0, 100) for _ in range(10)]
print(li)
count_sort(li)
print(li)

  count[val] += 1是代碼的精髓,不只統計了對應值的出現次數,完成了排序,還限制了不能用大於下標範圍的數字進行排序,若是超出下標返回,會提示報錯。

二、計數排序和系統內置sort函數性能對比

from cal_time import *

@cal_time
def count_sort(li, max_count=100):
    """
    計數排序
    :param li:
    :param max_count:
    :return:
    """
    count = [0 for _ in range(max_count+1)]   # 生成一個值全爲0長度爲100+1的列表
    for val in li:   # 遍歷li列表的值
        count[val] += 1   # 值對應count列表下標,遍歷到便執行加1
    li.clear()   # 列表清空
    for ind, val in enumerate(count):  # 下標、值
        for i in range(val):    # 遍歷值(對應下標的統計次數)
            li.append(ind)      # 將下標值添加到li列表中(統計了幾回就添加幾回)


@cal_time
def sys_sort(li):
    li.sort()


import random,copy
li = [random.randint(0, 100) for _ in range(100000)]

li1 = copy.deepcopy(li)
li2 = copy.deepcopy(li)

count_sort(li1)
sys_sort(li2)
"""
count_sort running time: 0.0277559757232666 secs.
sys_sort running time: 0.02849888801574707 secs.
"""

  系統排序是用c語言寫的,所以運行效率很高,在這裏能夠看到計數排序效率比系統排序還要高。

三、計數排序時間複雜度

  對列表進行排序,已知列表中的數範圍都在0到100之間。設計時間複雜度爲O(n)的算法。0-100須要消耗一個長度是100的列。若是是一億就要開一億長的列。

  經常使用於年齡等排序。此時再也不往裏面append值,而是append對象。

3、桶排序(Bucket Sort)

  在計數排序中,若是遇到元素值的範圍比較大(好比在1到1億之間),是不適用的,改造算法就有桶排序。

  桶排序:首先將元素分在不一樣的桶中,再對每一個桶中的元素排序

  

一、桶排序初步代碼實現

def bucket_sort(li, n=100, max_num=10000):
    """
    桶排序
    :param li:
    :param n: 桶的個數
    :param max_num: 數字最大值
    :return:
    """
    buckets = [[] for _ in range(n)]   # 建立桶:列表生成式生成二維列表,[[], [],...,[]]
    for var in li:   # 遍歷列表全部數放在合適的桶裏
        # i = var // (max_num // n)   # i表示var放到幾號桶裏86//(10000//100)=0,因此放在0號桶,可是處理不了10000
        i = min(var // (max_num // n), n-1)  # 爲了解決10000這個數,將原i值和n-1=99做比較取小
        buckets[i].append(var)    # 將var放入對應的桶內

        # 保持桶內的順序(插入排序)
        for j in range(len(buckets[i])-1, 0, -1):  # 在列表[4,7,2,5]從後往前倒着取值
            if buckets[i][j] < buckets[i][j-1]:    # 若是後面的元素小於前一個元素就交換它
                buckets[i][j], buckets[i][j-1] = buckets[i][j-1], buckets[i][j]
            else:
                break

    # 將桶裏的數輸出出來
    sorted_li = []
    for buc in buckets:   # buc是每個桶
        sorted_li.extend(buc)
    return sorted_li


import random
li = [random.randint(0,10000) for i in range(100000)]  
print(li)
li = bucket_sort(li)
print(li)

二、桶排序的性能

  桶排序的表現取決於數據的分佈,也就是須要對不一樣的數據排序時採起不一樣的分桶策略

  n是列表的長度,k是桶的個數。

  平均狀況事件複雜度:O(n+k),相似於線性的複雜度。

  最壞狀況時間複雜度:O(n2k),最壞狀況下比O(n2)還要高。

  空間複雜度:O(nk),佔用了一個桶的空間,佔用了O(nk)。

4、基數排序(radix sort)

  基數排序(radix sort)屬於「分配式排序」(distribution sort),又稱「桶子法」(bucket sort)或bin sort,顧名思義,它是透過鍵值的部份資訊,將要排序的元素分配至某些「桶」中,藉以達到排序的做用,基數排序法是屬於穩定性的排序。

一、多關鍵字排序

  假如如今有一個員工表,要求按照薪資排序,薪資相同的員工按照年齡排序。

  先按照年齡進行排序,再按照薪資進行穩定的排序。

  對32,13,94,52,17,54,93排序,是否能夠看做多關鍵字排序?能夠,十位看做是第一關鍵字,個位看做是第二關鍵字。

二、圖解多關鍵字排序

  先按照個位分桶:

  

  接下來依次輸出每一個桶的數據:

  

  這樣就實現了按個位數進行排序,知足了個位數小的必定在前面

  接下來要按照十位數來分桶:

  

  再次依次輸出每一個桶的數據:

  

三、基數排序代碼實現

def radix_sort(li):
    """基數排序,裝桶輸出不作排序"""
    max_num = max(li)    # 最大值99->2, 888->3, 10000->5
    it = 0
    while 10 ** it <= max_num:  # 若是10的it次方小於等於max_num,即知足循環條件
        buckets = [[] for _ in range(10)]   # 生成10個桶
        for var in li:
            """
            取數字個位數的值:987%10  取餘
            取數字十位數的值:(987//10)%10  取整再取餘
            取數字百位數的值:(987//100)%10   對100取整再取餘
            """
            digit = (var // 10 ** it) % 10
            buckets[digit].append(var)   # 分桶

        # 分桶完成,將數據依次取出
        li.clear()
        for buc in buckets:
            li.extend(buc)   # 將數據從新寫回li

        it += 1


import random
li = list(range(1000))
random.shuffle(li)
radix_sort(li)
print(li)

 分拆函數的寫法:

def list_to_buckets(li, base, iteration):
    """
    列表到桶
    :param li:
    :param base: 分的桶的個數
    :param iteration: 裝桶是第幾回迭代
    :return:
    """
    buckets = [[] for _ in range(base)]
    for number in li:
        digit = (number // (base ** iteration)) % base
        buckets[digit].append(number)
    return buckets

def buckets_to_list(buckets):
    return [x for bucket in buckets for x in bucket]
    # li = []
    # for bucket in buckets:
    #     for num in bucket:
    #         li.append(num)

def radix_sort(li, base=10):
    maxval = max(li)
    it = 0
    while base ** it <= maxval:
        li = buckets_to_list(list_to_buckets(li, base, it))
        it += 1
    return li

import random
li = [random.randint(0,100) for _ in range(10)]
random.shuffle(li)
s = radix_sort(li)
print(s)   # [3, 10, 22, 27, 38, 43, 45, 54, 59, 72]

四、基數排序的特性和效率

  時間複雜度:O(kn),這裏的k = log10n,而快速排序是O(nlog2n)。因而可知基數排序比快排要快。可是當數字範圍愈來愈大,k愈來愈大時,效率會慢於快排。

  空間複雜度:O(k+n),基數排序空間上會消耗一個桶,空間消耗也是比較大的。所以最經常使用的仍是快速排序和python自帶的排序

相關文章
相關標籤/搜索