一. 算法基础
1. 算法概念
1)算法就是一个计算过程,解决问题的方法
2)时间复杂度小结
(1)时间复杂度:用来评估算法运行时间的一个式子
(2)一般来说,时间复杂度高的算法比复杂度低的算法慢
(3)常见的时间复杂度(按效率排序)
O(1) < O(logn) < O(n) < O(nlogn) < O(n2) < O(n2 * logn) < O(n3)
(4)不常见的时间复杂度
O(n!) O(2n) O(nn)
(5)如何判断时间复杂度
循环减半的过程为O(logn)
几次循环就是n的几次方的复杂度
# O(1) print('Hello World')
# O(n) for i in range(n): print('Hello World')
# O(n2) for i in range(n): for j in range(n): print('Hello World')
# O(n3) for i in range(n): for j in range(n): for k in range(n): print('Hello World')
# O(1) print('Hello World') print('Hello World') print('Hello World')
# O(n2) for i in range(n): print('Hello World') for j in range(n): print('Hello World')
# O(n2) for i in range(n): for j in range(i): print('Hello World')
# O(logn) while n > 1: print(n) n = n / 2
3)空间复杂度小结
空间复杂度用来评估算法内存占用的大小
一般用空间换取时间
2. 列表
1)列表查找:从列表中查找指定元素
- 输入:列表、待查找元素
- 输出:元素下标或未查找到元素
顺序查找
- 从列表第一个元素开始,顺序进行搜索,直到找到为止
def linear_search(li, val):
for index, value in enumerate(li):
if value == val:
return index
return None
二分查找
- 从有序列表的候选区data[0:n]开始,通过对查找的值与候选区中间值的比较,可以使候选区减少一半
# O(n)
def linear_search(data_set, value):
for i in range(len(data_set)):
if data_set[i] == value:
return i
return
# O(logn)
def bin_search(data_set, value):
low = 0;
high = len(data_set) - 1
while low <= high:
mid = (low + high) / 2
if data_set[mid] == value:
return mid
elif data_set[mid] > value:
high = mid - 1
else:
low = mid + 1
else:
return None
2)列表排序:将无序列表变为有序列表
升序与降序
内置排序函数:sort()
应用场景:
- 各种榜单
- 各种表格
- 给二分查找用
- 给其他算法用
3. 冒泡排序
列表每两个相邻的数,如果前边的比后边的大,那么交换这两个数
一趟排序完成后,则无序区减少一个数,有序区增加一个数
1)代码关键点:
- 趟
- 无序区
# 时间复杂度:O(n2)
# 空间复杂度:O(1)
def bubble_sort(li):
for i in range(len(li) - 1):
for j in range(len(li) - 1 - i):
if li[j] > li[j + 1]:
li[j], li[j + 1] = li[j + 1], li[j]
2)冒泡排序优化:
如果冒泡排序中执行一趟而没有发生交换,则列表已经是有序状态,可以直接结束算法
# 时间复杂度:O(n2)
# 空间复杂度:O(1)
def bubble_sort_optimised(li):
for i in range(len(li) - 1):
exchange = False
for j in range(len(li) - 1 - i):
if li[j] > li[j + 1]:
li[j], li[j + 1] = li[j + 1], li[j]
exchange = True
if not exchange:
return
4. 选择排序
- 一趟遍历记录最小的数,放到第一个位置
- 再一趟遍历记录剩余列表中最小的数,继续放置;
- 重复以上过程直到结束
1)代码关键点:
- 无序区
- 最小数的位置
# 时间复杂度:O(n2)
# 空间复杂度:O(1)
def select_sort(li):
for i in range(len(li) - 1):
min_index = i
for j in range(i + 1, len(li)):
if li[min_index] > li[j]:
min_index = j
if min_index != i:
li[min_index], li[i] = li[i], li[min_index]
5. 插入排序
1)插入排序思路:
- 列表被分为有序区和无序区两个部分。最初有序区只有一个元素
- 每次从无序区选择一个元素,插入到有序区的位置,直到无序区变空
# 时间复杂度:O(n2)
# 空间复杂度:O(1)
def insert_sort(li):
for i in range(1, len(li)):
j = i - 1
tmp = li[i]
while j >= 0 and li[j] > tmp:
li[j + 1] = li[j]
j -= 1
li[j + 1] = tmp
2)优化空间:应用二分查找来寻找插入点
6. 快速排序
1)快速排序思路:
- 取一个元素p(第一个元素),使元素p归位
- 列表被p分成两部分,左边都比p小,右边都比p大
- 递归完成排序
# O(nlogn)
def quick_sort(li, left, right):
if left < right:
mid = partitiion(li, left, right)
quick_sort(li, left, mid - 1)
quick_sort(li, mid + 1, right)
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
2)效率:快速排序的时间复杂度较小
3)快速排序的问题
- 最坏情况:时间复杂度从O(nlogn)升级为O(n2)
- 递归
7. 堆排序
1)树的简介
(1)树是一种数据结构,比如:目录结构
(2)树是一种可以递归定义的数据结构
(3)树是由n个节点组成的集合:
- 如果n=0,那这是一棵空树
- 如果n>0,那存在1个节点作为树的根节点,其他节点可以分为m个集合,每个集合本身又是一棵树
(4)一些概念:
根节点、叶子结点
树的深度(高度)
树的度
孩子节点/父节点
子树
(5)特殊且常用的树--二叉树
二叉树:度不超过2的树(节点最多有两个叉)
(6)满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树
(7)完全二叉树:叶节点只能出现在最下层和次下层,而且最下面一层的节点都集中在该层最左边的若干位置的二叉树
(8)二叉树的存储方式:
链式存储方式
顺序存储方式
父节点和左孩子节点的编号下标的关系:i(父节点) -> 2i + 1(子节点)
父节点和右孩子节点的编号下标的关系:i(父节点) -> 2i + 2(子节点)
2)堆
- 大根堆:一颗完全二叉树,满足任一节点都比其他孩子节点大
- 小根堆:一颗完全二叉树,满足任一节点都比其他孩子节点小
(1)堆的向下调整性质
假设:节点的左右子树都是堆,但自身不是
当根节点的左右子树都是堆时,可以通过一次向下的调整来将其变换成一个堆
(2)堆排序过程
- 建立堆
- 得到堆顶元素,为最大元素
- 去掉对顶,将堆最后一个元素放到堆顶,此时可通过一次调整重新使堆有序
- 堆顶元素为第二大元素
- 重复以上步骤,直到堆变空
def shift(li, low, high):
tmp = li[low]
i = low
j = 2 * i + 1
while j <= high: # 第二种跳出条件,j > high
if j < high and li[j + 1] > li[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):
n = len(li)
# 1. 建堆的过程
for i in range(n // 2 - 1, -1, -1):
shift(li, i, n - 1)
# 2. 挨个出数
for i in range(n - 1, -1, -1): # i表示此时堆的high位置
li[0], li[i] = li[i], li[0]
shift(li, 0, i - 1)
(3)堆排序--内置模块
优先队列:一些元素的集合,POP操作每次执行都会从优先队列中弹出最大(或最小)的元素。
堆--优先队列
Python内置模块--heapq
heapify(x)
heappush(heap, item)
heappop(heap)
利用heapq模块实现堆排序
def heapsort(li):
h = []
for value in li:
heappush(h, value)
return [heappop(h) for i in range(len(h))]
堆排序--topk问题
现在有n个数,设计算法得到前k大的数。
解决思路:
(1)排序后切片 O(nlogn);
(2)冒泡排序、选择排序、插入排序 O(kn)
(3)堆排序思路 O(nlogk)
- 取列表前k个元素建立一个小根堆。堆顶就是目前第k大的数。
- 依次向后遍历原列表,对于列表中的元素,如果小于堆顶,则忽略该元素;如果大于堆顶,则将堆顶更换为该元素,并且对堆进行一次调整
- 遍历列表所有元素后,倒序弹出堆顶
8. 归并排序
def merge(li, low, mid, high):
i = low
j = mid + 1
li_tmp = []
while i <= mid and j <= high:
if li[i] < li[j]:
li_tmp.append(li[i])
i += 1
else:
li_tmp.append(li[j])
j += 1
while i <= mid:
li_tmp.append(li[i])
i += 1
while j <= high:
li_tmp.append(li[j])
j += 1
for i in range(low, high + 1):
li[i] = li_tmp[i - low]
# 时间复杂度O(nlogn)
# 空间复杂度O(n)
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)
归并的应用:
- 分解:将列表越分越小,直至分成一个元素
- 终止条件:一个元素是有序的
- 合并:将两个有序列表归并,列表越来越大
快速排序、堆排序、归并排序小结:
三种排序算法的时间复杂度都是O(nlogn)
一般情况下,就运行时间而言:
快速排序 < 归并排序 < 堆排序
三种排序算法的缺点:
快速排序:极端情况下排序效率低
归并排序:需要额外的内容开销
堆排序:在快的排序算法中相对较慢
9. 希尔排序
希尔排序(Shell Sort)是一种分组插入排序算法
首先取一个整数d1 = n / 2,将元素分为d1个组,每组相邻量元素之间距离为d1,在各组内进行直接插入排序
取第二个整数d2 = d1 / 2,重复上述分组排序过程,知道di = 1,即所有元素在同一组内进行直接插入排序
希尔排序每趟并不使某些元素有序,而是使整体数据越来越接近有序;最后一趟排序使得所有数据有序
def insert_sort_gap(li, gap):
for i in range(gap, len(li)):
tmp = li[i]
j = i - gap
while j >= 0 and li[j] > tmp:
li[j+gap] = li[j]
j -= gap
li[j+gap] = tmp
def shell_sort(li):
d = len(li) // 2
while d >= 1:
insert_sort_gap(li, d)
d //= 2
10. 桶排序
首先将元素分在不同的桶中,再对每个桶中的元素排序
def bucket_sort(li, n=100, max_num=10000):
buckets = [[] for _ in range(n)]
for var in li:
i = min(var // (max_num // n), n-1)
buckets[i].append(var)
for j in range(len(buckets[i]) - 1, 0, -1):
if buckets[i][j] < buckets[i][j-1]:
buckets[i][j], buckets[i][j-1] = buckets[i][j-1], buckets[i][j]
else:
break
sorted_li = []
for buc in buckets:
sorted_li.extend(buc)
return sorted_li
桶排序的表现取决于数据的分布。
平时情况时间复杂度:O(n+k)
最坏情况时间复杂度:O(n2k)
空间复杂度:O(nk)
11. 基数排序
def radix_sort(li):
max_num = max(li)
it = 0
while 10 ** it <= max_num:
buckets = [[] for _in range(10)]
for var in li:
digit = (var // 10 ** it) % 10
buckets[digit].append(var)
li.clear()
for buc in buckets:
li.extend(buc)
it += 1
时间复杂度:O(kn)
空间复杂度:O(k+n)
k表示数字位数
12. 算法的相关试题
1)给两个字符串s和t,判断t是否为s的重新排列后的组成的单词
s = 'anagram', t = 'nagaram', return True
s = 'rat', t = 'cat', return False
def isAnagram(self, s, t):
return sorted(list(s)) == sorted(list(t))
def isAnagram(self, s, t):
dict_s = {}
dict_t = {}
for chr_s in s:
dict_s[chr_s] = dict_s.get(chr_s, 0) + 1
for chr_t in t:
dict_t[chr_t] = dict_t.get(chr_t, 0) + 1
return dict_s == dict_t
2)给定一个m*n的二维列表,查找一个数是否存在。列表有下列特性:
每一行的列表从左到右已经排好序
每一行第一个数比上一行最后一个数大
def searchMatrix(self, matrix, target):
h = len(matrix)
if h == 0:
return False
w = len(matrix[0])
if w == 0:
return False
left = 0
right = w * h - 1
while left <= right:
mid = (left + right) // 2
i = mid // w
j = mid % w
if matrix[i][j] == target:
return True
elif matrix[i][j] > target:
right = mid - 1
else:
left = mid + 1
else:
return False
3)给定一个列表和一个整数,设计算法找到两个数的下标,使得两个数之和为给定的整数。保证肯定进有一个结果。
例如:列表[1,2,5,4]与目标整数3,1+2=3,结果为(0,1)
def binary_search(self, li, left, right, val):
while left <= right:
mid = (left + right) // 2
if li[mid] == val:
return mid
elif li[mid] < val:
right = mid - 1
else:
left = mid + 1
else:
return None
def twoSum(self, nums, target):
for i in range(len(nums)):
a = nums[i]
b = target - a
if b >= a:
j = self.binary_search(li, i+1, len(nums)-1, b)
else:
j = self.binary_search(li, 0, i-1, b)
if j:
break
return sorted([i+1,j+1])
二. 数据结构
数据结构是指相互之间存在着一种或多种关系的数据元素的集合和该集合中数据元素之间的关系组成。简而言之,数据结构就是设计数据以何种方式组织并存储在计算机当中。
数据结构按照逻辑结构可分为线性结构、树结构、图结构
线性结构:数据结构中的元素存在一对一的相互关系
树结构:数据结构中的元素存在一对多的相互关系
图结构:数据结构中的元素存在多对多的相互关系
1. 列表
列表是一种基本数据类型
2. 栈
- 栈是一个数据集合,可以理解为只能在一端进行插入或删除操作的列表
- 栈的特点:后进先出LIFO(last-in,first-out)
- 栈的概念:栈顶、栈底
- 栈的基本操作:进栈(压栈):push;出栈:pop;取栈顶:gettop
3. 队列
- 队列(queue)是一个数据集合,仅允许在列表的一端进行插入,另一端进行删除
- 进行插入的一端成为队尾(rear),插入动作称为进队或入队
- 进行删除的一端称为队头(front),删除动作成为出队
- 队列的性质:先进先出(First-in,First-out)
环形队列:当队尾指针front == Maxsize - 1时,再前进一个位置就自动到0
队首指针前进1:front = (front + 1) % MaxSize
队尾指针前进1:rear = (rear + 1) % MaxSize
队空条件: rear = front
队满条件:(rear + 1) % MaxSize == front
双向队列:两端都支持进队和出队操作
双向队列的基本操作:队首进队、队首出队、队尾进队、队尾出队
4. 栈和队列的应用:迷宫问题
maze = [
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 0, 0, 1, 0, 0, 0, 1, 0, 1],
[1, 0, 0, 1, 0, 0, 0, 1, 0, 1],
[1, 0, 0, 0, 0, 1, 1, 0, 0, 1],
[1, 0, 1, 1, 1, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 1, 0, 0, 0, 0, 1],
[1, 0, 1, 0, 0, 0, 1, 0, 0, 1],
[1, 0, 1, 1, 1, 0, 1, 1, 0, 1],
[1, 1, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
]
dirs = [
lambda x,y: (x+1, y),
lambda x,y: (x-1, y),
lambda x,y: (x, y-1),
lambda x,y: (x, y+1),
]
def maze_path(x1, y1, x2, y2):
pass
5. 链表
链表是由一系列节点组成的元素集合。每个节点包含两部分,数据域item和指向下一个节点的指针next。通过节点之间的相互连接,最终串联成一个链表。
class Node(object):
def __init__(self, item):
self.item = item
self.next = None
1)创建链表:
(1)头插法
class Node:
def __init__(self, item):
self.item = item
self.next = None
def create_linklist_head(li):
head = Node(li[0])
for ele in li[1:]:
node = Node(ele)
node.next = head
head = node
return head
(2)尾插法
class Node:
def __init__(self, item):
self.item = item
self.next = None
def create_linklist_tail(li):
head = Node(li[0])
tail = head
for ele in li[1:]:
node = Node(ele)
tail.next = node
tail = node
return head
6. 双链表
双链表的每个节点有两个指针:一个指向后一个节点,另一个指向前一个节点
class Node(object):
def __init__(self, item=None)
self.item = item
self.next = None
self.prev = None
链表与顺序表
链表在插入和删除的操作上明显快于顺序表
链表的内存可以更灵活地分配
链表这种链式存储的数据结构对树和图的结构有很大的启发性
7. 哈希表
哈希表通过哈希函数来计算数据存储位置的数据结构,通常支持如下操作:
insert(key, value):插入键值对(key, value)
get(key):如果存在键为key的键值对则返回其value
delete(key):删除键为key的键值对
直接寻址表:key为k的元素放到k位置上
改进直接寻址表:哈希(Hashing)
构建大小为m的寻址表T
key为k的元素放到h(k)位置上
h(k)是一个函数,其将域U映射到表T[0,1,...,m-1]
哈希表(Hash Table,又称为散列表),是一种线性表的存储结构。哈希表又一个直接寻址表和一个哈希函数组成。哈希函数h(k)将元素关键字k作为自变量,返回元素的存储下标。
哈希冲突:由于哈希表的大小有限,而要存储的值得总数是无限的,因此对于任何哈希函数,都会出现两个不同元素映射到同一个位置的情况,这种情况叫做哈希冲突。
解决哈希冲突--开放寻址法
开放寻址法:如果哈希函数返回的位置已经有值,则可以向后探查新的位置来存储这个值。
线性探查:如果位置i被占用,则探查i+1, i+2...
二次探查:如果位置i被占用,则探查i+1(2), i-1(2), i+2(2), i-2()2
二度哈希:有n个哈希函数,当使用第1个哈希函数h1发生冲突时,则尝试使用h2,h3,...
解决哈希冲突--拉链法
拉链法:哈希表每个位置都连接一个链表,当冲突发生时,冲突的元素将被加到该位置链表的最后
哈希表的实现:
class LinkList:
class Node:
def __init__(self, item=None):
self.item = item
self.next = None
class LinkListIterator:
def __init__(self, node):
self.node = node
def __next__(self):
if self.node:
cur_node = self.node
self.node = cur_node.next
return cur_node.item
else:
raise StopIteration
def __iter__(self):
return self
def __init__(self, iterable=None):
self.head = None
self.tail = None
if iterable:
self.extend(iterable)
def append(self, obj):
s = LinkList.Node(obj)
if not self.head:
self.head = s
self.tail = s
else:
self.tail.next = s
self.tail = s
def extend(self, iterable):
for obj in iterable:
self.append(obj)
def find(self, obj):
for n in self:
if n == obj:
return True
else:
return False
def __iter__(self):
return self.LinkListIterator(self.head)
def __repr__(self):
return "<<" + ",".join(map(str, self)) + ">>"
class HashTable:
def __init__(self, size=101):
self.size = size
self.T = [LinkList() for i in range(self.size)]
def h(self, k):
return k % self.size
def insert(self, k):
i = self.h(k)
if self.find(k):
print("Duplicated Insert")
else:
self.T[i}.append(k)
def find(self, k):
i = self.h(k)
return self.T[i].find(k)
8. 树
树是一种数据结构
树是一种可以递归定义的数据结构
树是由n个节点组成的集合:如果n=0,那这是一棵空树;如果n>0,那存在1个节点作为树的根节点,其他节点可以分为m个集合,每个集合本身又是一棵树
1)二叉树
二叉树的链式存储:将二叉树的节点定义为一个对象,节点之间通过类似链表的链接方式来连接
节点定义:
class BiTreeNode:
def __init__(self, data):
self.data = data
self.lchild = None
self.rchild = None
(1)二叉搜索树
二叉搜索树是一棵二叉树且满足性质:设x是二叉树的一个节点。如果y是x左子树的一个节点,那么y.key <= x.key;如果y是x右子树的一个节点,那么y.key >= x.key。
二叉搜索树的操作:查询、插入、删除
class BiTreeNode:
def __init__(self, data):
self.data = data
self.lchild = None
self.rchild = None
self.parent = None
class BST:
def __init__(self):
self.root = None
def insert(self, node, val):
if not node:
node = BiTreeNode(val)
elif val < node.data:
node.lchild = self.insert(node.lchild, val)
node.lchild.parent = node
elif val > node.data:
node.rchild = self.insert(node.rchild, val)
node.rchild.parent = node
return node
def insert_no_rec(self, val):
p = self.root
if not p:
self.root = BiTreeNode(val)
return
while True:
if val < p.data:
if p.lchild:
p = p.lchild
else:
p.lchild = BiTreeNode(val)
p.lchild.parent = p
return
elif val > p.data:
if p.rchild:
p = p.rchild
else:
p.rchild = BiTreeNode(val)
p.rchild.parent = p
return
else:
return
def query(self, node, val):
if not node:
return None
if node.data < val:
return self.query(node.rchild, val)
elif node.data > val:
return self.query(node.lchild, val)
else:
return node
def query_no_rec(self, val):
p = self.root
while p:
if p.data < val:
p = p.rchild
if p.data > val:
p = p.lchild:
else:
return P
return None
def __remove_node_1(self, node):
# 情况1:node是叶子节点
if not node.parent:
self.root = None
if node == node.parent.lchild: # node是它父亲的左孩子
node.parent.lchild = None
else:
node.parent.rchild = None
def __remove_node_21(self, node):
# 情况2.1:node只有一个左孩子
if not node.parent:
self.root = node.lchild
node.lchild.parent = None
elif node == node.parent.lchild:
node.lchild.parent = node.parent
else:
node.parent.rchild = node.lchild
node.lchild.parent = node.parent
def __remove_node_22(self, node):
# 情况2.2: node只有一个右孩子
if not node.parent:
self.root = node.rchild
elif node == node.parent.lchild:
node.parent.lchild = node.rchild
node.rchild.parent = node.parent
else:
node.parent.rchild = node.rchild
node.rchild.parent = node.parent
def delete(self, val):
if self.root:
node = self.query_no_rec(val)
if not node:
return False
if not node.lchild and not node.rchild:
self.__remove_node_1(node)
elif not node.rchild:
self.__remove_node_21(node)
elif not node.lchild:
self.__remove_node_22(node)
else:
min_node = node.rchild
while min_node.lchild:
min_node = min_node.lchild
node.data = min_node.data
if min_node.rchild:
self.__remove_node_22(min_node)
else:
self.__remove_node_1(min_node)
def pre_order(self, root):
if root:
print(root.data, end=',')
self.pre_order(root.lchild)
self.pre_order(root.rchild)
def in_order(self, root):
if root:
self.in_order(root.lchild)
print(root.data, end=',')
self.in_order(root.rchild)
def post_order(self, root):
if root:
self.post_order(root.lchild)
self.post_order(root.rchild)
print(root.data, end=',')
二叉搜索树的效率:
平均情况下,二叉搜索树进行搜索的时间复杂度为O(lgn),最坏情况下二叉搜索树可能非常倾斜
解决方案:随机化插入、AVL树
(2)AVL树
AVL树:AVL树是一颗自平衡的二叉搜索树
AVL树具有以下性质:根的左右子树的高度之差的绝对值不能超过1,;根的左右子树都是平衡二叉树
AVL树--插入
- 插入一个节点可能会破坏AVL树的平衡,可以通过旋转操作来进行修正
- 插入一个节点后,只有从插入节点到根节点的路径上的节点的平衡可能被改变。我们需要找出第一个破坏了平衡条件的节点,称之为K。K的两棵子树的高度差2
不平衡的出现可能有4中情况
a. 不平衡是由于对K的右孩子的右子树插入导致的:左旋
b. 不平衡是由于对K的左孩子的左子树插入导致的:右旋
c. 不平衡是由于对K的右孩子的左子树插入导致的:右旋-左旋
d. 不平衡是由于对K的左孩子的右子树插入导致的:左旋-右旋
class AVLNode(object):
def __init__(self, data):
self.data = data
self.lchild = None
self.rchild = None
self.parent = None
self.bf = 0
class BST(object):
def __init__(self, li=None):
self.root = None
self.li = li
def rotate_left(self, p, c):
s2 = c.child
p.rchild = s2
if s2:
s2.parent = p
c.lchild = p
p.parent = c
p.bf = 0
p.bf = 0
return c
def rotate_right(self, p, c):
s2 = c.rchild
p.lchild = s2
if s2:
s2.parent = p
c.rchild = p
p.parent = c
p.bf = 0
c.bf = 0
return c
def rotate_right_left(self, p, c):
g = c.lchild
s3 = g.rchild
c.lchild = s3
if s3:
s3.parent = c
g.rchild = c
c.parent = g
s2 = g.lchild
p.rchild = s2
if s2:
s2.parent = p
g.lchild = p
p.parent = g
if g.bf > 0:
c.bf = 0
p.bf = -1
else:
p.bf = 0
c.bf = 1
g.bf = 0
return g
def rotate_left_right(self, p, c):
g = c.rchild
s2 = g.lchild
c.rchild = s2
if s2:
s2.parent = c
g.lchild = c
c.parent = g
s3 = g.rchild
p.lchild = s3
if s3:
s3.parent = p
g.rchild = p
p.parent = g
if g.bf < 0:
p.bf = 1
c.bf = 0
elif g.bf > 0:
p.bf = 0
c.bf = 1
else:
p.bf = 0
c.bf = -1
g.bf = 0
return g
def insert_no_rec(self, val):
p = self.root
if not p:
self.root = BiTreeNode(val)
return
while True:
if val < p.data:
if p.lchild:
p = p.lchild
else:
p.lchild = BiTreeNode(val)
p.lchild.parent = p
node = p.lchild
break
elif val > p.data:
if p.rchild:
p = p.rchild
else:
p.rchild = BiTreeNode(val)
p.rchild.parent = p
node = p.rchild
break
else:
return
while node.parent:
if node.parent.lchild == node:
if node.parent.bf < 0:
g = node.parent.parent
x = node.parent
if node.bf > 0:
n = self.rotate_left_right(node.parent, node)
else:
n = self.rotate_right(node.parent, node)
elif node.parent.bf > 0:
node.parent.bf = 0
break
else:
node.parent.bf = -1
node = node.parent
continue
else:
if node.parent.bf > 0:
g = node.parent.parent
x = node.parent
if node.bf < 0:
n = self.rotate_right_left(node.parent, node)
else:
n = self.rotate_left(node.parent, node)
elif node.parent.bf < 0:
node.parent.bf = 0
break
else:
node.parent.bf = 1
node = node.parent
continue
n.parent = g
if g:
if node.parent = g.lchild:
g.lchild = n
else:
g.rchild = n
break
else:
self.root = n
break
(3)二叉搜索树扩展应用--B树
B树(B-Tree):B树是一棵自平衡的多路搜索树。常用于数据库的索引。
三. 算法进阶
1. 贪心算法
贪心算法是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,所做出的是在某种意义上的局部最优解。
# 找钱问题
t = [100, 50, 20, 5]
def change(t, n):
m = [0 for _ in range(len(t))]
for i, money in enumerate(t):
m[i] = n /// money
n = n % money
return m,n
print(change(t, 575))
分数背包的实现
goods = [(60, 10), (100, 20), (120, 30)] # 每个商品元组表示(价格,重量)
goods.sort(key=lambda x: x[0]/x[1], reverse=True)
def fractional_backpack(goods, w):
m = [0 for _ in range(len(goods))]
total_v = 0
for i, (price, weight) in enumerate(goods):
if w >= weight:
m[i] = 1
w -= weight
else:
m[i] = w / weight
total_v += m[i] * price
w = 0
break
return total_v, m
fractional_backpack(goods, 50)
数字组合排序问题:
from functools import cmp_to_key
li = [32, 94, 128, 1286, 6, 71]
def xy_cmp(x, y):
if x+y < y+x:
return 1
elif x+y > y+x:
return -1
else:
return 0
def number_join(li):
li = list(map(str, li))
li.sort(key=cmp_to_key(xy_cmp))
return ''.join(li)
print(number_join(li))
活动场地安排问题
activities = [(1, 4), (3, 5), (0, 6), (5, 7), (3, 9), (5, 9), (6, 10), (8, 11), (8, 12), (2, 14), (12, 16)]
activities.sort(key=lambda x:x[1])
def activity_selection(a):
res = [a[0]]
for i in range(1, len(a)):
if a[i][0] >= res[-1][1]:
res.append(a[i])
return res
print(activity_selection(activities))
2. 钢条切割问题
最优子结构:
- 可以将求解规模为n的原问题,划分为规模更小的子问题:完成一次切割后,可以将产生的两段钢条看成两个独立的钢条切割问题
- 组合两个字问题的最优解,并在所有可能的两段切割方案中选取组合收益最大的,构成原问题的最优解
- 钢条切割满足最优子结构:问题的最优解由相关子问题的最优解组合而成,这些子问题可以独立求解
p = [0, 1, 5, 8, 9, 10, 17, 17, 20, 24, 30]
def cut_rod_recurision_1(p, n):
if n == 0:
return 0
else:
res = p[n]
for i in range(1, n):
res = max(res, cut_rod_recurision_1(p, i) + cut_rod_recurision_1(p, n-i))
return res
def cut_rod_recurision_2(p, n):
if n == 0:
return 0
else:
res = 0
for i in range(1, n+1):
res = max(res, p[i] + cut_rod_recurision_2(p,n-i))
return res
print(cut_rod_recurision_2(p, 9))
动态规划解法:
递归算法由于重复求解相同子问题,效率极低
动态规划的思想:
- 每个子问题只求解一次,保存求解结果
- 之后需要此问题时,只需查找保存的结果
动态规划法解法:
def cut_rod_dp(p, n):
r = [0 for _ in in range(n+1)]
for i in range(1, n+1):
res = 0
for j in range(1, i+1):
res = max(res, p[j] + r[i - j])
r[i] = res
return r[n]
def cut_rod_extend(p, n):
r = [0]
s = [0]
for i in range(1, n+1):
res_r = 0
res_s = 0
for j in range(1, i + 1):
if p[j] + r[r-j] > res_r:
res_r = p[j] + r[i - j]
res_s = j
r.append(res_r)
s.append(res_s)
return r[n], s
def cut_rod_solution(p, n):
r, s = cut_rod_extend(p, n)
ans = []
while n > 0:
ans.append(s[n])
n -= s[n]
return ans
3. 最长公共子序列
最长公共子序列问题:给定两个序列X和Y,求X和Y长度最长的公共子序列。
应用场景:字符串相似度对比
最长公共子序列求长度的算法:
def lcs_length(x, y):
m = len(x)
n = len(y)
c = [[0 for _ in range(n+1)] for _ in range(m+1)]
for i in range(1, m+1):
for j in range(1, n+1):
if x[i-1] == y[j-1]:
c[i][j] = c[i-1][j-1] + 1
else:
c[i][j] = max(c[i-1][j], c[i][j-1])
return c[m][n]
def lcs(x, y):
m = len(x)
n = len(y)
c = [[0 for _ in range(n+1)] for _ in range(m+1)]
b = [[0 for _ in range(n+1)] for _ in range(m+1)]
for i in range(1, m+1):
for j in range(1, n+1):
if x[i-1] == y[j-1]:
c[i][j] = c[i-1][j-1] + 1
b[i][j] = 1
elif c[i-1][j] > c[i][j-1]:
c[i][j] = c[i-1][j]
b[i][j] = 2
else:
c[i][j] = c[i][j-1]
b[i][j] = 3
return c[m][n], b
def lcs_trackback(x, y):
c, b = lcs(x, y)
i = len(x)
j = len(j)
res = []
while i > 0 and j >0:
if b[i][j] == 1:
res.append(x[i-1])
i -= 1
j -= 1
elif b[i][j] == 2:
i -= 1
else:
j -= 1
return "".join(reverse(res))