十大排序详解:冒泡、选择、插入、希尔、归并、快速、堆、计数、桶、基数排序(python版)

十大排序详解

    • 冒泡排序
    • 选择排序
    • 插入排序
    • 希尔排序
    • 归并排序
    • 快速排序
    • 堆排序
    • 计数排序
    • 桶排序
    • 基数排序

冒泡排序

比较相邻的元素。如果第一个比第二个大,就交换他们两个。

对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。

针对所有的元素重复以上的步骤,除了最后一个。

持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

def bubble_sort1(sequence):
    for i in range(1, len(sequence)):
        for j in range(0, len(sequence) - i):
            if sequence[j] > sequence[j + 1]:
                sequence[j], sequence[j + 1] = sequence[j + 1], sequence[j]
    return sequence


if __name__ == '__main__':
    sequence = [12, 27, 46, 16, 25, 37, 22, 29, 15, 47, 48, 34]
    print(sequence)
    print(bubble_sort1(sequence))

算法优化:
立一个 flag,当在一趟序列遍历中元素没有发生交换,则证明该序列已经有序。

def bubble_sort(sequence):
    for i in range(1, len(sequence)):
        flag = False
        for j in range(0, len(sequence) - i):
            if sequence[j] > sequence[j + 1]:
                sequence[j], sequence[j + 1] = sequence[j + 1], sequence[j]
                flag = True
        if flag == False:
            break
    return sequence


if __name__ == '__main__':
    sequence = [12, 16, 22, 27, 46, 29]
    print(sequence)
    print(bubble_sort(sequence))

选择排序

首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。

再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。

重复第二步,直到所有元素均排序完毕。

def select_sort(sequence):
    for i in range(len(sequence) - 1):
        minIndex = i  # 记录最小数的索引
        for j in range(i + 1, len(sequence)):
            if sequence[j] < sequence[minIndex]:
                minIndex = j
        # 将该数放到已排序序列的末尾
        sequence[minIndex], sequence[i] = sequence[i], sequence[minIndex]
    return sequence


if __name__ == '__main__':
    sequence = [12, 27, 46, 16, 25, 37, 22, 29, 15, 47, 48, 34]
    print(sequence)
    print(select_sort(sequence))

插入排序

将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。

从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)

def insertionSort(arr):
    """
    preindex是已排好序数据中被比较的那个数据下标,初始认为第一个数据是排好序的。
    current保存要插入的那个数据
    注意是向前找
    """
    for i in range(len(arr)):
        preIndex = i - 1
        current = arr[i]
        while preIndex >= 0 and arr[preIndex] > current:
            arr[preIndex + 1] = arr[preIndex]  # 将已经排好序的数据arr[preIndex]后挪,
            preIndex -= 1
        arr[preIndex + 1] = current  # 插入到[preIndex + 1]位置
    return arr


if __name__ == '__main__':
    sequence = [12, 27, 46, 16, 25, 37, 22, 29, 15, 47, 48, 34]
    print(sequence)
    print(insertionSort(sequence))

算法优化:
折半插入:在实现直接插入排序的基础上,对已经排好序的数据进行二分搜索来决定待排序数据应该插在哪边,从而减少比较次数。

希尔排序

希尔排序基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。

选择一个增量序列 t1,t2,…,tk,其中 ti>tj,tk=1;

按增量序列的个数 k,对序列进行 k 趟排序;

每趟排序,根据对应的增量 ti,将待排序序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。仅增量因子为 1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

def shellSort(arr):
    """
    希尔排序的间隔序列不推荐用2的幂(1 2 4 8 16...),因为这样直到间隔为1之前,都不会将奇数位置与偶数位置的元素进行比较,这样是低效的。
    希尔自己认为可以用2的幂 - 1序列(1 3 7 15...),后来又有文章建议用3x + 1(1 4 13 40 121...),都是可以的
    """
    import math
    gap = 1
    while (gap < len(arr) / 3):
        gap = gap * 3 + 1
    while gap > 0:
        for i in range(gap, len(arr)):
            temp = arr[i]
            j = i - gap  # 寻找前一个以gap为间隔的数据
            while j >= 0 and arr[j] > temp:
                arr[j + gap] = arr[j]
                j -= gap
            arr[j + gap] = temp
        gap = math.floor(gap / 3)  # floor返回数字的下舍整数
    return arr


if __name__ == '__main__':
    sequence = [12, 27, 46, 16, 25, 37, 22, 29, 2]
    print(sequence)
    print(shellSort(sequence))

算法详解:希尔排序

归并排序

把长度为 n 的输入序列分成两个长度为 n/2 的子序列;

对这两个子序列分别采用归并排序;

将两个排序好的子序列合并成一个最终的排序序列。

十大排序详解:冒泡、选择、插入、希尔、归并、快速、堆、计数、桶、基数排序(python版)_第1张图片
可以看到这种结构很像一棵完全二叉树,下面采用递归去实现(也可采用迭代的方式去实现)。

再来看治阶段,将两个已经有序的子序列合并成一个有序序列,比如上图中的最后一次合并,要将[4,5,7,8]和[1,2,3,6]两个已经有序的子序列,合并为最终序列[1,2,3,4,5,6,7,8],来看下实现步骤:
十大排序详解:冒泡、选择、插入、希尔、归并、快速、堆、计数、桶、基数排序(python版)_第2张图片

import math


def merge_sort(sequence):
    if (len(sequence) < 2):
        return sequence
    mid = math.floor(len(sequence) / 2)
    left, right = sequence[0:mid], sequence[mid:]
    return merge(merge_sort(left), merge_sort(right))


def merge(left, right):
    result = []
    while left and right:
        if left[0] <= right[0]:
            result.append(left.pop(0))
        else:
            result.append(right.pop(0))
    while left:
        result.append(left.pop(0))
    while right:
        result.append(right.pop(0))
    return result


if __name__ == '__main__':
    sequence = [12, 27, 46, 16, 25, 37, 22, 29, 15, 47, 48, 34]
    print(sequence)
    print(merge_sort(sequence))

快速排序

使用分治法来把一个串分为两个子串,从数列中挑出一个元素,称为"基准"(pivot)。

重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区操作。

递归地把小于基准值元素的子数列和大于基准值元素的子数列排序。

def quick_sort(li, start, end):
    # 分治 一分为二
    # start=end ,证明要处理的数据只有一个
    # start>end ,证明右边没有数据
    if start >= end:
        return
    # 定义两个游标,分别指向0和末尾位置
    left = start
    right = end
    # 把0位置的数据,认为是中间值即mid保存基准值
    mid = li[left]
    while left < right:
        # 让右边游标往左移动,目的是找到小于mid的值,放到left游标位置
        while left < right and li[right] >= mid:
            right -= 1
        li[left] = li[right]
        # 让左边游标往右移动,目的是找到大于mid的值,放到right游标位置
        while left < right and li[left] < mid:
            left += 1
        li[right] = li[left]
    # while结束后,把mid放到中间位置,left=right(注意此处left=right)
    li[left] = mid
    # 递归处理左边的数据
    quick_sort(li, start, left - 1)
    # 递归处理右边的数据
    quick_sort(li, left + 1, end)


if __name__ == '__main__':
    l = [6, 5, 4, 3, 2, 1]
    quick_sort(l, 0, len(l) - 1)
    print(l)

堆排序

将初始待排序关键字序列(R1,R2…Rn)构建成大顶堆,此堆为初始的无序区。

将堆顶元素 R[1]与最后一个元素 R[n]交换,此时得到新的无序区(R1,R2,…Rn-1)和新的有序区(Rn),且满足 R[1,2…n-1]<=R[n]。

由于交换后新的堆顶 R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,…Rn-1)调整为新堆,然后再次将 R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2…Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为 n-1,则整个排序过程完成。

def siftdown(elems, e, begin, end):  # 向下筛选(begin为e元素下标)
    i, j = begin, begin * 2 + 1  # j为i的左子结点
    while j < end:
        if j + 1 < end and elems[j] > elems[j + 1]:  # 如果左子结点大于右子结点
            j += 1  # 则将j指向右子结点(因为j为i的左子结点,+1就变成右子节点)
        if e < elems[j]:  # j已经指向两个子结点中较小的位置,
            break  # 如果插入元素e小于j位置的值,则为3者中最小的
        elems[i] = elems[j]  # 能执行到这一步的话,说明j位置元素是三者中最小的,则将其上移到父结点位置
        i, j = j, j * 2 + 1
        # 更新i为被上移为父结点的原来的j的位置
        # 更新j为更新后i位置的左子结点(j * 2 + 1),如果以j为根的子树不满足小堆,需要重新筛选
    elems[i] = e  # 如果e已经是某个子树3者中最小的元素,则将其赋给这个子树的父结点


def heap_sort(elems):
    end = len(elems)
    # 构造最小堆堆(end // 2 - 1通常得到最后一个非叶子节点)
    # 遍历二叉树的非叶子节点自下往上的构造小顶堆,针对每个非叶子节点,都跟它的左右子节点比较,把最小的值换到这个子树的父节点
    for i in range(end // 2 - 1, -1, -1):
        siftdown(elems, elems[i], i, end)

    for i in range((end - 1), 0, -1):  # 进行堆排序.i最后一个值为1,不需要到0
        e = elems[i]  # 将末尾元素赋给e
        elems[i] = elems[0]  # 交换堆顶与末尾元素
        siftdown(elems, e, 0, i)  # 重新调整小顶堆(将i作为siftdown中的end因为最后一个元素是排好序的,只需要排i之前的)

    return (elems)


if __name__ == "__main__":
    # 实现降序
    print(heap_sort([5, 6, 8, 1, 2, 4, 9]))

叶子节点与非叶子节点关系
堆排序及原理详解

计数排序

找出待排序的数组中最大和最小的元素;

统计数组中每个值为 i 的元素出现的次数,存入数组 C 的第 i 项;

对所有的计数累加(从 C 中的第一个元素开始,每一项和前一项相加);

反向填充目标数组:将每个元素 i 放在新数组的第 C(i)项,每放一个元素就将 C(i)减去 1。

def counting_sort(sequence):
    if sequence == []:
        return []
    sequence_len = len(sequence)
    sequence_max = max(sequence)  # 找出待排序的数组中最大的元素
    sequence_min = min(sequence)  # 找出待排序的数组中最小的元素
    counting_arr_length = sequence_max - sequence_min + 1  # 数组容量(即为元素种类)
    counting_arr = [0] * counting_arr_length
    for number in sequence:
        #  这里counting_arr[number - sequence_min]和下面的counting_arr[sequence[i] - sequence_min]相对应
        counting_arr[number - sequence_min] += 1  # 统计数组中数据元素出现次数
    for i in range(1, counting_arr_length):  # 计数累加
        counting_arr[i] = counting_arr[i] + counting_arr[i - 1]  # counting_arr[i]储存小于等于数据i的个数
    ordered = [0] * sequence_len
    for i in range(sequence_len - 1, -1, -1):  # 反向填充目标数组
        # 这里counting_arr[sequence[i] - sequence_min]和上面的counting_arr[number - sequence_min]相对应
        ordered[counting_arr[sequence[i] - sequence_min] - 1] = sequence[i]
        counting_arr[sequence[i] - sequence_min] -= 1  # 将相应计数减1
    return ordered


if __name__ == '__main__':
    sequence = [5, 2, 6, 9, 10, 7, 3, 9, 10, 5]
    print(sequence)
    print(counting_sort(sequence))

精简版

def count_sort(arr):
    # 如果数组长度小于 2 则直接返回
    # (小于2则为有序数组)
    if len(arr) < 2:
        return arr
    # 获取数组中最大值
    max_num = max(arr)
    # 开辟一个计数列表(长度为取值范围)
    count = [0 for _ in range(max_num + 1)]
    # 循环操作下标位置数+1(等于是记录每个数在数组中出现了多少次)
    for val in arr:
        count[val] += 1
    # 原数组清空,留待下面重新插入
    arr.clear()
    # 遍历计数列表中的值和下标(值的数量),
    # 从0开始,所以最终是从小到大排序
    for ind, val in enumerate(count):
        # 下面按照值中的数量进行循环
        # (通过上面累计加1我们知道:值中数量就是下标数字出现的次数)
        for i in range(val):
            # 有多少,则会追加多少次
            arr.append(ind)
    return arr

桶排序

设置一个定量的数组当作空桶;

遍历输入数据,并且把数据一个一个放到对应的桶里去;

对每个不是空的桶进行排序;

从不是空的桶里把排好序的数据拼接起来。

import math

DEFAULT_BUCKET_SIZE = 5  # 指定默认桶容量


def insertion_sort(sequence):
    for index in range(1, len(sequence)):
        while (index > 0 and sequence[index - 1] > sequence[index]):
            sequence[index], sequence[index - 1] = sequence[index - 1], sequence[index]
            index = index - 1
    return sequence


def bucket_sort(sequence, bucketSize=DEFAULT_BUCKET_SIZE):
    # DEFAULT_BUCKET_SIZE指定默认桶容量
    if (len(sequence) == 0):
        return []
    minValue = min(sequence)
    maxValue = max(sequence)
    bucketCount = math.floor((maxValue - minValue) / bucketSize) + 1  # 在桶容量一定的情况下,求最多需要多少个桶来储存
    buckets = []
    for i in range(0, bucketCount):
        buckets.append([])
    for i in range(0, len(sequence)):  # 遍历数据,将数据依次放进桶中
        buckets[math.floor((sequence[i] - minValue) / bucketSize)].append(sequence[i])
    sortedArray = []
    for i in range(0, bucketCount):
        if buckets[i]:  # 将每一个不是空的桶进行排序
            insertion_sort(buckets[i])
            for j in range(0, len(buckets[i])):
                sortedArray.append(buckets[i][j])
    return sortedArray


if __name__ == '__main__':
    sequence = [38, 27, 14, 35, 18, 41, 63, 26, 98, 92]
    print(bucket_sort(sequence))

基数排序

先求出待排序列表中的最大值,并求出了最大值的位数place。然后创建了10个桶,从数字的个位数开始,将数据进行分桶,所有数据都分完桶之后,将数据从桶中取出,按顺序重新赋值给待排序列表。

def radix_sort(array):
    max_num = max(array)
    place = 1
    # 使用Python内置函数max()求出了待排序列表中的最大值,并求出了最大值的位数place
    while max_num >= 10 ** place:
        place += 1
    for i in range(place):
        buckets = [[] for _ in range(10)]  # 这里取10是因为位数上只有0~9一共10个不同的数
        for num in array:
            radix = int(num / (10 ** i) % 10)
            buckets[radix].append(num)
        j = 0
        for k in range(10):  
            for num in buckets[k]:
                array[j] = num
                j += 1
    return array


if __name__ == '__main__':
    array = [25, 17, 33, 17, 22, 13, 32, 15, 9, 25, 27, 18]
    print(radix_sort(array))

你可能感兴趣的:(算法,python,排序算法,算法)