''' 并查集: 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)
完美的加入了低价斯特拉算法. 单元最短路径算法.用辅助set工具避免了重新写堆算法.
代码量很少.效率应该是VlogE
''' 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 = [] self.distance=float('inf') def __cmp__(self,other): return cmp(self.distance, other.distance) def __lt__(self,other):#operator < return self.distance < other.distance def __ge__(self,other):#oprator >= return self.distance >= other.distance def __gt__(self,other):#oprator >= return self.distance > other.distance def __le__(self,other):#oprator <= return self.distance <= other.distance 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就行 ''' #左神用的是栈,来直接模拟递归过程.好像跟递归写法差不多 def dfs_me(node): visited = set([node])#set或者list自动是全局变量.在多少重子函数里面他都是能调用外面的list. def mini_dfs(node): print(node.val)#遍历的操作 for i in node.nexts: if i not in visited: mini_dfs(i) visited.add(i) mini_dfs(node) return print('ceshi0') dfs_me(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 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) ''' 慕课网课程: 自环边,平行边.有这2种边的叫复杂图. 我们上面左神的表达方法:就是邻接表. ''' ''' 单源最短路径:直接动态规划即可.很容易 显然满足最优子结构.a到b再到c的最短路径.一定是a到b的最短路径加上b到c的最短路径. ''' ''' dijkstra 地理课死啦算法.前提:图中不能有负权边. ''' #格式刷快捷键ctrl+k 再 ctrl+f def dijkstra(graph,start):#为了记录路径直接在node里面加一个last_node属性即可.他记录了这个节点在最短路径 #这个过程里面他的上一步是哪个节点. #然后继续加一个distance属性,来记录这个节点他距离最开始节点的距离. #start是一个node对象. visited = set() pq = PQueue() pq_assist=set() #用来判断元素是否在pq中.pq加入元素他就加入元素,pq删除他就删除! #总之我这么构造的原因就是为了避免重新修改heapq代码.和node类 #初始化start点 start.distance=0 start.last_node=start visited.add(start) #直接在优先队列里面存入node.只存入node即可.排序函数里面写distance即可. #修改时候直接修改node即可,对队列里面数值进行修改. pq.put(start) pq_assist.add(start) while pq.empty()!=True: tmp=pq.get() #所以一开始这个tmp就是start pq_assist.remove(tmp) visited.add(tmp) #找tmp相邻的边. for i in tmp.edges: obj=i.to#obj是新节点v相连的to节点. if obj not in visited: if tmp.distance+i.weight<obj.distance: obj.distance=tmp.distance+i.weight#说是松弛操作,但是看起来非常像拉紧操作!他起名时脑袋进水了 obj.last_node=tmp if obj not in pq_assist: pq.put(obj) pq_assist.add(obj) a=PQueue() a.put(3) a.put(3) a.put(3) a.put(3) #graph = GraphGenerator([[1,2,3],[2,4,5],[2,6,7],[4,6,5],[1,6,99],[99,98,999]])这个是测试用的图 dijkstra(graph,graph.nodes[1]) print(graph.nodes[98].distance)
继续修改
''' 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 = [] self.distance=float('inf') def __cmp__(self,other): return cmp(self.distance, other.distance) def __lt__(self,other):#operator < return self.distance < other.distance def __ge__(self,other):#oprator >= return self.distance >= other.distance def __gt__(self,other):#oprator >= return self.distance > other.distance def __le__(self,other):#oprator <= return self.distance <= other.distance 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就行 ''' #左神用的是栈,来直接模拟递归过程.好像跟递归写法差不多 def dfs_me(node): visited = set([node])#set或者list自动是全局变量.在多少重子函数里面他都是能调用外面的list. def mini_dfs(node): print(node.val)#遍历的操作 for i in node.nexts: if i not in visited: mini_dfs(i) visited.add(i) mini_dfs(node) return print('ceshi0') dfs_me(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 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) ''' 慕课网课程: 自环边,平行边.有这2种边的叫复杂图. 我们上面左神的表达方法:就是邻接表. ''' ''' 单源最短路径:直接动态规划即可.很容易 显然满足最优子结构.a到b再到c的最短路径.一定是a到b的最短路径加上b到c的最短路径. ''' ''' dijkstra 地理课死啦算法.前提:图中不能有负权边. ''' #格式刷快捷键ctrl+k 再 ctrl+f def dijkstra(graph,start):#为了记录路径直接在node里面加一个last_node属性即可.他记录了这个节点在最短路径 #这个过程里面他的上一步是哪个节点. #然后继续加一个distance属性,来记录这个节点他距离最开始节点的距离. #start是一个node对象. visited = set() pq = PQueue() pq_assist=set() #用来判断元素是否在pq中.pq加入元素他就加入元素,pq删除他就删除! #总之我这么构造的原因就是为了避免重新修改heapq代码.和node类 #初始化start点 start.distance=0 start.last_node=start visited.add(start) #直接在优先队列里面存入node.只存入node即可.排序函数里面写distance即可. #修改时候直接修改node即可,对队列里面数值进行修改. pq.put(start) pq_assist.add(start) while pq.empty()!=True: tmp=pq.get() #所以一开始这个tmp就是start pq_assist.remove(tmp) visited.add(tmp) #找tmp相邻的边. for i in tmp.edges: obj=i.to#obj是新节点v相连的to节点. if obj not in visited: if tmp.distance+i.weight<obj.distance: obj.distance=tmp.distance+i.weight#说是松弛操作,但是看起来非常像拉紧操作!他起名时脑袋进水了 obj.last_node=tmp if obj not in pq_assist: pq.put(obj) pq_assist.add(obj) a=PQueue() a.put(3) a.put(3) a.put(3) a.put(3) #graph = GraphGenerator([[1,2,3],[2,4,5],[2,6,7],[4,6,5],[1,6,99],[99,98,999]])这个是测试用的图 dijkstra(graph,graph.nodes[1]) print(graph.nodes[6].last_node.val) ''' 总结一下我写的这个函数: 1.利用了辅助set技巧.来实现对队列里面元素的跟踪看他是否在队列中. 话说一个队列连一个in方法都没有,很坑! 2.设计最重要的部分,是因为python对于一个对象,如果把它放到任意容器中,你只要修改对象的属性,那么 在容器中的这个对象也自动修改.(同一个对象的本质应该是引用.)利用这点直接在队列外面修改对象属性 即可.不用管队列内部了.其实也没法访问队列内部,队列不能随机存取.用对象就避免了这个复杂操作. 从而提高时间效率. 3.对于最短路径的跟踪方法.也是用对象里面加入一个last_node属性.取值是一个node对象. 利用他就能查询最短路径的上一个节点是哪个节点了. '''
继续添加功能
''' 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 = [] self.distance = float('inf') #为了在下面单源最短路径问题.所以这里直接建立时候初始化到无穷. def __cmp__(self,other): return cmp(self.distance, other.distance) def __lt__(self,other):#operator < return self.distance < other.distance def __ge__(self,other):#oprator >= return self.distance >= other.distance def __gt__(self,other):#oprator >= return self.distance > other.distance def __le__(self,other):#oprator <= return self.distance <= other.distance 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就行 ''' #左神用的是栈,来直接模拟递归过程.好像跟递归写法差不多 def dfs_me(node): visited = set([node])#set或者list自动是全局变量.在多少重子函数里面他都是能调用外面的list. def mini_dfs(node): print(node.val)#遍历的操作 for i in node.nexts: if i not in visited: mini_dfs(i) visited.add(i) mini_dfs(node) return print('ceshi0') dfs_me(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 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) ''' 慕课网课程: 自环边,平行边.有这2种边的叫复杂图. 我们上面左神的表达方法:就是邻接表. ''' ''' 单源最短路径:直接动态规划即可.很容易 显然满足最优子结构.a到b再到c的最短路径.一定是a到b的最短路径加上b到c的最短路径. ''' ''' dijkstra 地理课死啦算法.前提:图中不能有负权边. ''' #格式刷快捷键ctrl+k 再 ctrl+f def dijkstra(graph,start):#为了记录路径直接在node里面加一个last_node属性即可.他记录了这个节点在最短路径 #这个过程里面他的上一步是哪个节点. #然后继续加一个distance属性,来记录这个节点他距离最开始节点的距离. #start是一个node对象. visited = set() pq = PQueue() pq_assist = set() #用来判断元素是否在pq中.pq加入元素他就加入元素,pq删除他就删除! #总之我这么构造的原因就是为了避免重新修改heapq代码.和node类 #初始化start点 start.distance = 0 start.last_node = start visited.add(start) #直接在优先队列里面存入node.只存入node即可.排序函数里面写distance即可. #修改时候直接修改node即可,对队列里面数值进行修改. pq.put(start) pq_assist.add(start) while pq.empty() != True: tmp = pq.get() #所以一开始这个tmp就是start pq_assist.remove(tmp) visited.add(tmp) #找tmp相邻的边. for i in tmp.edges: obj = i.to#obj是新节点v相连的to节点. if obj not in visited: if tmp.distance + i.weight < obj.distance: obj.distance = tmp.distance + i.weight#说是松弛操作,但是看起来非常像拉紧操作!他起名时脑袋进水了 obj.last_node = tmp if obj not in pq_assist: pq.put(obj) pq_assist.add(obj) a = PQueue() a.put(3) a.put(3) a.put(3) a.put(3) #graph = #GraphGenerator([[1,2,3],[2,4,5],[2,6,7],[4,6,5],[1,6,99],[99,98,999]])这个是测试用的图 dijkstra(graph,graph.nodes[1]) print(graph.nodes[6].last_node.val) ''' 总结一下我写的这个函数: 1.利用了辅助set技巧.来实现对队列里面元素的跟踪看他是否在队列中. 话说一个队列连一个in方法都没有,很坑! 2.设计最重要的部分,是因为python对于一个对象,如果把它放到任意容器中,你只要修改对象的属性,那么 在容器中的这个对象也自动修改.(同一个对象的本质应该是引用.)利用这点直接在队列外面修改对象属性 即可.不用管队列内部了.其实也没法访问队列内部,队列不能随机存取.用对象就避免了这个复杂操作. 从而提高时间效率. 3.对于最短路径的跟踪方法.也是用对象里面加入一个last_node属性.取值是一个node对象. 利用他就能查询最短路径的上一个节点是哪个节点了. ''' ''' bellman ford算法:处理带负权的边的图的单元最短路径问题.比上面的distra方法更简单.但是效率慢非常多. 算法不需要辅助结构,就是一码for 循环即可. ''' def bellmanford(graph,start): #初始化start点 start.distance = 0 #初始化,也是下面判断负权环的本质. start.last_node = start #求|vertex|-1就是松弛的次数. times=len(graph.nodes.values())-1 for i in range(times): for j in graph.edges: #做松弛操作 weight=j.weight u=j.from1 v=j.to if v.distance>u.distance+weight: v.distance=u.distance+weight v.last_node=u #判断是否有负权环 for j in graph.edges: weight=j.weight u=j.from1 v=j.to if v.distance>u.distance+weight: return False return True print('测试') bellmanford(graph,graph.nodes[1]) print(graph.nodes[6].last_node.val)#效果不错 ''' 总结:图的单元最短路径问题: 1.权都>=0:一定用特斯拉方法.因为他速度快 2.bellman方法为啥么一定要松弛|vertex|-1次呢? 因为每一个点无法用自己把自己松弛了.只能用其他人把自己松弛了.所以至多上面这么多次.才合理 但是如果有负权圈.那么还会继续下降. 原因就是start这个点的0会发生变化,负权图会把0给降低.这显然就不符合最短路径了.所以直接return False '''
dijstrat算法改过来了.上面的自己乱想的不对.还是用索引堆来实现.看懂确实很麻烦,推荐liubobo讲的c++算法与数据结构(慕课网)里面有具体的c++实现.
''' 索引堆 通过liubobo老师的c++算法与数据结构 (慕课网) 结构还是很复杂的,当然比dat和红黑树要简单多了.通过2个辅助数组来实现的 如果看不懂可以参考上面的视频课程的第4章的内容.里面讲解很详细,我只是把c++代码修改 成了python而已.非常强大,比如在图论中经常需要维护一个可以随意修改里面元素的堆结构. 这时候索引堆就非常管用了.最短路径特斯拉算法,liubobo就是这么实现的. ''' ''' 实现使用2个辅助数组来做.有点像dat.用哈希表来做修改不行,只是能找到这个索引,而需要change操作 还是需要自己手动写.所以只能用双数组实现. #引入索引堆的核心就是为了改变堆里面任意一个元素的值,然后继续维护这个堆. ''' '''下面手动写堆''' '''做大根堆然后输出升序排列'''#感觉之前写的都不对,heapify太弱了,不能按方向调整. #需要修改成带shift up,shift down操作的堆,最终目标实现双辅助数组的最大索引堆 ''' 下面我们把这个堆改成索引堆,叫index_max_heap 初始化时候.把数据这个list给index_max_heap对象里面的data这个属性是隐含的数据 同时索引index数组,是暴露给用户访问的.data[i]=item,index[count+1]=i 然后,我们之后的插入操作是插入索引为i,内容为item的元素, 总之:我们用索引i来替换item来进行堆里面的swap操作,然后最后堆里面第一个位置存index[0],表示 存的是data[index[0]].也就是index[i]表示的是堆里面第i个位置在data数据集里面的下表.(说起来很绕). 堆里面第几个元素就去index[几]里面找钥匙.然后去data[钥匙]里面去提取内容. ''' class Maxheap(): def __init__(self,capacity): self.data=[0]*capacity #这个默认插入0非常不好,比如我索引只插入了1,'wo' 和 2,'we' #但是堆里面还是有10个元素.但是你如果不插入元素的画, #你建立堆时候给的元素少了会发生bug.没法比较进行堆维护. #所以使用的时候一定要注意,用多少capacity就给多少. #如果需要占位的时候,空余的占位要自己补上default值. #如果能动态的赋予堆空间就好了! self.indexes=[-1]*capacity #因为索引不可能是负的,所以用-1占位 self.reverse=[-1]*capacity #因为索引不可能是负的,所以用-1占位 self.count=0 self.capacity=capacity def size(self): return self.count def empty(self): return self.count==0 def shiftup(self,count): while count>0 and self.data[self.indexes[(count-1)//2]]self.indexes[(count-1)//2],self.indexes[count]=self.indexes[count],self.indexes[(count-1)//2]#这一步只是交换index提高了交换效率 #下面2行是公里,因为上面变了,所以下面需要跑一下这2行.坐下对应修复 self.reverse[self.indexes[(count-1)//2]]=(count-1)//2 self.reverse[self.indexes[count]]=count count=(count-1)//2 def shiftdown(self,k):#把堆的k索引的元素进行shiftdown操作, #每一次这个操作都能把k位置作为head的子树给heapify了. while 2*k+1<=self.count-1 : left=2*k+1 right=min(2*k+2,self.count-1) tmpindex=k if self.data[self.indexes[left]]>self.data[self.indexes[k]]: tmpindex=left if self.data[self.indexes[right]]>self.data[self.indexes[tmpindex]]: tmpindex=right if tmpindex==k: return else: self.indexes[tmpindex],self.indexes[k]=self.indexes[k],self.indexes[tmpindex] self.reverse[self.indexes[tmpindex]]=tmpindex self.reverse[self.indexes[k]]=k k=tmpindex #插入索引为i的数据是item def insert(self,i,item):#建立只需要shift up assert(self.count+1<=self.capacity) assert(i>=0 and i<self.capacity) self.data[i]=item self.indexes[self.count]=i self.reverse[i]=self.count self.shiftup(self.count)#把count位置的元素向上移动维护堆 self.count+=1 def pop(self):#弹出堆定元素 assert(self.count>0) self.indexes[0],self.indexes[self.count-1]=self.indexes[self.count-1],self.indexes[0] self.reverse[self.indexes[0]]=0 self.reverse[self.indexes[self.count-1]]=-1#pop就不会被访问了所以给-1 output=self.data[self.indexes[self.count-1]] self.count-=1 if self.count>0: self.shiftdown(0)#把索引为0的进行shiftdown操作 return output def pop_index(self):#弹出堆定元素 assert(self.count>0) self.indexes[0],self.indexes[self.count-1]=self.indexes[self.count-1],self.indexes[0] self.reverse[self.indexes[0]]=0 self.reverse[self.indexes[self.count-1]]=-1#pop就不会被访问了所以给-1 output=self.indexes[self.count-1] self.count-=1 if self.count>0: self.shiftdown(0)#把索引为0的进行shiftdown操作 return output def show_data(self):#因为堆,需要不动态删除,为了速度.所以不要的元素只是把它放到count-1 这个 #index后面而已,通过show_data来读取data中有效元素 #利用index来遍历更准确和不会bug out=[] index_now=self.indexes[:self.size()] for i in index_now: if i!=-1: out.append(self.data[i]) out return out def heapify(self,list1):#把数组直接建立成一个最大堆 self.data=list1#python的列表动态的,直接赋值即可.不用管capacity self.capacity=len(list1) self.count=self.capacity for i in range((self.capacity-2)//2,-1,-1): self.shiftdown(i) return self.data def heapsort(self,list1):#直接pop 就实现了.因为前面都已经写好了 self.heapify(list1) while self.count>1: self.pop()#弹出一个 return self.data def get_item(self,i): return self.data[i] def change(self,i,newitem):#需要返回索引i在堆中的第几个坐标上.比如堆中第一个元素是10,那么change(10)返回0 #这种操作,叫反向查找技术,非常牛逼class,实现不难,思想牛逼.常用.思想很像dat #先修改data self.data[i]=newitem j=self.reverse[i] self.shiftup(j) self.shiftdown(j) return self.data[i] #下面是测试 print('测试大index堆') a=Maxheap(10) a.insert(0,'wo')#0是索引,'wo'是value.对value比较大小来建堆,但是堆里面的元素都是index. a.insert(1,'we') a.insert(3,'a') a.change(3,'jjk') print(a.pop_index()) print(a.indexes)#弹出的元素不会彻底删除,而只是把它的索引放到self.size后面了. print(a.show_data()) #从这里面就看出来之前最大的wo已经被弹出了. #并且indexMaxheap也已经效果出来了.可以随意修改index为3的元素了,并且 #自动维护这个堆. #然而图论需要的是minheap.我改成indexminheap,特斯拉算法需要 #capacity是最大容量#是最小堆所以加入无穷做初始化,动态做data可以自己实现时候直接修改data的实现方式. class indexminheap(): def __init__(self,capacity):#capacity是最大容量 self.data=[float('inf')]*capacity #这个默认插入0非常不好,比如我索引只插入了1,'wo' 和 2,'we' #但是堆里面还是有10个元素.但是你如果不插入元素的画, #你建立堆时候给的元素少了会发生bug.没法比较进行堆维护. #所以使用的时候一定要注意,用多少capacity就给多少. #如果需要占位的时候,空余的占位要自己补上default值. self.indexes=[-1]*capacity #因为索引不可能是负的,所以用-1占位 self.reverse=[-1]*capacity #因为索引不可能是负的,所以用-1占位 self.count=0 self.capacity=capacity def size(self): return self.count def empty(self): return self.count==0 def shiftup(self,count): while count>0 and self.data[self.indexes[(count-1)//2]]>self.data[self.indexes[count]]: self.indexes[(count-1)//2],self.indexes[count]=self.indexes[count],self.indexes[(count-1)//2]#这一步只是交换index提高了交换效率 #下面2行是公里,因为上面变了,所以下面需要跑一下这2行.坐下对应修复 self.reverse[self.indexes[(count-1)//2]]=(count-1)//2 self.reverse[self.indexes[count]]=count count=(count-1)//2 def shiftdown(self,k):#把堆的k索引的元素进行shiftdown操作, #每一次这个操作都能把k位置作为head的子树给heapify了. while 2*k+1<=self.count-1 : left=2*k+1 right=min(2*k+2,self.count-1) tmpindex=k if self.data[self.indexes[left]]<self.data[self.indexes[k]]: tmpindex=left if self.data[self.indexes[right]]<self.data[self.indexes[tmpindex]]: tmpindex=right if tmpindex==k: return else: self.indexes[tmpindex],self.indexes[k]=self.indexes[k],self.indexes[tmpindex] self.reverse[self.indexes[tmpindex]]=tmpindex self.reverse[self.indexes[k]]=k k=tmpindex #插入索引为i的数据是item def insert(self,i,item):#建立只需要shift up assert(self.count+1<=self.capacity) assert(i>=0 and i<self.capacity) self.data[i]=item self.indexes[self.count]=i self.reverse[i]=self.count self.shiftup(self.count)#把count位置的元素向上移动维护堆 self.count+=1 def pop(self):#弹出堆定元素 assert(self.count>0) self.indexes[0],self.indexes[self.count-1]=self.indexes[self.count-1],self.indexes[0] self.reverse[self.indexes[0]]=0 self.reverse[self.indexes[self.count-1]]=-1#pop就不会被访问了所以给-1 output=self.data[self.indexes[self.count-1]] self.count-=1 if self.count>0: self.shiftdown(0)#把索引为0的进行shiftdown操作 return output def pop_index(self):#弹出堆定元素 assert(self.count>0) self.indexes[0],self.indexes[self.count-1]=self.indexes[self.count-1],self.indexes[0] self.reverse[self.indexes[0]]=0 self.reverse[self.indexes[self.count-1]]=-1#pop就不会被访问了所以给-1 output=self.indexes[self.count-1] self.count-=1 if self.count>0: self.shiftdown(0)#把索引为0的进行shiftdown操作 return output def show_data(self):#因为堆,需要不动态删除,为了速度.所以不要的元素只是把它放到count-1 这个 #index后面而已,通过show_data来读取data中有效元素 #利用index来遍历更准确和不会bug out=[] index_now=self.indexes[:self.size()] for i in index_now: if i!=-1: out.append(self.data[i]) out return out def heapify(self,list1):#把数组直接建立成一个最大堆 self.data=list1#python的列表动态的,直接赋值即可.不用管capacity self.capacity=len(list1) self.count=self.capacity for i in range((self.capacity-2)//2,-1,-1): self.shiftdown(i) return self.data def heapsort(self,list1):#直接pop 就实现了.因为前面都已经写好了 self.heapify(list1) while self.count>1: self.pop()#弹出一个 return self.data def get_item(self,i): return self.data[i] def change(self,i,newitem):#需要返回索引i在堆中的第几个坐标上.比如堆中第一个元素是10,那么change(10)返回0 #这种操作,叫反向查找技术,非常牛逼class,实现不难,思想牛逼.常用.思想很像dat #先修改data self.data[i]=newitem j=self.reverse[i] self.shiftup(j) self.shiftdown(j) return self.data[i] print('测试小index堆') #下面是测试 a=indexminheap(10) a.insert(0,'wo')#0是索引,'wo'是value.对value比较大小来建堆,但是堆里面的元素都是index. a.insert(1,'we') a.insert(3,'a') a.change(3,'jjk') print(a.pop_index()) print(a.indexes)#弹出的元素不会彻底删除,而只是把它的索引放到self.size后面了. print(a.show_data()) #从这里面就看出来之前最大的wo已经被弹出了. #并且indexMaxheap也已经效果出来了.可以随意修改index为3的元素了,并且 #自动维护这个堆. ''' 并查集: 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 = [] self.distance = float('inf') #为了在下面单源最短路径问题.所以这里直接建立时候初始化到无穷. def __cmp__(self,other): return cmp(self.distance, other.distance) def __lt__(self,other):#operator < return self.distance < other.distance def __ge__(self,other):#oprator >= return self.distance >= other.distance def __gt__(self,other):#oprator >= return self.distance > other.distance def __le__(self,other):#oprator <= return self.distance <= other.distance 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就行 ''' #左神用的是栈,来直接模拟递归过程.好像跟递归写法差不多 def dfs_me(node): visited = set([node])#set或者list自动是全局变量.在多少重子函数里面他都是能调用外面的list. def mini_dfs(node): print(node.val)#遍历的操作 for i in node.nexts: if i not in visited: mini_dfs(i) visited.add(i) mini_dfs(node) return print('ceshi0') dfs_me(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 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) ''' 慕课网课程: 自环边,平行边.有这2种边的叫复杂图. 我们上面左神的表达方法:就是邻接表. ''' ''' 单源最短路径:直接动态规划即可.很容易 显然满足最优子结构.a到b再到c的最短路径.一定是a到b的最短路径加上b到c的最短路径. ''' ''' dijkstra 地理课死啦算法.前提:图中不能有负权边. ''' #格式刷快捷键ctrl+k 再 ctrl+f def dijkstra(graph,start):#为了记录路径直接在node里面加一个last_node属性即可.他记录了这个节点在最短路径 #这个过程里面他的上一步是哪个节点. #然后继续加一个distance属性,来记录这个节点他距离最开始节点的距离. #start是一个node对象. visited = set() capacity=len(graph.nodes.values()) pq = indexminheap(capacity+1) #队列里面的东西是index是node.val data里面是node.distance这样就可以用索引堆来实现了 #初始化start点 start.distance = 0 start.last_node = start visited.add(start) #直接在优先队列里面存入node.只存入node即可.排序函数里面写distance即可. #修改时候直接修改node即可,对队列里面数值进行修改. pq.insert(start.val,start.distance) while pq.empty() != True: tmp = pq.pop_index() #所以一开始这个tmp就是start if tmp==-1:#表示弹到default值了,根本不是边了,边都处理完了 return tmp=graph.nodes[tmp] #value转node visited.add(tmp) #找tmp相邻的边. for i in tmp.edges: obj = i.to#obj是新节点v相连的to节点. if obj not in visited: if tmp.distance + i.weight < obj.distance: pq.change(obj.val,tmp.distance + i.weight) obj.distance = tmp.distance + i.weight #说是松弛操作,但是看起来非常像拉紧操作!他起名时脑袋进水了 ''' !!!!!!!!!!!!!这个地方好像有问题,虽然队列里面内容直接修改了,但是队列里面 自动会维护最小堆性质么?应该不自动维护最小堆性质. 但是为什么测试了2个例子全对?难道他自动维护堆了? ''' obj.last_node = tmp if obj.val not in pq.indexes: pq.insert(obj.val,obj.distance) #graph = #GraphGenerator([[1,2,3],[2,4,5],[2,6,7],[4,6,5],[1,6,99],[99,98,999]])这个是测试用的图 print('ceshi特斯拉开始') graph = GraphGenerator([[1,2,10],[1,4,5],[2,3,1],[2,4,2],[3,5,4],[4,2,3],[4,3,9],[4,5,2],[5,1,7] ,[5,3,6]]) dijkstra(graph,graph.nodes[1]) print(graph.nodes[5].last_node.val)#效果不错,返回2节点的last_node,也就是最短路径下的2的上一个节点 print('测试特斯拉结束,图论最难的算法') ''' 经过图论的学习.越来越发现莫装逼,装逼招雷劈,现有的算法能掌握好久已经不错了. 并查集,索引堆,哈希表,set,图论算法.能掌握熟练就已经很难了.像我之前写的用辅助set来替代索引堆果断打脸 幸好及时改过来了,毕竟半个多世纪的大神们发明的算法不是随便你一想就能做优化的.没几十年创造不了新算法. ''' ''' 总结一下我写的这个函数: 1.利用了辅助set技巧.来实现对队列里面元素的跟踪看他是否在队列中. 话说一个队列连一个in方法都没有,很坑! 2.设计最重要的部分,是因为python对于一个对象,如果把它放到任意容器中,你只要修改对象的属性,那么 在容器中的这个对象也自动修改.(同一个对象的本质应该是引用.)利用这点直接在队列外面修改对象属性 即可.不用管队列内部了.其实也没法访问队列内部,队列不能随机存取.用对象就避免了这个复杂操作. 从而提高时间效率. 3.对于最短路径的跟踪方法.也是用对象里面加入一个last_node属性.取值是一个node对象. 利用他就能查询最短路径的上一个节点是哪个节点了. ''' ''' bellman ford算法:处理带负权的边的图的单元最短路径问题.比上面的distra方法更简单.但是效率慢非常多. 算法不需要辅助结构,就是一码for 循环即可. ''' def bellmanford(graph,start): #初始化start点 start.distance = 0 #初始化,也是下面判断负权环的本质. start.last_node = start #求|vertex|-1就是松弛的次数. times=len(graph.nodes.values())-1 for i in range(times): for j in graph.edges: #做松弛操作 weight=j.weight u=j.from1 v=j.to if v.distance>u.distance+weight: v.distance=u.distance+weight v.last_node=u #判断是否有负权环 for j in graph.edges: weight=j.weight u=j.from1 v=j.to if v.distance>u.distance+weight: return False return True print('测试') bellmanford(graph,graph.nodes[1]) ''' 总结:图的单元最短路径问题: 1.权都>=0:一定用特斯拉方法.因为他速度快 2.bellman方法为啥么一定要松弛|vertex|-1次呢? 因为每一个点无法用自己把自己松弛了.只能用其他人把自己松弛了.所以至多上面这么多次.才合理 但是如果有负权圈.那么还会继续下降. 原因就是start这个点的0会发生变化,负权图会把0给降低.这显然就不符合最短路径了.所以直接return False ''' #下面测试优先队列里面修改后是否优先队列还能自动维护堆的性质. class node(): def __init__(self,val): self.distance=val def __cmp__(self,other): return cmp(self.distance, other.distance) def __lt__(self,other):#operator < return self.distance < other.distance def __ge__(self,other):#oprator >= return self.distance >= other.distance def __gt__(self,other):#oprator >= return self.distance > other.distance def __le__(self,other):#oprator <= return self.distance <= other.distance pq = PQueue() a=node(111) b=node(2) c=node(3) pq.put(a) pq.put(b) pq.put(c) a.distance=999999999999 print(pq.get().distance)#结果显然不维护.所以上面我写的特斯拉算法是错误的,还是需要索引堆来实现.
''' 索引堆 通过liubobo老师的c++算法与数据结构 (慕课网) 结构还是很复杂的,当然比dat和红黑树要简单多了.通过2个辅助数组来实现的 如果看不懂可以参考上面的视频课程的第4章的内容.里面讲解很详细,我只是把c++代码修改 成了python而已.非常强大,比如在图论中经常需要维护一个可以随意修改里面元素的堆结构. 这时候索引堆就非常管用了.最短路径特斯拉算法,liubobo就是这么实现的. ''' ''' 实现使用2个辅助数组来做.有点像dat.用哈希表来做修改不行,只是能找到这个索引,而需要change操作 还是需要自己手动写.所以只能用双数组实现. #引入索引堆的核心就是为了改变堆里面任意一个元素的值,然后继续维护这个堆. ''' '''下面手动写堆''' '''做大根堆然后输出升序排列'''#感觉之前写的都不对,heapify太弱了,不能按方向调整. #需要修改成带shift up,shift down操作的堆,最终目标实现双辅助数组的最大索引堆 ''' 下面我们把这个堆改成索引堆,叫index_max_heap 初始化时候.把数据这个list给index_max_heap对象里面的data这个属性是隐含的数据 同时索引index数组,是暴露给用户访问的.data[i]=item,index[count+1]=i 然后,我们之后的插入操作是插入索引为i,内容为item的元素, 总之:我们用索引i来替换item来进行堆里面的swap操作,然后最后堆里面第一个位置存index[0],表示 存的是data[index[0]].也就是index[i]表示的是堆里面第i个位置在data数据集里面的下表.(说起来很绕). 堆里面第几个元素就去index[几]里面找钥匙.然后去data[钥匙]里面去提取内容. ''' class Maxheap(): def __init__(self,capacity): self.data=[0]*capacity #这个默认插入0非常不好,比如我索引只插入了1,'wo' 和 2,'we' #但是堆里面还是有10个元素.但是你如果不插入元素的画, #你建立堆时候给的元素少了会发生bug.没法比较进行堆维护. #所以使用的时候一定要注意,用多少capacity就给多少. #如果需要占位的时候,空余的占位要自己补上default值. #如果能动态的赋予堆空间就好了! self.indexes=[-1]*capacity #因为索引不可能是负的,所以用-1占位 self.reverse=[-1]*capacity #因为索引不可能是负的,所以用-1占位 self.count=0 self.capacity=capacity def size(self): return self.count def empty(self): return self.count==0 def shiftup(self,count): while count>0 and self.data[self.indexes[(count-1)//2]]self.indexes[(count-1)//2],self.indexes[count]=self.indexes[count],self.indexes[(count-1)//2]#这一步只是交换index提高了交换效率 #下面2行是公里,因为上面变了,所以下面需要跑一下这2行.坐下对应修复 self.reverse[self.indexes[(count-1)//2]]=(count-1)//2 self.reverse[self.indexes[count]]=count count=(count-1)//2 def shiftdown(self,k):#把堆的k索引的元素进行shiftdown操作, #每一次这个操作都能把k位置作为head的子树给heapify了. while 2*k+1<=self.count-1 : left=2*k+1 right=min(2*k+2,self.count-1) tmpindex=k if self.data[self.indexes[left]]>self.data[self.indexes[k]]: tmpindex=left if self.data[self.indexes[right]]>self.data[self.indexes[tmpindex]]: tmpindex=right if tmpindex==k: return else: self.indexes[tmpindex],self.indexes[k]=self.indexes[k],self.indexes[tmpindex] self.reverse[self.indexes[tmpindex]]=tmpindex self.reverse[self.indexes[k]]=k k=tmpindex #插入索引为i的数据是item def insert(self,i,item):#建立只需要shift up assert(self.count+1<=self.capacity) assert(i>=0 and i<self.capacity) self.data[i]=item self.indexes[self.count]=i self.reverse[i]=self.count self.shiftup(self.count)#把count位置的元素向上移动维护堆 self.count+=1 def pop(self):#弹出堆定元素 assert(self.count>0) self.indexes[0],self.indexes[self.count-1]=self.indexes[self.count-1],self.indexes[0] self.reverse[self.indexes[0]]=0 self.reverse[self.indexes[self.count-1]]=-1#pop就不会被访问了所以给-1 output=self.data[self.indexes[self.count-1]] self.count-=1 if self.count>0: self.shiftdown(0)#把索引为0的进行shiftdown操作 return output def pop_index(self):#弹出堆定元素 assert(self.count>0) self.indexes[0],self.indexes[self.count-1]=self.indexes[self.count-1],self.indexes[0] self.reverse[self.indexes[0]]=0 self.reverse[self.indexes[self.count-1]]=-1#pop就不会被访问了所以给-1 output=self.indexes[self.count-1] self.count-=1 if self.count>0: self.shiftdown(0)#把索引为0的进行shiftdown操作 return output def show_data(self):#因为堆,需要不动态删除,为了速度.所以不要的元素只是把它放到count-1 这个 #index后面而已,通过show_data来读取data中有效元素 #利用index来遍历更准确和不会bug out=[] index_now=self.indexes[:self.size()] for i in index_now: if i!=-1: out.append(self.data[i]) out return out def heapify(self,list1):#把数组直接建立成一个最大堆 self.data=list1#python的列表动态的,直接赋值即可.不用管capacity self.capacity=len(list1) self.count=self.capacity for i in range((self.capacity-2)//2,-1,-1): self.shiftdown(i) return self.data def heapsort(self,list1):#直接pop 就实现了.因为前面都已经写好了 self.heapify(list1) while self.count>1: self.pop()#弹出一个 return self.data def get_item(self,i): return self.data[i] def change(self,i,newitem):#需要返回索引i在堆中的第几个坐标上.比如堆中第一个元素是10,那么change(10)返回0 #这种操作,叫反向查找技术,非常牛逼class,实现不难,思想牛逼.常用.思想很像dat #先修改data self.data[i]=newitem j=self.reverse[i] self.shiftup(j) self.shiftdown(j) return self.data[i] #下面是测试 print('测试大index堆') a=Maxheap(10) a.insert(0,'wo')#0是索引,'wo'是value.对value比较大小来建堆,但是堆里面的元素都是index. a.insert(1,'we') a.insert(3,'a') a.change(3,'jjk') print(a.pop_index()) print(a.indexes)#弹出的元素不会彻底删除,而只是把它的索引放到self.size后面了. print(a.show_data()) #从这里面就看出来之前最大的wo已经被弹出了. #并且indexMaxheap也已经效果出来了.可以随意修改index为3的元素了,并且 #自动维护这个堆. #然而图论需要的是minheap.我改成indexminheap,特斯拉算法需要 #capacity是最大容量#是最小堆所以加入无穷做初始化,动态做data可以自己实现时候直接修改data的实现方式. class indexminheap(): def __init__(self,capacity):#capacity是最大容量 self.data=[float('inf')]*capacity #这个默认插入0非常不好,比如我索引只插入了1,'wo' 和 2,'we' #但是堆里面还是有10个元素.但是你如果不插入元素的画, #你建立堆时候给的元素少了会发生bug.没法比较进行堆维护. #所以使用的时候一定要注意,用多少capacity就给多少. #如果需要占位的时候,空余的占位要自己补上default值. self.indexes=[-1]*capacity #因为索引不可能是负的,所以用-1占位 self.reverse=[-1]*capacity #因为索引不可能是负的,所以用-1占位 self.count=0 self.capacity=capacity def size(self): return self.count def empty(self): return self.count==0 def shiftup(self,count): while count>0 and self.data[self.indexes[(count-1)//2]]>self.data[self.indexes[count]]: self.indexes[(count-1)//2],self.indexes[count]=self.indexes[count],self.indexes[(count-1)//2]#这一步只是交换index提高了交换效率 #下面2行是公里,因为上面变了,所以下面需要跑一下这2行.坐下对应修复 self.reverse[self.indexes[(count-1)//2]]=(count-1)//2 self.reverse[self.indexes[count]]=count count=(count-1)//2 def shiftdown(self,k):#把堆的k索引的元素进行shiftdown操作, #每一次这个操作都能把k位置作为head的子树给heapify了. while 2*k+1<=self.count-1 : left=2*k+1 right=min(2*k+2,self.count-1) tmpindex=k if self.data[self.indexes[left]]<self.data[self.indexes[k]]: tmpindex=left if self.data[self.indexes[right]]<self.data[self.indexes[tmpindex]]: tmpindex=right if tmpindex==k: return else: self.indexes[tmpindex],self.indexes[k]=self.indexes[k],self.indexes[tmpindex] self.reverse[self.indexes[tmpindex]]=tmpindex self.reverse[self.indexes[k]]=k k=tmpindex #插入索引为i的数据是item def insert(self,i,item):#建立只需要shift up assert(self.count+1<=self.capacity) assert(i>=0 and i<self.capacity) self.data[i]=item self.indexes[self.count]=i self.reverse[i]=self.count self.shiftup(self.count)#把count位置的元素向上移动维护堆 self.count+=1 def pop(self):#弹出堆定元素 assert(self.count>0) self.indexes[0],self.indexes[self.count-1]=self.indexes[self.count-1],self.indexes[0] self.reverse[self.indexes[0]]=0 self.reverse[self.indexes[self.count-1]]=-1#pop就不会被访问了所以给-1 output=self.data[self.indexes[self.count-1]] self.count-=1 if self.count>0: self.shiftdown(0)#把索引为0的进行shiftdown操作 return output def pop_index(self):#弹出堆定元素 assert(self.count>0) self.indexes[0],self.indexes[self.count-1]=self.indexes[self.count-1],self.indexes[0] self.reverse[self.indexes[0]]=0 self.reverse[self.indexes[self.count-1]]=-1#pop就不会被访问了所以给-1 output=self.indexes[self.count-1] self.count-=1 if self.count>0: self.shiftdown(0)#把索引为0的进行shiftdown操作 return output def show_data(self):#因为堆,需要不动态删除,为了速度.所以不要的元素只是把它放到count-1 这个 #index后面而已,通过show_data来读取data中有效元素 #利用index来遍历更准确和不会bug out=[] index_now=self.indexes[:self.size()] for i in index_now: if i!=-1: out.append(self.data[i]) out return out def heapify(self,list1):#把数组直接建立成一个最大堆 self.data=list1#python的列表动态的,直接赋值即可.不用管capacity self.capacity=len(list1) self.count=self.capacity for i in range((self.capacity-2)//2,-1,-1): self.shiftdown(i) return self.data def heapsort(self,list1):#直接pop 就实现了.因为前面都已经写好了 self.heapify(list1) while self.count>1: self.pop()#弹出一个 return self.data def get_item(self,i): return self.data[i] def change(self,i,newitem):#需要返回索引i在堆中的第几个坐标上.比如堆中第一个元素是10,那么change(10)返回0 #这种操作,叫反向查找技术,非常牛逼class,实现不难,思想牛逼.常用.思想很像dat #先修改data self.data[i]=newitem j=self.reverse[i] self.shiftup(j) self.shiftdown(j) return self.data[i] print('测试小index堆') #下面是测试 a=indexminheap(10) a.insert(0,'wo')#0是索引,'wo'是value.对value比较大小来建堆,但是堆里面的元素都是index. a.insert(1,'we') a.insert(3,'a') a.change(3,'jjk') print(a.pop_index()) print(a.indexes)#弹出的元素不会彻底删除,而只是把它的索引放到self.size后面了. print(a.show_data()) #从这里面就看出来之前最大的wo已经被弹出了. #并且indexMaxheap也已经效果出来了.可以随意修改index为3的元素了,并且 #自动维护这个堆. ''' 并查集: 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 = [] self.distance = float('inf') #为了在下面单源最短路径问题.所以这里直接建立时候初始化到无穷. def __cmp__(self,other): return cmp(self.distance, other.distance) def __lt__(self,other):#operator < return self.distance < other.distance def __ge__(self,other):#oprator >= return self.distance >= other.distance def __gt__(self,other):#oprator >= return self.distance > other.distance def __le__(self,other):#oprator <= return self.distance <= other.distance 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就行 ''' #左神用的是栈,来直接模拟递归过程.好像跟递归写法差不多 def dfs_me(node): visited = set([node])#set或者list自动是全局变量.在多少重子函数里面他都是能调用外面的list. def mini_dfs(node): print(node.val)#遍历的操作 for i in node.nexts: if i not in visited: mini_dfs(i) visited.add(i) mini_dfs(node) return print('ceshi0') dfs_me(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 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) ''' 慕课网课程: 自环边,平行边.有这2种边的叫复杂图. 我们上面左神的表达方法:就是邻接表. ''' ''' 单源最短路径:直接动态规划即可.很容易 显然满足最优子结构.a到b再到c的最短路径.一定是a到b的最短路径加上b到c的最短路径. ''' ''' dijkstra 地理课死啦算法.前提:图中不能有负权边. ''' #格式刷快捷键ctrl+k 再 ctrl+f def dijkstra(graph,start):#为了记录路径直接在node里面加一个last_node属性即可.他记录了这个节点在最短路径 #这个过程里面他的上一步是哪个节点. #然后继续加一个distance属性,来记录这个节点他距离最开始节点的距离. #start是一个node对象. visited = set() capacity=len(graph.nodes.values()) pq = indexminheap(capacity+1) #队列里面的东西是index是node.val data里面是node.distance这样就可以用索引堆来实现了 #初始化start点 start.distance = 0 start.last_node = start visited.add(start) #直接在优先队列里面存入node.只存入node即可.排序函数里面写distance即可. #修改时候直接修改node即可,对队列里面数值进行修改. pq.insert(start.val,start.distance) while pq.empty() != True: tmp = pq.pop_index() #所以一开始这个tmp就是start if tmp==-1:#表示弹到default值了,根本不是边了,边都处理完了 return tmp=graph.nodes[tmp] #value转node visited.add(tmp) #找tmp相邻的边. for i in tmp.edges: obj = i.to#obj是新节点v相连的to节点. if obj not in visited: if tmp.distance + i.weight < obj.distance: pq.change(obj.val,tmp.distance + i.weight) obj.distance = tmp.distance + i.weight #说是松弛操作,但是看起来非常像拉紧操作!他起名时脑袋进水了 ''' !!!!!!!!!!!!!这个地方好像有问题,虽然队列里面内容直接修改了,但是队列里面 自动会维护最小堆性质么?应该不自动维护最小堆性质. 但是为什么测试了2个例子全对?难道他自动维护堆了? ''' obj.last_node = tmp if obj.val not in pq.indexes: pq.insert(obj.val,obj.distance) #graph = #GraphGenerator([[1,2,3],[2,4,5],[2,6,7],[4,6,5],[1,6,99],[99,98,999]])这个是测试用的图 print('ceshi特斯拉开始') graph = GraphGenerator([[1,2,10],[1,4,5],[2,3,1],[2,4,2],[3,5,4],[4,2,3],[4,3,9],[4,5,2],[5,1,7] ,[5,3,6]]) dijkstra(graph,graph.nodes[1]) print(graph.nodes[5].last_node.val)#效果不错,返回2节点的last_node,也就是最短路径下的2的上一个节点 print('测试特斯拉结束,图论最难的算法') ''' 经过图论的学习.越来越发现莫装逼,装逼招雷劈,现有的算法能掌握好久已经不错了. 并查集,索引堆,哈希表,set,图论算法.能掌握熟练就已经很难了.像我之前写的用辅助set来替代索引堆果断打脸 幸好及时改过来了,毕竟半个多世纪的大神们发明的算法不是随便你一想就能做优化的.没几十年创造不了新算法. ''' ''' 总结一下我写的这个函数: 1.利用了辅助set技巧.来实现对队列里面元素的跟踪看他是否在队列中. 话说一个队列连一个in方法都没有,很坑! 2.设计最重要的部分,是因为python对于一个对象,如果把它放到任意容器中,你只要修改对象的属性,那么 在容器中的这个对象也自动修改.(同一个对象的本质应该是引用.)利用这点直接在队列外面修改对象属性 即可.不用管队列内部了.其实也没法访问队列内部,队列不能随机存取.用对象就避免了这个复杂操作. 从而提高时间效率. 3.对于最短路径的跟踪方法.也是用对象里面加入一个last_node属性.取值是一个node对象. 利用他就能查询最短路径的上一个节点是哪个节点了. ''' ''' bellman ford算法:处理带负权的边的图的单元最短路径问题.比上面的distra方法更简单.但是效率慢非常多. 算法不需要辅助结构,就是一码for 循环即可. ''' def bellmanford(graph,start): #初始化start点 start.distance = 0 #初始化,也是下面判断负权环的本质. start.last_node = start #求|vertex|-1就是松弛的次数. times=len(graph.nodes.values())-1 for i in range(times): for j in graph.edges: #做松弛操作 weight=j.weight u=j.from1 v=j.to if v.distance>u.distance+weight: v.distance=u.distance+weight v.last_node=u #判断是否有负权环 for j in graph.edges: weight=j.weight u=j.from1 v=j.to if v.distance>u.distance+weight: return False return True print('测试') bellmanford(graph,graph.nodes[1]) ''' 总结:图的单元最短路径问题: 1.权都>=0:一定用特斯拉方法.因为他速度快 2.bellman方法为啥么一定要松弛|vertex|-1次呢? 因为每一个点无法用自己把自己松弛了.只能用其他人把自己松弛了.所以至多上面这么多次.才合理 但是如果有负权圈.那么还会继续下降. 原因就是start这个点的0会发生变化,负权图会把0给降低.这显然就不符合最短路径了.所以直接return False ''' #下面测试优先队列里面修改后是否优先队列还能自动维护堆的性质. class node(): def __init__(self,val): self.distance=val def __cmp__(self,other): return cmp(self.distance, other.distance) def __lt__(self,other):#operator < return self.distance < other.distance def __ge__(self,other):#oprator >= return self.distance >= other.distance def __gt__(self,other):#oprator >= return self.distance > other.distance def __le__(self,other):#oprator <= return self.distance <= other.distance pq = PQueue() a=node(111) b=node(2) c=node(3) pq.put(a) pq.put(b) pq.put(c) a.distance=999999999999 print(pq.get().distance)#结果显然不维护.所以上面我写的特斯拉算法是错误的,还是需要索引堆来实现. class node(): def __init__(self,val): self.distance=val def __cmp__(self,other): return cmp(self.distance, other.distance) def __lt__(self,other):#operator < return self.distance < other.distance def __ge__(self,other):#oprator >= return self.distance >= other.distance def __gt__(self,other):#oprator >= return self.distance > other.distance def __le__(self,other):#oprator <= return self.distance <= other.distance pq = indexminheap(999) a=node(111) b=node(2) c=node(3) pq.insert(1,a.distance) pq.insert(2,b.distance) pq.insert(3,c.distance) pq.change(1,8000) print(pq.pop())#用索引堆显然就得到了2.就是我们要的效果.用索引来修改堆中任意元素并且能维护堆
扩充了floyd算法
#代码说明:实现了并查集,大根索引堆,小根索引堆,图的dfs,bfs,最小生成树:k算法,p算法 #单源最短路径:dijistras算法和bellman算法 #图的类建立:GraphGenerator(matrix) 把一个矩阵里面元素是[from,end,边长] 数据写到图里面 #floyd算法:计算图中任意两点的最短距离 ''' 索引堆 通过liubobo老师的c++算法与数据结构 (慕课网) 结构还是很复杂的,当然比dat和红黑树要简单多了.通过2个辅助数组来实现的 如果看不懂可以参考上面的视频课程的第4章的内容.里面讲解很详细,我只是把c++代码修改 成了python而已.非常强大,比如在图论中经常需要维护一个可以随意修改里面元素的堆结构. 这时候索引堆就非常管用了.最短路径特斯拉算法,liubobo就是这么实现的. ''' ''' 实现使用2个辅助数组来做.有点像dat.用哈希表来做修改不行,只是能找到这个索引,而需要change操作 还是需要自己手动写.所以只能用双数组实现. #引入索引堆的核心就是为了改变堆里面任意一个元素的值,然后继续维护这个堆. ''' '''下面手动写堆''' '''做大根堆然后输出升序排列'''#感觉之前写的都不对,heapify太弱了,不能按方向调整. #需要修改成带shift up,shift down操作的堆,最终目标实现双辅助数组的最大索引堆 ''' 下面我们把这个堆改成索引堆,叫index_max_heap 初始化时候.把数据这个list给index_max_heap对象里面的data这个属性是隐含的数据 同时索引index数组,是暴露给用户访问的.data[i]=item,index[count+1]=i 然后,我们之后的插入操作是插入索引为i,内容为item的元素, 总之:我们用索引i来替换item来进行堆里面的swap操作,然后最后堆里面第一个位置存index[0],表示 存的是data[index[0]].也就是index[i]表示的是堆里面第i个位置在data数据集里面的下表.(说起来很绕). 堆里面第几个元素就去index[几]里面找钥匙.然后去data[钥匙]里面去提取内容. ''' class IndexMaxheap(): def __init__(self,capacity): self.data=[0]*capacity #这个默认插入0非常不好,比如我索引只插入了1,'wo' 和 2,'we' #但是堆里面还是有10个元素.但是你如果不插入元素的画, #你建立堆时候给的元素少了会发生bug.没法比较进行堆维护. #所以使用的时候一定要注意,用多少capacity就给多少. #如果需要占位的时候,空余的占位要自己补上default值. #如果能动态的赋予堆空间就好了! self.indexes=[-1]*capacity #因为索引不可能是负的,所以用-1占位 self.reverse=[-1]*capacity #因为索引不可能是负的,所以用-1占位 self.count=0 self.capacity=capacity def size(self): return self.count def empty(self): return self.count==0 def shiftup(self,count): while count>0 and self.data[self.indexes[(count-1)//2]]self.indexes[(count-1)//2],self.indexes[count]=self.indexes[count],self.indexes[(count-1)//2]#这一步只是交换index提高了交换效率 #下面2行是公里,因为上面变了,所以下面需要跑一下这2行.坐下对应修复 self.reverse[self.indexes[(count-1)//2]]=(count-1)//2 self.reverse[self.indexes[count]]=count count=(count-1)//2 def shiftdown(self,k):#把堆的k索引的元素进行shiftdown操作, #每一次这个操作都能把k位置作为head的子树给heapify了. while 2*k+1<=self.count-1 : left=2*k+1 right=min(2*k+2,self.count-1) tmpindex=k if self.data[self.indexes[left]]>self.data[self.indexes[k]]: tmpindex=left if self.data[self.indexes[right]]>self.data[self.indexes[tmpindex]]: tmpindex=right if tmpindex==k: return else: self.indexes[tmpindex],self.indexes[k]=self.indexes[k],self.indexes[tmpindex] self.reverse[self.indexes[tmpindex]]=tmpindex self.reverse[self.indexes[k]]=k k=tmpindex #插入索引为i的数据是item def insert(self,i,item):#建立只需要shift up assert(self.count+1<=self.capacity) assert(i>=0 and i<self.capacity) self.data[i]=item self.indexes[self.count]=i self.reverse[i]=self.count self.shiftup(self.count)#把count位置的元素向上移动维护堆 self.count+=1 def pop(self):#弹出堆定元素 assert(self.count>0) self.indexes[0],self.indexes[self.count-1]=self.indexes[self.count-1],self.indexes[0] self.reverse[self.indexes[0]]=0 self.reverse[self.indexes[self.count-1]]=-1#pop就不会被访问了所以给-1 output=self.data[self.indexes[self.count-1]] self.count-=1 if self.count>0: self.shiftdown(0)#把索引为0的进行shiftdown操作 return output def pop_index(self):#弹出堆定元素 assert(self.count>0) self.indexes[0],self.indexes[self.count-1]=self.indexes[self.count-1],self.indexes[0] self.reverse[self.indexes[0]]=0 self.reverse[self.indexes[self.count-1]]=-1#pop就不会被访问了所以给-1 output=self.indexes[self.count-1] self.count-=1 if self.count>0: self.shiftdown(0)#把索引为0的进行shiftdown操作 return output def show_data(self):#因为堆,需要不动态删除,为了速度.所以不要的元素只是把它放到count-1 这个 #index后面而已,通过show_data来读取data中有效元素 #利用index来遍历更准确和不会bug out=[] index_now=self.indexes[:self.size()] for i in index_now: if i!=-1: out.append(self.data[i]) out return out def heapify(self,list1):#把数组直接建立成一个最大堆 self.data=list1#python的列表动态的,直接赋值即可.不用管capacity self.capacity=len(list1) self.count=self.capacity for i in range((self.capacity-2)//2,-1,-1): self.shiftdown(i) return self.data def heapsort(self,list1):#直接pop 就实现了.因为前面都已经写好了 self.heapify(list1) while self.count>1: self.pop()#弹出一个 return self.data def get_item(self,i): return self.data[i] def change(self,i,newitem):#需要返回索引i在堆中的第几个坐标上.比如堆中第一个元素是10,那么change(10)返回0 #这种操作,叫反向查找技术,非常牛逼class,实现不难,思想牛逼.常用.思想很像dat #先修改data self.data[i]=newitem j=self.reverse[i] self.shiftup(j) self.shiftdown(j) return self.data[i] #下面是测试 print('测试大index堆') a=IndexMaxheap(10) a.insert(0,'wo')#0是索引,'wo'是value.对value比较大小来建堆,但是堆里面的元素都是index. a.insert(1,'we') a.insert(3,'a') a.change(3,'jjk') print(a.pop_index()) print(a.indexes)#弹出的元素不会彻底删除,而只是把它的索引放到self.size后面了. print(a.show_data()) #从这里面就看出来之前最大的wo已经被弹出了. #并且indexMaxheap也已经效果出来了.可以随意修改index为3的元素了,并且 #自动维护这个堆. #然而图论需要的是minheap.我改成indexminheap,特斯拉算法需要 #capacity是最大容量#是最小堆所以加入无穷做初始化,动态做data可以自己实现时候直接修改data的实现方式. class indexminheap(): def __init__(self,capacity):#capacity是最大容量 self.data=[float('inf')]*capacity #这个默认插入0非常不好,比如我索引只插入了1,'wo' 和 2,'we' #但是堆里面还是有10个元素.但是你如果不插入元素的画, #你建立堆时候给的元素少了会发生bug.没法比较进行堆维护. #所以使用的时候一定要注意,用多少capacity就给多少. #如果需要占位的时候,空余的占位要自己补上default值. self.indexes=[-1]*capacity #因为索引不可能是负的,所以用-1占位 self.reverse=[-1]*capacity #因为索引不可能是负的,所以用-1占位 self.count=0 self.capacity=capacity def size(self): return self.count def empty(self): return self.count==0 def shiftup(self,count): while count>0 and self.data[self.indexes[(count-1)//2]]>self.data[self.indexes[count]]: self.indexes[(count-1)//2],self.indexes[count]=self.indexes[count],self.indexes[(count-1)//2]#这一步只是交换index提高了交换效率 #下面2行是公里,因为上面变了,所以下面需要跑一下这2行.坐下对应修复 self.reverse[self.indexes[(count-1)//2]]=(count-1)//2 self.reverse[self.indexes[count]]=count count=(count-1)//2 def shiftdown(self,k):#把堆的k索引的元素进行shiftdown操作, #每一次这个操作都能把k位置作为head的子树给heapify了. while 2*k+1<=self.count-1 : left=2*k+1 right=min(2*k+2,self.count-1) tmpindex=k if self.data[self.indexes[left]]<self.data[self.indexes[k]]: tmpindex=left if self.data[self.indexes[right]]<self.data[self.indexes[tmpindex]]: tmpindex=right if tmpindex==k: return else: self.indexes[tmpindex],self.indexes[k]=self.indexes[k],self.indexes[tmpindex] self.reverse[self.indexes[tmpindex]]=tmpindex self.reverse[self.indexes[k]]=k k=tmpindex #插入索引为i的数据是item def insert(self,i,item):#建立只需要shift up assert(self.count+1<=self.capacity) assert(i>=0 and i<self.capacity) self.data[i]=item self.indexes[self.count]=i self.reverse[i]=self.count self.shiftup(self.count)#把count位置的元素向上移动维护堆 self.count+=1 def pop(self):#弹出堆定元素 assert(self.count>0) self.indexes[0],self.indexes[self.count-1]=self.indexes[self.count-1],self.indexes[0] self.reverse[self.indexes[0]]=0 self.reverse[self.indexes[self.count-1]]=-1#pop就不会被访问了所以给-1 output=self.data[self.indexes[self.count-1]] self.count-=1 if self.count>0: self.shiftdown(0)#把索引为0的进行shiftdown操作 return output def pop_index(self):#弹出堆定元素 assert(self.count>0) self.indexes[0],self.indexes[self.count-1]=self.indexes[self.count-1],self.indexes[0] self.reverse[self.indexes[0]]=0 self.reverse[self.indexes[self.count-1]]=-1#pop就不会被访问了所以给-1 output=self.indexes[self.count-1] self.count-=1 if self.count>0: self.shiftdown(0)#把索引为0的进行shiftdown操作 return output def show_data(self):#因为堆,需要不动态删除,为了速度.所以不要的元素只是把它放到count-1 这个 #index后面而已,通过show_data来读取data中有效元素 #利用index来遍历更准确和不会bug out=[] index_now=self.indexes[:self.size()] for i in index_now: if i!=-1: out.append(self.data[i]) out return out def heapify(self,list1):#把数组直接建立成一个最大堆 self.data=list1#python的列表动态的,直接赋值即可.不用管capacity self.capacity=len(list1) self.count=self.capacity for i in range((self.capacity-2)//2,-1,-1): self.shiftdown(i) return self.data def heapsort(self,list1):#直接pop 就实现了.因为前面都已经写好了 self.heapify(list1) while self.count>1: self.pop()#弹出一个 return self.data def get_item(self,i): return self.data[i] def change(self,i,newitem):#需要返回索引i在堆中的第几个坐标上.比如堆中第一个元素是10,那么change(10)返回0 #这种操作,叫反向查找技术,非常牛逼class,实现不难,思想牛逼.常用.思想很像dat #先修改data self.data[i]=newitem j=self.reverse[i] self.shiftup(j) self.shiftdown(j) return self.data[i] print('测试小index堆') #下面是测试 a=indexminheap(10) a.insert(0,'wo')#0是索引,'wo'是value.对value比较大小来建堆,但是堆里面的元素都是index. a.insert(1,'we') a.insert(3,'a') a.change(3,'jjk') print(a.pop_index()) print(a.indexes)#弹出的元素不会彻底删除,而只是把它的索引放到self.size后面了. print(a.show_data()) #从这里面就看出来之前最大的wo已经被弹出了. #并且indexMaxheap也已经效果出来了.可以随意修改index为3的元素了,并且 #自动维护这个堆. ''' 并查集: 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 = [] self.distance = float('inf') #为了在下面单源最短路径问题.所以这里直接建立时候初始化到无穷. def __cmp__(self,other): return cmp(self.distance, other.distance) def __lt__(self,other):#operator < return self.distance < other.distance def __ge__(self,other):#oprator >= return self.distance >= other.distance def __gt__(self,other):#oprator >= return self.distance > other.distance def __le__(self,other):#oprator <= return self.distance <= other.distance 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就行 ''' #左神用的是栈,来直接模拟递归过程.好像跟递归写法差不多 def dfs_me(node): visited = set([node])#set或者list自动是全局变量.在多少重子函数里面他都是能调用外面的list. def mini_dfs(node): print(node.val)#遍历的操作 for i in node.nexts: if i not in visited: mini_dfs(i) visited.add(i) mini_dfs(node) return print('ceshi0') dfs_me(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 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) ''' 慕课网课程: 自环边,平行边.有这2种边的叫复杂图. 我们上面左神的表达方法:就是邻接表. ''' ''' 单源最短路径:直接动态规划即可.很容易 显然满足最优子结构.a到b再到c的最短路径.一定是a到b的最短路径加上b到c的最短路径. ''' ''' dijkstra 地理课死啦算法.前提:图中不能有负权边. ''' #格式刷快捷键ctrl+k 再 ctrl+f def dijkstra(graph,start):#为了记录路径直接在node里面加一个last_node属性即可.他记录了这个节点在最短路径 #这个过程里面他的上一步是哪个节点. #然后继续加一个distance属性,来记录这个节点他距离最开始节点的距离. #start是一个node对象. visited = set() capacity=len(graph.nodes.values()) pq = indexminheap(capacity+1) #队列里面的东西是index是node.val data里面是node.distance这样就可以用索引堆来实现了 #初始化start点 start.distance = 0 start.last_node = start visited.add(start) #直接在优先队列里面存入node.只存入node即可.排序函数里面写distance即可. #修改时候直接修改node即可,对队列里面数值进行修改. pq.insert(start.val,start.distance) while pq.empty() != True: tmp = pq.pop_index() #所以一开始这个tmp就是start if tmp==-1:#表示弹到default值了,根本不是边了,边都处理完了 return tmp=graph.nodes[tmp] #value转node visited.add(tmp) #找tmp相邻的边. for i in tmp.edges: obj = i.to#obj是新节点v相连的to节点. if obj not in visited: if tmp.distance + i.weight < obj.distance: pq.change(obj.val,tmp.distance + i.weight) obj.distance = tmp.distance + i.weight #说是松弛操作,但是看起来非常像拉紧操作!他起名时脑袋进水了 ''' !!!!!!!!!!!!!这个地方好像有问题,虽然队列里面内容直接修改了,但是队列里面 自动会维护最小堆性质么?应该不自动维护最小堆性质. 但是为什么测试了2个例子全对?难道他自动维护堆了? ''' obj.last_node = tmp if obj.val not in pq.indexes: pq.insert(obj.val,obj.distance) #graph = #GraphGenerator([[1,2,3],[2,4,5],[2,6,7],[4,6,5],[1,6,99],[99,98,999]])这个是测试用的图 print('ceshi特斯拉开始') graph = GraphGenerator([[1,2,10],[1,4,5],[2,3,1],[2,4,2],[3,5,4],[4,2,3],[4,3,9],[4,5,2],[5,1,7] ,[5,3,6]]) dijkstra(graph,graph.nodes[1]) print(graph.nodes[5].last_node.val)#效果不错,返回2节点的last_node,也就是最短路径下的2的上一个节点 print('测试特斯拉结束,图论最难的算法') ''' 经过图论的学习.越来越发现莫装逼,装逼招雷劈,现有的算法能掌握好久已经不错了. 并查集,索引堆,哈希表,set,图论算法.能掌握熟练就已经很难了.像我之前写的用辅助set来替代索引堆果断打脸 幸好及时改过来了,毕竟半个多世纪的大神们发明的算法不是随便你一想就能做优化的.没几十年创造不了新算法. ''' ''' 总结一下我写的这个函数: 1.利用了辅助set技巧.来实现对队列里面元素的跟踪看他是否在队列中. 话说一个队列连一个in方法都没有,很坑! 2.设计最重要的部分,是因为python对于一个对象,如果把它放到任意容器中,你只要修改对象的属性,那么 在容器中的这个对象也自动修改.(同一个对象的本质应该是引用.)利用这点直接在队列外面修改对象属性 即可.不用管队列内部了.其实也没法访问队列内部,队列不能随机存取.用对象就避免了这个复杂操作. 从而提高时间效率. 3.对于最短路径的跟踪方法.也是用对象里面加入一个last_node属性.取值是一个node对象. 利用他就能查询最短路径的上一个节点是哪个节点了. ''' ''' bellman ford算法:处理带负权的边的图的单元最短路径问题.比上面的distra方法更简单.但是效率慢非常多. 算法不需要辅助结构,就是一码for 循环即可. ''' def bellmanford(graph,start): #初始化start点 start.distance = 0 #初始化,也是下面判断负权环的本质. start.last_node = start #求|vertex|-1就是松弛的次数. times=len(graph.nodes.values())-1 for i in range(times): for j in graph.edges: #做松弛操作 weight=j.weight u=j.from1 v=j.to if v.distance>u.distance+weight: v.distance=u.distance+weight v.last_node=u #判断是否有负权环 for j in graph.edges: weight=j.weight u=j.from1 v=j.to if v.distance>u.distance+weight: return False return True print('测试') bellmanford(graph,graph.nodes[1]) ''' 总结:图的单元最短路径问题: 1.权都>=0:一定用特斯拉方法.因为他速度快 2.bellman方法为啥么一定要松弛|vertex|-1次呢? 因为每一个点无法用自己把自己松弛了.只能用其他人把自己松弛了.所以至多上面这么多次.才合理 但是如果有负权圈.那么还会继续下降. 原因就是start这个点的0会发生变化,负权图会把0给降低.这显然就不符合最短路径了.所以直接return False ''' #下面测试优先队列里面修改后是否优先队列还能自动维护堆的性质. class node2(): def __init__(self,val): self.distance=val def __cmp__(self,other): return cmp(self.distance, other.distance) def __lt__(self,other):#operator < return self.distance < other.distance def __ge__(self,other):#oprator >= return self.distance >= other.distance def __gt__(self,other):#oprator >= return self.distance > other.distance def __le__(self,other):#oprator <= return self.distance <= other.distance #下面举个例子来说明索引堆的妙用:和堆的区别: pq = PQueue() a=node2(1) b=node2(2) c=node2(3) pq.put(a) pq.put(b) pq.put(c) a.distance=999999999999 print(pq.get().distance)#结果显然不维护.所以上面我写的特斯拉算法是错误的,还是需要索引堆来实现. class node1(): def __init__(self,val): self.distance=val pq = indexminheap(999) a=node1(1) b=node1(2) c=node1(3) pq.insert(1,a.distance) #插入元素第一个1,表示index,第二个a.distance表示他的优先级系数. pq.insert(2,b.distance) pq.insert(3,c.distance) pq.change(1,999) print(pq.pop())#用索引堆显然就得到了2.就是我们要的效果.用索引来修改堆中任意元素并且能维护堆 #这就是索引堆的强大之处.可以随意修改堆中元素的属性,还能同时维护堆的结构 #下面写floyd算法:他是求图中所有的2点之间的距离.当然效率N^3,很慢.只返回距离,不记录中间路线 def floyd(graph): node_all=graph.nodes.values() output={}#key是(from,to) value是distance #初始化output.#也就是把边的信息给output #自己到自己距离是0 for i in node_all: output[(i,i)]=0 for i in graph.edges: output[(i.from1,i.to)]=i.weight for k in node_all:#k是中间点 for i in node_all: for j in node_all: #python 括号内部的东西随便换行 output[(i,j)]=min(output.setdefault((i,k),float('inf'))+ output.setdefault((k,j),float('inf')), output.setdefault((i,j),float('inf'))) index_output={} for i in output: index_output[(i[0].val,i[1].val)]=output[i] return output,index_output print(floyd(graph)[1]) ''' graph = GraphGenerator([[1,2,10],[1,4,5],[2,3,1],[2,4,2],[3,5,4],[4,2,3],[4,3,9],[4,5,2],[5,1,7] ,[5,3,6]])上一行代码返回这个图的两两点最短距离,注意写的是单向图,2到4距离2 4到2距离是3.对于双向图很容易只需要把边里面from,to互换后跟以前矩阵一起送 给graph即可. '''