补学图论算法:算法竞赛入门经典(第二版)第十一章:
倒排索引还没有实现!
下面是左神的图论算法,并查集笔记.和一个美团题目.
'''
https://www.nowcoder.com/live/11?page=1
还是继续刷左程云的算法题.
2.给定一个非负数的数组, 代表一个容器。 例如数组[0,1,0,2,1,0,1,3,2,1,2,1], 就是
以下图形中黑色的部分。如果用这个容器接水的话, 请问可以接多少水? 还以这个数组为例,
可以接 6 格水, 就是以下图形中蓝色的部分。
3.给定一个非负数的数组, 数组中的每个值代表一个柱子的高度, 柱子的宽度是 1。 两个柱
子之间可以围成一个面积, 规定: 面积=两根柱子的最小值* 两根柱子之间的距离。 比如数
组[3,4,2,5]。 3 和 4 之间围成的面积为 0, 因为两个柱子是相邻的, 中间没有距离。 3 和
2 之间围成的面积为 2, 因为两个柱子的距离为 1, 且 2 是最短的柱子, 所以面积=1*2。3 和 5 之间围成的面积为 6, 因为两个柱子的距离为 2, 且 3 是最短的柱子, 所以面积=
3*2。 求在一个数组中, 哪两个柱子围成的面积最大, 并返回值。
#第三个题目显然就是单调栈的使用.
第二个题目一直没看懂.看懂了,就是第一节课课件里面的第三页的图片.
本质就是求数组的波谷即可.
第二个题目就是本质是求滑动窗口的最大值问题.比如位置i
那么就求比i小的位置的最大值,和比i大的位置的最大值.两个最大值最小的那个如果比i位置的数
大,那么我就返回这个差值就是i位置能存水的数量.
我去:滑动窗口的最大值问题.本质双端队列.
'''
'''
1.求两个子数组最大的累加和
【题目】
给定一个数组, 其中当然有很多的子数组, 在所有两个子数组的组合中, 找到相
加和最大的一组, 要求两个子数组无重合的部分。 最后返回累加和。
【要求】
时间复杂度达到 O(N)
想到按每一个分店来分,然后分别算2个.那么算法是N^2的.
但是要N怎么算?
辅助数组么:
怎么个辅助数组法.
我开一个left数组:left[i]表示原来list[:i]上面的子数组最大累加和.
right[i]............................list[i:]...................
然后我i位置2个部分的最大累加和就是left[i]+right[i].
这样就O(N)了.
'''
def main(list1):
def qiu_left(list1):
out=-float('inf')
sum=0
p=[]
for i in range(len(list1)):
sum+=list1[i]
if sum>out:
out=sum
p.append(out)
if sum<0:
sum=0
return p
def qiu_right(list1):
return qiu_left(list1[::-1])
a=qiu_left(list1)
b=qiu_right(list1)
out=-float('inf')
for i in range(len(list1)-1):#注意这里的范围.
tmp=a[i]+b[len(list1)-i-2]
if tmp>out:
out=tmp
return out
print(main([32,432,65,675,-999,-65756]))
'''
2.未排序正数数组中累加和为给定值的最长子数组长度
【题目】
给定一个数组 arr, 该数组无序, 但每个值均为正数, 再给定一个正数 k。 求 arr
的所有子数组中所有元素相加和为 k 的最长子数组长度。
例如, arr=[1,2,1,1,1], k=3。
累加和为 3 的最长子数组为[1,1,1], 所以结果返回 3。
【要求】
时间复杂度 O(N), 额外空间复杂度 O(1)
●类似背包问题,求出所有能拼成k的子数组的长度然后取max,但是复杂度很高.
貌似也不太高,用一个指针来表示读到那个位置即可.但是达不到O(N).
●感觉还是辅助数组.首先的到数组sum:sum[i]表示原来数组从0到i的求和.
把sum里面元素放哈希表里面key是sum[i] val 是i
然后给sum[i]找是否存在k+sum[i].整体效率N
●左神破题点:子数组问题:必须以那个位置作为结尾的情况下怎么怎么样...这是面试里面的套路.
'''
#经过左神的讲解.原来是双指针都在左面,然后开始移动,然后维护指针括住区域的sum即可.
def main(list1,k):
left=0
right=0
sum=list1[0]
p=-1
while right<=len(list1)-1:
if sum==k:
tmp=right-left+1
if tmp>p:
p=tmp
if sum>k:
sum-=list1[left]
left+=1
if sum<=k and right<len(list1)-1: #便捷条件要扣
sum+=list1[right+1]
right+=1
if right==len(list1)-1 and sum<=k: #便捷条件要扣
break
return p
print(main([3,5,6,8,1,1,1,1,1,0.5,0.5,0.5,0.3],4))
'''
https://www.nowcoder.com/live/2/17/1
'''
'''
网盘内容:https://www.52pojie.cn/forum.php?mod=viewthread&tid=726182
'''
'''
学习第六课.图论算法.我学的最差的东西,也是结构性强,套路性强的题目,比赛很喜欢靠图论题目.
写过算法导论里面的,但是理解很差.也记不住.
'''
'''
美团2016笔试题目:
一个数组.求array[b]-array[a]最大值,并且b比a大.
N^2随便写,但是要N的效率呢?题目显然要一个切分点,那么每个切分之后效率是O(1).
这种效率一般都是预处理数组才能做到.所以需要做一个数组1[i]表示从0到i最小值.
一个数组2[i]表示从i到最后的最大值.然后两个一剪即可.
'''
def main(list1):
mini=[]
zuixiao=float('inf')
for i in range(len(list1)):
if list1[i]<zuixiao:
zuixiao=list1[i]
mini.append(zuixiao)
list1=list1[::-1]
maxi=[]
zuida=-float('inf')
for i in range(len(list1)):
if list1[i]>zuixiao:
zuida=list1[i]
maxi.append(zuida)
out=-float('inf')
for i in range(len(list1)):
tmp=maxi[i]-mini[len(list1)-2-i]
if tmp>out:
out=tmp
return out
print(main([2,4,6,8,9,5,99999,-265]))#测试一下基本对了.强大的预处理数组技巧.美团的题目还是水平可以的.
'''
并查集:
1.用于查如何A,B是否在一个集合中.
2.每一个集合设立一个头结点.其他都连向他
3.集合合并就是把小的集合挂到大的集合下面即可
4.优化.查询到一个a在b这个头结点下面,那么直接把a.next=b
'''
class bingcha():
def __init__(self):
self.fathermap={}
self.sizemap={}
def make_sets(self,list1):#把数据集list赋值到并查集里面做初始化
for i in range(len(list1)):
self.fathermap[list1[i]]=list1[i]
self.sizemap[list1[i]]=1
def find_father(self,node):#返回node的父节点是谁,然后把node挂到父节点上.
father=node
if self.fathermap[node]!=node:
father=self.find_father(self.fathermap[node])
self.fathermap[node]=father
return father
def union(self,node1,node2):
father1=self.find_father(node1)
father2=self.find_father(node2)
if father1!=father2:
size1=self.sizemap[father1]
size2=self.sizemap[father2]
if size1>=size2:
self.fathermap[node2]=father1
self.sizemap[father1]+=size2
else:
self.fathermap[node1]=father2
self.sizemap[father2]+=size1
def in_same_set(self,node1,node2):
return self.find_father(node1)==self.find_father(node2)
a=bingcha()
a.make_sets([1,2,3,4,5])
a.union(1,2)
a.union(1,3)
a.union(1,4)
print(a.in_same_set(2,4))
print(a.find_father(4)) #解决了并查集的代码实现.从直观上也能看出来,当已经查询或者插入了N次
#再进行查询操作的画效率O(1).因为都已经连到根了.搜索1次即可.
#继续理解并查集:他用树的加速来实现了并和查的操作,虽然他效率非常快,
#但是不能进行交的操作.这就是他跟set的区别.set复杂度O(N).
#并查集在图里面很实用.虽然面试对图考的不多,但是应该掌握.
#下面就是左神给的图论问题模板,非常强大.能实现所有图论问题
#使用方法:对有向图就用graphgenerate,插入所有边即可.顺道就所有点都有了
# 对无向图,就插入2次,一次是from to 一次是to from即可.
'''
开始搞图:设计好几个类,然后存图,左神给的是邻接数组.
'''
class node():
def __init__(self,val):
self.val=val
self.in1=0
self.out=0
self.nexts=[]
self.edges=[]
class edge():
def __init__(self,weight,from1,to):
self.weight=weight
self.from1=from1
self.to=to
#需要手动写上这几个比较函数.
def __cmp__(self,other):
return cmp(self.weight, other.weight)
def __lt__(self,other):#operator <
return self.weight < other.weight
def __ge__(self,other):#oprator >=
return self.weight >= other.weight
def __gt__(self,other):#oprator >=
return self.weight > other.weight
def __le__(self,other):#oprator <=
return self.weight <= other.weight
class Graph():
def __init__(self):
self.nodes={} #结构是key是题目给的编号,value是自己构造的node节点对象.
#node.value也是编号.
#因为要做复杂处理,所以第一步都是把对应编号转化成为node对象来进行处理.
self.edges=set()
def GraphGenerator(matrix):#给矩阵,每一行都是 from,end,边长, 3个元素组成.
graph=Graph()
for i in range(len(matrix)):
from1=matrix[i][0]
to=matrix[i][1]
weight=matrix[i][2]
graph.nodes.setdefault(from1,node(from1))
graph.nodes.setdefault(to,node(to))
fromNode=graph.nodes[from1]
toNode=graph.nodes[to]
newEdge=edge(weight,fromNode,toNode)#这里面用node来做edge参数好么?
fromNode.nexts.append(toNode)
fromNode.out+=1
toNode.in1+=1
fromNode.edges.append(newEdge)
graph.edges.add(newEdge)
return graph
'''
宽度优先遍历也叫广度优先遍历.
'''
'''
先写宽度便利:#利用一个队列和一个set
'''
import queue
def bfs(node):
q=queue.Queue()
q.put(node)
visited=set([node])
while q.empty()==False:
tmp=q.get()
print(tmp.val)#遍历的操作
for i in tmp.nexts:
if i not in visited:
visited.add(i)
q.put(i)
graph=GraphGenerator([[1,2,3],[2,4,5],[2,6,7],[4,6,5],[1,6,99],[99,98,999]])
print('ceshi')
(bfs(graph.nodes[1])) #graph.nodes[1]表示1号节点对应的node
'''
深度优先:只用一个set就行
'''
##我自己写的菜鸟版本.函数每次都带visited.太慢了.左神用的是栈,来直接模拟递归过程.
#def dfs(node): #为了设立局部所以用函数嵌套来写,因为第一次运行时候,跟后面的代码要不同,
# #多一行初始化visited,然后后面为了保持每个visited在每一次函数时候都一样,就传进去.
# visited=set([node])
# def mini_dfs(node,visited):
# print(node.val)#遍历的操作
# for i in node.nexts:
# if i not in visited:
# mini_dfs(i,visited)
# visited.add(i)
# mini_dfs(node,visited)
# return
#dfs(graph.nodes[1])
def dfs(node):#大神的版本
visited=set()
fuzhu=[node]
while fuzhu!=[]:
node=fuzhu.pop()
if node not in visited:
print(node.val)
visited.add(node) #node打印过了,就赶紧把他放visited里面.避免重复访问.
for i in node.nexts:
if i not in visited:#如果还有node的儿子i是没有访问过的,那么需要把node,i压回去.
#就是用这个栈来替代递归过程.也就是递归改递推.
fuzhu.append(node)
fuzhu.append(i)
break
print('ceshi2')
dfs(graph.nodes[1])
'''
拓扑排序:
找到全部入度为0的,全做完,然后删除这些节点相关的点和边,继续循环即可.
'''
def tuopu(graph):#任何一个有向无环图才可以拓扑排序
a=queue.Queue()
for i in graph.nodes.values():
if i.in1==0:#入度是0.in是关键字没发使用,所以改成in1
a.put(i)
result=[]
while a.empty()==False:
tmp=a.get()
result.append(tmp)
for i in tmp.nexts:
i.in1-=1
if i.in1==0:
a.put(i)
return result
print('测试3')
for i in tuopu(graph):
print(i.val)
'''
最小生成树p算法.
'''
'''
最小生成树k算法. 这尼玛:直接贪心,每一次都选权重最小的边.不产生回路就加进来.
最后所有点都有了,就结束.你妈这么简单??????居然不会产生bug.顿时感觉最小生成树很low
左神的最牛逼代码,用并查集来判断回路.有公共祖先就是有回路.
'''
from queue import PriorityQueue as PQueue
import heapq
def krustalMST(graph):#k算法实在是太短了.同样下面K算法也可以处理不连通的情况
output=set()
a=bingcha()
a1=list(graph.nodes.values())
a.make_sets(a1)#把所有nodes放并查集里面
pq = PQueue()#给edge类加一个cmp方法即可.
for i in graph.edges:
pq.put(i)
while pq.empty()!=True:
tmp=pq.get()
#如果tmp的from 和to 是在并查集里面有公共祖先的就不要了
if a.in_same_set(tmp.from1,tmp.to)!=True:#表示不是环路
#那么就加入这个变
output.add(tmp)
a.union(tmp.from1,tmp.to)
return output#返回的是边的set
print('ceshi4')
for i in krustalMST(graph):
print(i.weight,i.from1.val,i.to.val)#效果可以.虽然是针对无向图,但是没有插入反向from1,to也效果
#一样,因为我们考察的是边.
'''
最小生成树:P算法.随便加进去一个点,然后找这个点的edge,加到pq里面,pq弹出一个最小的,加入tonode.循环
即可.
'''
def PrimMST(graph):
output=set()
result=set()
pq=PQueue()
for i in graph.nodes.values():#这个循环来处理整个图不联通的情况.
if i not in output:
output.add(i)
for edge in i.edges:
pq.put(edge)
while pq.empty()!=True:
tmp=pq.get()#道理都是每一次尽量走最短的边,
if tmp.to not in output:
result.add(tmp)#当弹出的边正好to节点没有访问,就是我们要的,放入result中!
output.add(tmp.to)
for pp in tmp.to.edges:
pq.put(pp) #当新插入节点后,边的队列pq也更新一下即可.
return result
print('ceshi5')
for i in PrimMST(graph):
print(i.weight,i.from1.val,i.to.val)