要一直走下去

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

快速排序:

'''
快速排序:时间复杂度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三人组对比
时间而言:
快速排序 < 归并排序 < 堆排序
缺点:
快速排序:有最坏情况
堆排序:速度相对较慢
归并排序:占用内存

稳定性:相同的对象,排序后不改变其在原列表中的先后位置,就是稳定的,否则不稳定(跳着交换的不稳定,挨着交换的稳定)
空间复杂度:是递归的空间复杂度,有递归的就存在空间复杂度

 

posted on 2019-08-26 11:14  要一直走下去  阅读(681)  评论(0编辑  收藏  举报