第一部分,查找和排序
查找:顺序查找和二分查找
排序:牛逼三人组(快速排序,堆排序,归并排序)
lowB三人组(冒泡,插入,选择)
其他排序(希尔,计数,基数,桶)
顺序查找和二分查找
# 顺序查找和二分查找
def linear_search(li,var):
for i in li:
if var==i:
return True
else:
return False
def binary_search(li,var):
left = 0
right = len(li)-1
mid = (left+right)//2
while left < right:
if var > li[mid]:
left = mid+1
mid = (left+right)//2
if var < li[mid]:
right = mid-1
mid = (left+right)//2
if var == li[mid]:
return True
else:
return False
a=[1,2,3,4,5,6,7,8,9]
linear_search(a,10)
False
low B排序三人组:冒泡排序,插入排序,选择排序
# low B排序三人组:冒泡排序,插入排序,选择排序
#冒泡排序: 依次前后对比,前大于后,则换,一轮后最后一个位置为最大值,然后开始下一轮,n-1轮后全部有序
def bubble_sort(li):
for i in range(len(li)-1,-1,-1):
for j in range(i):
if li[j] > li[j+1]:
li[j],li[j+1] = li[j+1],li[j]
print(li)
# 选择排序:类似冒泡排序,首先选择列表的第一位的数据,依次和后面的数据尽心对比,如果大,就交换,一轮结束,第一位的为最小
def select_sort(li):
for i in range(len(li)):
for j in range(i,len(li)):
if li[i] > li[j]:
li[i],li[j] = li[j],li[i]
print(li)
# 插入排序:类似与扑克牌,取一张,排一张
def insert_sort(li):
for i in range(1,len(li)):
tem = li[i]
j=i-1
while j >=0 and li[j] > tem:
li[j+1] =li[j]
j-=1
li[j+1] = tem
print(li)
b=[2,1,3,7,33,6,7,8,5]
insert_sort(b)
[1, 2, 3, 5, 6, 7, 7, 8, 33]
牛B三人组:快速排序,堆排序,归并排序
# 牛B三人组:快速排序,堆排序,归并排序
# 快速排序原理:
# 1、取任意一个元素使得元素归位,也就是,左边所有数比它小,右边所有数比其大,
# 2、对归位后元素位置的左边和右边依次进行归位,直到全部元素都归位。
# 堆排序:
# 1、将列表建成堆,(堆是一种完全二叉树)首先对堆从下往上依次进行向下调整,使得整个堆有序
# 2、堆顶元素为最大值,将其存储,得到有序组第一个值
# 3、将堆最后一个位置的元素放置堆顶,经过向下调整,再次得到有序堆,在把堆顶元素存储,得到有序组的第二个值,依次进行,直到堆为空。
# 归并排序原理:还是递归实现
# 1、假如一个序列,左边一部分有序,右边一部分有序,可以通过归并将其整体有序
# 2、对于一个完整的列表,最小到单个元素是有序的,就可以通过归并和递归的思想将其整体有序 -->
# 快速排序实现:元素归位+递归
# 首先某一个元素归位
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
#递归实现排序
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)
return li
b=[2,1,3,4,1,1,4,5,6,3]
quick_sort(b,0,len(b)-1)
[1, 1, 1, 2, 3, 3, 4, 4, 5, 6]
# 堆排序,原理二叉树+向下调整+循环
# 建堆,
# 取最上面元素,将最下面元素换到堆顶
# 向下调整成完全二叉树
# 重复直到树变成空。
# 先建立向下调整函数
def sift(data,low,high):
# 假设只有堆顶元素无序
i = low
j = 2*i+1
tmp = data[low]
while j <= high:
if j+1 <=high and data[j+1]>data[j]:
j = j+1
if data[i] < data[j]:
data[i],data[j] = data[j],data[i]
i = j
j = 2*i+1
else:
break
# if data[j+1]:
# if data[j] > data[j+1]: #左孩子大于右孩子
# if data[i] < data[j]:
# data[i],data[j] = data[j],data[i]
# i=j
# j=2*i+1
# else: #右孩子大于左孩子
# if data[i] < data[j+1]:
# data[i],data[j+1] = data[j+1],data[i]
# i = j+1
# j = 2*i+1
# else:
# if data[i] < data[j]:
# data[i],data[j] = data[j],data[i]
return data
# 排序函数
def dui_sort(data,low,high):
for i in range(high//2,-1,-1):
sift(data,i,high)
print(data)
while high > 0:
data[high],data[low] = data[low],data[high]
high -= 1
sift(data,low,high)
return data
def head_sort(data):
n=len(data)
for i in range((n-2)//2,-1,-1):
sift(data,i,n-1)
for i in range(n-1,-1,-1):
data[0],data[i]=data[i],data[0]
sift(data,0,i-1)
return data
b=[2,9,8,7,6,5,4,3]
dui_sort(b,0,len(b)-1)
[9, 7, 8, 3, 6, 5, 4, 2]
[2, 3, 4, 5, 6, 7, 8, 9]
#归并排序
# 归并函数
def merge(data,left,mid,right):
#假设left-mid,mid-right各自有序,将其合并有序
tmp=[]
i = left
j = mid+1
while i <= mid and j <= right:
if data[i] > data[j]:
tmp.append(data[j])
j += 1
else:
tmp.append(data[i])
i += 1
if i > mid: # 左边先结束
for var in data[j:right+1]:
tmp.append(var)
if j > right: # 右边先结束
for var in data[i:mid+1]:
tmp.append(var)
data[left:right+1] = tmp
return data
#归并排序
def merge_sort(data,left,right):
if left < right: #至少两个元素
mid = (left + right) // 2
merge_sort(data,left,mid)
merge_sort(data,mid+1,right)
merge(data,left,mid,right)
return data
b = [3,2,1,4,5,2,6,6,3,8]
merge_sort(b,0,len(b)-1)
[1, 2, 2, 3, 3, 4, 5, 6, 6, 8]
其他排序方法
# # 其他排序方法:
# 希尔排序原理:插入排序变形,分组插入排序
# 首先取整数d1=n/2,将元素分为d1个组,每组相邻元素间距为d1,各组内进行插入排序
# 取第二个整数d2=d1/2,重复进行插入排序
# 每趟并不会使得某些元素有序,而是使得整体数据越来越有序,最后一趟使得所有数据语序
# 计数排序原理:
# 数数据中的每个数有几个,遍历统计,最后按照统计的数排好序
# 快,时间复杂度为O(n),但是有很大局限,
# 需要知道最大数,还得都是整数,还得额外开一个同样大小的空间
# 桶排序原理:计数排序的升级
# 先将元素分在不同的桶中,对桶中的元素排序,对元素范围大的适用
# 效率取决于数的分布,均匀效果会好,平均效率是O(n+k),最差O(n^2*k)
# 基数排序原理:
# 对于多关键字排序:比如要求员工表按照薪资排序,薪资相同的按照年龄排序,
# 就可以先按照年龄排序,再按照薪资排序,最后的顺序就满足要求,这个是基于排序是稳定排序
# 多位整数的排序也可以看成过关键字排序:位数多的大,相同位数的最高位大的大。
# 所谓稳定排序,是指同样的数字,之前在前面的排完序还是在前面。
# 希尔排序:比堆排序稍慢
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
li = [3,2,1,6,4,2,6]
shell_sort(li)
print(li)
[1, 2, 2, 3, 4, 6, 6]
# 计数排序
def count_sort(li,max_count):
count = [0 for _ in range(max_count+1)]
for var in li:
count[var] += 1
li.clear()
for ind,var in enumerate(count):
for i in range(var):
li.append(ind)
li = [3,1,2,4,1,5,6,3,6,4]
count_sort(li,6)
print(li)
[1, 1, 2, 3, 3, 4, 4, 5, 6, 6]
# 桶排序:
def bucket_sort(li,n=100,max_num=10000):
# n表示桶数
# max_num表示最大值
bucket = [[] for _ in range(n)]
for var in li:
i = min(var // (max_num // n), n-1)
bucket[i].append(var)
for j in range(len(bucket[i])-1, 0 ,-1):
if bucket[i][j] < bucket[i][j-1]:
bucket[i][j],bucket[i][j-1] = bucket[i][j-1],bucket[i][j]
else:
break
sorted_li = []
for buc in bucket:
sorted_li.extend(buc)
return sorted_li
import random
li = [random.randint(0,10000) for i in range(100)]
li=bucket_sort(li)
print(li)
[39, 98, 156, 164, 537, 662, 718, 1082, 1115, 1248, 1279, 1326, 1345, 1372, 1516, 1545, 1578, 1749, 1945, 2060, 2160, 2232, 2329, 2388, 2620, 2661, 2900, 3096, 3104, 3163, 3382, 3403, 3429, 3491, 3828, 3884, 3908, 3947, 3962, 4054, 4343, 4506, 4646, 4694, 4843, 4869, 4913, 5031, 5108, 5263, 5387, 5649, 5799, 5811, 5894, 6018, 6171, 6230, 6450, 6474, 6475, 6609, 6658, 6692, 6700, 6711, 6787, 6879, 6947, 7062, 7094, 7172, 7222, 7385, 7403, 7493, 7525, 7570, 7571, 7592, 7831, 8023, 8095, 8187, 8212, 8430, 8436, 8643, 8683, 8814, 8870, 8897, 9024, 9197, 9265, 9274, 9324, 9330, 9779, 9782]
# 基数排序
# 对整数排序,按照个位,十位等的大小进行排序
def redix_sort(li):
max_num=max(li)
ti=0
while 10**ti<=max_num:
buckets=[[] for _ in range(10)]
for var in li:
digit=(var//10**ti)%10
buckets[digit].append(var)
li.clear()
for buc in buckets:
li.extend(buc)
ti+=1
return li
a=[21,32,14,53,63,17,77,898,456,363]
print(a)
[21, 32, 14, 53, 63, 17, 77, 898, 456, 363]
redix_sort(a)
[14, 17, 21, 32, 53, 63, 77, 363, 456, 898]
def twoSum( nums, target) :
for i in range(0,len(nums)-2):
for j in range(i+1,len(nums)-1):
if nums[i]+nums[j]==target:
return [i,j]
a=list(range(100))
random.shuffle(a) #打乱a
print(a)
[94, 85, 64, 40, 15, 8, 75, 89, 41, 73, 30, 34, 57, 63, 86, 48, 47, 60, 3, 62, 95, 92, 77, 46, 79, 74, 70, 10, 51, 23, 84, 66, 56, 32, 42, 43, 22, 83, 25, 24, 58, 61, 7, 33, 9, 54, 27, 65, 11, 36, 90, 6, 99, 69, 98, 4, 93, 59, 2, 5, 38, 87, 72, 45, 16, 96, 39, 37, 44, 53, 26, 14, 17, 67, 52, 50, 97, 29, 13, 19, 68, 49, 78, 55, 28, 18, 81, 80, 1, 88, 21, 31, 82, 20, 71, 76, 35, 0, 12, 91]
a=np.random.randint(100,size=20)
print(a)
[68 41 10 56 14 78 9 79 31 49 82 83 39 84 76 25 60 63 9 84]
第二部分 数据结构:
逻辑结构和物理结构
逻辑结构分为:线性结构,树结构,图结构
列表,栈,队列,链表,哈希表,树,二叉树,AVL树
# 32位机器上面,一个整数占了4个字节,一个整数占32位也就是四个字节,一个地址占四个字节。
# c++的数组:
# 元素是连续存储在内存中,取数直接从内存中取,
# 数组中的元素种类必须相同,长度必须固定,因为要在开数组的同时确定元素位置和大小
# python中的列表:
# 元素随机存储在内存中,列表中保存元素地址,再通过地址寻找元素,这也使得列表中元素种类可以不同,长度也可以变化。
1、栈性质是先进后出
# 编写栈:
#栈性质是先进后出
class Stack():
def __init__(self):
self.stack=[]
#入栈
def push(self,element):
self.stack.append(element)
#出栈
def pop(self):
return self.stack.pop()
#获取栈顶元素:
def get_pop(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)
elif match[ch]==stack.get_pop():
stack.pop()
else:
return False
if stack.is_empty():
return True
else:
return False
s='[](){({)}}'
brace_match(s)
False
2、队列:先进先出,
#队列:先进先出,为了保证空间可以重复利用,将队列的队首和队尾链接在一起成环,这样就可以节约空间
# 为了节约空间可以将队列设计成环形队列,为了区分队空和队满,front位置不放元素,当front和rear相等,则队空,rear=front-1时认为队满
class Queue():
def __init__(self,size):
self.queue=[0 for _ in range(size)]
self.size=size
self.front=0 #对首
self.rear=0 #队尾
def push(self,element):
if not self.is_fill():
self.rear=(self.rear+1)%self.size
self.queue[self.rear]=element
else: #队列满后不能在加
raise IndexError('Queue is filled')
def pop(self):
if not self.is_empty():
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_fill(self):
return (self.rear+1)%self.size==self.front
# 队列有包
from collections import deque
m=Queue(5)
m.push(1)
m.push(2)
m.push(3)
m.pop()
1
#(x1,y1)=(1,1),(x2,y2)=(15,15)
#栈走迷宫,深度优先,
def maze_path(maze,x1,y1,x2,y2):
#[x1,y1]表示迷宫入口,[x2,y2表示迷宫出口],迷宫中0表示路,1表示墙
#先将栈建好,将迷宫入口压栈,栈内的值表示走迷宫的路径
stack=[]
stack.append((x1,y1))
# 建立取根据当前节点取下一个节点的遍历列表
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)]
#当栈的长度大于0,可以循环
while len(stack)>0:
# 取得当前节点
curnode=stack[-1]
#拿到当前节点,首先判断节点是否为终点,若是,打印路径返回True
if curnode[0]==x2 and curnode[1]==y2:
for p in stack:
print(p)
return True
#根据当前节点取下一节点
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:
return False
# maze=[
# [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
# [1,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,1],
# [1,0,1,0,1,1,1,1,1,1,1,0,1,0,1,0,1],
# [1,0,0,0,1,0,1,0,0,0,1,0,1,0,1,0,1],
# [1,1,1,1,1,0,1,0,1,0,1,0,1,0,1,0,1],
# [1,0,0,0,0,0,1,0,1,0,0,0,1,0,1,0,1],
# [1,0,1,1,1,1,1,0,1,1,1,1,1,1,1,0,1],
# [1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1],
# [1,0,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1],
# [1,0,0,0,1,0,1,0,0,0,0,0,1,0,0,0,1],
# [1,1,1,0,1,0,1,0,1,1,1,0,1,1,1,1,1],
# [1,0,0,0,1,0,0,0,0,0,1,0,1,0,0,0,1],
# [1,0,1,1,1,1,1,1,1,1,1,0,1,0,1,0,1],
# [1,0,0,0,1,0,0,0,0,0,1,0,0,0,1,0,1],
# [1,1,1,0,1,0,1,1,1,0,1,1,1,1,1,0,1],
# [1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1],
# [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
# ]
maze=[
[1,1,1,1,1,1,1,1],
[1,0,0,0,0,0,1,1],
[1,0,1,1,0,0,0,1],
[1,0,1,1,0,1,0,1],
[1,0,1,1,1,1,1,1],
[1,0,0,0,0,1,1,1],
[1,1,1,1,0,0,0,1],
[1,1,1,1,1,1,1,1]
]
res=maze_path(maze,1,1,15,15)
# 队列走迷宫:广度优先
from collections import deque
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 print_r(path):
realpath=[]
curnd=path[-1]
while curnd!=path[0]:
realpath.append(curnd)
curnd=path[curnd[2]]
realpath.append(path[0])
realpath.reverse()
return realpath
def maze_path_que(maze,x1,y1,x2,y2):
# 先定义好队列,路径,将迷宫入口压栈
queue=deque()
path=[]
queue.append((x1,y1,-1))
maze[x1][y1]=2
#只要队列有数据就循环
while len(queue) > 0:
#定义当前节点,并记录路径
curnode=queue.popleft()
path.append(curnode)
#设置终止条件,也就是到了迷宫终点就停止循环
if curnode[0]==x2 and curnode[1]==y2:
print(print_r(path))
return True
#根据当前节点计算下一节点,如果下一节点为0,则将下一节点压入队列,
#标志迷宫表示走过该位置
for dir in dirs:
nextnode=dir(curnode[0],curnode[1])
if maze[nextnode[0]][nextnode[1]]==0:
queue.append((*nextnode,len(path)-1))
maze[nextnode[0]][nextnode[1]]=2
print('没有路')
return False
maze_path_que(maze,1,1,6,6)
没有路
False
# 关于while和for后面接else:
# 循环体被break或者return,则else不执行,其他情况继续执行
3、链表实现哈希表
# 链表实现哈希表
# 首先封装好链表
class LinkList():
# 先定义节点
class Node():
def __init__(self,item):
self.item = item
self.next = None
# 定义迭代器
class Lkiter():
def __init__(self,node):
self.node = node
def __next__(self):
if self.node:
curnode = self.node
self.node = curnode.next
return curnode.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,item):
# 头插法:
item_node = self.Node(item)
if not self.head:
self.head = item_node
else:
item_node.next = self.head
self.head = item_node
# # 尾插法
# item_node = self.Node(item)
# if not self.head:
# self.head = item_node
# self.tail = item_node
# else:
# self.tail.next = item_node
# self.tail = item_node
def extend(self,li):
for ele in li:
self.append(ele)
def find(self,obj):
for n in self:
if n == obj:
return True
else:
return False
def __iter__(self):
return self.Lkiter(self.head)
def __repr__(self):
return '<<'+','.join(map(str,self))+'>>'
lk = LinkList()
lk.extend([1,2,3])
lk.append(2)
print(lk)
<<2,3,2,1>>
#python 的集合和字典都是哈希表
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)
ht = HashTable()
ht.insert(1)
ht.insert(2)
ht.insert(3)
print(ht.T[1])
<<1>>
4、二叉树的遍历,二叉搜索树建立,主要功能:插入,查找,删除
# 二叉树的遍历
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)
print(root.data,end = ',')
def level_order(root): #层次遍历
queue = deque()
queue.append(root)
if len(queue)>0:
cur = queue.popleft()
if cur.lchild:
lchild = cur.lchild
if cur.rchild:
rchild = cur.rchild
queue.append(lchild)
queue.append(rchild)
print(cur.data,end = ',')
# 二叉搜索树建立,只要功能:插入,查找,删除
# 是二叉树的一种应用,特征是左子节点<节点<右子节点,通过这个特征可以进行数字的查找和排序
class BiTnode():
def __init__(self,data):
self.data = data
self.lchild = None
self.rchild = None
self.parent = None
class BST():
def __init__(self,li):
self.root = None
if li:
for var in li:
self.insert_no_rec(var)
# 插入功能,两种方法:递归和非递归
def insert(self,node,obj): #递归做法
if not node: #如果节点为空
node = BiTnode(obj)
elif obj < node.data:
node.lchild = self.insert(node.lchild,obj)
node.lchild.parent = node
elif obj > node.data:
node.rchild = self.insert(node.rchild,obj)
node.rchild.parent = node
return node
def insert_no_rec(self,obj): # 非递归做法
p = self.root
if not p: #如果根节点为空
self.root = BiTnode(obj)
return
while True:
if obj < p.data:
if p.lchild: #左子树为非空
p = p.lchild
else: #左子树为空
p.lchild = BiTnode(obj)
p.lchild.parent = p
elif obj > p.data:
if p.rchild:
p = p.rchild
else:
p.rchild = BiTnode(obj)
p.rchild.parent = p
else:
return
#定义遍历:前序遍历,中序遍历,后续遍历
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 = ',')
# 查找功能,两种方法:递归华和非递归
def query(self,node,obj): #递归做法
if not node:
return None
if obj == node.data:
return node
elif obj < node.data:
return self.query(node.lchild,obj)
else:
return self.query(node.rchild,obj)
def query_no_rec(self,obj): #非递归做法
p = self.root
while p:
if p.data < obj:
p = p.rchild
elif p.data > obj:
p = p.lchild
else:
return p
else:
return None
# 删除操作,先判断删除的是什么位置的节点,并先按照各自的情况编写内部函数,以便直接掉用
def __remove_node1__(self,node):
#情况1,node就是叶节点,
if not node.parent:
self.root = None
if node == node.parent.lchild:
node.parent.lchild = None
else:
node.parent.rchild = None
def __remove_node21__(self,node):
#情况21:删除的node只有一个左孩子
if not node.parent: #根节点
self.root = node.lchild
node.lchild.parent = None
if node == node.parent.lchild:
node.parent.lchild = node.lchild
node.lchild.parent = node.parent
else:
node.parent.rchild = node.lchild
node.lchild.parent = node.parent
def __remove_node22__(self,node):
# 情况22:node只有一个右孩子
if not node.parent:
self.root = node.rchild
node.rchild = None
if 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
#完整删除操作
#分为三种情况:1、删除的是叶节点,就直接删除
# 2、删除的节点只含有一个子节点,那么按照上面的函数将其子节点和其父节点进行链接
# 3、删除的节点包含两个子节点:用右子树的最左侧的节点N(也就是右子树的最小值)进行更换,并将N的子节点和N的父节点进行链接
def delete(self,obj):
p = self.query_no_rec(obj)
if p: #树里面存在节点
if not p.lchild and not p.rchild: #情况1,叶节点
self.__remove_node21__(p)
elif not p.lchild: #情况22:node只有右节点
self.__remove_node22__(p)
elif not p.rchild: #情况21:只有左节点
self.__remove_node21__(p)
else: #情况3:有两个节点
# 先找到右子树的最左侧节点node_l,将其值付给node
node_l = p.rchild
while node_l.lchild:
node_l = node_l.lchild
p.data = node_l.data
#如果node_l有右子节点,那么将其子节点和父节点链接,将node的子节点和P的子节点进行链接
if node_l.rchild:
self.__remove_node22__(node_l)
else:
self.__remove_node1__(node_l)
else:
return 'Error Delete'
tree = BST([1,4,3,2,5,6,7,9,8])
tree.in_order(tree.root)
print('')
tree.delete(4)
tree.in_order(tree.root)
1,2,3,4,5,6,7,8,9,
1,2,3,5,6,7,8,9,
tree.delete(10)
'Error Delete'
5、AVL树是一颗自平衡的二叉树,
为了解决二叉树最坏情况下非常倾斜的情况,解决方法就是随机化插入
# AVL树是一颗自平衡的二叉树,为了解决二叉树最坏情况下非常倾斜的情况,解决方法就是随机化插入
# 性质包括:
# 根的左右子树的高度差的绝对值不超过1
# 根的左右子树都是平衡二叉树
# 做法就是定义一个balace_factor用来记录每个节点的左右子树高度差
# 插入一个节点后只有从插入节点到根节点的路径上的节点的平衡被破坏,我们需要找到第一个被破坏平衡的节点,称为K,K的左右子子树高度差为2
# 维持树的平衡就是通过旋转,不平衡的出现右四种情况:
# 1、左旋:由于对右孩子的右子树插入导致
# 2、右旋:对左孩子的左子树插入导致
# 3、先左旋后右旋:左孩子的右子树
# 4、先右旋后左旋:右孩子的左子树
class Avlnode(BiTnode):
def __init__(self,val):
BiTnode.__init__(self,val)
self.bf = 0
class AVLTree():
def __init__(self,li):
self.root = None
for val in li:
self.insert_no_rec(val)
def insert_no_rec(self,val):
#1、插入
p = self.root
if not p:
self.root = Avlnode(val)
return
else:
while True:
if val < p.data:
if p.lchild:
p = p.lchild
else:
p.lchild = Avlnode(val)
p.lchild.parent = p
node = p.lchild
break
elif val > p.data:
if p.rchild:
p = p.rchild
else:
p.rchild = Avlnode(val)
p.rchild.parent = p
node = p.rchild
break
else:
break
# 2、更新bf
while node.parent:
#循环的终止条件是父节点不存在
if node.parent.lchild == node:
# 如果node节点是父节点的左孩子
if node.parent.bf > 0:
# 父节点原来是1,插入后是0
node.parent.bf = 0
break
elif node.parent.bf == 0:
# 父节点原来是0,插入后是-1
node.parent.bf = -1
node = node.parent
continue
else:
# 父节点原来是-1,插入后是-2,需要旋转使其平衡
# g = node.parent.parent
# x = node.parent
if node.bf < 0:
#左孩子的左子树导致不平衡
n = self.rotate_right(node.parent,node)
elif node.bf > 0:
# node.bf>0,左孩子的右子树导致不平衡
n = self.rotate_left_right(node.parent,node)
break
else:
# node节点是父节点的右孩子
if node.parent.bf > 0:
#父节点原来是1,插入后是2,需要旋转
# g = node.parent.parent
# x = node.parent
if node.bf > 0:
#右孩子的右子树导致不平衡
n = self.rotate_right(node.parent,node)
elif node.bf < 0:
# 右孩子的左子树导致不平衡
n = self.rotate_right_left(node.parent,node)
break
elif node.parent.bf == 0:
# 夫节点原来时0,插入后是1,
node.parent.bf = 1
node = node.parent
continue
else:
# 父节点原来是-1,插入后是0
node.parent.bf = 0
break
# n.parent = g
# if g:
# if x == g.lchild:
# g.lchild = n
# else:
# g.rchild = n
# break
# else:
# self.root = n
# break
def rotate_left(self,p,c):
#左旋:右孩子的右子树进行插入导致不平衡
s2 = c.lchild
p.rchild = s2
if s2:
s2.parent = p
c.lchild = p
c.parent = p.parent
# 注意这个不能放在if里面,因为不管p.parent是否存在,c.parent = p.parent
if p.parent:
if p.parent.lchild == p:
p.parent.lchild = c
else:
p.parent.rchild = c
else:
self.root = c
p.parent = c
# 更新bf
#只有插入导致不平衡进行旋转时,bf更新是这个结果,删除导致的不平衡还有其他情况
c.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
c.parent = p.parent
if p.parent:
if p == p.parent.lchild:
p.parent.lchild = c
else:
p.parent.rchild = c
else:
self.root = c
p.parent = c
#更新bf
c.bf = 0
p.bf = 0
return c
def rotate_left_right(self,p,c):
# 左旋后右旋:左孩子的右子树插入导致不平衡
g = c.rchild
s2 = g.lchild
c.rchild = s2
if s2:
s2.parent = c
c.parent = g
g.lchild = c
s3 = g.rchild
p.lchild = s3
if s3:
s3.parent = p
g.rchild = p
g.parent = p.parent
if p.parent:
if p == p.parent.lchild:
p.parent.lchild = g
else:
p.parent.rchild = g
else:
self.root = g
p.parent = g
#更新bf
if g.bf > 0:
c.bf = -1
p.bf = 0
elif g.bf < 0:
c.bf = 0
p.bf = 1
else:
c.bf = 0
p.bf = 0
g.bf = 0
return g
def rotate_right_left(self,p,c):
# 右旋后左旋:右孩子的左子树插入导致不平衡
g = c.lchild
s2 = g.lchild
p.rchild = s2
if s2:
s2.parent = p
g.lchild = p
g.parent = p.parent
if p.parent:
if p == p.parent.lchild:
p.parent.lchild = g
else:
p.parent.rchild = g
else:
self.root = g
p.parent = g
s3 = g.rchild
c.lchild = s3
if s3:
s3.parent = c
c.parent = g
g.rchild = c
#更新bf
if g.bf > 0:
c.bf = 0
p.bf = -1
elif g.bf < 0:
p.bf = 0
c.bf = 1
else:
# 插入的是g
c.bf = 0
p.bf = 0
g.bf = 0
return g
avl = AVLTree([9,8,7,6,5,4,3,2,1])
in_order(avl.root)
1,2,3,4,5,6,7,8,9,
pre_order(avl.root)
6,4,2,1,3,5,8,7,9,
第三部分 算法进阶
贪心算法
动态规划
欧几里得算法和RSA加密算法
贪心算法
都是一个最优化问题
1、找零问题:找N元,有100,50,20,5,1四种面值,怎么使得所需钱币数量最少
# 1、找零问题:找N元,有100,50,20,5,1四种面值,怎么使得所需钱币数量最少
t = [100,50,20,5,1]
def change(t,n):
m = [0 for i in range(len(t))]
for i,money in enumerate(t):
m[i] = n//money # 整除
n = n% money # 取余
return m,n
print(change(t,376))
([3, 1, 1, 1, 1], 0)
2、背包问题
n个商品,第i个商品价值Vi,重Wi,希望尽可能拿走价值多的商品,背包最多拿W千克的东西
# 2、背包问题
# n个商品,第i个商品价值Vi,重Wi,希望尽可能拿走价值多的商品,背包最多拿W千克的东西
#0-1背包:不能拿一部分,商品是完整的,比如金条
#分数背包:对于一个商品,小偷可以拿走一部分,比如金砂
# 分数背包
goods = [(60,10),(100,20),(120,30)] # (价值,重量)
def fraction_backpack(goods,w):
goods.sort(key = lambda x:x[0]/x[1],reverse= True )
m = [0 for _ in range(len(goods))]
total_v = 0
for i,(price,weight) in enumerate(goods):
if w >= weight:
m[i] = 1
total_v += price
w -= weight
else:
m[i] = w / weight
total_v += m[i]*price
w = 0
break
return total_v,m
print(fraction_backpack(goods,50))
(240.0, [1, 1, 0.6666666666666666])
3、拼接最大数字问题:有n个非负整数,将其按照字符串拼接方式进行拼接得到最大整数
思路:第一位最大的排最前,第一位相同的看第二位,依次进行
# 3、拼接最大数字问题:有n个非负整数,将其按照字符串拼接方式进行拼接得到最大整数
# 思路:第一位最大的排最前,第一位相同的看第二位,依次进行
li = [32,94,128,1286,71]
# 方法1:
def number_join(li):
li_len = max([len(str(var)) for var in li])
li_str = [str(var) for var in li]
for i in range(li_len,-1,-1):
li_str.sort(key = lambda x:x[i%(len(x))],reverse = True)
num = ''
for i in li_str:
num += i
return num
# 方法2
from functools import cmp_to_key
def xy_cmp(x,y):
if x+y > y+x:
return -1
elif x+y < y+x:
return 1
else:
return 0
def number_join2(li):
li = list(map(str,li))
li.sort(key = cmp_to_key(xy_cmp))
return ''.join(li)
# 方法2等价于方法3:
class Solution:
def largestNumber(self, nums):
"""
:type nums: List[int]
:rtype: str
"""
from functools import cmp_to_key
temp = list(map(str,nums))
temp.sort(key = cmp_to_key(lambda x,y:int(x+y)-int(y+x)),reverse = True )
return ''.join(temp if temp[0]!='0' else '0')
# cmp_to_key()函数:它有两个传入参数x,y 当x>y时返回1 等于时返回0,否则返回-1,只能在类似于min,max,sort这种函数里面使用
print(number_join2(li))
9471321286128
4、活动选择问题:
n个活动用一个场地,每个活动都有一个开始时间si和结束时间Fi,也就是活动在[si,fi)区间占用场地,怎么安排能使得场地举办活动最多:
# 4、活动选择问题:n个活动用一个场地,每个活动都有一个开始时间si和结束时间Fi,也就是活动在[si,fi)区间占用场地,怎么安排能使得场地举办活动最多:
# 贪心结论:最先结束的活动一定是最优解的一部分
# 证明:
# 假设A是所有活动中最先结束的活动,B是最优解中最先结束的活动
# if A=B,结论成立
# if A!=B,则B的结束时间一定晚于A,则用A换掉B后也是最优解
def chose_act(li):
li.sort(key = lambda x:x[1])
chose = []
chose.append(li[0])
for i in range(1,len(li)):
if chose[-1][1] < li[i][0]:
chose.append(li[i])
return chose
li = [(1,4),(3,5),(0,6),(5,7),(3,9),(5,9),(6,10),(8,11),(8,12),(2,14),(12,16)]
chose_act(li)
[(1, 4), (5, 7), (8, 11), (12, 16)]
a = '123'
b = '456'
动态规划:算法思想,速度比较快
用于:基因测序,基因比对
1、斐波那契数列Fn = Fn-1 + Fn-2,
#1、斐波那契数列Fn = Fn-1 + Fn-2,用递归和非递归的方式求解第n项
#非递归:
def fibnacci_no_rec(n):
if n == 0:
return 0
elif n == 1 or n==2:
return 1
else:
fn_2 = 0
fn_1 = 1
fn = 1
for i in range(2,n+1):
fn = fn_1 + fn_2
fn_2 = fn_1
fn_1 = fn
return fn
def fibnacci_no_rec2(n):
f = [0,1,1]
if n>2:
for i in range(n-2):
num = f[-1] + f[-2]
f.append(num)
return f[n]
# 递归:慢,由于动态规划子问题的重复计算是随着n增大指数级增大
def fibnacci(n):
if n==0:
return 0
if n==1:
return 1
else:
return fibnq(n-1)+fibnq(n-2)
fibnq(10)
55
2、钢条切割问题:给定长度和价格关系表,问:怎么切割使得总收益最大
# 2、钢条切割问题:给定长度和价格关系表,问:怎么切割使得总收益最大
# 设最优收益值为Rn,可以得出递归式:
# Rn = max(Pn,R1+Rn-1,R2+Rn-2,...,Rn-1+R1) = max(Pi+Rn-i)
# Pn表示长度为n不切割情况
# 该问题满足最优子结构:问题最优解由相关子问题的最优解组合而成,子问题可以独立求解
#p = [0,1,5,8,9,10,17,17,20,21,23,24,26,27,27,28,30,33,36,39,40]
p = [0,1,5,8,9,10,17,17,20,24,30]
#递归做法:
def cut_rod_rec1(p,n):
if n == 0:
return 0
else:
res = p[n]
for i in range(1,n):
res = max(res,cut_rod_rec(p,i) + cut_rod_rec(p,n-i))
return res
def cut_rod_rec2(p,n):
if n == 0:
return 0
else:
res = 0
for i in range(1,n+1):
res = max(res,p[i] + cut_rod_rec2(p,n-i))
return res
print(cut_rod_rec(p,10))
# 由于递归的指数级爆炸时间复杂度O(2^n),随着n增大,第一种递归比第二种递归会越来越慢很多
# 解决这个问题的方法就是:每个子问题求解一次后保存结果,之后直接查找保存的结果即可,而不用继续递归
# 用循环或者动态规划的思想解决这个问题,时间复杂度为O(n^2)
def cut_roa_dp(p,n):
if n == 0:
return 0
else:
res = [0]
for i in range(1, n + 1):
for j in range(1, i + 1): #计算i时的最优解
num = max(num.,res[i-j]+p[j])
res.append(num)
return res[n]
cut_roa_dp(p,10)
30
30
# 上面的方法只解决了找到最优解的问题,但是没有输出怎么切割
# 怎么切割:只需要记录最优解第一步切的下标即可,递归就能找出完整的切割方案
# 在上面的函数中加入一个新的变量记录下标
def cut_roa_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): #计算i时的最优解
if p[j] + r[i-j] > res_r:
res_r = p[j] + r[i-j]
res_s = j
r.append(res_r) #r[i]表示n=i时的最优价格
s.append(res_s) #s[i]表示n=i时的最优方案第一步切割的下标
#要想返回切割方案,需要依次取s中的值
per = [] #n时的最优切割方案
i = n
while i > 0:
per.append(s[i])
i -= s[i]
return r[n],s,per
print(cut_roa_extend(p,9))
(25, [0, 1, 2, 3, 2, 2, 6, 1, 2, 3], [3, 6])
3、最长公共子序列(lcs):字符串相似度对比,模糊查找
#3、最长公共子序列(lcs):字符串相似度对比,模糊查找
# 暴力枚举:长度为n的序列的子序列个数为2^n,所以一个M和一个N长度的LCS情况由2^(m+n)种
# 最优子结构定理:如果X[-1]和Y[-1]相同,那么Z,其LCS的最后一位一定是X[-1]即Y[-1]的最后一位,
# 如果不同:如果Z[-1]!=X[-1],那么Z是Xm-1和Y的LCS;如果Z[-1]!=Y[-1],那么Z一定是X和Yn-1的LCS
#由上定理可得最优解的递推式:
# c[i,j] = 0 if i = 0或者j = 0
# = c[i-1,j-1]+1 if i,j>0且xi = yj
# = max(c[i,j-1],c[i-1,j]) if i,j>0 且xi!=yj
# c[i,j]表示xi和yj的LCS的长度
def lcs_length(x,y):
# 计算最长公共子序列的长度
m = len(x)
n = len(y)
c = [[0 for _ in range(n+1)] for _ in range(m+1)] # m+1行n+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
#找出最长公共子序列
# 先定义出箭头矩阵
def lcs(x,y):
m = len(x)
n = len(y)
c = [[0 for _ in range(n+1)] for _ in range(m+1)] # m+1行n+1列
b = [[0 for _ in range(n+1)] for _ in range(m+1)] # m+1行n+1列
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
# 回溯找出LCS
def lcs_traceback(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:
j -= 1
return ''.join(reversed(res))
x = 'abcbdab'
y = 'bdcaba'
lcs_traceback(x,y)
'bdab'
欧几里得算法计算最大公约数
# 欧几里得算法计算最大公约数,实现
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 = gcd(self.a,self.b)
self.a /= x
self.b /= x
def gcd(self,a,b):
# 最大公约数
if b == 0:
return a
else:
return self.gcd(b,a%b)
def zgs(self,a,b):
# 计算最小公倍数
x =self.gcd(a,b)
return a*b/x
def __add__(self,other):
# 1/12 + 1/20
a = self.a
b = self.b
c = other.a
d = other.b
x = self.gcd(b,d)
fenmu = self.zgs(b,d)
fenzi = a*(d/x) + c*(b/x)
return Fraction(fenzi,fenmu)
def __str__(self):
return '%d/%d' %(self.a,self.b)
print(gcd2(2,6))
2
a = Fraction(1,3)
b = Fraction(1,2)
print(a+b)
5/6
RSA加密算法
# 传统加密和现代加密算法区别:
# 传统加密算法是私密的,可以用暴力枚举进行解密
# 现代加密算法是公开的,密钥是私密的,也分为对称加密和非对称加密,非对称加密公钥用来加密,是公开的,私钥用来解密,是私有的
# RSA是一种非对称的加密系统
# 过程:
# 1、随机选取两个质数p和q
# 2、计算n=p*q
# 3、选取一个和fai(n)互质的小奇数e,fai(n) = (p-1)*(q-1)
# 4、对模fai(n),计算e的乘法逆元d,也就是满足(e*d)%fai(n) = 1
# 5、公钥(e,n),私钥(d,n) ,
# 注意:数学可以证明一对公钥和私钥是唯一匹配的,
# 由于当n很大时,想要把n拆成两个质数很难,所以想要通过公钥破解私钥很难,所以可以用来加密
# 加密过程:c = (m^e)%n #m是原文件,c是加密文件
# 解密过程:m = (c^d)%n
p = 53
q=59
n=p*q
fai=(p-1)*(q-1)
e = 3
fai/3
d = 2011 #这个是经过计算或者说解密的
(e*d)%fai
m=87
c=(m**e)%n
m_j = (c**d)%n # 解出来m_j=m
print(fai)
3016
print(m_j)
87