Python数据结构与算法
递归实例:汉诺塔问题
分析:
n个盘子时:
1.把 n-1 个圆盘从A经过C移动到B(保留最底下的大圆盘)
2.把第 n 个圆盘从A移动到C
3.把 n-1 个小圆盘从B经过A移动到C
代码:
def hanoi(n, a, b, c): # 把n个圆盘从a经过b移动到c
if n > 0:
# 把上面的n-1个圆盘从a经过c移动到b
hanoi(n-1, a, c, b)
# 将最底下的第n个圆盘移动到c
print("moving from %s to %s" % (a, c))
# 把n-1个圆盘从b经过a移动到c
hanoi(n-1, b, a, c)
hanoi(3, 'A', 'B', 'C')
列表查找 二分查找
列表查找(线性查找)
def linear_search(li, val):
for ind, v in enumerate(li): # enumerate 可对列表(字符串)进行遍历,同时获得索引和值
if v == val:
return ind
else:
return Mone
二分查找|时间复杂度O(log(n))
注意:二分查找要求列表有序排列
def binary_search(li, val):
left = 0
right = len(li) - 1
while(left <= right): # 候选区有值
mid = (left + right) // 2
if li[mid] == val:
return mid
elif li[mid] > val: # 待查找的值在mid的左边,更新右边界right
right = mid - 1
else: # 待查找的值在mid的右边,更新左边界left
left = mid + 1
else:
return None
li = [1, 2, 3, 4, 5, 6, 7, 8, 9]
binary_search(li, 3)
测试两个列表查找的速度
# 在同一个文件夹下面新建一个cal_time.py文件,用来测量函数的运行速度
import time
def cal_time(func):
def wrapper(*args, **kwargs):
t1 = time.time()
result = func(*args, **kwargs)
t2 = time.time()
print("%s running time: %s secs." % (func.__name__, t2 - t1))
return result
return wrapper
# 将上面的查找函数的文件里面导入cal_time
from cal_time import *
@cal_time
def linear_search(li, val):
for ind, v in enumerate(li): # enumerate 可对列表(字符串)进行遍历,同时获得索引和值
if v == val:
return ind
else:
return Mone
@cal_time
def binary_search(li, val):
left = 0
right = len(li) - 1
while(left <= right): # 候选区有值
mid = (left + right) // 2
if li[mid] == val:
return mid
elif li[mid] > val: # 待查找的值在mid的左边,更新右边界right
right = mid - 1
else: # 待查找的值在mid的右边,更新左边界left
left = mid + 1
else:
return None
li = list(range(100000))
linear_search(li, 38900)
binary_search(li, 38900)
列表排序
排序:将一组"无序"的记录序列调整为"有序"的记录序列
内置排序函数:sort()
排序Low B三人组
冒泡排序
1.列表每两个相邻的数,如果前面比后面大,则交换这两个数
2.一趟排序完成后,则无序区域减少一个数,有序区增加一个数
代码关键点:趟 [n-i],无序区范围[n-i-1]
import random
def bubble_sort(li):
for i in range(len(li)-1): # 第i趟
for j in range(len(li)-i-1):
if li[j] > li[j+1]:
li[j], li[j+1] = li[j+1], li[j]
li = [random.randint(0, 10000) for i in range(10)] # 随机生成长度为1000的范围在(0,10000)的列表
print(li)
bubble_sort(li)
print(li)
# 优化
def bubble_sort(li):
for i in range(len(li)-1): # 第i趟
exchange = False
for j in range(len(li)-i-1):
if li[j] > li[j+1]:
li[j], li[j+1] = li[j+1], li[j]
exchange = True
print(li)
if not exchange:
return
选择排序
1.一趟排序记录最小的数,放在第一个位置
2.再一趟排序记录列表无序区最小的数,放在第二个位置
关键:有序区和无序区,无序区最小值的位置
# 简单移动的选择排序
def select_sort_simple(li):
li_new = []
for i in range(len(li)): # 需要n遍
min_val = min(li)
li_new.append(min_val)
li.remove(min_val)
return li_new
缺点:1.要开辟一个新的等大的列表空间,占用空间内存; 2.min() remove()两个函数还是会遍历一遍列表,耗时
# 正常的选择排序
def select_sort(li):
for i in range(len(li)-1):
min_loc = i # 定义一个初始值位置,用来与最小值交换位置
for j in range(i+1, len(li)):
if li[j] < li[min_loc]:
li[j], li[min_loc] = li[min_loc], li[j]
li = [3, 4, 2, 1, 6, 5, 8, 7, 9]
select_sort(li)
print(li)
插入排序
def insert_sort(li):
for i in range(1, len(li)): # i 表示摸到的牌的下标
tmp = li[i]
j = i - 1 # j 指的是手里面的牌的下标
while li[j] > tmp and j >=0:
li[j+1] = li[j]
j -= 1
li[j+1] = tmp
排序NB三人组
快速排序
快速排序思路:
1.取一个元素p(第一个元素),使元素p归位
2.列表被p分成两部分,左边都比p小,右边都比p大
3.递归完成排序
时间复杂度:O(log(n))
def partition(li, left, right):
tmp = li[left]
while left < right:
while left < right and li[right] >= tmp: # 从右边找比tmp小的数
right -= 1 # 往左走一步
li[left] = li[right] # 把右边的值写在左边的空位上
while left < right and li[left] <= tmp:
left += 1
li[right] = li[left] # 把左边的值写在右边空位上
li[left] = tmp # 将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)
堆排序
树与二叉树的知识:
树是一种数据结果,如:目录结构。树是一种可以递归定义的数据结构。
树是由n个节点组成的集合:
如果n=0,那这是一棵空树;
如果n>0,那存在1个节点作为树的根节点,其他点可以分为m个集合,每个集合本身又是一棵树。
树的深度:从根节点到最下叶子节点的步长
树的度:树中根节点存在最多 叶子节点的数量
二叉树:度不超过2的树
两个孩子节点被区分为左孩子节点和右孩子节点
满二叉树:一个二叉树,如果每一层的节点数都达到最大值,则这个二叉树就是满二叉树
完全二叉树:叶子节点只能出现在最下层和次下层,并且最下面一层的节点都集中在该层最左边 的若干位置的二叉树
堆:一种特殊的完全二叉树结构
大根堆:一棵完全二叉树,满足任一节点都比其孩子节点大
小根堆:一棵完全二叉树,满足任一节点都比其孩子节点小
堆排序:堆的向下调整性质
堆排序过程:
1.建立堆
2.得到堆顶元素,为最大元素
3.去掉堆顶,将堆最后一个元素放到堆顶,此时棵=可通过一次调整重新使用堆有序
4.堆顶元素为第二大元素
5.重复步骤3,直到堆变空
代码实现:
# 调整
def sift(li, low, high):
"""
:param li: 列表
:param low: 堆的根节点位置
:param high: 堆最后一个元素的位置
:return:
"""
i = low # 最开始指向根节点
j = 2 * 1 - 1 # j最开始为i节点的左孩子
tmp = li[low] # 把堆顶存起来
while j <= high: # 只要j位置有数
if j + 1 <= high and li[j] < li[j+1]: # 如果右孩子有并且比较大
j = j + 1 # j指向右孩子
if li[j] > tmp: # 比较孩子与堆顶的大小,目的将堆顶元素找到合适的位置
li[i] = li[j]
i = j # 往下一次找
j = 2 * i - 1
else: # 堆顶元素更大,将tmp的位置放在i位置上
li[i] = tmp
break
else:
li[i] = tmp #把tmp放到叶子节点上
# 使用sift实现堆排序的过程
def heap_sort(li):
n = len(li)
for i in range((n // 2 - 1), -1, -1):
# i表示建对滴时候调整的部分根的下标
sift(li, i, (n - 1))
# 递推完成
for i in range(n - 1, -1, -1):
# i指向当前堆的最后一个元素
li[0], li[i] = li[i], li[0]
sift(li, 0, i - 1) # i-1是最新的high
print(li)
li = [i for i in range(100)]
import random
randpm.shuffle(li)
heap_sort(li)
python自带的堆排序模块
import heapq # q->queue 优先队列
import random
li = list(range(100))
random.shuffle(li)
print(li)
heapq.heapify(li) # 建堆
for i in range(len(li)):
print(heapq.heappop(li), end=',')
应用:topk,从n个数中,选出k个大的数
# 调整小顶堆函数
def sift(li, low, high):
"""
:param li: 列表
:param low: 堆的根节点位置
:param high: 堆最后一个元素的位置
:return:
"""
i = low # 最开始指向根节点
j = 2 * 1 - 1 # j最开始为i节点的左孩子
tmp = li[low] # 把堆顶存起来
while j <= high: # 只要j位置有数
if j + 1 <= high and li[j] > li[j+1]: # 如果右孩子有并且比较大
j = j + 1 # j指向右孩子
if li[j] < tmp: # 比较孩子与堆顶的大小,目的将堆顶元素找到合适的位置
li[i] = li[j]
i = j # 往下一次找
j = 2 * i - 1
else: # 堆顶元素更大,将tmp的位置放在i位置上
li[i] = tmp
break
else:
li[i] = tmp #把tmp放到叶子节点上
# 使用sift实现堆排序的过程
def topk(li, k):
heap = li[0: k] # 先切k个数,建立小顶堆
for i in range((k // 2 - 1), -1, -1):
sift(heap, i, (k - 1))
# 1.建堆 将剩下的数遍历一遍与heap[0]比较,大的留下
for i in range(k, len(li) - 1):
if li[i] > heap[0]:
heap[0] = li[i]
sift(heap, 0, k - 1)
# 2.遍历
for i in range(k-1, -1, -1):
heap[0], heap[i] = heap[i], heap[0]
sift(heap, 0, i-1)
# 3.出数
return heap
import random
li = list(range(1000))
random.shuffle(li)
print(topk(li, 10))
归并排序
# 一次归并
def merge(li, low, mid, high): # low为列表最左边位置,mid为中间位置,high为最右边位置
i = low
j = mid + 1
ltmp = []
while i <= mid and j <= high: #只要左右两边都有数
if li[i] < li[j]:
ltmp.append(li[i])
else:
ltmp.append(li[j])
# while执行完。肯定有一部分没有数
while i <= mid:
ltmp.append(li[i])
i += 1
while j <= high:
ltmp.append(li[j])
j += 1
li[low: high + 1] = ltmp
# li = [2, 4, 5, 7, 1, 3, 6, 8]
# merge(li, 0, 3, 7)
# print(li)
"""
使用归并:
分解:将列表越分越小,直至分成一个元素
终止条件:一个元素是有序的
合并:将两个序列归并,列表越来越大
"""
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)
NB三人组小结:
三种排序算法的时间复杂度都是O(nlogn)
一般情况下,就运行时间而言:
快速排序<归并排序<堆排序
三种排序算法的缺点:
快速排序:极端情况下排序效率抵
归并排序:需要额外的内存开销
堆排序:在快的排序算法中相对较慢
其他排序
希尔排序
希尔排序(Shell Sort)是一种分组插入排序算法
首先取一个整数a=n/2,将元素分为a个组,每组相邻量元素之间距离为a,在各组内进行直接插入排序
取第二个整数b=a/2,重复上述分组排序过程,直到c=1,即所有元素在同一组内进行直接插入排序
希尔排序每趟并不使某些元素有序,而是使整体数据越来越接近有序;最后一趟排序使得所有数据有序
def insert_sort_gap(li, gap):
for i in range(gap, len(li)): # i表示摸到的牌的下标
tmp = li[i]
j = i - gap
while j >= 0 and lil[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
计数排序
def count_sort(li, max_count=100):
count = [0 for _ in range(max_count + 1)]
for val in li:
count[val] += 1
li.clear()
for ind, val in enumerate(count):
for i in range(val):
li.append(ind)
import random
li = [random.randint(0, 100) for _ in range(1000)]
print(li)
count_sort(li)
print(li)
桶排序
首先将元素分在不同的桶中,再 对每个桶中的元素排序
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) # i 表示var放到几号桶里
buckets[i].append(var) # 把var放到对应桶里
for j in range(len(bucket[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
基数排序
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].apend(var)
# 分桶完成
li.clear()
for buc in buckets:
li.extend(buc)
# 把数重新写回li
it += 1
import random
li = list(range(10000))
random.shuffle(li)
radix_sort(li)
print(li)
数据结构
数据结构按照其逻辑结构可分为线性结构、树结构、图结构
线性结构:数据结构中的元素存在一对一的相互关系
树结构:数据结构中的元素存在一对多的相互关系
图结构:数据结构中的元素存在多对多的相互关系
栈:
栈是一个数据集合,可以理解为只能在一端进行插入或删除操作的列表
栈的特点:后进先出 LIFO
栈的概念:栈顶、栈底
栈的基本操作:
进栈(压栈):push
出栈:pop
取栈顶:gettop
"""
用列表实现栈
"""
class Stack:
def __init__(self):
self.stack = []
def push(self, element):
self.stack.append(element)
def pop(self):
return self.stack.pop()
def get_top(self):
if len(self.stack) > 0:
return self.stack[-1]
else:
return None
def is_empty(self):
return len(self.stack) == 0
stack = Stack()
stack.push(1)
stack.push(2)
stack.push(3)
print(stack.pop())
print(stack.get_top())
栈的应用:括号匹配问题
给出一个字符串,其中包含小括号、中括号、大括号,求该字符串中括号是否匹配
class Stack:
def __init__(self):
self.stack = []
def push(self, element):
self.stack.append(element)
def pop(self):
return self.stack.pop()
def get_top(self):
if len(self.stack) > 0:
return self.stack[-1]
else:
return None
def is_empty(self):
return len(self.stack) == 0
def brace_match(s)
match = {"}": "{", "]": "[", ")": "("}
stack = Stack()
for ch in s:
if ch in {'(', '[', '{'}:
stack.push(ch)
else: # ch in {')', ']', '}'}
if stack.is_empty():
return False
elif stack.get_top() == match[ch]:
stack.pop()
else:
return False
if stack.is_empty():
return True
else:
return False
队列
- 队列(Queue)是一个数据集合,仅允许在列表的一端进行插入,另一端进行删除
- 进行插入的一端称为队尾(rear),插入动作称为进队或入队
- 进行删除的一端称为队头(front),删除动作称为出队
- 队列的性质:先进先出(First-in, First-out)
队列的实现方式——环形队列
class Queue:
def __init__(self, size=100):
self.queue = [0 for _ in range(size)]
self.size = size
self.rear = 0 # 队尾指针
self.front = 0 # 队首指针
# 入队
def push(self, element):
if not self.is_filled():
self.rear = (self.rear + 1) % self.size
self.queue[self.rear] = elemnet
else:
raise IndexError("Queue is filled.")
# 出队
def pop(self):
if not self.isempty():
self.front = (self.front + 1) % self.size
return self.queue[self.front]
else:
raise IndexError("Queue is empty.")
# 判断是否为空队列
def is_empty(self):
return self.rear == self.front
# 判断是否为满队
def is_filled(self):
return (self.rear + 1) % self.size == self.front
python的内置队列-双向队列
from collections import deque
q = deque() # 队列满时,队首会自动出队
q.append(1) # 队尾进队
print(q.popleft()) # 队首出队
print(q.popleft()) # 报错
# 用于双向队列
q.appendleft(1) # 队首进队
q.pop() # 队尾出队
栈和队列的应用——迷宫问题
"""
栈————深度优先搜索
回溯法
思路:从一个节点开始,任意找下一个能走的点,当找不到能走的点时,退回上一个点寻找是否有其他方向的点。
使用栈存储当前路径
"""
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_stack(x1, y1, x2, y2):
stack = []
stack.append((x1, y1))
while(len(stack) > 0):
curNode = stack[-1] # 当前节点
# 判断是否走到终点
if curNode[0] == x2 and curNode == y2:
for p in stack:
print p
return True
# x,y四个方向x-1,y; x+1,y; x,y-1; x,y+1
for dir in dirs:
nextNode = dir(curNode[0], curNode[1])
# 如果下一个节点能走
if maze[nextNode[0]][nextNode[1]] == 0:
stack.append(nextNode)
maze[nextNode[0]][nextNode[1]] = 2 # 2表示为已经走过
break
else:
maze[nextNode[0]nextNode[1]] = 2
stack.pop()
else:
print("没有路")
return False
"""
队列————广度优先搜索
思路:从一个节点开始,寻找所有接下来能继续走的点,继续不断寻找,直到找到出口
使用队列存储当前正在考虑的节点
"""
from collections import deque
def print_r(path):
curNode = path[-1] # 找到的终点
realpath = [] # 建立一个列表,存储最短路径上面的值
while curNode[2] == -1: # 与起点重合
realpath.append(curNode[0: 2]) # 将节点的x,y值写入realpath中
curNode = path[curNode[2]] # 使用节点对应的上一个节点的指针位置,找到上一个节点
realpath.append(curNode[0: 2]) # 起点
realpath.reverse()
for node in realpath:
print(node)
def maze_path_queue(x1, y1, x2, y2):
queue = deque()
queue.append((x1, y1, -1)) # 第三个位置用来存储上上一步对应的位置指针
path = []
while len(queue) > 0:
curNode = queue.pop()
path.append(curNode)
# 到达终点
if curNode[0] == x2 and curNode[1] == y2:
print_r(path)
return True
for dir in dirs:
nextNode = dir(curNode[0], curNode[1])
if maze[nextNode[0]][nextNode[1]] == 0:
queue.append((nextNode[0], nextNode[1], len(path) - 1)) # 后续节点进队,记录哪个节点带他来的
maze[nextNode[0]][nextNode[1]] = 2
else:
print("没有路")
return False
链表
链表是由一系列节点组成的元素集合。每个节点包含两部分,数据域item和指向下一个节点的指针next。通过节点之间的相互连接,最终串联成一个链表
"""
创建链表:头插法、尾插法
"""
class Node:
def __init__(self, item):
self.item = item
self.next = None
def create_linklist_head(li): # 头插法
head = Node(list[0])
for element in li[1: ]:
node = Node(element)
node.next = head
head = node
return head
def create_linklist_tail(li): # 尾插法
head = Node(li[0])
tail = head
for element in li[1: ]:
node = Node(element)
tail.next = node
tail = node
return head
def print_linklist(lk):
while lk:
print(lk.item, end=',')
lk = lk.next
链表的插入和删除操作
# 插入
p.next = curNode.next
curNode.next = p
# 删除
p = curNode.next
curNode.next = curNode.next.next
del p
双链表
双链表的每个节点有两个指针:一个指向后一个节点,另一个指向前一个节点
# 双链表的创建、插入和删除
# 创建
class Node:
def __init__(self, item=None):
self.item = item
self.next = None
self.prior = None
# 插入
p.next = curNode.next
curNode.next.prior = p
curNode.next = p
p.prior = curNode
# 删除
p = curNode.next
p.next.prior = curNode
curNode.next = p.next
del p
哈希表
哈希表一个通过哈希函数来计算数据存储位置的数据结构,通常支持如下操作:
insert(key, value):插入键值对(key, value)
get(key):如果存在键为key的键值对则返回其value,否则返回空值
delete(key):删除键为key的键值对
解决哈希冲突——拉链法
拉链法:哈希表每个位置都连接一个链表,当冲突发生时,冲突的元素将被加到该位置链表的最后
常见的哈希函数
除法哈希表:
h(k) = k % m
乘法哈希法:
h(k) = floor(m*(A*key%1))
全域哈希法:
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)
树
# 实现文件夹
class Node: # 文件节点的累
def __init__(self, name, type='dir')
self.name = name
self.type = type # "die" or "file"
self.children = []
self.parent = None
def __repr__(self):
return self.name
class FileSystemTree: # 树的类
def __init__(self, name, type='dir')
self.root = name
self.now = self.root
def mkdir(self, name):
# name 以/结尾
if name[-1] != "/":
name += '/'
node = Node(name)
self.now.children.append(node)
node.parent = self.now
def ls(self):
return self.now.children
def cd(self, name):
if name[-1] != "/":
name += "/"
for child in self.now.children:
if child.name == name:
self.now = child
return
raise ValueError("invalid dir")
二叉树
# 前序遍历
def pre_order(root):
if root:
print(root.data, end=',')
pre_order(root.lchild)
pre_order(root.rchild)
# 中序遍历
def in_order(root):
if root:
in_order(root.lchild)
print(root.data, end=',')
in_order(root.rchild)
# 后序遍历
def post_order(root):
if root:
post_order(root.lchild)
post_order(root.rchild)
podt_order(root.data)
# 层次遍历
from collections import deque
def level_order(root):
queue = deque()
queue.append(root)
while len(queue) > 0: # 只要队不空
node = queue.popleft()
print(node.data, end=',')
if node.lchild:
queue.append(node.lchild)
if node.rchild:
queue.append(node.rchild)
二叉搜索树:
二叉搜索树是一颗二叉树且满足性质:设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, li=None):
self.root = None
if li:
for val in li:
self.insert_no_rec(val)
def insert(self, node, val):
if not node:
node = BiTreeNode(val)
elif val < node.data:
node.lchild = insert(node.lchild, val)
node.lchild.parent = node
elif val > node.data:
node.rchild = 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
elif:
p.lchild = BiTreeNode(val)
p.lchild.parent = p
return
elif val > p.data:
if p.rchild:
p = p.rchild
elif:
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 __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是他父亲的右孩子
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.parent.lchild = node.lchild
node.lchild.parent = node.parent
else:
node.parent.rchild = node.lchile
node.lchild.parent = node.parent
def __remove_node_22(self, node):
# 情况2 .2:node只有一个右孩子
if not node.parent:
self.root = node.rchild
node.rchild.parent = None
elif node == node.parent.lchild:
node.parent.lchild = node.rchild
node.rchild.parent = node.parent
else:
node.parent.rchild = node.rchild
node.rchile.parent = node.parent
def delete(self, val):
if self.root: # 不是空树
node = self.query(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: # 两个孩子都有.找到右边最下边的左孩子,这个节点满足比node的最孩子都大,比右孩子都小
min_node = node.rchild
while min_node.lchild:
min_node = min_node.lchild
node.data = min_node.data
# 删除min_node
if min_node.rchild:
self.__remove_node_22(min_node)
else:
self.__remove_node_1(min_node)
AVL树:AVL树是一棵自平衡的二叉树
AVL树具有以下性质:
- 根的左右子树的高度之差的绝对值不能超过1
- 根的左右子树都是平衡二叉树
from bst import BiTreeNode, BST
class AVLNode(BiTreeNode):
def __init__(self, data):
BiTreeNode.__init__(self, data)
self.bf = 0
class AVLTree(BST):
def __init__(self, li=None):
def rotate_left(self, p, c):
s2 = c.lchild
p.rchild = s2
if s2:
s2.parent = p
c.lchild = p
p.parent = c
p.bf = 0
c.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
# 更新后的bf
if g.bf > 0:
p.bf = -1
g.bf = 0
elif g.bf < 0:
p.bf = 0
c.bf = 1
else: # 此时S1、S2、S3、S4 的高度都为0
p.bf = 0
c.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
# 更新bf
if g.bf > 0:
p.bf = 0
c.bf = -1
elif g.bf < 0:
p.bf = 1
c.bf = 0
else:
p.bf = 0
c.bf = 0
return g
def insert_no_rec(self, val):
# 1.和BST一样,先插入
p = self.root
if not p:
self.root = BiTreeNode(val)
return
while True:
if val < p.data:
if p.lchild:
p = p.lchild
elif: # 左孩子不存在
p.lchild = BiTreeNode(val)
p.lchild.parent = p
node = p.lchild # node 存储的就是插入的节点
elif val > p.data:
if p.rchild:
p = p.rchild
elif:
p.rchild = BiTreeNode(val)
p.rchild.parent = p
node = p.rchild
else: # val = p.data
return
# 2.更新bf
while node.parent:
if node.parent.lchild == node: # 传递是从左子树来的,左子树更沉了
# 更新node.parentd 的bf = -1
if node.parent.bf < 0: # 原来node.parent.bf = -1, 更新后变成-2
# 旋转
# 看node哪边沉
g = node.parent.parent # 为了连接旋转后的子树
x = node.parent # 旋转前的子树根
if node.bf > 0: # 连接
n = self.rotate_lefr_right(node.parent, node)
else:
n = self.rotate_right(node.parent, node)
# 记得:将n和g连起来
elif node.parent.bf > 0: # 原来node.parent.bf = 1,更新后变成0
node.parent.bf = 0
break
else: # 原来node.parent.bf = 0,更新后变成-1
node.parent.bf = -1
node = node.parent
continue
else: # 传递是从右子树来的,右子树更沉了
# 更新node.parent.bf += 1
if node.parent.bf > 0: # 原来node.parent.bf =1,更新后变成2
# 旋转
# 看node哪边沉
g = node.parent.parent # 为了连接旋转后的子树
x = node.parent # 旋转前的子树根
if node.bf < 0: # node.bf = 1
n = self.rotate_right_left(node.parent, node)
else: # node.bf = -1
n = self.rotate_left(node.parent, node)
elif node.parent.bf < 0: # 原来node.parent.bf = -1,更新后变成0
node.parent.bf = 0
break
else: # 原来node.parent.bf = 0,更新后变成1
node.parent.bf = 1
node = node.parent
continue
# 连接旋转后的子树
n.parent = g
if g: # g 不为空
if x == g.lchild:
g.lchild = n
else:
g.rchild = n
break
else:
self.root = n
break
贪心算法
# 找零问题
t = [100. 50, 20, 5, 1]
def change(t, n):
m = [0 for _ in range(t)]
for i, money in enumerate(t):
m[i] = n // money
n = n % money
reutrn m, n
print(change(t, 367))
背包问题——贪心算法
# 分数背包————分数背包
good = [(60, 10), (100, 20), (120, 30)]
good.sort(key=lambda x: x[0]/x[1], reverse=True)
def fractional_backoack(goods, w):
m = [0 for _ in range(len(goods))]
total_v = 0
for i, (prize, weight) in enumerate(goods):
if w >= weight:
m[i] = 1
total_v += prize
w -= weight
else:
m[i] = w / weight
total_v += m[i] * prize
w = 0
break
return total_v, m
print(fraction_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.appent(a[i])
return res
print(activity_selection(activities))
动态规划
钢条切割问题——动态规划
import time
def cal_time(func):
def wrapper(*args, **kwargs):
t1 = time.time()
result = func(*args, **kwarge)
t2 = time.time()
print("%s running time: %s secs." % (func.__name__,t2 - t1))
return result
return wrapper
p = [0, 1, 5, 8, 9, 10, 17, 17, 20, 21, 23, 24, 26, 27, 27, 28, 30, 33, 36, 39, 40]
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
@cal_time
def c1(p, n):
return cut_rod_recurision_1(p, n)
# 自顶向下递归实现
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
@cal_time
def c2(p, n):
return cut_rod_recurision_2(p, n)
# 动态规划——自底向上
def cut_rod_dp(p, n):
r = [0]
for iin range(1, n + 1):
res = 0
for j in range(1, 1 + 1):
res = max(res, p[j] + r[i - j])
r.append(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 - j)
if p[j] + r[i - 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
r, s = cut_rod_extend(p, 10)
print(r, s)
print(cut_rod_solution(p, 9))
动态规划:最优化的问题试用(最大…最小…)
最长公共子序列——动态规划
# 最长公共子序列
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][j - 1], c[i - 1][j])
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)] # 1-左上方,2-上方,3-左方
for i in range(1, m + 1):
for j in range(1, n + 1):
if x[i - 1] == y[j - 1]: # i j位置上的字符匹配的时候,来自于左上方
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(y)
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: # =3,来自于左方->不匹配
j -= 1
return "".join(reversed(res))
print(loc_trackback("ABCBDAB", "BDCABA"))
欧几里得算法
最大公约数——欧几里得算法
def gcd(a, b):
if b == 0:
return a
else:
return gcd(b, a % b)
def gcd2(a, b):
while b > 0:
r = a % b
a = b
b = r
return a
分数的四则运算
class Fraction:
def __init__(self, a, b):
self.a = a
self.b = b
x = self.gcd(a, b)
self.a /= x
self.b /= x
def gcd(self, a, b):
while b > 0:
r = a % b
a = b
b = r
return a
def zgs(self, a, b):
x = self.gcd(a, b)
return (a / x) * b
def __add__(self, other):
a = self.a
b = self.b
c = other.a
d = other.b
fenmu = self.zgs(b, d)
fenzi = a * fenmu / b + c * fenmu / d
return Fraction(fenzi, fenmu)
def __str__(self):
return "%d/%d" % (self.a, self.b)
RSA加密算法
设计模式
设计模式:对软件设计中普遍存在(反复出现)的各种问题,所提出的解决方案。每个设计模式系统的命名、解释和评价了面向对象系统中一个重要的和重复出现的设计
接口
接口:若干抽象方法的集合
作用:限制实现接口的类必须按照接口给定的调用方式实现这些方法;对高层模板隐藏了类的内部是实现
面向对象设计SLOLID原则
设计模式分类