学习总结
学习内容
位运算
位运算符
如何从十进制转换为二进制
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)的原理和实现
布隆过滤器 实现
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
排序
十大经典排序算法(动图演示)
十大经典排序算法(动图演示)
排序算法分类
- 比较类排序:
通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序。
- 非比较类排序:
不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序。
算法复杂度
相关概念
稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
时间复杂度:对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。
空间复杂度:是指算法在计算机,内执行时所需存储空间的度量,它也是数据规模n的函数。
初级排序 - O(n^2)
-
选择排序(Selection Sort)
每次找最小值,然后放到待排序数组的起始位置。 eg: 5 8 5 2 3 4 选择排序 后 两个 5 的顺序会变 -
插入排序(Insertion Sort)
从前到后逐步构建有序序列;对于未排序数据,在已排序序列中从后
向前扫描,找到相应位置并插入。 -
冒泡排序(Bubble Sort)
嵌套循环,每次查看相邻的元素如果逆序,则交换。
冒泡排序(Bubble Sort)
冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。
算法描述
- 比较相邻的元素。如果第一个比第二个大,就交换它们两个;
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
- 针对所有的元素重复以上的步骤,除了最后一个;
- 重复步骤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)是一种简单直观的排序算法。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
算法描述
- 初始状态:无序区为R[1…n],有序区为空;
- 第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个的新无序区; - 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)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
算法描述
- 从第一个元素开始,该元素可以认为已经被排序;
- 取出下一个元素,在已经排序的元素序列中从后向前扫描;
- 如果该元素(已排序)大于新元素,将该元素移到下一位置;
- 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
- 将新元素插入到该位置后;
- 重复步骤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)
- 数组元素依次建立小顶堆
- 依次取堆顶元素,并删除
快速排序(Quick Sort)
快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
算法描述
- 从数列中挑出一个元素,称为 “基准”(pivot);
- 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
- 递归地(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-路归并。
算法描述
- 把长度为n的输入序列分成两个长度为n/2的子序列;
- 对这两个子序列分别采用归并排序;
- 将两个排序好的子序列合并成一个最终的排序序列。
代码实现
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)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。(大顶堆,小顶堆)
算法描述
方法一:
可以直接使用系统的大顶堆(小顶堆)
方法二:
自己维护一个大顶堆(小顶堆)
- 把长度为n的输入序列分成两个长度为n/2的子序列;
- 对这两个子序列分别采用归并排序;
- 将两个排序好的子序列合并成一个最终的排序序列。
代码实现
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