算法

冒泡排序

冒泡排序只会操作相邻的两个数据。每次冒泡操作都会对相邻的两个元素进行比较,看是否满足大小关系要求。如果不满足就让它俩互换。一次冒泡会让至少一个元素移动到它应该在的位置,重复 n 次,就完成了 n 个数据的排序工作。


比如对一组数据 4,5,6,3,2,1​从小到到大进行排序的,第一趟冒泡过程是:

在这里插入图片描述

经过一次冒泡操作之后,6 这个元素已经存储在正确的位置上。要想完成所有数据的排序,只要进行 6 次这样的冒泡操作就行了:

在这里插入图片描述

泡排序算法的原理比较容易理解,具体的代码看下面,可以结合着代码来看前面讲的原理。

def bubble_sort(arr):
    """冒泡排序,arr是列表"""
    n = len(arr)
    if n <= 1:
        return
    for i in range(n):
        # 提前退出标志位
        flag = False
        for j in range(n - i - 1):
            if arr[j] > arr[j + 1]:
                arr[j], arr[j + 1] = arr[j + 1], arr[j]  # 交换
                flag = True  # 此次冒泡有数据交换
        if not flag:
            break


if __name__ == "__main__":
    nums = [4, 1, 33, 7, 5, 23, 56, 11, 10]

    print("排序之前:", nums)
    bubble_sort(nums)
    print("排序之后:", nums)
运行效果:

排序之前: [4, 1, 33, 7, 5, 23, 56, 11, 10]
排序之后: [1, 4, 5, 7, 10, 11, 23, 33, 56]

插入排序(Insertion Sort)

一个有序的数组,只要遍历数组找到数据应该插入的位置将其插入,就能继续保持数组有序。

在这里插入图片描述

可以通过这种方法保持集合中的数据一直有序,插入排序算法就是采用这种思想来进行排序的:

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

如下图,要排序的数据是 4,5,6,1,3,2,其中左侧为已排序区间,右侧是未排序区间

在这里插入图片描述

def insertion_sort(arr):
	    for i in range(1, len(arr)):
	        value = arr[i]  # 待插入元素,arr[0:i-1]作为有序序列
	        j = i - 1  # j指向有序序列的末端
	        while j >= 0 and arr[j] > value:
	            arr[j + 1] = arr[j]
	            j -= 1
	        arr[j + 1] = value  # 插入数据
	

if __name__ == "__main__":
    nums = [4, 1, 33, 7, 5, 23, 56, 11, 10]

    print("排序之前:", nums)
    insertion_sort(nums)
    print("排序之后:", nums)
运行效果:

排序之前: [4, 1, 33, 7, 5, 23, 56, 11, 10]
排序之后: [1, 4, 5, 7, 10, 11, 23, 33, 56]

选择排序(Selection Sort)

选择排序算法也分已排序区间和未排序区间,每次会从未排序区间中找到最小的元素,将其放到已排序区间的末尾。

在这里插入图片描述

代码实现:

def selection_sort(arr):
    for i in range(len(arr) - 1):
        min_temp = i
        for j in range(i + 1, len(arr)):
            if arr[min_temp] > arr[j]:
                min_temp = j
        arr[i], arr[min_temp] = arr[min_temp], arr[i]
    return arr


if __name__ == "__main__":
    nums = [4, 1, 33, 7, 5, 23, 56, 11, 10]

    print("排序之前:", nums)
    selection_sort(nums)
    print("排序之后:", nums)
运行效果:

排序之前: [4, 1, 33, 7, 5, 23, 56, 11, 10]
排序之后: [1, 4, 5, 7, 10, 11, 23, 33, 56]

希尔排序

希尔排序(以发明者的名字命名),是 插入排序 的一种更高效的改进版本。

实际思路:

先拆分成N个组的子数组
对每个子数组进行插入排序,让子数组有序
再拆分成 N 个子数组
对每个子数组进行插入排序,让子数组有序
.... 直到无法再拆分为止。

拆分原理:

第一次的跨度:用数组长度/2 第二次的跨度:上一次的跨度/2 直到跨度为0结束

前提:需要先对插入排序有较深的理解。





实现的思路:

在这里插入图片描述

代码实现:


def shell_sort(arr):
    incr = len(arr) // 2
    while incr > 0:
        for i in range(incr, len(arr), incr):
            val = arr[i]  # 待插入元素,arr[0:i:incr]作为有序序列
            j = i - incr  # j指向有序序列的末端
            while j >= 0 and val < arr[j]:
                arr[j + incr] = arr[j]
                j -= incr
            arr[j + incr] = val
        incr //= 2


if __name__ == "__main__":
    nums = [4, 5, 6, 3, 2, 1]

    print("排序之前:", nums)
    shell_sort(nums)
    print("排序之后:", nums)
运行效果:

排序之前: [4, 5, 6, 3, 2, 1]
排序之后: [1, 2, 3, 4, 5, 6]

归并排序

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

在这里插入图片描述

归并排序使用的就是分治思想。分治,顾名思义,就是分而治之,将一个大问题分解成小的子问题来解决。小的子问题解决了,大问题也就解决了。

分治算法一般都是用递归来实现的。分治是一种解决问题的处理思想,递归是一种编程技巧。

代码实现:

def merge(a, b):
    temp_list = list()  # 用来存放合并之后的数据
    left = right = 0
    while left < len(a) and right < len(b):
        if a[left] < b[right]:
            temp_list.append(a[left])
            left += 1
        else:
            temp_list.append(b[right])
            right += 1

    if left == len(a):
        for i in b[right:]:
            temp_list.append(i)
    else:
        for i in a[left:]:
            temp_list.append(i)

    return temp_list


def merge_sort(lists):
    if len(lists) <= 1:
        return lists
    middle = len(lists) // 2
    left = merge_sort(lists[:middle])  # 左半部
    right = merge_sort(lists[middle:])  # 右半部
    return merge(left, right)


if __name__ == "__main__":
    nums = [4, 5, 6, 3, 2, 1]

    print("排序之前:", nums)
    ret = merge_sort(nums)
    print("排序之后:", ret)
运行效果:

排序之前: [4, 5, 6, 3, 2, 1]
排序之后: [1, 2, 3, 4, 5, 6]

快速排序

速排序算法(Quicksort),简称为“快排”。

快速排序的原理:

快速排序的核心思想是(如下图)

1.先确定一个基准数,让后按照比较规则,如本例是升序排列,则将比基数大的放到右边,比基数小的放到左边。

2.接下来各边重复步骤1,直到全部排序完毕。

在这里插入图片描述

代码实现

def partition(arr, left, right):
    pivot_key = arr[left]

    while left < right:
        # 从右侧开始找一个小于 pivot_key的数据
        while left < right and arr[right] >= pivot_key:
            right -= 1
        arr[left] = arr[right]  # 将找到的右侧小于pivot_key的值,放入到左侧

        # 从左侧开始找一个大于 pivot_key的数据
        while left < right and arr[left] <= pivot_key:
            left += 1
        arr[right] = arr[left]  # 将找到的数据放入到右侧

    arr[left] = pivot_key  # 将pivot_key数据放到中间位置
    return left


def quick_sort(arr, left, right):
    """快速排序函数"""
    # arr[] --> 排序数组
    # left  --> 起始索引
    # right  --> 结束索引
    if left < right:
        pi = partition(arr, left, right)
        quick_sort(arr, left, pi - 1)
        quick_sort(arr, pi + 1, right)


if __name__ == "__main__":
    nums = [7, 10, 8, 9, 1, 5]
    n = len(nums)
    print("排序前的数组:", nums)
    quick_sort(nums, 0, n - 1)
    print("排序后的数组:", nums)
运行效果:

排序前的数组: [7, 10, 8, 9, 1, 5]
排序后的数组: [1, 5, 7, 8, 9, 10]

在这里插入图片描述

归并排序的处理过程是由下到上的,先处理子问题,然后再合并。快速排序的处理过程是由上到下的,先分区,然后再处理子问题。

桶排序(Bucket sort)

桶排序核心思想是将要排序的数据分到几个有序的桶里,每个桶里的数据再单独进行排序。桶内排完序之后,再把每个桶里的数据按照顺序依次取出,组成的序列就是有序的了

在这里插入图片描述

桶排序要求要排序的数据需要很容易就能划分成 m 个桶,并且,桶与桶之间有着天然的大小顺序。这样每个桶内的数据都排序完之后,桶与桶之间的数据不需要再进行排序。其次要求数据在各个桶之间的分布是比较均匀的。

代码实现:

import math


# m表示桶的个数
def bucket_sort(arr, m):
    if len(arr) < 2:
        return
    # 扫描最小值和最大值
    min_temp, max_temp = arr[0], arr[0]
    for i in range(0, len(arr)):
        if arr[i] < min_temp:
            min_temp = arr[i]
        elif arr[i] > max_temp:
            max_temp = arr[i]
    bucket_size = math.ceil((max_temp - min_temp + 1) / m)
    buckets = []
    for i in range(m):
        buckets.append([])
    # 将数组中值分配到各个桶里
    for data in arr:
        bucket_index = (data - min_temp) // bucket_size
        buckets[bucket_index].append(data)
    # 对每个桶进行排序,时间复杂度小于O(nlogn)的排序算法都可以
    k = 0
    for bucket in buckets:
        bucket.sort()
        for data in bucket:
            arr[k] = data
            k += 1


if __name__ == "__main__":
    nums = [7, 10, 8, 9, 1, 5]
    print("排序前:", nums)
    bucket_sort(nums, 4)
    print("排序前:", nums)



桶排序应用
桶排序比较适合用在外部排序(数据存储在外部磁盘中)中:

比如有 10GB 的订单数据,希望按订单金额(假设金额都是正整数)进行排序,但是内存只有几百 MB。

可以先扫描一遍文件,分析订单金额所处的数据范围。假设经过扫描之后订单金额最小是 1 元,最大是 10 万元。将所有订单根据金额划分到 100 个桶里,第一个桶存储金额在 1 元到 1000 元之内的订单,第二桶存储金额在 1001 元到 2000 元之内的订单,以此类推。每一个桶对应一个文件,并且按照金额范围的大小顺序编号命名(00,01,02…99)。

理想的情况下,如果订单金额在 1 到 10 万之间均匀分布,那订单会被均匀划分到 100 个文件中,每个小文件中存储大约 100MB 的订单数据,就可以将这 100 个小文件依次放到内存中,用快排来排序。等所有文件都排好序之后,只需要按照文件编号,从小到大依次读取每个小文件中的订单数据,并将其写入到一个文件中,那这个文件中存储的就是按照金额从小到大排序的订单数据了。

但订单按照金额在 1 元到 10 万元之间并不一定是均匀分布的 ,有可能某个金额区间的数据特别多,针对这些划分之后还是比较大的文件,可以继续划分,比如,订单金额在 1 元到 1000 元之间的比较多,我们就将这个区间继续划分为 10 个小区间,1 元到 100 元,101 元到 200 元,201 元到 300 元…901 元到 1000 元。如果划分之后,101 元到 200 元之间的订单还是太多,无法一次性读入内存,那就继续再划分,直到所有的文件都能读入内存为止。
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值