算法

本章节主要介绍了二分查找法、三种低级排序的方法(冒泡排序、选择排序、插入排序)、三种高级排序方法(快速排序、堆排序、归并排序)。

二分查找法

二分查找法是基于此列表是有序的,比如是一个升序。

def binary_search(li, val):
    """
    二分查找法,成功找到值则返回索引,否则返回-1
    :param li:
    :param val:
    :return:
    """
    low = 0
    height = len(li) - 1
    while low <= height:
        mid = (low + height) // 2
        if li[mid] > val:
            height = mid - 1
        elif li[mid] < val:
            low = mid + 1
        else:
            return mid
    else:
        return -1

冒泡排序

它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素列已经排序完成。

def bubble_sort(li):
    """
    冒泡排序
    :param li:
    :return:
    """
    for i in range(len(li) - 1):
        # i表示的是第几趟
        for j in range(0, len(li) - i - 1):
            if li[j] > li[j + 1]:
                li[j], li[j + 1] = li[j + 1], li[j]


def bubble_sort2(li):
    """
    冒泡排序改进版,就是一趟没有任何改变,则直接结束
    :param li:
    :return:
    """
    for i in range(len(li) - 1):
        # i表示的是第几趟
        change = False
        for j in range(0, len(li) - i - 1):
            if li[j] > li[j + 1]:
                li[j], li[j + 1] = li[j + 1], li[j]
                change = True
        if not change:
            return

选择排序

首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

def select_sort(li):
    for i in range(len(li) - 1):
        min_loc = i  # 最小数位置
        for j in range(i + 1, len(li) - 1):
            if li[j] < li[min_loc]:
                min_loc = j
        li[i], li[min_loc] = li[min_loc], li[i]

插入排序

插入排序的思想:把列表分成有序区和无序区,拿无序区的第一个数,然后把此数放到有序区合适的位置。如此循环,直到无序区没有数。

def insert_sort(li):
    for i in range(1, len(li)):
        # 无序区的第一个数
        tmp = li[i]
        # 有序区的最后一个位置
        j = i - 1
        while li[j] > tmp and j >= 0:
            li[j + 1] = li[j]
            j -= 1
        li[j+1] = tmp

快速排序

快速排序的思想:从列表中取一个元素(一般是第1个),然后遍历列表,使得此元素的左边均比它小,右边的元素均比它大,然后继续通过递归实现排序

def partition(li, left, right):
    """
    此函数用于对于列表li,首先从一个随机位置和第一个位置交换数值,然后把第一个数放到一个位置,此位置的左边
    数值均比它小,右边的数值均比它大,返回此位置的索引
    :param li: 列表
    :param left: 最左边的位置
    :param right: 最右边的位置
    :return: 返回此位置的索引
    """
    # 交换数
    random_index = random.randint(left + 1, right)
    li[left], li[random_index] = li[random_index], li[left]
    tmp = li[left]
    while left < right:
        # 当left=right时,即找到位置时,退出循环
        while left < right and li[right] >= tmp:
            # 当找到比tmp小的数时退出循环
            right -= 1
        li[left] = li[right]
        while left < right and li[left] <= tmp:
            # 当找到比tmp大的数时退出循环
            left += 1
        li[right] = li[left]
    li[left] = tmp
    return left


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)

堆排序

概念解析:
二叉树:度不超过2的树(节点最多有两个叉)
满二叉树:如果每层的节点数都达到最大值的二叉树
完全二叉树:叶节点只能出现在最下层或是次下层,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树

小根堆:满足父亲节点均比孩子节点小的完全二叉树
大根堆:满足父亲节点均比孩子节点大的完全二叉树
向下调整:顶端节点不符合堆的字义,而其左右的子树符合堆的定义,可通过向下调整帮顶端找到合适的位置。

堆排序的基本思想是:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了
步骤一 构造初始堆。将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)。
a.假设给定无序序列结构如下

2.此时我们从最后一个非叶子结点开始(叶结点自然不用调整,第一个非叶子结点 arr.length/2-1=5/2-1=1,也就是下面的6结点),从左至右,从下至上进行调整。

4.找到第二个非叶节点4,由于[4,9,8]中9元素最大,4和9交换。

这时,交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中6最大,交换4和6。

此时,我们就将一个无需序列构造成了一个大顶堆。
步骤二 将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换。
a.将堆顶元素9和末尾元素4进行交换

b.重新调整结构,使其继续满足堆定义

c.再将堆顶元素8与末尾元素5进行交换,得到第二大元素8.

后续过程,继续进行调整,交换,如此反复进行,最终使得整个序列有序

def sift(li, low, high):
    """
    向下调整
    有一个堆,最顶端(省长)不是堆,其左右堆均是堆,现在要调整省长的位置
    :param li: 列表
    :param low: 堆的开始位置
    :param high: 堆的结束位置
    :return:
    """
    tmp = li[low]  # 堆顶端的值
    i = low  # 父亲的位置
    j = 2 * i + 1  # 左孩子的位置
    while j <= high:
        # 如果右孩子存在并且右孩子较大
        if j + 1 <= high and li[j + 1] > li[j]:
            # 把孩子当中较大的给j保存
            j += 1
        # 较大的孩子如果比省长大,则原省长位置就给此孩子
        if tmp < li[j]:
            li[i] = li[j]
            i = j
            j = 2 * i + 1
        else:
            break
    li[i] = tmp



def heap_sort(li):
    """
    对列表li利用堆进行排序,首先建堆,然后挨个数
    :param li:
    :return:
    """
    n = len(li)
    # 第一步、建堆
    for i in range(n // 2 - 1, -1, -1):
        sift(li, i, n - 1)
    # 第二步,挨个出数
    # j表示最后一个元素的位置
    for j in range(n - 1, -1, -1):
        # 交换省长和傀儡的位置,同时元素减1
        li[0], li[j] = li[j], li[0]
        sift(li, 0, j - 1)

利用堆模块排序

import heapq


def heap_sort(li):
    """
    利用heapq模块实现排序功能
    :param li:
    :return:
    """
    # 构造堆
    heapq.heapify(li)
    n = len(li)
    new_li = []
    for i in range(n):
        # heapop每次会从列表获取最小的值
        new_li.append(heapq.heappop(li))
    return new_li
heapq.nlargest(k, li)  # 从列表中获取最大的k个值
heapq.nsmallest(k, li)  # 从列表中获取最小的k个值

归并排序

归并排序是用分治思想,分治模式在每一层递归上有三个步骤:
分解(Divide):将n个元素分成个含n/2个元素的子序列。
解决(Conquer):用合并排序法对两个子序列递归的排序。
合并(Combine):合并两个已排序的子序列已得到排序结果。

def merge(li, low, mid, high):
    """
    一次归并
    :param li:
    :param low:
    :param mid:
    :param high:
    :return:
    """
    i = low
    j = mid + 1
    l_tmp = []
    # 左右两边的指针都还未遍历完时
    while i <= mid and j <= high:
        if li[i] < li[j]:
            l_tmp.append(li[i])
            i += 1
        else:
            l_tmp.append(li[j])
            j += 1
    while i <= mid:
        l_tmp.append(li[i])
        i += 1
    while j <= high:
        l_tmp.append(li[j])
        j += 1
    li[low:high+1] = l_tmp


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)
posted on 2020-06-15 15:11  Treelight  阅读(124)  评论(0编辑  收藏  举报