几种基础算法的理解以及python实现,冒泡/二分/选择排序/插入排序/归并排序/快速排序

相关术语

稳定性

概念:如果值相同的元素在排序前后保证着排序前的相对位置,则称为稳定的排序,反之则为不稳定排序

时间复杂度和稳定性一览

这里只说平均复杂度

  • 冒泡 - O(n²) - 稳定排序
  • 插入 - O(n²) - 稳定排序
  • 选择 - O(n²) - 稳定排序
  • 快速 - O(nlog2(n)) - 不稳定排序
  • 归并 - O(nlog2(n)) - 不稳定排序

查找数据(python实现)

暴力查找

从数据集的第一个数据项开始,直到找到最后一个数据项,看是否找得到target

# 无序表中查询
def search(l, target):
    for i in l:
        if i == target:
            return True
    return False

二分查找

二分查找只能是在有序表的前提下进行,但是如果是无序表,先需要排序的情况下,可能就会由于不同的排序情况决定时间复杂度

# 有序表中查询数据项,时间复杂度为 O(log2(N))
def bin_search(l, target):
    left, right = 0, len(l) - 1
    while left <= right:
        mid = (right + left) // 2
        if l[mid] > target:
            right = mid - 1
        elif l[mid] < target:
            left = mid + 1
        else:
            return True
    return False

排序(python实现)

冒泡排序

类似于河里的水泡一样,由小到大慢慢浮出水面。

  1. 是指将列表中的数据从第一个开始,两两比较,保证较大的元素排在较小元素的后面,以此类推,直至整个列表全部两两比较完成,此时列表中的最后一个位置应该就为当前列表的最大值。
  2. 重复上面的步骤,这次比较完的结果应该是列表中倒数第二个数据为次大值,以此类推
  3. 直至所有元素有序(外部控制循环的次数,为 len(list) - 1次)
# 最直白的方法
def bubble_sort(l):
    # 控制冒泡的次数
    for i in range(len(l) - 1):
        # 比较到当前列表有序位置的前一个即可
        for j in range(len(l) - i - 1):
            if l[i] > l[i + 1]:
                l[i], l[i+1] = l[i+1], l[i]
	return l

# 简单改良版(减少列表有序部分的比较次数)
def bubble_sort(l):
    for i in range(len(l) - 1):
        is_sorted = True
        for j in range(len(l) - i - 1):
            if l[j] > l[j + 1]:
                l[j], l[j+1] = l[j+1], l[j]
	            # 但凡有交换,就说明当前长度的列表还没有排好序
	            is_sorted = False
        # 内部循环走完为True,说明[:len(l) - i - 1]的顺序已经是有序的,然后又因为外面的大循环把[len(l) - i - 1:]部分元素已经排好序,所以整个列表已经是有序列表,不需要进行重复的判断了
        if is_sorted:
            break
    return l

# 优化版本(记录最后一次交换元素的位置,主要在于后面已经有序的部分不在进行比较)
def bubble_sort(l):
    last_position = 0
    sort_border = len(l) - 1
    # 控制要比较的总次数
    for i in range(len(l) - 1):
        is_sorted = True
        # sort_border后面都是有序的,不需要进行重复比较了
        for j in range(sort_border):
            if l[j] > l[j + 1]:
                l[j], l[j + 1] = l[j + 1], l[j]
                is_sorted = False
                # 记录最后一次交换的索引位置
                last_position = j
        sort_border = last_position
        if is_sorted:
            break
    return l

r = bubble_sort(l)
print(r)

选择排序(O(n²))

一般来讲,每次循环默认选定列表第一个位置为当前列表最大值,将此位置的值和后面的值做比对,如果发现更大的值,则记录该值的位置,直到遍历到列表末尾,将末尾的值和最大位置的值交换,这样最大的值便出现在列表的最后一个位置

def selection_sort(l):
    if not l:
        return
    for i in range(len(l) - 1):
        # 每一次记录最大值的位置,并和最后一个没排序的位置进行交换
        max_index = 0
        for j in range(1, len(l)-i):
            if l[j] > l[max_index]:
                max_index = j
        l[-(i + 1)], l[max_index] = l[max_index], l[-(i + 1)]

    return l

插入排序

概念:将原列表想象成两部分,前面是已经排好序的,后面是乱序的,依次将乱序部分的每一个数据都和前面排好序的部分进行比较,插入到相应的位置

步骤

  1. 最开始,将列表第一个数据加入到前面部分,这时**原列表就可看作第一个数据(排好序部分)和剩下的数据(未排序部分)**两部分,并同时将第二个数据(未排序中的第一个数据)和第一个数据(排好序的最后一个数据)进行比对,根据大小插入到相应的位置,此时有两个数据已经有序
  2. 然后将第三个数据(未排序中的第一个数据)和前两个数据(已经有序的两个)相比较,并移动比自身更大的数据,排序,这样便排好3个数据
  3. 以此类推,直到整个后面部分(未排序)的数据都添加到前面部分(有序)中
def insertion_sort(l):
    for i in range(1, len(l)):
        # 将当前元素从后向前和已经排序的进行比较,l[j]就是当前未排序中的第一个元素
        for j in range(i, 0, -1):
            if l[j] < l[j - 1]:
                l[j], l[j - 1] = l[j - 1], l[j]
    return l

归并排序

递归,将问题规模变小,直至有个退出的条件,解决每个子问题,最后将所有子问题的答案拼接到一起即可

# 两个方法的核心思想差不多,在于合并操作有点点的区别
# ############################ 第一个方法 ##############################
def merge_sort(l):
    if len(l) <= 1:
        return l
    mid = len(l) // 2
    left = merge_sort(l[:mid])
    right = merge_sort(l[mid:])
    
    # 这个复杂度要高一点,毕竟涉及到pop操作
    result = []
    while left and right:
        if left[0] <= right[0]:
            result.append(left.pop(0))
        else:
            result.append(right.pop(0))
    result.extend(right if right else left)
    return result
# ############################ 第一个方法 ##############################

# ############################ 第二个方法 ##############################
# 这里分成两个部分,一个用来专门递归,控制流程,一个专门用来将递归出来的子列表进行排序操作
# 归并排序
def merge_sort(l):
    if len(l) <= 1:
        return l
    mid = len(l) // 2
    left = merge_sort(l[:mid])
    right = merge_sort(l[mid:])
    return merge(left, right)


def merge(nums1, nums2):
    r = []
    # 两个指针分别指向两个列表当前数据的索引
    left, right = 0, 0
    while left < len(nums1) and right < len(nums2):
        if nums1[left] <= nums2[right]:
            r.append(nums1[left])
            left += 1
        else:
            r.append(nums2[right])
            right += 1
    r += nums1[left:]
    r += nums2[right:]
    return r
# ############################ 第二个方法 ##############################

快速排序

概念:快速排序是冒泡排序的升级版,但是平均性能却有很大的提升,达到O(nlog2(n))的时间复杂度,最差情况就和冒泡的性能一样为O(n²)

理解:原来我一直以为快速排序和归并思想上异曲同工,都是通过减小规模的方式进行排序操作,但是深入的了解了才发现两者核心思想完全不是一个方向···这里不做大段的描述性陈述,网上有很多,书上也有很多,细节可以自己去看,只能说,自己多敲几回,自己敲,出bug就想想为什么,实在不行再看正解,自己去想原理,想想就真的通了,以下几点只是把我的理解分享一下···

  1. 快速排序也是通过元素之间的交换对列表排序
  2. 函数(partatition)每次执行一次,都能100%保证至少一个元素(prviot元素)在列表中的顺序是正确的(也就是说,每次执行完整个函数,这个列表中至少有一个元素(prviot的数据)被原地更改,并且放到了整个列表他应该所在的位置,好好想想这句话
  3. 承接第二点2来说,其他元素有可能有移动,有可能没移动,取决于数据分布状况
  4. 每次partatition函数返回的结果就是,prviot位置的元素已经位置正确,那么就调整剩下的元素的顺序,分成prviot左边部分以及右边部分,(prviot不需要进行排序操作了)
  5. 以此类推,每次都能保证至少一个元素有序,其他元素也会根据prviot的值进行相应的调整,直至递归结束条件达成
# 为什么这么写,因为用户只需要提供要排序的序列即可,所以在quick内封装了一个helper函数用来递归,并提供相关参数,这里采用双边循环法,左右各有一个指针指向变化的元素
# 调用接口
def quick_sort(l):
    left = 0
    right = len(l) - 1
    quick_sort_help(l, left, right)

# 辅助函数
def quick_sort_help(l, left, right):
    if left < right:
        index = partition(l, left, right)
        quick_sort_help(l, left, index - 1)
        quick_sort_help(l, index + 1, right)

# 选择基准值,并将标准值的左右两边汇聚到一起,并返回基准值位置的索引
def partition(l, left, right):
    prviot = l[left]
    start = left
    end = right
    while start < end:
        while l[end] >= prviot:
            end -= 1
        while l[start] <= prviot:
            start += 1
        if start < end:
            l[start], l[end] = l[end], l[start]
    l[left], l[end] = l[end], l[left]
    return end
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值