极客时间 算法训练营 第七周总结

学习总结

学习内容

位运算

位运算符

在这里插入图片描述

在这里插入图片描述

如何从十进制转换为二进制

如何从十进制转换为二进制

XOR - 异或

异或:相同为 0,不同为 1。也可用“不进位加法”来理解。

x ^ 0 = x : x 异或 0
x ^ 1s = ~x : x 异或 全1(1s)
x ^ ~x = 1s : x 异或 ~x
x ^ x = 0

指定位置的位运算

描述 写法
1. 将 x 最右边的 n 位清零 x & (~0 << n)
2. 获取 x 的第 n 位值(0 或者 1) (x >> n) & 1
3. 获取 x 的第 n 位的幂值 x & (1 << (n -1))
4. 仅将第 n 位置为 1 x | (1 << n)
5. 仅将第 n 位置为 0 x & (~ (1 << n))
6. 将 x 最高位至第 n 位(含)清零 x & ((1 << n) - 1)
7. 将第 n 位至第 0 位(含)清零 x & (~ ((1 << (n + 1)) - 1))

在这里插入图片描述

位运算要点

• 判断奇偶:
x % 2 == 1 —> (x & 1) == 1
x % 2 == 0 —> (x & 1) == 0
• x >> 1 —> x / 2
即: x = x / 2; —> x = x >> 1;
mid = (left + right) / 2; —> mid = (left + right) >> 1;

• X = X & (X-1) 清零最低位的 1
• X & -X => 得到最低位的 1
• X & ~X => 0

布隆过滤器 Bloom Filter

一个很长的二进制向量和一系列随机映射函数。
布隆过滤器可以用于检索一个元素是否在一个集合中。
优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
判断 不存在 必定不存在; 判断 存在 不一定存在

布隆过滤器(Bloom Filter)的原理和实现

布隆过滤器(Bloom Filter)的原理和实现

布隆过滤器 实现

from bitarray import bitarray
import mmh3

# bitarray 数组 存放的是二进制位 , 二进制数组

class BloomFilter:
    def __init__(self, size, hush_num):
        """
        :param size:   bitarray 的大小
        :param hush_num:    一个元素 被分成多少个二进制位
        """
        self.size = size
        self.hash_num = hash_num
        self.bit_array = bitarray(size)
        self.bit_array.setall(0)

    def add(self, s):
        """
        :param s:  添加的元素
        """
        for seed in range(self.hash_num):
            result = mmh3.hash(s, seed) % self.size
            # result 为 bit_array 二进制数组的索引
            self.bit_array[result] = 1

    def lookup(self, s):
        for seed in range(self.hash_num):
            result = mmh3.hash(s, seed) % self.size
            if self.bit_array[result] == 0:
                return "Nope"
        return "Probably"

bf = BloomFilter(500000, 7)
bf.add("1234567890")
print (bf.lookup("1234567890"))
print (bf.lookup("123456789"))

LRU Cache

LRU - least recently used

• 两个要素: 大小 、替换策略
• Hash Table + Double LinkedList
• O(1) 查询
O(1) 修改、更新

在这里插入图片描述
替换算法总览: Cache replacement policies

https://en.wikipedia.org/wiki/Cache_replacement_policies

LRU Cache 实现

from collections import OrderedDict
class LRUCache(object):
    def __init__(self, capacity):
        """
        :param capacity:   容量大小
        """
        self.dic = OrderedDict()    # OrderedDict 字典的子类,保存了他们被添加的顺序
        self.remain = capacity

    def get(self, key):
        if key not in self.dic:
            return -1
        v = self.dic.pop(key)
        self.dic[key] = v  # key 的使用被更新了
        return v

    def put(self, key, value):
        if key in self.dic:  # 是否已存在
            self.dic.pop(key)
        else:
            if self.remain > 0:     # 是否已满
                self.remain -= 1
            else:  # 已满 按 FIFO 先进先出的顺序 返回键值对。
                self.dic.popitem(last=False)
        self.dic[key] = value

排序

十大经典排序算法(动图演示)
十大经典排序算法(动图演示)

排序算法分类

  1. 比较类排序:

通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序。

  1. 非比较类排序:

不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序。

在这里插入图片描述

算法复杂度

在这里插入图片描述

相关概念

稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
时间复杂度:对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。
空间复杂度:是指算法在计算机,内执行时所需存储空间的度量,它也是数据规模n的函数。

初级排序 - O(n^2)

  1. 选择排序(Selection Sort)
    每次找最小值,然后放到待排序数组的起始位置。 eg: 5 8 5 2 3 4 选择排序 后 两个 5 的顺序会变

  2. 插入排序(Insertion Sort)
    从前到后逐步构建有序序列;对于未排序数据,在已排序序列中从后
    向前扫描,找到相应位置并插入。

  3. 冒泡排序(Bubble Sort)
    嵌套循环,每次查看相邻的元素如果逆序,则交换。

冒泡排序(Bubble Sort)

冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。

算法描述

  1. 比较相邻的元素。如果第一个比第二个大,就交换它们两个;
  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
  3. 针对所有的元素重复以上的步骤,除了最后一个;
  4. 重复步骤1~3,直到排序完成。

代码实现

def bubble_sort(arr):
    """
    冒泡排序(Bubble Sort)
    """
    length = len(arr)   # 待排序数组的长度 length 为 元素个数
    for i in range(0, length - 1):  # 第一层循环 0 <= i < length - 1 , 循环 length - 1个元素
        for j in range(0, length - 1 - i):  # 第二层循环 0 <= j < length - 1 - i , 循环 未排序的元素个数 
            if arr[j] > arr[j+1]:
                arr[j], arr[j+1] = arr[j+1], arr[j]
    return arr

选择排序(Selection Sort)

选择排序(Selection-sort)是一种简单直观的排序算法。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

算法描述

  1. 初始状态:无序区为R[1…n],有序区为空;
  2. 第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1…i-1]和R(i…n)。
    该趟排序从当前无序区中-选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1…i]和R[i+1…n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;
  3. n-1趟结束,数组有序化了。

代码实现

def selection_sort(arr):
    """
    选择排序(Selection Sort)
    """
    length = len(arr)  # 待排序数组的长度 length 为 元素个数
    for i in range(0, length - 1):   # 第一层循环 0 <= i < length - 1 , 循环 length - 1个元素
        min_index = i   # 无序区第一个元素   0 - i 为已排序区间
        for j in range(i+1,length):  # 第二层循环 i+1 <= j < length , 循环 未排序的元素
            if arr[j] < arr[min_index]:  # 找最小
                min_index = i
        # 找到无序区最小 与 无序区第一个元素 交换
        arr[i], arr[min_index] = arr[min_index], arr[i]
    return arr
插入排序(Insertion Sort)

插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

算法描述

  1. 从第一个元素开始,该元素可以认为已经被排序;
  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描;
  3. 如果该元素(已排序)大于新元素,将该元素移到下一位置;
  4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
  5. 将新元素插入到该位置后;
  6. 重复步骤2~5。

代码实现

def insertion_sort(arr):
    """
    插入排序(Insertion Sort)
    """
    length = len(arr)  # 待排序数组的长度 length 为 元素个数
    for i in range(1,length):    # 第一层循环 1 <= i < length , 循环 length - 1个元素,
        pre_index = i - 1  # 有序区间 的最后一个元素, 二层循环起始
        current = arr[i]  # 无序区间 待排序数
        # pre_index 大于等于0 并且 待排序数 小于 比较的已排序数
        # 那么 pre_index 数 后移 然后 pre_index 递减 继续从后往前比较 已排序数
        while pre_index >= 0 and current < arr[pre_index]:
            arr[pre_index+1] = arr[pre_index]
            pre_index -= 1
        # 循环退出 那么 找到 current 应该放在 排序区间的位置
        arr[preIndex + 1] = current
    return arr

高级排序 - O(N*LogN)

• 快速排序(Quick Sort)

数组取标杆 pivot,将小元素放 pivot左边,大元素放右侧,然后依次对右边和右边的子数组继续快排;以达到整个序列有序。

• 归并排序(Merge Sort)— 分治
1. 把长度为n的输入序列分成两个长度为n/2的子序列;
2. 对这两个子序列分别采用归并排序;
3. 将两个排序好的子序列合并成一个最终的排序序列。

• 堆排序(Heap Sort) — 堆插入 O(logN),取最大/小值 O(1)

  1. 数组元素依次建立小顶堆
  2. 依次取堆顶元素,并删除
快速排序(Quick Sort)

快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。

算法描述

  1. 从数列中挑出一个元素,称为 “基准”(pivot);
  2. 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
  3. 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

代码实现

def quick_sort(arr, begin, end):
    """
     快速排序(Quick Sort)
    """
    if end <= begin:    # 递归终止条件
        return
    # 先找标杆 pivot,将小元素放 pivot左边,大元素放右侧; 重点 partition 函数的实现
    pivot = partition(arr, begin, end)
    # 然后依次对标杆 pivot 右边和右边的子数组继续快排
    quick_sort(arr, begin, pivot-1)	  # 注意 不包括标杆
    quick_sort(arr, pivot + 1,end)


def partition(arr, begin, end):
    """
     partition 函数 用于查找 标杆位置并处理排序数组
    """
    pivot = end  # 选择 end 为标杆
    min_index = begin # counter: ⼩于pivot的元素的下标
    for i in range(begin, end): # 这里不包含 标杆位置
        if arr[i] < arr[pivot]:   # i 位置数据 小于 a[pivot]
            arr[min_index], arr[i] = arr[i], arr[min_index] # 交换 arr[min_index], arr[i] 数据位置
            min_index += 1  # 递增
    # 循环结束 min_index 就为 标杆应该所在位置
    arr[min_index], arr[pivot] = arr[pivot], arr[min_index]
    return min_index
归并排序(Merge Sort)

归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。

算法描述

  1. 把长度为n的输入序列分成两个长度为n/2的子序列;
  2. 对这两个子序列分别采用归并排序;
  3. 将两个排序好的子序列合并成一个最终的排序序列。

代码实现

def merge_sort(arr, left, right):
    """
    归并排序(Merge Sort)
    """
    if right <= left:   # 递归终止条件
        return
    # 先输入序列分成两个长度为n/2的子序列并对这两个子序列分别采用归并排序
    mid = (left + right) >> 1  # (left + right) / 2
    merge_sort(arr, left, mid)   #  注意 mid
    merge_sort(arr, mid + 1, right)
    # 排序好的子序列合, 重点 merge 函数的实现
    merge(arr, left, mid, right)


def merge(arr, left, mid, right):
    # left , mid 有序   mid + 1 , right 有序
    temp = [0 for _ in range( right - left + 1)]    # 中间数组, 需要额外的内存空间
    i = left
    j = mid + 1
    k = 0
    while i <= mid and j <= right:  # 合并 left , mid 有序数列  和  mid + 1 , right 有序数列
        if arr[i] <= arr[j]:
            temp[k] = arr[i]
            k, i = k + 1, i + 1
        else:
            temp[k] = arr[j]
            k, j = k + 1, j + 1
    while i <= mid:  # left , mid 有序数列 剩余
        temp[k] = arr[i]
        k, i = k + 1, i + 1
    while j <= right:   # mid + 1 , right 有序数列 剩余
        temp[k] = arr[j]
        k, j = k + 1, j + 1
        
    # 将 排序好的 temp 放入 arr 中
    for p in range(len(temp)):
        arr[left + p] = temp[p]

归并 和 快排

归并 和 快排 具有相似性,但步骤顺序相反

归并:先排序左右子数组,然后合并两个有序子数组,重要的是merge合并函数

快排:先调配出左右子数组,然后对于左右子数组进行排序,重要的是partition找标杆并处理的函数

堆排序(Heap Sort)

堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。(大顶堆,小顶堆)

算法描述
方法一:
可以直接使用系统的大顶堆(小顶堆)
方法二:
自己维护一个大顶堆(小顶堆)

  1. 把长度为n的输入序列分成两个长度为n/2的子序列;
  2. 对这两个子序列分别采用归并排序;
  3. 将两个排序好的子序列合并成一个最终的排序序列。

代码实现

import heapq
def heap_sort1(arr):
    """
    堆排序(Heap Sort),使用系统的堆
    """
    heap = []
    for value in arr:
        heapq.heappush(heap, value)    # 建堆
    for i in range(len(arr)):
        arr[i] = heapq.heappop(heap)    # heappop(heap)将数组堆中的最小元素弹出

def heap_sort2(arr):
    """
    堆排序(Heap Sort),自己维护堆
    """
    n = len(arr)
    # 建立大顶堆
    for i in range(n, -1, -1):
        heapify(arr, n, i)

    # 一个个交换元素   从小到大排列
    for i in range(n - 1, 0, -1):
        arr[i], arr[0] = arr[0], arr[i]  # 交换
        heapify(arr, i, 0)


def heapify(arr, n, i):
    largest = i
    l = 2 * i + 1  # left = 2*i + 1
    r = 2 * i + 2  # right = 2*i + 2

    if l < n and arr[i] < arr[l]:
        largest = l

    if r < n and arr[largest] < arr[r]:
        largest = r

    if largest != i:
        arr[i], arr[largest] = arr[largest], arr[i]  # 交换

        heapify(arr, n, largest)

特殊排序 - O(n)

• 计数排序(Counting Sort)
计数排序要求输入的数据必须是有确定范围的整数。将输入的数据值转化为键存
储在额外开辟的数组空间中;然后依次把计数大于 1 的填充回原数组

• 桶排序(Bucket Sort)
桶排序 (Bucket sort)的工作的原理:假设输入数据服从均匀分布,将数据分到有
限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式
继续使用桶排序进行排)。

• 基数排序(Radix Sort)
基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类
推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按
高优先级排序。

计数排序(Counting Sort)

计数排序不是基于比较的排序算法,其核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。

算法描述

找出待排序的数组中最大和最小的元素;
统计数组中每个值为i的元素出现的次数,存入数组count的第i项;
对所有的计数累加(从count中的第一个元素开始,每一项和前一项相加);
反向填充目标数组:将每个元素i放在新数组的第count(i)项,每放一个元素就将count(i)减去1。

代码实现

def counting_sort(arr, max_value):   # max_value 为 arr 中最大值
    """
    计数排序(Counting Sort)
    """
    n = len(arr)
    result = [0 for _ in range(n)]  # 设置输出序列并初始化为0
    count = [0 for _ in range(max_value + 1)]  # 设置计数序列并初始化为0,
    # 统计数组中每个值为i的元素出现的次数,存入数组count的第i项;
    for i in arr:    # i 不是下标是元素
        count[i] = count[i] + 1
    # 对所有的计数累加(从count中的第一个元素开始,每一项和前一项相加);
    for j in range(1, len(count)):  # j 是下标
        count[j] = count[j] + count[j - 1]

    # 反向填充目标数组:将每个元素i放在新数组的第count(i)项,每放一个元素就将count(i)减去1。
    for i in arr:   # i 不是下标是元素
        result[count[i]-1] = i
        count[i] = count[i] - 1
    return result
桶排序(Bucket Sort)

桶排序的基本思想是:把数组a划分为n个大小相同子区间(桶),每个子区间各自排序,最后合并。桶排序要求数据的分布必须均匀,不然可能会失效。计数排序是桶排序的一种特殊情况,可以把计数排序当成每个桶里只有一个元素的情况。

算法描述

设置一个定量的数组当作空桶;
遍历输入数据,并且把数据一个一个放到对应的桶里去;
对每个不是空的桶进行排序;
从不是空的桶里把排好序的数据拼接起来。

代码实现

def bucket_sort(arr):
    """
    桶排序(Bucket Sort)
    """
    buckets = [0] * ((max(arr) - min(arr)) + 1)  # 初始化桶元素为0   , max(arr) - min(arr)) + 1 个桶
    for i in range(len(arr)):
        buckets[arr[i] - min(arr)] += 1  # 遍历数组arr,在桶的相应位置累加值,  这里每个中放同一个元素
    result = []
    for i in range(len(buckets)):
        if buckets[i] != 0:
            result += [i + min(arr)] * buckets[i]
    return result
基数排序(Radix Sort)

基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。

算法描述

取得数组中的最大数,并取得位数;
arr为原始数组,从最低位开始取每个位组成radix数组;
对radix进行计数排序(利用计数排序适用于小范围数的特点);

代码实现

def radix_sort(arr):
    """
    基数排序(Radix Sort)
    """
    i = 0  # 记录当前正在排拿一位,最低位为1
    max_num = max(arr)  # 最大值
    j = len(str(max_num))  # 记录最大值的位数

    while i < j:
        bucket_list = [[] for _ in range(10)]  # 因每一位数字都是0~9,建10个桶
        for x in arr:
            '''对于3个元素的数组[977, 87, 960],第一轮排序首先按照个位数字相同的
              放在一个桶s[7]=[977],s[7]=[977,87],s[0]=[960]
              执行后list=[960,977,87].第二轮按照十位数,s[6]=[960],s[7]=[977]
              s[8]=[87],执行后list=[960,977,87].第三轮按照百位,s[9]=[960]
              s[9]=[960,977],s[0]=87,执行后list=[87,960,977],结束。'''
            bucket_list[int(x / (10 ** i)) % 10].append(x)  #  # 977/10=97(小数舍去),87/100=0 找到位置放入桶数组
        print(bucket_list)
        arr.clear()
        for x in bucket_list:  # 放回原序列
            for y in x:
                arr.append(y)
        i += 1
发布了170 篇原创文章 · 获赞 16 · 访问量 2809

猜你喜欢

转载自blog.csdn.net/leacock1991/article/details/103333497
今日推荐