python数据结构与算法:排序算法总结

学习黑马程序员算法笔记

冒泡排序

冒泡排序是最基础的交换排序,会遍历序列n-1次,每一次遍历序列时都会挑选出剩余序列中的最大值,
冒泡排序的大概流程是:使用两个for循环,第一层for循环控制的是遍历的次数,设置成range(1,length),第二层for循环控制的是遍历序列长度。每一次遍历都会有一个最大的数排序好(从结尾想开头排序),所以每次遍历的时候在第二层控制遍历的序列的长度,减少时间复杂度,提高效率。
第一层for循环控制的是遍历的次数,
第二层for循环控制的是遍历的序列的长度,遍历次数count,序列长度是[0,length-count)
每次遍历选择最大值,一次从结尾向前从大到小排序
eg:原始数据:[16, 8, 9,25,5]
第一次遍历:count=1
遍历的序列长度:[0, 5-count),length=5(注:最后一个索引是3,会将索引4的数据读出。)
[8, 9, 16, 5, 25]
第二次遍历:count=2
遍历的序列的长度:[0,5-count),
[8, 9, 5, 16, 25 ] (注:25表示不参与排序,就是length-count实现控制参与排序的序列的长度,因为经过一次排序之后,参与排序的序列最后一个值对应的就是序列中的最大值 )
第三次遍历:count=3
遍历的序列的长度:[0,5-count),
[8, 5, 9, 16, 25 ]
第四次遍历:count=4
遍历的序列的长度:[0,5-count),
[5, 8, 9, 16, 25 ]
结果:[5, 8, 9, 16, 25]

def bubble_sort(sequence: list):
    """
    冒泡排序
    每一次遍历将最大值放在最后,每一次遍历查找都是将找出最大数值,一个列表中,会遍历n-1次,
    :param sequence:
    :return:
    """
    length = len(sequence)
    if length < 2:  # length=0:sequence是一个空的;length=1:只有一个数值,不用排序
        return
    for count in range(1, length):  # count代表的是第几遍遍历sequence,需要遍历length-1次之后才会完成所有的元素的对比
        # 每次循环结束之后,sequence的最后一个元素是最大值
        change = False  # 序列有没有改变过顺序,False表示没有改表过顺序,True表示顺序改变过
        for index in range(0, length - count):  # 对sequence进行一次排序
            if sequence[index] > sequence[index + 1]:  # 前一个元素大于后一个元素
                # 二者交换位置
                sequence[index], sequence[index + 1] = sequence[index + 1], sequence[index]
                change = True  #
        if not change:
            break

选择排序

遍历n-1次,每次遍历选出最小值对应的索引,遍历结束之后,进行数据交换,
交换的数据是第count次遍历就是索引是count对应的元素与最小值对应的索引出的值交换

# =========================================================================#
# 选择排序,每次循环都是选择序列中的最小值,
# 每次选择的是最小值的时候,排序算法是稳定的;
# 每次选择的是最大值的时候,排序算法是不稳定的
# =========================================================================#
def select_sort(sequence):
    """
    选择排序
    原理:第一次遍历找到所索引范围0-length-1的最小值放在索引0,第二次找到索引范围1-length-1的最小值放在索引1处,,,
    通过list[count], list[min_value_index] = list[min_value_index], list[count]交换数据
    :param sequence:
    :return:
    """
    length = len(sequence)
    if length < 2:
        return

    # 操作索引更方便
    # 使用count控制遍历此时,每一次新的遍历开始,寻找最小值的序列都会少一个长度,n-1次

    for count in range(1, length):  # count:1, 2, 3, ... , length - 1
        start_index = count - 1  # 开始的索引的位置
        min_value_index = start_index  # 用来记录最小值对应的索引,每一次遍历,count都会比上一次+1

        change = False# 需要不需要交换至
        for index in range(count, length):  # 遍历所有值,
            """
            每次循环,都会是将最小值移动到最左侧,
            下一次遍历时会将已经排序好的部分隔离出去,只遍历没有排序的部分
            """
            if sequence[index] < sequence[min_value_index]:  # 找最小值对应的下标
                min_value_index = index
                change = True
        # 将最小值放在剩余序列的开头
        if change:  # 如
            sequence[start_index], sequence[min_value_index] = sequence[min_value_index], sequence[start_index]

插入排序

从索引1开始遍历一直到索引n-1,第count次遍历就是以index=count为界左边是有序部分,右边是无序部分,
通过遍历对比index=count和有序部分的值,找到index对应的位置,

#  ======================================================================================#
#  插入算法,是一种简单直观的排序算法,
#  工作原理是通过构建有序序列,对于未排序的数据,在已排序序列中从后向前扫描,找到相应的位置并插入。
#  插入排序在实现上,在从后向前扫描过程中,需要反复把已排序的元素逐步向后挪位,为新的元素提供插入空间。
#  最优时间复杂度:O(n)
#  最坏时间复杂度O(n^2)
#  稳定性:稳定
#  ======================================================================================#
def insertion_sort(sequence: list):
    length = len(sequence)  # 序列长度
    # 从右边的无需序列中取出第几个元素执行线面的过程
    # 划分:左边是有序序列,右边是无序序列,每次遍历都是从右边取出一个元素,通过在左边做对比,将元素放在对应的位置上
    for count in range(1, length):  # count代表的是遍历的次数,取值范围1~n-1,也是每次遍历要将比较的数值的索引
        # 对第count个元素进行对比插入到左边有序的序列中
        # 下面遍历作用是将第count个元素在左边有序部分找到对应的位置
        for index in range(count, 0, -1):  # 将索引为count的数值和count, ..., 1的数值全部比对,
            if sequence[index] < sequence[index - 1]:  # 如果满足数值前移的条件,就将index-1对应的数值移动到index
                sequence[index - 1], sequence[index] = sequence[index], sequence[index - 1]  # 交换数据
            else:  # 不需要交换的意思就是:原序列的第count个元素在左边有序部分找到了它的对应的位置
                break

希尔排序

希尔排序是改进版的插入算法,通过增量来控制序列分组,最终达到排序效果

#  =============================================================================================#
#  希尔排序:插入排序的一种,也称缩小增量排序,是直接排序算法的一中更高效的改进版本。
#  希尔排序是非稳定的排序算法,希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;
#  随着增量的减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法终止。
#  gap的取值是需要数学计算的,通过计算才能确定最高效的gap的取值
#  稳定性:不稳定
#  =============================================================================================#
def shell_sort(sequence: list):
    length = len(sequence)
    if length < 2:  # 空列表或者只有一个元素的列表是不需要排序的
        return

    gap = length // 2
    while gap > 0:  # 控制gap的步长
        # 插入排序
        for count in range(gap, length):
            for index in range(count, 0, -gap):
                if sequence[index] < sequence[index - gap]:  # 前一个比后一个的值大
                    sequence[index - gap], sequence[index] = sequence[index], sequence[index - gap]
                else:
                    break
        gap = gap // 2  # 缩短gap步长

快速排序(必须掌握的排序算法

从序列中挑选出一个元素作为“基准”,通过遍历最终得到将左边序列是小于等于基准的元素,右边序列是大于基准的元素,
通过递归算法,分别对左边子序列和右边子序列进行快速排序,

#  ===================================================================================================================#
#  快速排序,又称划分交换排序,通过一趟排序将要排序的数据分割成独立的两部分,
#  其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对两部分数据分别进行快速排序,
#  整个排序过程可以递归进行,以此达到整个数据换成有序序列。
#  步骤为  1.从数列中挑选出一个元素,称为基准;
#         2.重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准后面(相同的数可以放在任一边),
#         在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区操作;
#         3.递归把小于基准值元素的子数列和大于基准值元素的子数列排序
#  最优时间复杂度:O(n*log(n))
#  最坏时间复杂度:O(n^2)
#  稳定性:不稳定
#  ===================================================================================================================#
def quick_sort(sequence: list, first, last):
    # 只有一个元素,low=0时,不满足条件时退出执行,相当于对序列不进行任何操作,
    # 当first=last时说明基准左侧只有一个元素,无需排序,
    # first=last+1时,说明基准的位置没有改变,没有元素排序
    if first >= last:
        return

    mid_value = sequence[first]  # 挑选基准,将基准挑选出来之后,相当于在序列中空余出一个位置,可以对对应位置进行赋值,不用数值交换
    low = first  # 值比mid_value小的值对应的索引
    high = last  # 值比mid_value大的值对应的索引

    # 寻找基准的位置索引,并将基准两侧按照左侧全是小于等于基准的,右侧全是大于基准的原则对序列进行排列
    # 每交换一次数值之后,low和high交替改变,交替条件是:有数值的交换
    while low < high:  # 循环结束之后,low = high
        # high左移
        while low < high and sequence[high] >= mid_value:  # 等号位于哪相等的元素放在哪边
            high -= 1
        # 跳出循环的有两个条件:low=high,说明找到了基准的位置;sequence[high]<mid_value(基准),需要交换值
        sequence[low] = sequence[high]  # sequence[high]<mid_value,交换元素

        # low右移
        while low < high and sequence[low] < mid_value:
            low += 1
        sequence[high] = sequence[low]

    # 循环结束之后,low=high,low和high指向的索引位置就是基准所在的位置
    sequence[low] = mid_value
    # 对基准左侧的子序列进行排序,当first=low时,说明基准是序列的最小值
    quick_sort(sequence, first, low - 1)
    # 对基准的右侧序列进行排序,当low=last时说明基准是序列中的最大值
    quick_sort(sequence, low + 1, last)

归并排序

归并排序首先是对半将序列分开,之后分别对两边的序列进行排序,使用递归实现

#  ======================================================================================================#
#  归并排序:使用递归
#  归并排序是采用分治法的一个典型应用。归并排序的思想就是先递归分解数组,在合并数组。
#  将数组分解最小之后,人后合并两个有序数组,基本思想是比较两个数组的最前面的数,谁小就先取谁,
#  取了后相应的指针就往后移一位。人后再比较,直至一个数组为空,最后把另一个数组的剩余部分复制过来即可。
#  最优时间复杂度:O(n*log(n))
#  最坏时间复杂度:O(n*log(n))
#  稳定性:稳定
#  做为对比的话:归并排序时间复杂度最小,但是空间复杂度是最大的,因为是需要一个新的列表来保存输出,需要新的内存空间
#  ======================================================================================================#
def merge_sort(sequence: list) -> list:
    # 拆分过程,将一个序列对半拆分,奇数右边比左边多
    length = len(sequence)

    # 退出递归的条件,只有一个元素的序列是不需要排序的
    if length <= 1:
        return sequence

    """以下部分不一定能够执行到,当只有一个元素时,就没有必要拆分了,就会直接将列表返回"""
    mid = length // 2  # 拆分的中点

    # left 采用归并排序之后形成的有序的新的列表
    left_sequence = merge_sort(sequence[:mid])  # 输入是对列表的切片,所以输入是一个列表
    length_left = len(left_sequence)  # 长度
    # right 采用归并排序之后形成的有序的新的列表
    right_sequence = merge_sort(sequence[mid:])  # 输入是对列表的切片,所以输入是一个列表
    length_right = len(right_sequence)  # 长度

    # 合并元素merge(left, right),将两个有序的子序列合并成一个整体
    left_pointer = 0  # 左边子序列的指针
    right_pointer = 0  # 右边子序列的指针
    result = []  # 保存结果的列表

    # 循环控制合并元素,任何一个指针到头就会跳出循环,跳出循环时,另一个子序列还会有剩余元素,将剩余的元素直接加到result之后
    while left_pointer < length_left and right_pointer < length_right:  # 指针不能超过列表长度范围
        # 遍历对比两个子序列的值,小的先放在result列表中
        if left_sequence[left_pointer] <= right_sequence[right_pointer]:  # 感觉等于号放在这,算法是稳定的
            result.append(left_sequence[left_pointer])
            left_pointer += 1

        else:
            result.append(right_sequence[right_pointer])
            right_pointer += 1

    # 将剩余部分的元素添加进result之后
    result += left_sequence[left_pointer:]
    result += right_sequence[right_pointer:]

    return result

二分查找

#  ========================================================================================================#
#  二分查找:操作对象必须是有序的,使用的对象只能是顺序表
#  ========================================================================================================#
def binary_search1(sequence: list, item):
    """
    递归方法
    :param sequence:
    :param item:
    :return: bool:True:找到了,False:没有找到
    """
    length = len(sequence)
    # 退出递归的条件
    if length > 0:
        mid = length // 2

        if sequence[mid] == item:
            return True
        elif item < sequence[mid]:  # 左边
            return binary_search1(sequence[:mid], item)
        else:  # 右边
            return binary_search1(sequence[mid + 1:], item)
    else:  # length = 0,说明没有在这个序列中查找到item
        return False


def binary_search2(sequence, item):
    """
    非递归方法
    :param sequence:
    :param item:
    :return:
    """
    length = len(sequence)
    first = 0
    last = length - 1

    # 跳出循环时,有两种情况:找到了返回的是True;没有找到
    while first <= last:
        mid = (first + last) // 2
        if sequence[mid] == item:
            return True
        elif item < sequence[mid]:  # 左边
            last = mid - 1
        else:  # 右边
            first = mid + 1
    return False  # 执行这一句时,说明没有找到item

猜你喜欢

转载自blog.csdn.net/weixin_50727642/article/details/122304029