十大排序算法总结

冒泡排序:

冒泡排序算法的运作如下:

  • 比较相邻的元素。如果第一个比第二个大(升序),就交换他们两个。
  • 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数(相比于冒泡排序,我更愿意称之为沉底排序)。
  • 针对所有的元素重复以上的步骤,除了上一步已经排好的元素。
  • 每次持续对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

   #相比于下面的写法更好理解
   def bubble_sort(list_):
       """冒泡排序(稳定排序)"""
       for i in range(len(list_) - 1,0,-1):
       	   # i代表每次遍历的右端点
           # 外层循环控制循环次数
           for j in range(i):
               # 内层循环控制每一次的循环,每一次循环把最大的数值放到最后
               if list_[j] > list_[j + 1]:
                   list_[j], list_[j + 1] = list_[j + 1], list_[j]
       return list_

   def bubble_sort(list_):
       """冒泡排序(稳定排序)"""
       for i in range(len(list_) - 1):
           # 外层循环控制循环次数
           for j in range(len(list_) - i - 1):
               # 内层循环控制每一次的循环,每一次循环把最大的数值放到最后
               if list_[j] > list_[j + 1]:
                   list_[j], list_[j + 1] = list_[j + 1], list_[j]
       return list_
   
   
   def bubble_sort_1(list_):
       # 优化1(优化外层循环)
       for i in range(len(list_) - 1):
           # 每次遍历标志位都要先置为0,才能判断后面的元素是否发生了交换
           flag = True
           # 外层循环控制循环次数
           for j in range(len(list_) - i - 1):
               # 内层循环控制每一次的循环,每一次循环把最大的数值放到最后
               if list_[j] > list_[j + 1]:
                   list_[j], list_[j + 1] = list_[j + 1], list_[j]
                   flag = False  # 只要有发生了交换,flag就置为False
           # 判断标志位是否为True,如果为True,说明一次遍历中没有进行交换,即元素已经有序,所以直接结束(正是这个优化,使得当元素有序时,时间复杂度为O(n),这就是我们说的冒泡排序的最好时间复杂度为O(n))
           if flag:
               return list_
       return list_
   
   
   def bubble_sort_2(list_):
       # 优化2(优化内层循环)
       k = len(list_) - 1
       # pos变量用来标记循环里最后一次交换的位置
       pos = 0
       for i in range(len(list_) - 1):
           # 每次遍历标志位都要先置为0,才能判断后面的元素是否发生了交换
           flag = True
           # 外层循环控制循环次数
           for j in range(k):
               # 内层循环控制每一次的循环,每一次循环把最大的数值放到最后
               if list_[j] > list_[j + 1]:
                   list_[j], list_[j + 1] = list_[j + 1], list_[j]
                   flag = False  # 只要有发生了交换,flag就置为False
                   pos = j  # 循环里最后一次交换的位置j赋给pos,下次循环就不需要判断pos及其右边的元素了,因为已经有序了
           k = pos
           # 判断标志位是否为True,如果为True,说明一次遍历中没有进行交换,即元素已经有序,所以直接结束
           if flag:
               return list_
       return list_
   
   
   # a = [2, 1, 4, 3, 5, 7, 6, 8, 10, 9, 12, 14, 13, 11, 1, 2, 3]
   # # a = [1, 2, 3, 4, 5, 6]  # 本来就有序的,通过优化1可以使最好时间复杂度达到O(n)
   # # a = [2, 1, 3, 5, 4, 6, 8, 7, 9, 10, 11, 12]  # 最后几个元素已经有序的,通过优化2可以减少内部循环比较次数
   # # 传递a[:],不会改变a的值
   # print(bubble_sort(a[:]))
   # print(bubble_sort_1(a[:]))
   # print(bubble_sort_2(a[:]))
   # # print(a)
冒泡排序动图演示:

在这里插入图片描述

选择排序

n个记录的直接选择排序可经过n-1趟直接选择排序得到有序结果。具体算法描述如下:
初始状态:无序区为R[1…n],有序区为空;第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1…i-1]和R(i…n)。该趟排序从当前无序区中-选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1…i]和R[i+1…n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;
n-1趟结束,数组有序化了。

    def selection_sort(list_):
        """选择排序(不稳定排序)"""
        for i in range(len(list_) - 1):
            for j in range(i + 1, len(list_)):
                if list_[i] > list_[j]:
                    list_[i], list_[j] = list_[j], list_[i]
            # print(list_)
        return list_

	def selection_sort(list_):
	    """选择排序(不稳定排序)"""
	    for i in range(len(list_) - 1):
	        min_ = i
	        for j in range(i + 1, len(list_)):
	        	#在未排序的元素中,找到最小元素的下标
	        	#注意,不是找到最小元素,而是找到最小元素的下标,因为后面这个下标的元素还要和i下标的元素进行交换
	            if list_[j] < list_[min_]:
	                min_ = j
	        #最后进行一次交换,不需要每次都交换
	        list_[min_], list_[i] = list_[i], list_[min_]
	    return list_
    
    # a = [1, 2, 4, 3, 5, 7, 6, 8, 10, 9, 12, 14, 13, 11, 1, 2, 3]
    # # 传递a[:],不会改变a的值
    # print(selection_sort(a[:]))
    # # print(a)
选择排序动图演示:

在这里插入图片描述

插入排序

一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下:

  • 1.从第一个元素开始,该元素可以认为已经被排序;
  • 2.取出下一个元素,在已经排序的元素序列中从后向前扫描;
  • 3.如果该元素(已排序)大于新元素,将该元素移到下一位置;
  • 4.重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
  • 5.将新元素插入到该位置后;
  • 6.重复步骤2~5。
    # def insertion_sort(list_):
    #     """插入排序(稳定排序)"""
    #     for i in range(1, len(list_)):
    #         temp = list_[i]
    #         flag = -1
    #         for j in range(i - 1, -1, -1):
    #             # if list_[i] < list_[j]:
    #             if temp < list_[j]:
    #                 # 这里一定要用temp进行比较,找了半天bug,唉!
    #                 # 因为list[i]值有可能会改变,值有可能变成list[j+1]的值
    #                 list_[j + 1] = list_[j]
    #                 flag = j
    #         if flag != -1:
    #             list_[flag] = temp
    #         # print(i, flag, list_)
    #     return list_
    
    
    def insertion_sort(list_):
        """插入排序(稳定排序)"""
        # 从第二个位置,即下标为1的元素开始向前插入
        for i in range(1, len(list_)):
            # 从第i个元素开始向前比较,如果小于前一个元素,交换位置
            for j in range(i, 0, -1):
                if list_[j] < list_[j - 1]:
                    list_[j], list_[j - 1] = list_[j - 1], list_[j]
                # 加上else,可以使最好时间复杂度为O(n)
                else:
                    # 左半部分已经有序了,所以如果无序部分的第一个元素不比
                    # 有序部分的最后一个元素小的话,代表此元素已经有序了
                    # 可以退出了,不需要再比了
                    break
            # print(list_)
        return list_


	# 我自己的实现
	def insertion_sort(list_):
	    for i in range(1, len(list_)):
	        temp = list_[i]
	        flag = False
	        j = i - 1
	        for j in range(i - 1, -1, -1):
	            # 把前面有序的部分往后移动,空出的位置就是当前元素要插入的位置
	            if temp < list_[j]:
	                flag = True
	                list_[j + 1] = list_[j]
	            else:
	                break
	        # 只有当循环里面发生移动的时候,才插到当前位置,否则,代表当前元素位置是对的
	        if flag:
	            list_[j] = temp
	    return list_
	        
    
    a = [1, 2, 4, 3, 5, 7, 6, 8, 10, 9, 12, 14, 13, 11, 1, 2, 3]
    # a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    # 传递a[:],不会改变a的值
    print(insertion_sort(a[:]))
    # insertion_sort(a[:])
    # print(a)
插入排序动图演示:

在这里插入图片描述

希尔排序

def shell_sort(list_):
    """希尔排序(不稳定排序)"""
    n = len(list_)
    gap = n // 2
    while gap:
        # 插入算法,与普通的插入算法的区别就是gap步长
        for i in range(gap, n):
            # for j in range(i, 0, -1):
            for j in range(i, 0, -gap):
                if list_[j] < list_[j - gap]:
                    list_[j], list_[j - gap] = list_[j - gap], list_[j]
                else:
                    break
        # 缩短gap步长
        gap = gap // 2
    return list_


a = [1, 2, 4, 3, 5, 7, 6, 8, 10, 9, 12, 14, 13, 11, 1, 2, 3, 3, 2, 1, 4, 5, 3, 2, 5, 6, 7, 9]
# a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# 传递a[:],不会改变a的值
print(shell_sort(a[:]))
# insertion_sort(a[:])
# print(a)

快速排序

方法1,填坑法

图示如下:
在这里插入图片描述

    import numpy as np
    
    
    def quick_sort(alist, start, end):
        """快速排序,开头作为枢轴,从右到左进行判断"""
        # 这个判断必须加
        if start >= end:
            return
        pivot = alist[start]  ####
        left = start
        right = end
        while left < right:
            # 从右边开始找,直到找到一个小于基准的元素
            while left < right and alist[right] >= pivot:
                right -= 1
            # 这个if判断不加也行
            if left < right:
                alist[left] = alist[right]
                # 左指针右移(这个元素就是刚刚交换过来的小于基准的,所以不用再进行判断了,所以向右一步)
                left += 1
            # 从左边开始找,直到找到一个大于基准的元素
            while left < right and alist[left] <= pivot:
                left += 1
            if left < right:
                alist[right] = alist[left]
                # 右指针左移(这个元素就是刚刚交换过来的大于基准的,所以不用再进行判断了,所以向左一步)
                right -= 1
        # 从循环退出时,left=right
        alist[left] = pivot
        quick_sort(alist, start, left - 1)
        quick_sort(alist, left + 1, end)


    def quick_sort(alist, start, end):
        """快速排序,结尾作为枢轴,从左到右进行判断"""
        # 这个判断必须加
        if start >= end:
            return
        pivot = alist[end]  ####
        left = start
        right = end
        while left < right:
      	    while left < right and alist[left] <= pivot:
                left += 1
            if left < right:
                alist[right] = alist[left]            
                right -= 1
            while left < right and alist[right] >= pivot:
                right -= 1
            # 这个if判断不加也行
            if left < right:
                alist[left] = alist[right]
                left += 1
        # 从循环退出时,left=right
        alist[left] = pivot
        quick_sort(alist, start, left - 1)
        quick_sort(alist, left + 1, end)
    
    
    a = np.array([7, 7, 4, 5, 0, 6, 8, 10, 9, 12, 14, 13, 11, 1, 2, 7, 3])
    # x的值改变不影响a的值
    x = a.copy()
    quick_sort(x, 0, len(a) - 1)
    print(x)
    print(a)
方法2,交换法

参见:

比如要排序 6 1 2 7 9 3 4 5 10 8:分别从初始序列“6 1 2 7 9 3 4 5 10 8”两端开始“探测”。先从右往左找一个小于 6 的数,再从左往右找一个大于 6 的数,然后交换他们。这里可以用两个变量 i 和 j,分别指向序列最左边和最右边。我们为这两个变量起个好听的名字“哨兵 i”和“哨兵 j”。刚开始的时候让哨兵 i 指向序列的最左边(即 i=1),指向数字 6。让哨兵 j 指向序列的最右边(即 j=10),指向数字 8。

在这里插入图片描述
首先哨兵 j 开始出动。因为此处设置的基准数是最左边的数,所以需要让哨兵 j 先出动,这一点非常重要(请自己想一想为什么)。哨兵 j 一步一步地向左挪动(即 j–),直到找到一个小于 6 的数停下来。接下来哨兵 i 再一步一步向右挪动(即 i++),直到找到一个数大于 6 的数停下来。最后哨兵 j 停在了数字 5 面前,哨兵 i 停在了数字 7 面前。
在这里插入图片描述
在这里插入图片描述

现在交换哨兵 i 和哨兵 j 所指向的元素的值。交换之后的序列如下。

6 1 2 5 9 3 4 7 10 8
在这里插入图片描述
在这里插入图片描述

到此,第一次交换结束。接下来开始哨兵 j 继续向左挪动(再友情提醒,每次必须是哨兵 j 先出发)。他发现了 4(比基准数 6 要小,满足要求)之后停了下来。哨兵 i 也继续向右挪动的,他发现了 9(比基准数 6 要大,满足要求)之后停了下来。此时再次进行交换,交换之后的序列如下。
6 1 2 5 4 3 9 7 10 8

第二次交换结束,“探测”继续。哨兵 j 继续向左挪动,他发现了 3(比基准数 6 要小,满足要求)之后又停了下来。哨兵 i 继续向右移动,糟啦!此时哨兵 i 和哨兵 j 相遇了,哨兵 i 和哨兵 j 都走到 3 面前。说明此时“探测”结束。我们将基准数 6 和 3 进行交换。交换之后的序列如下。

3 1 2 5 4 6 9 7 10 8

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
到此第一轮“探测”真正结束。此时以基准数 6 为分界点,6 左边的数都小于等于 6,6 右边的数都大于等于 6。回顾一下刚才的过程,其实哨兵 j 的使命就是要找小于基准数的数,而哨兵 i 的使命就是要找大于基准数的数,直到 i 和 j 碰头为止。

注:如果选取最左边的数arr[left]作为基准数,那么先从右边开始可保证i,j在相遇时,相遇数是小于(等于)基准数的,如果选取最右边的数arr[right]作为基准数,那么先从左边开始可保证i,j在相遇时,相遇数是大于(等于)基准数的。

    def quick_sort(alist, start, end):
        """快速排序,开头作为枢轴,从右到左进行判断"""
        # 这个判断必须加
        if start >= end:
            return
        pivot = alist[start]  ####
        low = start
        high = end
        while low < high:
            # 从右向左
	        while low < high and alist[high] >= pivot:
	            high -= 1
	        while low < high and alist[low] <= pivot:
	            low += 1
            if low < high:
                alist[low], alist[high] = alist[high], alist[low]
        # print(alist[low] <= pivot)
        # 最终将基准数归位,下面两句等价,因为退出循环时,low==high
        alist[start], alist[low] = alist[low], pivot #######
        # alist[start], alist[high] = alist[high], pivot
        quick_sort(alist, start, low - 1)
        quick_sort(alist, low + 1, end)

    def quick_sort(alist, start, end):
        """快速排序,结尾作为枢轴,从左到右进行判断"""
        # 这个判断必须加
        if start >= end:
            return
        pivot = alist[end]   ######
        low = start
        high = end
        while low < high:
            # 从左向右
            while low < high and alist[low] <= pivot:
	            low += 1
	        while low < high and alist[high] >= pivot:
	            high -= 1
            if low < high:
                alist[low], alist[high] = alist[high], alist[low]
        # print(alist[low] >= pivot)
        # 最终将基准数归位,下面两句等价,因为退出循环时,low==high
        alist[end], alist[low] = alist[low], pivot #######
        # alist[end], alist[high] = alist[high], pivot
        quick_sort(alist, start, low - 1)
        quick_sort(alist, low + 1, end)

归并排序

    def merge_sort(alist):
        """归并排序"""
        n = len(alist)
        if n <= 1:
            return alist
        mid = n // 2
        # left_li 采用归并排序后形成的有序的新的列表
        left_li = merge_sort(alist[:mid])
        # right_li 采用归并排序后形成的有序的新的列表
        right_li = merge_sort(alist[mid:])
        # 将两个有序的子序列合并为一个新的整体
        # merge(left_li,right_li)
        left_pointer, right_pointer = 0, 0
        result = []
        while left_pointer < len(left_li) and right_pointer < len(right_li):
            if left_li[left_pointer] <= right_li[right_pointer]:
                result.append(left_li[left_pointer])
                left_pointer += 1
            else:
                result.append(right_li[right_pointer])
                right_pointer += 1
        result += left_li[left_pointer:]
        result += right_li[right_pointer:]
        return result
    
    
    a = [1, 2, 5, 4, 6, 7, 9, 3, 6, 0]
    b = merge_sort(a)
    print(a)
    print(b)

归并排序动图演示:

在这里插入图片描述

堆排序:
堆排序详解
堆排序的Python实现(附详细过程图和讲解)
堆排序的理解分析
堆排序算法之初始堆建立总结

这里的排序是指排序成非递减数列(升序)(用大根堆)。
非递增数列(降序)(用小根堆)

堆排序的基本思路:

  • 首先将待排序的数组构造出一个大根堆
  • 取出这个大根堆的堆顶节点(最大值),与堆的最下最右的元素进行交换,然后把剩下的元素再构造出一个大根堆
  • 重复第二步,直到这个大根堆的长度为1,此时完成排序。

我自己的思路
1.先把待排序数组建成大根堆(从最后一个非叶节点开始,依次往上,调整每一个非叶节点(调整方式是把每一个非叶节点与其较大的子节点交换),调整好的数列第一个元素是最大的)
2.交换第一个和最后一个元素(现在最后一个元素是最大的,已经满足了),再把从第一个到最后一个未排序的元素组成的数组建成大根堆(建大根堆方式和第一步相同)。
3.重复2,直到所有元素都排序好。

图示:
1.根据我的方法调整得到的不是大根堆,充其量只保证根节点是所有元素中最大的:
在这里插入图片描述
2.交换元素后,再建大根堆,每一个叶子节点都需要调整,所以时间复杂度是O(n/2)
在这里插入图片描述
时间复杂度是O(n/2) + [O(n/2)+O((n-1)/2)+O((n-2)/2)+…O(1/2) = O(n/2)+O((n^2+n/4)/4) = O(n^2)(我这种思路是不对的,堆排序时间复杂度应该是O(nlgn),我的方法根本不是堆排序)

    def heap_sort(alist):
        length = len(alist) - 1
        not_leaf_node = length // 2
        # 根据alist建一个大根堆(调整每一个非叶节点)
        # 从最后一个非叶节点开始往上调整
        for i in range(not_leaf_node):
            heap_adjust(alist, not_leaf_node - i, length)
        print(alist[1:], end='\n\n')
        for i in range(length, 0, -1):
            # 把第一个结点(最大值)和最后一个结点交换
            alist[1], alist[i] = alist[i], alist[1]
            print(alist[1:])
            # 把最后的已排序好的元素去掉,重新把前面未排序的alist再调整为大根堆
            # 从最后一个非叶节点开始往上调整,和上面的思路一样
            for j in range(not_leaf_node):
                heap_adjust(alist, not_leaf_node - j, i - 1)
            print(alist[1:], end='\n\n')
        return alist[1:]
    
    
    def heap_adjust(alist, i, j):
        # 三个判断是为了让已经排序好的在最后的元素不再进行调整
        if j < 2 * i:
            pass
        elif j == 2 * i:
            if alist[j] > alist[i]:
                alist[i], alist[j] = alist[j], alist[i]
        else:
            if alist[2 * i] > alist[i] or alist[2 * i + 1] > alist[i]:
                if alist[2 * i] >= alist[2 * i + 1]:
                    alist[2 * i], alist[i] = alist[i], alist[2 * i]
                else:
                    alist[2 * i + 1], alist[i] = alist[i], alist[2 * i + 1]
    
    
    if __name__ == '__main__':
        alist = [50, 16, 30, 10, 60, 90, 2, 80, 70]
        alist.insert(0, 0)
        print(heap_sort(alist))

堆排序的正确思路:
1.先把待排序数组建成大根堆(从最后一个非叶节点开始,依次往上,调整每一个非叶节点,但与我之前的想法不同的是,调整每个非叶节点之后,还要注意,此次的调整会不会影响当前非叶节点的非叶 子节点还要从当前非叶节点,一直往下,到最后一个非叶节点,都要保证每一个非叶节点的值都大于其子节点的值,调整好的数列第一个元素是最大的,且每个元素的值大于其左右子节点的值,具体操作如下图1)
2.交换第一个和最后一个元素(现在最后一个元素是最大的,已经满足了),再把从第一个到最后一个未排序的元素组成的数组建成大根堆(这里建大根堆方式和第一步中的方式不太一样,而是从根节点开始,往下调整,直到调整到上一次调整后的已排序元素的父节点为止,注:由于之前已经是一个堆了,现在最多只是首元素需要调整,只需要沿着一条路径,O(lgn)复杂度就可以了)。
3.重复2,直到所有元素都排序好。

图示:
1.
在这里插入图片描述
2.

在这里插入图片描述

从根节点2开始往下交换,(与其较大的子节点进行交换),只会沿一条线,所以时间复杂度为O(logn)
在这里插入图片描述
时间复杂度是O(n)[建堆] + O(n*lgn)[首尾交换后,调整堆] = O(nlgn)

参考:

堆排序中建堆过程时间复杂度O(n)怎么来的?

    from collections import deque
    
    
    def swap_param(L, i, j):
        L[i], L[j] = L[j], L[i]
        return L
    
    
    def heap_adjust(L, start, end):
        temp = L[start]
    
        i = start
        j = 2 * i
   		while j <= end:
        	if (j < end) and (L[j] < L[j + 1]):
            	j += 1
        	if temp < L[j]:
            	L[i] = L[j]
            	i = j
            	j = 2 * i
        	else:
            	break
    	L[i] = temp


    def heap_sort(L):
        L_length = len(L) - 1

    	first_sort_count = L_length // 2
    	for i in range(first_sort_count):
        	heap_adjust(L, first_sort_count - i, L_length)

    	for i in range(L_length - 1):
        	L = swap_param(L, 1, L_length - i)
        	heap_adjust(L, 1, L_length - i - 1)

    	return [L[i] for i in range(1, len(L))]


    def main():
        L = deque([50, 16, 30, 10, 60,  90,  2, 80, 70])
        L.appendleft(0)
        print heap_sort(L)
    
    
    if __name__ == '__main__':
        main()



### 比较一下各个排序算法的大概时间:

    import numpy as np
    import timeit
    
    """比较一下每个算法的时间"""
    x = np.arange(10000)
    # 数字个数还不够多,所以只是简单的比较一下
    x = np.random.permutation(x)
    sort_1 = timeit.Timer("bubble_sort.bubble_sort_1(x.copy())", "from __main__ import x;import bubble_sort")
    print("bubble_sort:", sort_1.timeit(number=1), "seconds") 
    sort_2 = timeit.Timer("selection_sort.selection_sort(x.copy())", "from __main__ import x;import selection_sort")
    print("selection_sort:", sort_2.timeit(number=1), "seconds") 
    sort_3 = timeit.Timer("insertion_sort.insertion_sort(x.copy())", "from __main__ import x;import insertion_sort")
    print("insertion_sort:", sort_3.timeit(number=1), "seconds")  
    sort_4 = timeit.Timer("shell_sort.shell_sort(x.copy())", "from __main__ import x;import shell_sort")
    print("shell_sort:", sort_4.timeit(number=1), "seconds")  
    sort_5 = timeit.Timer("quick_sort.quick_sort(x.copy(),0,len(x)-1)", "from __main__ import x;import quick_sort")
    print("quick_sort:", sort_5.timeit(number=1), "seconds")  
    sort_6 = timeit.Timer("merge_sort.merge_sort(list(x.copy()))", "from __main__ import x;import merge_sort")
    print("merge_sort:", sort_6.timeit(number=1), "seconds")  
    sort_7 = timeit.Timer("heap_sort.heap_sort(list(x.copy()))", "from __main__ import x;import heap_sort")
    print("heap_sort:", sort_7.timeit(number=1), "seconds")  

执行两次,输出结果如下:

第一次结果:
bubble_sort: 41.37893530824798 seconds
selection_sort: 36.76040679346766 seconds
insertion_sort: 15.426445501773813 seconds
shell_sort: 7.004434187005941 seconds
quick_sort: 0.09137854267068235 seconds
merge_sort: 0.08412521788113736 seconds
heap_sort: 0.08508587114629051 seconds

第二次结果:
bubble_sort: 44.71397844733827 seconds
selection_sort: 39.21281035055468 seconds
insertion_sort: 15.52704762458194 seconds
shell_sort: 13.61129115725214 seconds
quick_sort: 0.0906817418554624 seconds
merge_sort: 0.08633759973321276 seconds
heap_sort: 0.07730641200717514 seconds

可以看出,快排,归并排序和堆排序确实都很快。

又分别试了100000,1000000,10000000个随机数的三者排序时间,结果如下:

100000个随机数排序:
quick_sort: 1.535183860396925 seconds
merge_sort: 1.329339947444152 seconds
heap_sort: 1.2203657445180598 seconds

1000000个随机数排序:
quick_sort: 13.63125887197319 seconds
merge_sort: 13.200295944027681 seconds
heap_sort: 14.405367931509346 seconds

10000000个随机数排序:
quick_sort: 179.77304082357566 seconds
merge_sort: 159.00747503413984 seconds
heap_sort: 202.5589797914676 seconds
计数排序、基数排序、桶排序则属于非比较排序,算法时间复杂度O(n),优于比较排序。但是也有弊端,会多占用一些空间,相当于是用空间换时间。
比较和非比较的区别
  • 常见的快速排序、归并排序、堆排序、冒泡排序等属于比较排序。在排序的最终结果里,元素之间的次序依赖于它们之间的比较。每个数都必须和其他数进行比较,才能确定自己的位置。
  • 在冒泡排序之类的排序中,问题规模为n,又因为需要比较n次,所以平均时间复杂度为O(n²)。在归并排序、快速排序之类的排序中,问题规模通过分治法消减为logN次,所以时间复杂度平均O(nlogn)。
    比较排序的优势是,适用于各种规模的数据,也不在乎数据的分布,都能进行排序。可以说,比较排序适用于一切需要排序的情况。
  • 计数排序、基数排序、桶排序则属于非比较排序。非比较排序是通过确定每个元素之前,应该有多少个元素来排序。针对数组arr,计算arr之前有多少个元素,则唯一确定了arr在排序后数组中的位置。
    非比较排序只要确定每个元素之前的已有的元素个数即可,所有一次遍历即可解决。算法时间复杂度O(n)。
  • 非比较排序时间复杂度底,但由于非比较排序需要占用空间来确定唯一位置。所以对数据规模和数据分布有一定的要求。

1.计数排序(适用于元素个数有限):

  • 找出待排序的数组中最大和最小的元素;
  • 统计数组中每个值为i的元素出现的次数,存入数组counting_list的第i项;
  • 对所有的计数累加(从counting_list中的第一个元素开始,每一项和前一项相加);
  • 反向填充目标数组:将每个元素i放在新数组的counting_list[i]下标处,每放一个元素就将counting_list(i)减去1(下一个相同的元素的位置)。
# 计数排序
def counting_sort(alist):  # k = max(a)
    n = len(alist)  # 计算a序列的长度
    out = [0 for _ in range(n)]  # 设置输出序列并初始化为0
    counting_list = [0 for _ in range(max(alist) + 1)]  # 设置计数序列并初始化为0,
    # 统计alist中每个元素出现的次数,例如counting_list[3]=4,代表在alist中3出现了4次
    for elem in alist:
        counting_list[elem] = counting_list[elem] + 1
    # print(counting_list) #[0, 2, 8, 2, 2, 0, 1, 2, 2, 2]
    # 对于每一个元素i,统计小于等于i的元素个数,例如,counting_list[2]=10,代表小于等于2的元素共有10个
    for i in range(1, len(counting_list)):
        counting_list[i] += counting_list[i - 1]
    # print(counting_list) [0, 2, 10, 12, 14, 14, 15, 17, 19, 21]
    for elem in alist:
        # 注意elem对应的下标要减1,例如,counting_list[2]=10
        # 那么元素2在新的数组out中占据的位置是[10-alist.count(2),9],即2-9
        out[counting_list[elem] - 1] = elem
        counting_list[elem] = counting_list[elem] - 1
    return out


alist = [2, 2, 3, 8, 7, 1, 2, 2, 2, 7, 3, 9, 8, 2, 1, 4, 2, 4, 6, 9, 2]
print(counting_sort(alist))

# [0, 2, 8, 2, 2, 0, 1, 2, 2, 2]
# [0, 2, 10, 12, 14, 14, 15, 17, 19, 21]
# [1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 4, 4, 6, 7, 7, 8, 8, 9, 9]

2.桶排序:
3.基数排序:

# 基数排序
"""基数排序适用于:
(1)数据范围较小,建议在小于1000
(2)每个数值都要大于等于0"""


def radix_sort(alist, maxdigit):
    """
    :param alist:
    :param maxdigit: 最大数据位数
    :return:
    """
    for i in range(maxdigit):  # maxdigit轮排序
        s = [[] for _ in range(10)]  # 因每一位数字都是0~9,建10个桶
        for elem in alist:
            # 从每个元素的最低位开始进行排序
            s[elem // (10 ** i) % 10].append(elem)
        alist = [a for b in s for a in b]
        print(alist)
    return alist


alist = [3, 447, 318, 15, 47, 125, 36, 264, 275, 32, 46, 4, 149, 50, 48]
print(radix_sort(alist, 3))

# [50, 32, 3, 264, 4, 15, 125, 275, 36, 46, 447, 47, 318, 48, 149] #个位排序
# [3, 4, 15, 318, 125, 32, 36, 46, 447, 47, 48, 149, 50, 264, 275] #十位排序
# [3, 4, 15, 32, 36, 46, 47, 48, 50, 125, 149, 264, 275, 318, 447] #百位排序
# [3, 4, 15, 32, 36, 46, 47, 48, 50, 125, 149, 264, 275, 318, 447]

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

参考:

1.十大经典排序算法最强总结
2.十大经典排序算法总结

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值