六種主流排序(冒泡、插入、選擇、快速、堆、歸併)類實現,性能測試與分析

----------------------------此模塊已上傳到Python內置pypi庫,可直接下載使用---------------

----------------------------命令: sudo pip install Lysort-------------------------------------

一、在常見的六種排序中(冒泡、插入、選擇、快速、堆、歸併),很多時候由於對它們內在的思想不夠了解,因此我們可以嘗試將其包裝成一個類,並提供幾個可以直接調用的類方法,便於日後的直接使用,這個模塊可以讓我們深入理解分治與遞歸思想。

由於還要進行性能測試,所以需要一個裝飾器作包裝類內部方法,但在設計上,若將其封裝與類內部,相比外部會使程序更加緊湊,而方法調用方面,由於排序方法可能很多,故不應該直接賦值於類,因爲這樣的賦值以後可能會導致使得類內部的數據混亂,所以應該爲其提供一些類方法供直接調用,在編寫思想上也將更加靈活。

由於列表的內存與賦值機制,使用deepcopy方法深拷貝測試,以防止被其他方法篡改數據,失去測試意義。

二、六種排序思想上的實現:

冒泡排序:左右兩兩相比,一遍找出一個極值。

選擇排序:一遍比一值,隨着運行後期比冒泡少比較很多值,理論效率高於冒泡。

插入排序:數據只與前面進行對比,目的只是找到自己的合適位置後,將其數據插入

快速排序:將數據兩兩分之,找出一個基準值作爲界限,隨後再次將其遞歸,重複遞歸直到low與high相等,排序完成。

堆排序: 將數據成爲完全二叉樹,且父節點比左右雙親節點值大(大頂堆),堆頂堆尾交換後修復堆,重複迭代直到排序完成。

歸併排序:分治法,數據分成有序(單獨)的個體小塊,然後每兩組之間比較並交叉插入直到溢出索引,重複遞歸直到排序完成。

 

代碼實現:

# Sort class
import time
import random
import sys
import copy

# 解鎖最大遞歸數,防止快速排序溢出
sys.setrecursionlimit(1000000)

"""
使用生成器實現可迭代的任意範圍任意數量的數字
並給出get_list()方法可供調用生成列表
"""


class Rand_Iter(object):
    def __init__(self, start, stop, num):
        self.start = start
        self.stop = stop
        self.num = num
        self.list = []

    def get_iter(self):
        while self.num > 0:
            rand = random.randint(self.start, self.stop)
            yield rand
            self.num -= 1

    def get_list(self):
        self.list = []
        self.iter = self.get_iter()
        if iter is not None:
            for val in self.iter:
                self.list.append(val)
        print("list created :", self.list)
        return self.list


"""
功能:實現sort()類
目的:直接調用,並能打印出運行時間
"""


class Sort():
    def __init__(self, data=None):
        self.data = data

    def __call__(self):
        return "My name is sort, my data: "

    # 封裝並將其包裝爲內置調用
    def time_test(func):
        def wrapper(self, *args, **kwargs):
            time_start = time.time()
            loading = func(self, *args, **kwargs)
            time_cost = time.time() - time_start
            print(func.__name__ + " cost time : " + str((time_cost)))
            return loading

        return wrapper

    @classmethod
    @time_test
    def bubble_sort(self, data):  # 冒泡排序
        for i in range(len(data) - 1):
            for j in range(len(data) - 1 - i):
                if data[j] > data[j + 1]:
                    data[j], data[j + 1] = data[j + 1], data[j]
        return data

    @classmethod
    @time_test
    def insert_sort(self, data):  # 插入排序
        for i in range(1, len(data)):
            insert_value = data[i]
            j = i
            while j > 0 and data[j - 1] > insert_value:
                data[j] = data[j - 1]
                j -= 1
            data[j] = insert_value
        return data

    @classmethod
    @time_test
    def selection_sort(self, data):  # 選擇排序
        for i in range(0, len(data)):
            for j in range(i, len(data)):
                if data[i] > data[j]:
                    data[i], data[j] = data[j], data[i]
        return data

    @classmethod
    def sub_sort(cls, low, high, data):  # 快速排序分部一
        key = data[low]
        while low < high:
            while low < high and data[high] >= key:
                high -= 1
            data[low] = data[high]
            while low < high and data[low] < key:
                low += 1
            data[high] = data[low]
        data[low] = key
        return low

    @classmethod
    def quick_sort_step(cls, low, high, data):  # #快排分部二
        # low: the first element index
        # high: the end element index

        if low < high:
            key = cls.sub_sort(low, high, data)
            cls.quick_sort_step(low, key - 1, data)
            cls.quick_sort_step(key + 1, high, data)
        return data

    @classmethod
    @time_test
    def quick_sort(cls, low, high, data):  # 供外部調用的方法,防止裝飾器重複打印
        # high = len(list) - 1
        list = cls.quick_sort_step(low, high, data)
        return list

    @classmethod
    def merge_sort_step(cls, list):            #歸併排序,分治過程

        if len(list) <= 1:
            return list
        num = len(list) // 2
        left = cls.merge_sort_step(list[:num])
        right = cls.merge_sort_step(list[num:])
        return cls.merge(left, right)  # 合併

    @classmethod
    def merge(cls, left, right):           #歸併過程,交叉比較直到有序
        left_index, right_index = 0, 0
        array = []
        while left_index < len(left) and right_index < len(right):
            if left[left_index] < right[right_index]:
                array.append(left[left_index])
                left_index += 1
            else:
                array.append(right[right_index])
                right_index += 1
        array += left[left_index:]
        array += right[right_index:]
        return array

    @classmethod
    @time_test                             
    def merge_sort(cls, list):                ## 供外部調用的方法,防止裝飾器重複打印
        list = cls.merge_sort_step(list)
        return list

    @classmethod
    def heap_ify(cls, list, size, root):  # 堆排序,向下遞歸重建堆
        # size = len(list)
        if root > size:
            return
        left = 2 * root + 1
        right = 2 * root + 2
        max = root
        if left < size and list[left] > list[root]:
            max = left
        if right < size and list[right] > list[max]:
            max = right
        if root != max:
            list[root], list[max] = list[max], list[root]
            cls.heap_ify(list, size, max)

    @classmethod
    def build_heap(cls, list, size):  # 創建堆
        # size  = len(list) - 2
        root = size - 1 // 2
        for i in range(root, -1, -1):
            cls.heap_ify(list, size + 1, i)

    @classmethod
    @time_test
    def heap_sort(cls, list, size):  # 外部調用方法
        # size = len(list) - 1
        cls.build_heap(list, size - 1)
        for i in range(size, -1, -1):
            list[0], list[i] = list[i], list[0]
            cls.heap_ify(list, i, 0)
        return list



if __name__ == "__main__":
    list = Rand_Iter(0, 100000, 30000).get_list()  # 生成30000個數字進行測試

    print(Sort.bubble_sort(copy.deepcopy(list)))

    print(Sort.selection_sort(copy.deepcopy(list)))

    print(Sort.insert_sort(copy.deepcopy(list)))

    print(Sort.quick_sort(0, len(copy.deepcopy(list)) - 1, copy.deepcopy(list)))

    print(Sort.heap_sort(copy.deepcopy(list), len(copy.deepcopy(list)) - 1))

    print(Sort.merge_sort(list))

三、效率與穩定性測試

在生成3萬個數據,且重複率低的情況下,六種排序的測試結果(排序功能均正常,由於篇幅不展示)

bubble_sort cost time : 58.23465037345886
selection_sort cost time : 33.53095078468323
insert_sort cost time : 32.03861880302429
quick_sort cost time : 0.07083845138549805
heap_sort cost time : 0.17736458778381348
merge_sort cost time : 0.11666345596313477

由此可見,在數據較爲龐大且重複率不高的情況下,冒泡排序效率最低,選擇排序比冒泡排序效率有所提升,插入排序比選擇提升不大,快速排序速度最快,並且遠遠超過前面三種排序方法,而歸併和堆排序則比快排稍慢一些,但是也遠遠超過了前三者。

將生成器改生成10個數據進行測試,其中重複率很低的情況下

  list = Rand_Iter(0, 100, 10).get_list()

測試結果爲插入排序速度最快, 選擇冒泡快排相差不大,堆排序和歸併由於要先創建堆和分治,所以速度最慢。

list created : [76, 95, 65, 27, 48, 58, 5, 90, 30, 52]
bubble_sort cost time : 9.775161743164062e-06
[5, 27, 30, 48, 52, 58, 65, 76, 90, 95]
selection_sort cost time : 9.059906005859375e-06
[5, 27, 30, 48, 52, 58, 65, 76, 90, 95]
insert_sort cost time : 5.245208740234375e-06
[5, 27, 30, 48, 52, 58, 65, 76, 90, 95]
quick_sort cost time : 9.5367431640625e-06
[5, 27, 30, 48, 52, 58, 65, 76, 90, 95]
heap_sort cost time : 1.7642974853515625e-05
[5, 27, 30, 48, 52, 58, 65, 76, 90, 95]
merge_sort cost time : 1.7404556274414062e-05
[5, 27, 30, 48, 52, 58, 65, 76, 90, 95]

Process finished with exit code 0

 

最後用生成器生成500萬個隨機數據進行測試,數據範圍0 - 50億,所以其中重複率很低,考慮到時間故只用堆/歸併/快速排序。

 list = Rand_Iter(0, 5000000000, 5000000).get_list()

測試結果:對於500萬數據的排序中,快排速度最快,耗時20秒,歸併次之,耗時30秒,堆排序墊底,耗時60秒。

heap_sort cost time : 60.18871188163757
quick_sort cost time : 20.67511534690857
merge_sort cost time : 30.793858289718628

Process finished with exit code 0

四、總結

由此可見,在數據量很小的情況下,六種排序差別不大,重複率高的話選擇排序有很好的效果,而當數據量龐大時,快速排序速度時最快的,歸併的效果也非常好,可見分治法十分有效。

而對於堆,其創建維護費了大功夫,但卻並沒有達到很好的效果,不知道這是否和其第一次建堆是使用迭代+遞歸實現有關,或許其迭代的n/2的次運算會使得運算量特別巨大,所以其效果反而不是很好,但事實真的如此嗎?爲了驗證,我們可以改寫局部代碼,這次在建堆後再開始計時。

 cls.build_heap(list, size - 1)
        time_ = time.time()
        for i in range(size, -1, -1):
            list[0], list[i] = list[i], list[0]
            cls.heap_ify(list, i, 0)
        print(time.time() - time_)
        return list

測試結果:

56.30396795272827

Process finished with exit code 0

事實卻並非如此,在大約60秒運算中,堆的建立只佔據了大約4秒的時間,而絕大部分的時間都用在了堆的維護上。假設在堆中有n個數據,其2/n的數據都用在了堆底層以上的建立,2/n的數據都在堆底,這理論上是可以大大減少遞歸深度的,向下層級的遞歸耗時應該並不大,但由於沒有用到分治法,所以揭示了後期的堆頂迭代過程,纔是堆排序效率不高的主要原因。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章