排序總結(四大類型10種排序+運行過程+圖解)

 

目錄

 

一、排序簡介以及代碼實現

1、插入型排序

(1)直接插入排序

(2)希爾排序

(3)鏈表的插入排序

2、比較換位型排序

(1)單向冒泡排序

(2)雙向冒泡排序

(3)快速排序

3、選擇最值型排序

(1)簡單選擇排序

(2)計數排序

(3)堆排序 

4、分治類排序

(1)歸併排序

5、各種排序的分析


一、排序簡介以及代碼實現

1、插入型排序

(1)直接插入排序

應用場景:越有序越高效。在一個基本上有序的列表裏面,插入一個新的元素,假設是遞增序列,插入一個最大值,只要插入到最後一個位置即可。例如一個排行榜裏面新增一個玩家。

特點:

  • 越有序越高效。
  • 沒到最後不知道結果。

步驟:

  1. 插入是插入到已有序列的末尾;
  2. 保存當前的值爲value;
  3. index用於記錄適合插入的位置下標。
  4. 通過index不斷遞減,插入元素和前一個元素進行比較,假如前一個元素比它大就讓前一個元素覆蓋當前元素,最後通過把之前保存的值賦值給index下標指向的元素得到最後排序的序列。

我打印了每一步的結果,可以發現插入排序是特點是在走完前三趟後,前三個就會有3個元素有序,但是這三個值不一定是最值。

def insert_sort(arr):
    """
    插入一個元素的時候,跟它的前一個元素進行比較,
    假如比它大,就讓他覆蓋當前元素,一直往前比較直到找到適合插入位置
    :param arr:要排序的數組
    :return:
    """
    if len(arr) <= 1:
        return arr
    for i in range(len(arr)):
        index = i  # 用於遍歷元素的下標
        value = arr[index]  # 保存插入元素的值
        while index > 0 and value < arr[index - 1]:
            arr[index] = arr[index - 1]
            index -= 1
        arr[index] = value
        print(arr)

測試:

if __name__ == '__main__':
    a = [2, 5, 0, 9, 7, 3, 1]
    insert_sort(a)

結果:

[2, 5, 0, 9, 7, 3, 1]
[2, 5, 0, 9, 7, 3, 1]
[0, 2, 5, 9, 7, 3, 1]
[0, 2, 5, 9, 7, 3, 1]
[0, 2, 5, 7, 9, 3, 1]
[0, 2, 3, 5, 7, 9, 1]
[0, 1, 2, 3, 5, 7, 9]

Process finished with exit code 0

(2)希爾排序

通過偏移量分成子數組,子數組的元素進行插入排序,排序完後減少偏移量直到偏移量爲1,變成直接插入排序。跟直接插入一樣都是後一個值和前一個值比較,加入比它小就換位。希爾排序的比較次數跟它選的偏移量相關,選好了偏移量有利於更快速的排序。

def shell_sort(arr, n):
    offerset = n # 偏移量
    while offerset >= 1:
        for i in range(offerset, len(arr)):
            value = arr[i]
            index = i - offerset
            while  0 <= index and value < arr[index]:
                arr[index + offerset] = arr[index]
                index -= offerset
            arr[index + offerset] = value
        offerset -= 1
        print(arr)

測試: 

if __name__ == '__main__':
    a = [2, 5, 3, 1, 7, 8, 6, 0]
    shell_sort(a, 3)

結果:

[1, 0, 3, 2, 5, 8, 6, 7]
[1, 0, 3, 2, 5, 7, 6, 8]
[0, 1, 2, 3, 5, 6, 7, 8]

Process finished with exit code 0

(3)鏈表的插入排序

鏈表的插入排序有點像是反轉單鏈表的思想都是把鏈表拆開用一個臨時指針保留未處理的剩下元素的隊列的頭部,防止斷鏈。假如當前元素比後一個元素小則不做交換,循環直到找到適合插入的位置,就可以通過鏈表插入的方式插到已排序的隊列中。

class Node:
    def __init__(self, value=None, next=None):
        self.value, self.next = value, next


class Linklist:
    def __init__(self, maxsize=None, value=None):
        self.maxsize = maxsize
        self.root = Node(value)
        self.length = 0
        self.tail = None

    def append(self, value):
        if self.length >= self.maxsize and self.maxsize is not None:
            raise Exception("the LinkedList is Full")
        node = Node(value)
        if self.root.next is None:
            self.root.next = node
        else:
            self.tail.next = node
        self.tail = node
        self.length += 1

    def popleft(self):
        if self.root.next is None:
            raise Exception("the LinkedList is empty")
        head_node = self.root.next
        self.root.next = head_node.next
        self.length -= 1
        return head_node.value

    def sort(self):
        """
        利用反轉鏈表的思想,把鏈表拆分成已排序部分和未排序部分,
        p和p1都是用於保存未排序鏈表的,而p的特點是它指向的元素需要和q當前元素結點進行值比較,和插入時更加方便
        :return:
        """
        if self.root.next is not None:
            p = self.root.next.next  # 記錄鏈表未排序元素防止斷鏈,此時的p初始化爲指向第二個元素。
            self.root.next.next = None  # 把連斷開,當鏈表只有一個元素時默認有序。
            while p is not None:  # p是指向q(當前元素)的下一個元素,
                pre = self.root
                q = pre.next
                while q is not None and q.value < p.value:# 尋找合適插入的位置,從頭結點後的第一個元素進行比較,直到找到前一個值比它小後一個值比它大
                    # pre和q同時往後一一個元素
                    pre = q
                    q = q.next
                # p1保存未處理的鏈表
                p1 = p.next
                # 典型的鏈表插入元素操作
                p.next = q
                pre.next = p
                p = p1

2、比較換位型排序

通過比較相鄰的值,假如是升序,後一個值比前一個值小,就進行換位。最典型的兩種排序是冒泡和快排。

(1)單向冒泡排序

通過相鄰的兩個數據進行對比,前一個元素比後一個元素大就換位,走完兩趟以後就會有一個最值往上冒到頂或者是沉到最底。

def bubble_sort(arr):
    """
    相鄰元素相互比較。
    :param arr:要排序的數組
    :return:
    """
    if len(arr) <= 1:
        return arr
    for i in range(len(arr)):
        for j in range(len(arr) - 1):
            if arr[j] > arr[j + 1]:
                arr[j], arr[j + 1] = arr[j + 1], arr[j]
        print(arr)

測試:

if __name__ == '__main__':
    a = [2, 5, 0, 9, 7, 3, 1]
    bubble_sort(a)

結果:

[2, 0, 5, 7, 3, 1, 9]
[0, 2, 5, 3, 1, 7, 9]
[0, 2, 3, 1, 5, 7, 9]
[0, 2, 1, 3, 5, 7, 9]
[0, 1, 2, 3, 5, 7, 9]
[0, 1, 2, 3, 5, 7, 9]
[0, 1, 2, 3, 5, 7, 9]

Process finished with exit code 0

(2)雙向冒泡排序

先從底向上(i從0開始往後掃描)從無序區冒出一個最小元素,再從上向底(j從最後往前掃描)沉澱一個最大元素。當i和j重合的時候結束排序。換句話來說就是即在排序過程中交替改變掃描方向。

def double_bubble(arr):
    """
    i指向第一個元素j指向最後的元素當i>=j時結束循環,
    i遇到小於它的換位,j遇到大於它的換位,
    i+=1, j+=1
    :param arr:排序的數組
    :return:
    """

    i = 0
    j = len(arr) - 1
    while i < j:
        for m in range(i, len(arr) - 1):
            if arr[m + 1] < arr[m]:
                arr[m + 1], arr[m] = arr[m], arr[m + 1]
        i += 1

        for n in range(j, 0, -1):
            if arr[n - 1] > arr[n]:
                arr[n - 1], arr[n] = arr[n], arr[n - 1]
        j -= 1
        print(arr)

 測試:


if __name__ == '__main__':
    a = [2, 5, 3, 1, 7, 8, 6, 0]
    b = double_bubble(a)

結果:

可以看見每一趟完成後會產出一個最大和最小值。

[0, 2, 3, 1, 5, 7, 6, 8]
[0, 1, 2, 3, 5, 6, 7, 8]
[0, 1, 2, 3, 5, 6, 7, 8]
[0, 1, 2, 3, 5, 6, 7, 8]

Process finished with exit code 0

(3)快速排序

特點:

  • 越無序越高效

方法一新建列表法:

第一個元素作爲基準,你可以新建一個列表,和基準作比較,比基準小就加入新列表,最後得到【比基準小】基準【比基準大】,這樣的列表,通過遞歸比基準小的列表和比基準大的列表就可以得到排序的列表。很簡單不實現了。

方法二指針移動法:

第一種指法:j是指向結尾的指針,i是指向頭的指針,第一個元素依然是基準,並且保存基準的值。首先是j往前走(j--)遇到比基準小的元素假如此時i<j,讓arr【j】的值覆蓋arr【i】的值,到i往前走(i++)遇到比基準大的元素假如此時i<j,讓arr【i】的值覆蓋arr【j】的值,是不是和上面的雙向冒泡有異曲同工之處。

第二種指針法:left指針向列表的結尾走,right向列表開頭走,left遇到比基準大就停下,right遇到比基準大停下,兩個換位。

方法一中value一定是保存值而不是指針,即不可以value = start應該是value = list[strat]或者value = list[i],因爲第一次交換就會把原來start的值覆蓋掉。

#version1
def quick(list, start, end):
    """
    j是指向結尾的指針,i是指向頭的指針,第一個元素依然是基準,並且保存基準的值。
    首先是j往前走(j--)遇到比基準小的元素假如此時i<j,讓arr【j】的值覆蓋arr【i】的值,
    到i往前走(i++)遇到比基準大的元素假如此時i<j,讓arr【i】的值覆蓋arr【j】的值
    :param list:
    :param start:
    :param end:
    :return:
    """
    if (start == 0 and end == 0) or start >= end:
        return list
    i = start
    j = end
    value = list[i]
    while i < j:
        while i < j and value < list[j]:
            j -= 1
        if i < j:
            list[i] = list[j]
        while i < j and value > list[i]:
            i += 1
        if i < j:
            list[j] = list[i]
    list[i] = value
    quick(list, start, i - 1)
    quick(list, i + 1, end)
    print(list)

測試:

# version1
if __name__ == '__main__':
    a = [4, 10, 5, 0, 9, 7, 3, 1]
    b = quick(a, 0, len(a) - 1)

結果:

# version1
[0, 1, 3, 4, 9, 7, 5, 10]
[0, 1, 3, 4, 5, 7, 9, 10]
[0, 1, 3, 4, 5, 7, 9, 10]
[0, 1, 3, 4, 5, 7, 9, 10]

Process finished with exit code 0

指針方法二: 

# version2
def quick_partition(list, beg, end):
    index = beg
    value = list[index]
    left = beg + 1
    right = end - 1

    while True:
        while left <= right and list[left] < value:
            left += 1

        while left <= right and list[right] > value:
            right -= 1

        if left > right:
            break
        else:
            list[left], list[right] = list[right], list[left]
    list[right], list[index] = list[index], list[right]
    return right


def sort_quick(list, beg, end):
    if beg < end:
        part = quick_partition(list, beg, end)
        sort_quick(list, beg, part)
        sort_quick(list, part + 1, end

測試:

# version2
if __name__ == '__main__':
    a = [2, 4, 7, 3, 0, 5, 1]
    sort_quick(a, 0, len(a))
    print(a)

結果:

# version2
[0, 1, 2, 3, 4, 5, 7]
Process finished with exit code 0

3、選擇最值型排序

特點:

  • 結束完全前可以的得到前k個最大值/最小值,不需要等排序完全結束。

(1)簡單選擇排序

每一趟選出一個最大值或者是最小值,第一個元素和n-1個元素進行對比,第二個元素和n-2個元素作對比如此類推。

def selection_sort(list):
    """
    第一個元素和n-1個元素進行對比,第二個元素和n-2個元素作對比如此類推
    :param list:需要排序的列表
    :return:
    """
    if len(list) <= 1:
        return list
    for i in range(len(list)-1):
        for j in range(i + 1, len(list)):
            if list[i] > list[j]:
                list[j], list[i] = list[i], list[j]
        print(list)

 測試:

if __name__ == '__main__':
    a = [10, 5, 0, 9, 7, 3, 1]
    b = selection_sort(a)

結果:

[0, 10, 5, 9, 7, 3, 1]
[0, 1, 10, 9, 7, 5, 3]
[0, 1, 3, 10, 9, 7, 5]
[0, 1, 3, 5, 10, 9, 7]
[0, 1, 3, 5, 7, 10, 9]
[0, 1, 3, 5, 7, 9, 10]

Process finished with exit code 0

(2)計數排序

比較次數比簡單選擇排序多,而且空間複雜度比簡單選擇排序要大。

步驟:

計數排序算法針對表中的每個關鍵字,掃描待排序表一趟,統計表中有多少關鍵字比該關鍵字小。 假設對某一個關鍵字,統計出數值爲C,那麼這個關鍵字在新的有序表中的位即爲C。

def count_sort(A):
    """
    計數排序算法針對表中的每個關鍵字,掃描待排序表一趟,統計表中有多少關鍵字比該關鍵字小。
    假設對某一個關鍵字,統計出數值爲C,那麼這個關鍵字在新的有序表中的位即爲C。
    :param A:待排序表
    :return:排序後的表
    """
    B = [None] * len(A)
    for i in range(len(A)):
        count = 0
        for j in range(len(A)):
            if A[i] > A[j]:
                count += 1
        B[count] = A[i]
        print(B)
    return B

測試:

if __name__ == '__main__':
    A = [2, 5, 3, 1, 7, 8, 6, 0, 9]  
    count_sort(A)

結果:

[None, None, 2, None, None, None, None, None, None]
[None, None, 2, None, 5, None, None, None, None]
[None, None, 2, 3, 5, None, None, None, None]
[None, 1, 2, 3, 5, None, None, None, None]
[None, 1, 2, 3, 5, None, 7, None, None]
[None, 1, 2, 3, 5, None, 7, 8, None]
[None, 1, 2, 3, 5, 6, 7, 8, None]
[0, 1, 2, 3, 5, 6, 7, 8, None]
[0, 1, 2, 3, 5, 6, 7, 8, 9]

Process finished with exit code 0

(3)堆排序 

通過把堆頂元素和堆底最右元素進行調換,彈出原來堆頂元素,通過sifdown當前的堆頂元素讓堆重新複合大頂堆的特性(父結點總是比子結點要大)。

特點:

  • 數據越多越高效。
class Full(Exception):
    pass


class Empty(Exception):
    pass


class Heap:
    def __init__(self, maxsize):
        self.maxsize = maxsize
        self.ele = [None] * self.maxsize
        self.count = 0

    def __len__(self):
        return self.count

    def add(self, value):
        """
        判斷堆是否爲滿,滿則不允許繼續添加
        添加元素到count的位置,
        進行上升操作,
        count += 1
        :param value: 添加新元素的值
        :return:
        """
        if self.count == self.maxsize:
            raise Full
        self.ele[self.count] = value
        self.sifup(self.count)
        self.count += 1

    def sifup(self, index):
        """
        index不斷往上走,不斷減少,減少到0時爲堆頂,
        找父結點比較當前添加元素的大小,
        父結點的下標是:
            1
          /  \
         3   4
        parent = (index-1)//2
        若是比父結點大則執行換位操作。
        :param index: 新添加元素的下標
        :return: 沒有返回值
        """
        if index > 0:
            parent = (index - 1) // 2
            if self.ele[parent] < self.ele[index]:
                self.ele[parent], self.ele[index] = self.ele[index], self.ele[parent]
                self.sifup(parent)

    def pop(self):
        """
        堆爲空,不可以再彈出數據,
        保存堆頂元素值
        count -= 1

        :param index: 爲count
        :return:
        """
        if self.count <= 0:
            raise Empty
        value = self.ele[0]
        self.count -= 1
        self.ele[0] = self.ele[self.count]
        self.sifdown(0)
        return value

    def sifdown(self, index):
        left = index * 2 + 1 # 左孩子下標
        right = index * 2 + 2 #右孩子下標
        largest = index
        #有左孩子 and 左孩子值比當前結點值大 and 左孩子值比右孩子值大
        if left <= self.count and self.ele[left] >= self.ele[index] and self.ele[left] >= self.ele[right]:
            largest = left
        # 有右孩子 and 右孩子值比當前結點值大 and 當前結點值比右孩子的值大
        if right < self.count and self.ele[right] > self.ele[index] and self.ele[right] > self.ele[left]:
            largest = right
        if largest != index:
            self.ele[largest],self.ele[index] = self.ele[index],self.ele[largest]
            self.sifdown(largest)

測試:

if __name__ == '__main__':
    li = [5,1,3,7,2,0,8,4]
    heap = Heap(len(li))
    for i in li:
        heap.add(i)
    for i in range(len(li)):
        print(heap.pop())

結果:

如果是想從小到大可以把堆寫成小根堆,或者是把當前的所有元素放到棧裏,再進行退棧。

8
7
5
4
3
2
1
0

Process finished with exit code 0

4、分治類排序

(1)歸併排序

元素兩兩(2**1)進行比較,大的一方排在後面,然後是分成每份四個元素進行排序(2**2) 如此類推(2**n)最後整個數組排序。

def merge(left, right):
    """
    傳進來左右兩個數組,分別用i,j充當遍歷left和right數組的指針,
    假如i指向的值比j指向的值小就讓它添加到result數組裏面,
    當其中一個數組遍歷到末尾,直接把另外一個數組剩下的元素全部添加到result數組裏面。
    :param left:
    :param right:
    :return:result結果數組
    """
    i = j = 0
    result = []
    while i < len(left) and j < len(right):
        if left[i] < right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            j += 1

        if len(left) == i and len(right) > j:
            while j < len(right):
                result.append(right[j])
                j += 1
        elif len(right) == j and len(left) > i:
            while i < len(left):
                result.append(left[i])
                i += 1
        print(result)
    return result

def merge_sort1(arr):
    """
    通過遞歸分別處理左數組和右數組返回排序的結果。
    :param arr: 要處理的數組
    :return: 每一次排序的結果
    """
    high = len(arr)
    mid = high//2
    left = merge_sort(arr[:mid])
    right = merge_sort(arr[mid:])
    return merge(left, right)

測試:

if __name__ == '__main__':
    a = [2, 5, 0, 9, 7, 3, 10]
    b = merge_sort1(a)

結果:

[0, 5]
[0]
[0, 2, 5]
[7, 9]
[3, 10]
[3]
[3, 7]
[3, 7, 9, 10]
[0]
[0, 2]
[0, 2, 3]
[0, 2, 3, 5, 7, 9, 10]

Process finished with exit code 0

5、各種排序的分析

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