补学图论算法:算法竞赛入门经典(第二版)第十一章:
补学图论算法:算法竞赛入门经典(第二版)第十一章:
倒排索引还没有实现!
下面是左神的图论算法,并查集笔记.和一个美团题目.
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
''' 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)