排序总结(四大类型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、各种排序的分析

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值