• 冒泡排序、选择排序、插入排序、快速排序、归并排序、计数排序的思想与实现


    1.冒泡排序

    # 冒泡排序在遍历时,每次只比较相邻的两个元素,并把较大的元素放在后面。

    # 冒泡排序的每次遍历,得到的较大数的位置都会被确定。

    是稳定的原地排序算法。

    最好情况下,要排序的数据已经是有序的,只需要进行一次冒泡操作,最好情况时间复杂度是O(n);最坏的情况是要排序的数据刚好是倒序排列,需要进行n次冒泡操作,时间复杂度是O(n2)。

     1 def bubble_sort(alist):
     2     """冒泡排序"""
     3     n = len(alist)
     4     for j in range(0, n-1):
     5         sorted_flag = False  # 提前退出冒泡循环的标志位,减少不必要的比较,优化算法
     6         for i in range(0, n-1-j):
     7             if alist[i] > alist[i+1]:
     8                 alist[i], alist[i+1] = alist[i+1], alist[i]
     9                 sorted_flag = True
    10         if sorted_flag is False:
    11             break
    12 
    13 if __name__ == "__main__":
    14     li = [12, 4, 32, 45, 333, 234, 23]
    15     bubble_sort(li)
    16     print(li)         # [4, 12, 23, 32, 45, 234, 333]


     

    2.选择排序

    选择排序算法的实现思路:数据划分已排序区间和未排序区间,选择排序每次会从未排序区间中找到最小的元素,将其与未排序区间的头部交换。

    # 具体排序步骤:

    A.列表分成:有序序列部分和无序序列部分,每次遍历从无序序列中选出最小的,放在有序的序列中;

    B.第一次从位置0开始遍历,选出最小的元素与位置0的元素交换位置;

    C.第二次从位置1开始遍历,选出次小的元素与位置1的元素交换位置;

    D.第三次从位置2开始遍历,选出再次小的元素与位置2的元素交换位置;

    E. ...。

    # 是不稳定的原地排序算法。

    # 最好、最坏时间复杂度都是O(n2)。

     1 def select_sort(alist):
     2     """选择排序"""
     3     n = len(alist)
     4     for j in range(0, n-1):  # j是遍历次数,无序序列最后剩下的元素肯定是最大值,不用遍历
     5         min_index = j  # 记录最小值位置
     6         for i in range(j+1, n):  # 从无序序列中选最小值
     7             if alist[min_index] > alist[i]:
     8                 min_index = i  # 找最小值所在位置
     9         alist[min_index], alist[j] = alist[j], alist[min_index]
    10 
    11 if __name__ == "__main__":
    12     li = [54, 26, 93, 17, 77, 31, 44, 55, 20]
    13     select_sort(li)
    14     print(li)        # [17, 20, 26, 31, 44, 54, 55, 77, 93]


    3.插入排序

    # 插入排序算法的思想:数据划分已排序区间和未排序区间,初始的已排序区间只有一个元素,即数组的第一个元素。然后取未排序区间的元素,在已排序区间中找到合适的插入位置将其插入,并保证已排序区间数据一直有序,重复这个过程,直到未排序区间中元素为空,算法结束。

    # 具体排序步骤:

    A.列表分成:有序序列部分和无序序列部分,每次选无序序列的第一个,插入到有序序列中的正确位置;

    B.每次选出的无序序列的第一个元素,都从有序序列的最后一个元素往前与之比较;

    C.若被选出的无序序列的第一个元素比有序序列的某个元素小,则互换位置(升序排列),反之找到正确的插入位置。

    # 是稳定的原地排序算法。

    # 最好情况下,要排序的数据已经是有序的,不需要搬移任何数据,只需要从尾到头遍历已经有序的数据,所以最好时间复杂度为

    O(n);最坏情况下,数据是倒序的,每次插入都相当于在数据的第一个位置插入新的数据,需要移动大量的数据,所以最坏情况时间复杂度为 O(n2)。

     1 def insert_sort(alist):
     2     """插入排序"""
     3     n = len(alist)
     4     for j in range(1, n):  # 刚开始时,默认列表位置0的元素为最小值
     5         for i in range(j, 0, -1):  # 下标在有序序列中前移一位,然后继续比较
     6             # j也是有序序列中进行比较的起始位置,因为比较是从后往前进行
     7             if alist[i] < alist[i-1]:
     8                 alist[i], alist[i-1] = alist[i-1], alist[i]
     9             else:
    10                 break  # 优化算法,减少不必要的比较
    11 
    12 if __name__ == "__main__":
    13     li = [54, 26, 93, 17, 77, 31, 44, 55, 20]
    14     insert_sort(li)
    15     print(li)


    4.快速排序

    # 快速排序算法利用的是分治思想:如果要排序数组的下标从p到r,选择p到r之间的任意一个数据作为pivot(分区点),遍历p到r之间的数据,将小于pivot的放到左边,将大于pivot的放到右边,将pivot放到中间。经过这一步骤之后,数组p到r之间的数据就被分成了三个部分,前面p到q-1之间都是小于pivot的,中间是pivot,后面的q+1到r之间是大于pivot的。

    # 具体排序步骤:

    A.在数据列表中找到一个分区点元素,小于分区点的数据都放在其左边,大于分区点的数据都放在其右边;

    B.分区后的数据子列表的最左侧元素,可以保存为该分区子列表下一轮分区操作的分区点;

    C.需要一个low游标指向小于分区点的数据,也需要一个high游标指向大于分区点的数据。在每个分区内,low从列表最左侧往右遍历,high从列表最右侧往左遍历;

    D.最开始时,low指向第一个分区点,分区点的值被保存下来,然后置空low指向的值;

    E.如果选列表最左侧元素作为分区点,则应先从high游标开始比较;

    F.如果high所指的值大于分区点,high左移一位,如果low所指的值小于分区点,low右移一位;

    G.如果high指向的值小于分区点时,把该值移动至low所在的位置,置空high所指的值,并固定high的位置;此时low所指的值小于分区点,low右移;如果low指向的值大于分区点时,把该值移动至high所在的空位置,置空low所指的值,并固定low的位置;此时high所指的值大于分区点,high左移;

    H.当low与high会合时,该位置就是分区点应该所处的正确位置。

    # 是不稳定的原地排序算法。

    # 最好情况下,如果每次分区操作,选择的分区点都很合适,正好能将大区间对等地一分为二,此时快速排序算法对应的的最好情况时间复杂度是O(nlogn);最坏情况下,如果数组中的数据原来已经是有序的,若每次选择最后一个元素作为分区点,则需要进行大约n次分区操作,才能完成快排的整个过程,每次分区平均要扫描大约n/2个元素,这种情况对应的最坏情况时间复杂度是O(n2)。

     1 def quick_sort(alist, first, last): # first是第一个元素的下标,last是最后一个元素的下标
     2     """快速排序"""
     3     if first >= last:  # 递归终止条件:当下标first不小于下标last时,排序结束
     4         return
     5     mid_value = alist[first]  # 保存分区点
     6     low, high = first, last
     7 
     8     while low < high:  # 控制high与low交替移动
     9         while low < high:  # 当游标还没有会合的时候,先比较high指向的数据
    10             if alist[high] >= mid_value:  # '='只需在一边判断即可,以保证相等的数据都在分区点同侧
    11                 high -= 1  # high左移
    12             else:  break
    13         alist[low] = alist[high]
    14         while low < high:  # 当游标还没有会合的时候,再比较low指向的数据
    15             if alist[low] < mid_value:
    16                 low += 1  # low右移
    17             else:  break
    18         alist[high] = alist[low]
    19     alist[low] = mid_value  # 大循环退出时,low=high,需要把保存的分区点放在该位置处
    20 
    21     # 由于快排只在一个数据列表内操作(原地排序),所以递归时不能使用列表切片作为子列表来传递参数
    22     quick_sort(alist, first, low-1)  # 对分区点左边的子列表排序
    23     quick_sort(alist, low+1, last)  # 对分区点右边的子列表排序
    24 
    25 if __name__ == "__main__":
    26     li = [54, 26, 93, 17, 77, 31, 44, 55, 20]
    27     quick_sort(li, 0, len(li)-1)
    28     print(li)   # [17, 20, 26, 31, 44, 54, 55, 77, 93]


    5.归并排序

    # 归并排序的核心思想:如果要排序一个数组,先把数组从中间分成前后两部分,然后对前后两部分分别排序,再将排好序的两部分合并在一起,这样整个数组就全部有序。

    # 具体排序步骤:

    A.归并排序需要先进行不断地拆分,直至拆分后的数据子序列只包含一个元素;

    B.先两两比较并排序,然后两两合并形成新的子序列,接着再两两比较并排序合并后的子序列,然后再合并...;

    C.在比较、合并时,需要一个left游标指向一个子序列的最左边,一个right游标指向另一个子序列的最左边;

    D.left与right指向的数据进行比较,将较小的值存放进新列表,该值的游标右移一位,然后继续比较。

    # 是稳定的非原地排序算法。

    # 归并排序的执行效率与要排序的原始数组的有序程度无关,其时间复杂度非常稳定,最好情况时间复杂度、最坏情况时间复杂度都是O(nlogn)。

     1 def merge_sort(alist):
     2     """归并排序"""
     3     n = len(alist)
     4     if n <= 1:  # 递归拆分的终止条件
     5         return alist
     6     mid = n // 2
     7     left_li = merge_sort(alist[:mid])  # left与right是归并排序后,形成的新的有序子列表
     8     right_li = merge_sort(alist[mid:])
     9 
    10     left_p, right_p = 0, 0
    11     result = []  # 存放合并的结果值
    12     while left_p < len(left_li) and right_p < len(right_li):  # 控制比较的过程
    13         if left_li[left_p] <= right_li[right_p]:  # 存放较小值,等号保证相等值在同侧
    14             result.append(left_li[left_p])
    15             left_p += 1
    16         else:
    17             result.append(right_li[right_p])   # 存放较小值
    18             right_p += 1
    19     result += left_li[left_p:]  # 将left_li或right_li剩下的元素添加进来
    20     result += right_li[right_p:]
    21 
    22     return result
    23 
    24 if __name__ == "__main__":
    25     li = [54, 26, 93, 17, 77, 31, 44, 55, 20]
    26     sorted_li = merge_sort(li)  # 由于算法是非原地算法,排序后的结果存储在了另一个列表中
    27     print(sorted_li)     # [17, 20, 26, 31, 44, 54, 55, 77, 93]


    6.计数排序

    # 计数排序要求要排序的n个数据所处的范围并不大,比如最大值是k,我们就可以把数据划分成k个桶。

    # 每个桶内的数据值都是相同的,省掉了桶内排序的时间(如给高考考生排序)。

    # 计数排序跟桶排序非常类似,只是桶的大小粒度不一样。

    # 计数排序只能给非负整数排序,如果要排序的数据是其他类型的,要将其在不改变相对大小的情况下,转化为非负整数。

    # 是稳定的原地排序。

    # 因为计数排序只涉及扫描遍历操作,所以时间复杂度是O(n)。

     1 def countingSort(arr, max_value):
     2     """计数排序"""
     3     bucket_num = max_value+1  # 桶的数量
     4     bucket = bucket_num * [0]  # 生成空桶
     5 
     6     for i in range(len(arr)):  # 数据入桶
     7         bucket[arr[i]] += 1
     8 
     9     sorted_count = 0  # 对所有桶内的数据计数
    10     for j in range(bucket_num):  # 数据出桶
    11         while bucket[j] > 0:  # 每个桶内的数据
    12             arr[sorted_count] = j
    13             sorted_count += 1
    14             bucket[j] -= 1
    15     return arr
    16 
    17 if __name__ == '__main__':
    18     array = [0, 2, 5, 5, 5, 6, 3, 1, 10, 9, 9, 7, 7, 7, 8]
    19     sorted_arr = countingSort(array, 10)
    20     print(sorted_arr)  # [0, 1, 2, 3, 5, 5, 5, 6, 7, 7, 7, 8, 9, 9, 10]

    计数排序图片来源:https://www.runoob.com/w3cnote/counting-sort.html

  • 相关阅读:
    关于负数补码的求解
    二维数组的行列指针
    复杂类型的解读
    单斜杠''的思考
    HTML 文本格式化实例--常用的标签
    P1 基础知识以及客户截面 【B站 SolidWorks2014教学视频 共计20讲】
    SolidWorks 2-5 草图的绘制
    SolidWorks 2-4 草图简介
    SolidWorks 模型创建的一般过程 2019年2月25日
    P3 3.HTML&CSS基础_HTML简介 (24'22")---------- 高质量HTML与CSS基础(共103讲)
  • 原文地址:https://www.cnblogs.com/kongzimengzixiaozhuzi/p/12953612.html
Copyright © 2020-2023  润新知