zoukankan      html  css  js  c++  java
  • NB三人组:快速排序&堆排序&归并排序

    快速排序:

    '''
    快速排序:时间复杂度O(nlog2n)
    利用归位函数进行递归调用
    归位函数-左边都是比某元素小的,右边都是比某元素大的
    
    快速排序至少比传统排序快100倍以上
    快排弱点:
    1.递归,比较消耗系统资源
    2.最坏情况,如果一个倒序列表,时间复杂度O(n²),可以在归位前,随机选择一个数和第一个数交换,来尽可能避免最坏情况的出现。
    '''
    import sys
    import random
    
    sys.setrecursionlimit(100000)  # Python默认递归最大深度999,可以修改为100000
    
    
    def partition(li, left, right):
        # 归位函数
        tmp = li[left]
        while left < right:
            while left < right and li[right] >= tmp:
                right -= 1
            li[left] = li[right]
            while left < right and li[left] <= tmp:
                left += 1
            li[right] = li[left]
        li[left] = tmp
        return left
    
    
    def _quick_sort(li, left, right):
        # 递归
        if left < right:  # 保证至少有两个元素才继续归位,有一个或0个元素就结束归位
            random_loc = random.randrange(left, right)      
            li[left], li[random_loc] = li[random_loc], li[left]  # 随机交换位置,使快排最坏情况出现几率降到极低
            mid = partition(li, left, right)
            _quick_sort(li, left, mid - 1)
            _quick_sort(li, mid + 1, right)
    
    
    def quick_sort(li):
        _quick_sort(li, 0, len(li) - 1)
    
    
    # li = list(range(10000, 0, -1)) # 快速排序最坏情况

    堆排序:

    堆排序--什么是堆
    堆:一种特殊的完全二叉树结构
    大根堆:一颗完全二叉树,满足任意节点都比其孩子节点大
    小根堆:一颗完全二叉树,满足任意节点都比其孩子节点小

    堆的向下调整:
    假设根节点的左右子树都是堆,但根节点不满足堆的性质,可以通过一次向下的调整将其变成一个堆
    当根节点的左右子树都是堆时,可以通过一次向下的调整将其变换成一个堆

    堆排序过程:
    1、构造堆:调整最后一个节点和其父节点的父子关系,再调整前一个节点的父子关系,按从下->上,从右->左的顺序依次调整,直到变成一个堆
    2、挨个出数:把堆顶元素拿出来(最大的),把最后一个子节点放到堆顶,再进行堆的向下调整变成一个堆,循环,直到全部取完

    堆排序代码:
    堆排序可以不用再开一个列表存有序值,可以记录堆的当前处理到的元素下标(第一次为最后一个子节点,依次向前循环记录),
    把堆顶元素拿出来,跟下标的值进行交换

    def sift(li, low, high):
        '''
        堆排序--堆的向下调整,此处是实现一个 “大根堆”,父节点的值大于两个子节点的值
        根节点的左右子树都是堆,但根节点不满足堆的性质,可以通过一次向下的调整将其变成一个堆
        :param li: 列表
        :param low: 堆顶节点的下标
        :param high: 堆的最后一个节点的下标,用来判断下标是否越界
        :return:
        '''
        # 技巧:用变量i和j保存当前的父节点下标和当前子节点中最大的数的下标,
        #      如果i的值>j的值,就结束循环,否则,ij一直向下找
        i = low
        j = 2 * low + 1
        tmp = li[low]  # 先把堆顶保存起来,用于后面进行移动
        while j <= high:
            if j + 1 <= high and li[j + 1] > li[j]:  # 存在右孩子并且右孩子比左孩子大
                j = j + 1  # j指向右孩子
            if li[j] > tmp:
                li[i] = li[j]  # 如果孩子比父亲大,把孩子顶上去
                i = j  # 向下看一层
                j = 2 * i + 1
            else:
                li[i] = tmp  # 如果父亲大,把tmp放在i的位置上
                break
        else:
            li[i] = tmp  # 如果i到底了,就把tmp赋值给i
    
    
    def heap_sort(li):
        # 第一步:建堆,由下向上建
        n = len(li)
        for i in range((n - 2) // 2, -1, -1):
            sift(li, i, n - 1)  # 建堆的时候最多就三个节点,可以让high=len(li)-1
        # 第二步:取数+向下调整
        for i in range(n - 1, -1, -1):
            li[0], li[i] = li[i], li[0]
            sift(li, 0, i - 1)
    
    
    import random
    
    li = [i for i in range(100)]
    random.shuffle(li)
    heap_sort(li)
    print(li)
    堆排序代码

     堆排序的时间复杂度为:O(nlog2n)   快速排序比堆排序稍微快一点

    补充知识:

    树的概念:
    根节点、叶子节点
    树的深度(高度)
    树的度(最大分叉) 6
    孩子节点/父节点
    子树

     

    二叉树:
    度为2的树,每个父节点最多有两个子节点

    满二叉树:
    如果每一个层的节点数都达到最大值,则这个二叉树是满二叉树

    完全二叉树:
    叶子节点只能出现在最下层和次下层,且最下面一层的节点都集中
    在该层最左边的若干位置的二叉树

    二叉树存储方式:1、链式存储 2、顺序存储
    import heapq
    import random
    
    # 可以实现一个叫“优先队列”的数据结构:大的先出,或小的先出
    li = list(range(100))
    random.shuffle(li)
    
    heapq.heapify(li)  # 建堆,默认为小根堆
    
    m = heapq.heappop(li)  # 每次弹出一个最小的元素,相当于弹出堆顶
    print(m)
    
    heapq.heappush(li, -1)
    m = heapq.heappop(li)
    print(m)
    
    #########
    # 0
    # -1
    #########
    Python内置的堆排序,可以实现“优先队列”
    topk问题
    需求:有n个数,设计算法取前k大的数。(k < n)
    解决方法:
    1、排序后切片 O(nlogn)
    2、排序LowB三人组 O(kn)
    3、堆排序 O(nlogk)


    堆排序解决topk问题的思路:
    1、取列表前k个元素建立一个小根堆。堆顶就是目前第k大的数
    2、依次向后遍历原列表,对于列表中的元素,如果小于堆顶,则
    忽略该元素;如果大于堆顶,则将堆顶更换为该元素,并且
    进行一次向下调整
    def sift(li, low, high):
        # topk需要小根堆
        i = low
        j = 2 * low + 1
        tmp = li[low]
        while j <= high:
            if j + 1 <= high and li[j + 1] < li[j]:
                j = j + 1
            if li[j] < tmp:
                li[i] = li[j]
                i = j
                j = 2 * i + 1
            else:
                li[i] = tmp
                break
        else:
            li[i] = tmp
    
    
    def topk(li, k):
        # 1,建小根堆
        heap = li[0:k]
        for i in range((k - 2) // 2, -1, -1):
            sift(heap, i, k - 1)
        # 2,遍历
        for i in range(k, len(li) - 1):
            if li[i] > heap[0]:
                heap[0] = li[i]
                sift(heap, 0, k - 1)
        # 3,出数
        for i in range(k - 1, -1, -1):
            heap[0], heap[i] = heap[i], heap[0]
            sift(heap, 0, i - 1)
        return heap
    
    
    import random
    
    li = [i for i in range(100)]
    random.shuffle(li)
    hp = topk(li, 10)
    print(hp)
    topk的堆排序实现
    归并排序过程:
    归并:两个有序列表变成一个有序列表的过程
    1、分解:长度n的列表分两半,两半分四半,...直到为1
    2、归并:从1归并为2,2归并为4,...直到n
    def merge(li, low, mid, high):
        '''
        归并方法,把两个有序列表归并成一个有序列表
    
        [1,3,4,6,]   [2,5,7,8,9,10,]
        :param li: 待归并的列表,两个列表合起来的
        :param low: 第一个列表的起始下标
        :param mid: 第一个列表的结束下标
        :param high: 第二个列表的结束下标
        :return:
        '''
        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 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):
        '''
        :param li: 待排序的列表
        :param low: 起始下标
        :param high: 终止下标
        :return:
        '''
        if low < high:  # 递归终止条件
            mid = (low + high) // 2
            _merge_sort(li, low, mid)  # 归并左边
            _merge_sort(li, mid + 1, high)  # 归并右边
            merge(li, low, mid, high)  # 左右归并
    
    
    def merge_sort(li):
        _merge_sort(li, 0, len(li) - 1)
    
    
    import random
    
    li = [i for i in range(100)]
    random.shuffle(li)
    merge_sort(li)
    print(li)
    归并排序
    NB三人组对比
    时间而言:
    快速排序 < 归并排序 < 堆排序
    缺点:
    快速排序:有最坏情况
    堆排序:速度相对较慢
    归并排序:占用内存

    稳定性:相同的对象,排序后不改变其在原列表中的先后位置,就是稳定的,否则不稳定(跳着交换的不稳定,挨着交换的稳定)
    空间复杂度:是递归的空间复杂度,有递归的就存在空间复杂度
  • 相关阅读:
    父容器直接清除浮动的css
    [原创 js]指定DOM中添加字符串函数
    跨浏览器的透明度解决方案(包括IE8 Beta 2)
    [原创 js]验证表单强度的js
    帧率、码流与分辩率之间关系
    隔行扫描和逐行扫描(interlace and progressive )
    VGA管脚定义
    红眼消除
    HDMI_VGA_CBVS同时显示
    分辨率与行场同步信号的关系
  • 原文地址:https://www.cnblogs.com/staff/p/11411418.html
Copyright © 2011-2022 走看看