zoukankan      html  css  js  c++  java
  • 常用排序算法(二)

    本篇主要介绍排序算法中的快速排序 堆排序和归并排序

    一 快速排序

    1 快排思路:

    • 取一个元素p(第一个元素),是元素p归位(去它该去的地方)
    • 列表被p分成两部分,左边的都比p小,右边的都比p大
    • 递归完成排序

    2 快速排序的问题:

    • 最坏情况
    •  递归

    3 图示说明

     4 如何实现归并

    先把5取出来,这时候就会有一个空位,从右边找比5小的数填充过来,现在右边有一个空位了,从左边找比5大的放到右边的空位上。依次类推,只要left和right碰在一起,这样就找到5的位置了

    如图示:

     

     这样在把找到的5的位置放进去去ok了

    实现代码:   

    def partition(li, left, right):
        '''
        归位
        :param li:
        :param left:
        :param right:
        :return:
        '''
        tmp = li[left]
        while left < right:
            while left < right and li[right] >= tmp:  # 从右面寻找比tmp小的数
                right -= 1  # 指针往左走一步
            li[left] = li[right]  # 在right找到比tmp小的数,将这个数从right放到left
            while left < right and li[left] <= tmp:
                left += 1
            li[right] = li[left]  # 把左边的值写到右边的空位
        li[left] = tmp  # 把tmp位
        return left
    
    li = [5, 7, 4, 6, 3, 1, 2, 9, 8]
    print(li)
    partition(li, 0, len(li) - 1)
    print(li)
    '''
    [5, 7, 4, 6, 3, 1, 2, 9, 8]
    [2, 7, 4, 6, 3, 1, 2, 9, 8]
    [2, 7, 4, 6, 3, 1, 7, 9, 8]
    [2, 1, 4, 6, 3, 1, 7, 9, 8]
    [2, 1, 4, 6, 3, 6, 7, 9, 8]
    [2, 1, 4, 3, 3, 6, 7, 9, 8]
    [2, 1, 4, 3, 3, 6, 7, 9, 8]
    [2, 1, 4, 3, 5, 6, 7, 9, 8]
    '''
    
    def quick_sort(li, left, right):
        if left < right:  # 至少两个元素
            mid = partition(li, left, right)
            quick_sort(li, left, mid - 1)
            quick_sort(li, mid + 1, right)
    
    quick_sort(li, 0, len(li) - 1)
    print(li)
    快排

    二 堆排序

    1 堆排序涉及到的概念

    什么是堆:

    • 堆是一种特殊的完全二叉树结构
    • 大根堆:一颗完全二叉树, 满足任一节点都比其他孩子节点大
    • 小根堆:一颗完全二叉树, 满足任一节点都比其孩子节点小

      

    大根堆:

     小根堆:

      什么是二叉树:

    •  度不超过2的树
    • 每个节点最多有两个孩子节点
    • 两个孩子节点被区分为左孩子节点和右孩子节点
    • 满二叉树:一个二叉树如果每一个层的节点数达到最大值,则这个二叉树就是满二叉树
    • 完全二叉树:叶节点只能出现在最下层和次下层,并且最下面一层的节点都集中在该层最左边的若干位置的二叉树

    完全二叉树:

     2 堆排序的实现过程

    • 建立堆

    • 得到堆顶元素,为最大元素

    • 去掉堆顶,将堆最后一个元素放到堆顶,此时可通过一次调整重新使堆有序。

    • 堆顶元素为第二大元素

    • 重复第三个步骤,直到堆变空

    首先把2和9的位置互换:

    互换位置后把2的位置进行调整,重新构造出一个大根堆:

    然后再把10和80的位置互换,继续进行上面的步骤:

      

     3 堆排序实现代码

    def sift(li, low, high):
        """
        调整成大顶堆,初始堆时,从下往上;交换堆顶与堆尾后,从上往下调整
        :param li: 列表
        :param low: 堆的根节点位置
        :param high: 堆的最后一个元素的位置
        :return:
        """
        # 当列表第一个是以下标0开始,结点下标为i,左孩子则为2*i+1,右孩子下标则为2*i+2;
        # 若下标以1开始,左孩子则为2*i,右孩子则为2*i+1
    
        i = low  # i最开始指向根节点
        j = 2 * i + 1  # j开始是左节点孩子
        tmp = li[low]  # 把堆顶存起来
        while j <= high:  # 只要j位置有数
            if j + 1 <= high and li[j + 1] > li[j]:  # 如果这个节点右孩子存在并且比左孩子大,将j指向右孩子节点
                j = j + 1  #
            if li[j] > tmp:
                li[i] = li[j]
                i = j  # 往下看一层
                j = 2 * i + 1
            else:  # tmp更大, 把tmp放到i的位置上
                break
        li[i] = tmp  # 把tmp放到某一级领导位置上
    
    
    def heap_sort(li):
        n = len(li)
        # 1 建堆
        for i in range((n - 2) // 2, -1, -1):
            # i表示建堆的时候调整的部分的根的下标
            sift(li, i, n - 1)
        # 2 挨个出数
        for j in range(n - 1, -1, -1): # j表示堆最后一个元素的位置
            # i指向当前堆的最后一个元素
            li[0], li[j] = li[j], li[0]
            sift(li, 0, j - 1)  # i-1是新的high(最后一个的位置)
    堆排序

    代码讲解:

    • 第一个循环做的事情是把序列调整为一个大根堆(sift函数)
    • 第二个循环是把堆顶元素和堆末尾的元素交换,然后把剩下的元素调整为一个大根堆(sift函数)

    我们要排序的序列为[50, 16, 30, 10, 60, 90, 2, 80, 70],而我们所谓的调整大根堆,其实就是按照从右往左,从下到上的顺序,把每颗小树调整为一个大根堆

     4 堆排序的时间复杂度

    在正式排序时,n个结点的完全二叉树的深度为⌊log2n⌋+1,并且有n个数据则需要取n-1次调整成大顶堆的操作,每次调整成大顶堆的时间复杂度为O(log2n)。因此,重建堆的时间复杂度可近似看做: O(nlogn)。

    5 堆排序python内置模块

    import heapq
    import random
    
    li = list(range(100))
    random.shuffle(li)
    
    heapq.heapify(li)  # 建立堆
    heapq.heappop(li)  # 为此弹出一个最小数
    n = len(li)
    for i in range(n):
        print(heapq.heappop(li))

     6 堆排序— topk问题

    现在又n个数,设计算法得到前K大的数。 (k<n)
    解决思路:

    • 第一种:排序号切片 O(nlogn)
    • 第二种:使用冒泡排序 O(k)
    • 第三种:堆排序思路 O(nlogk)

    堆排序实现思路:

    • 取列表前k个元素建立一个小根堆。对顶就是目前第K大的数
    • 依次向后遍历原列表,对于列表中的元素,如果小于堆顶则忽略该元素;如果大于堆顶,则将堆顶更换为该元素并且对堆进行一次调整
    • 遍历列表所有元素后, 倒序弹出堆顶

    三 归并排序

    1 什么是归并排序

    假设现在的列表分两段有序,如何将其合成为一个有序列表 [2,5,7,8,9|1,3,4,6]

    一次归并代码

    def merge(li, low, mid, high):
        i = low
        j = mid + 1
        ltmp = []
        while i <= mid and j <= high:  # 只要左右两边都有数
            if li[i] < li[j]:
                ltmp.append(li[i])
                i += 1
            else:
                ltmp.append(li[j])
                j += 1
        # while执行完肯定有一部分没数了
        while i <= mid:
            ltmp.append(li[i])
            i += 1
        while j <= high:
            ltmp.append(li[j])
            j += 1
        li[low:high + 1] = ltmp
    归并代码

    2 归并排序的实现

    分解:将列表约分越小,直到分成一个元素
    终止条件: 一个元素是有序的
    合并: 将两个有序列表归并,列表越来越大

     

    实现代码: 

    def merge(li, low, mid, high):
        i = low
        j = mid + 1
        ltmp = []
        while i <= mid and j <= high:  # 只要左右两边都有数
            if li[i] < li[j]:
                ltmp.append(li[i])
                i += 1
            else:
                ltmp.append(li[j])
                j += 1
        # while执行完肯定有一部分没数了
        while i <= mid:
            ltmp.append(li[i])
            i += 1
        while j <= high:
            ltmp.append(li[j])
            j += 1
        li[low:high + 1] = ltmp
    
    
    def merge_sort(li, low, high):
        if low < high:  # 至少有两个元素,递归
            mid = (low + high) // 2
            merge_sort(li, low, mid)
            merge_sort(li, mid + 1, high)
            merge(li, low, mid, high)

    四  NB三人组总结

    (1) 三种排序算法的时间复杂度都是O(nlogn)
    (2) 一般情况下,就运行时间而言: 快速排序<归并排序<堆排序
    (3) 三种排序算法的缺点:

    • 快速排序:极端情况下排序效率低
    • 归并排序:需要额外的内存开销
    • 堆排序: 在快的排序算法中相对较慢

    五 其他排序算法

    1 希尔排序

    希尔排序是一种插入排序算法 过程如下:

    • 首先取一个整数d1=n/2,将元素分为d1个组,每组相邻量元素之间距离为d1,在各组内进行直接插入排序
    • 取第二个整数d2=d1/2,重复上述分组排序过程,直到di=1,即所有元素在同一组内进行直接插入排序。
    • 希尔排序每趟并不使某些元素有序,而是使整体数据越来越接近有序;最后一趟排序使得所有数据有序。

    实现代码:

    def insert_sort_gap(li, gap):
        for i in range(gap, len(li)):
            tmp = li[i]
            j = i - gap
            while j >= 0 and li[j] > tmp:
                li[j + gap] = li[j]
                j -= gap
            li[j + gap] = tmp
    
    
    def shell_sort(li):
        d = len(li) // 2  # 长度除以2
        while d >= 1:
            insert_sort_gap(li, d)
            d //= 2

    2 计数排序

    对列表进行排序, 已知列表的范围都在0到100之间,设计时间复杂度为O(n)的算法

    def count_sort(li, max_count=100):
        count = [0 for _ in range(max_count + 1)]
        for val in li:
            count[val] += 1
        li.clear()
        for index, val in enumerate(count):
            for i in range(val):
                li.append(index)

    3 桶排序

    在计数排序中列表范围只能再0到100之间,如果元素的范围⽐较⼤(⽐如在1到1亿之间)计数算法则无法实现,

    桶排序(Bucket Sort):⾸先将元素分在不同的桶中,在对每个桶中的元素排序

     实现代码:

    def bucket_sort(li, n=100, max_num=10000):
        buckets = [[] for _ in range(n)]  # 创建n个桶
        for var in li:
            i = min(var // (max_num // n), n - 1)  # i 表示var放到几号桶里
            buckets[i].append(var)  # 把var加到桶里边
            # 保持桶内的顺序
            for j in range(len(buckets[i]) - 1, 0, - 1):
                if buckets[i][j] < buckets[i][j - 1]:
                    buckets[i][j], buckets[i][j - 1] = buckets[i][j - 1], buckets[i][j]
                else:
                    break
        sorted_li = []
        for buc in buckets:
            sorted_li.extend(buc)
    
        return sorted_li

     4 基数排序

    多关键字排序:假如现在有一个员工表,要求按照薪资排序,年龄相同的员工按照年龄排序。

    • 先按照年龄进行排序, 在安装薪资简写稳定排序
    • 对 32,13,94,52,17,54,93排序,是否可以看做多关键字排序?

    基数排序效率:

    • 时间复杂度: O(kn)
    • 空间复杂度: O(k+n)
    • K表示数字位数

    def radix_sort(li):
        max_num = max(li)  # 最大值 例如: 99->2, 888->3, 10000->5
        it = 0
        while 10 ** it <= max_num:
            buckets = [[] for _ in range(10)]
            for val in li:
                # 取位数 列:987 it=1 987//10-98  98%10->8; it=2 987//100->9  9%10=9
                digit = (val // 10 ** it) % 10
                buckets[digit].append(val)
            li.clear()
            # 把数重新写回li
            for buc in buckets:
                li.extend(buc)
    
            it += 1
    
    import random
    
    li = list(range(10000))
    random.shuffle(li)
    radix_sort(li)
    print(li)
  • 相关阅读:
    项目职责
    hibernate配置文件hibernate.cfg.xml的详细解释
    Hibernate环境搭建
    struts2标签使用详解
    EL表达式
    getparameter()和getattribution()的区别的 java详细
    形式参数和实在参数
    JSTL详解实例
    论文ei,sci检索,JCR-SCI分区,中科院分区连接
    随机森林实例
  • 原文地址:https://www.cnblogs.com/harryblog/p/10650537.html
Copyright © 2011-2022 走看看