只搞最基础的算法:1.线段树,数组树,堆排序,快排,并查集,二叉树遍历,优先队列
快排
'''快速排序''' '''我最新理解是,给数组头和尾两个指针, 然后分别向中间跑,如果发现逆序就交换,一直交换到指针相碰就停止就写好了fenge函数''' #关键就是这个分割函数的写法 def fenge(a): left=0 right=len(a)-1 tmp=a[0]#tmp就是分割的点的值 for i in range(len(a)): if left==right: #主体思想是2重循环来写,然后每一个循环都先加入break条件. break while 1: if left==right: break if a[right]<tmp: #如果有反序就交换过来 a[right],a[left]=a[left],a[right] break else: right-=1 while 1: #更上面完全类似了. if left==right: break if a[left]>tmp: a[right],a[left]=a[left],a[right] break else: left+=1 return a,left def sort(a): if len(a)==0: return [] if len(a)==2: if a[0]>a[1]: return [a[1],a[0]] else: return a if len(a)==1: return a a,left=fenge(a) #[a[left]]把一个数变成数组 return sort(a[:left])+[a[left]]+sort(a[left+1:]) a=[34,57,4,101,5,97,99,234,3423423] import random a=range(1,1000000) a=list(a) random.shuffle(a) print (sort(a)[:100])#百万级别还是有点慢大概10s不到的样子
算法主要就是需要一些经典的基础算法的熟练掌握和迅速写出理解这方面的东西最重要,现在开始补.
https://www.cnblogs.com/mycapple/category/402448.html 这个博客写的很详细.我把里面的思想都实现一下做练习
https://wenku.baidu.com/view/32652a2d7375a417866f8f51.html
1.线段树
#线段树:线段树的基本定义:线段树是解决数列维护问题的一种常用手段.基本能保证每一个操作 #的复杂度都是O(logn)的级别.而我们知道正常一个数组的查询是需要O(n)的也就是遍历. '''所以当n的级别很大到10亿以上,如果我们还需要1s内解决问题就需要用线段树来查询和修改''' '''对于二叉树而言:n0表示叶子节点的数目,n1为度为1的节点数目,n2为度为2的节点数目, 边的数目S=n1+2*n2''' '''度为0的节点数比度为2的节点数多1:证明:首先二叉树中子树的节点有n1+2n2个,所以二叉树的 总共节点数是1+n1+2n2,另外一种是n0+n1+2n2,所以证毕,并且也知道一个结论是二叉树的 节点数比边数多1''' '''利用上面2个结论我们知道线段树这个2叉树的节点数目是2n-1''' '''线段树处理这样的问题: 把问题简化一下: 在自然数,且所有的数不大于30000的范围内讨论一个问题:现在已知n条线段,把端点依次输入告诉你,然后有m个询问,每个询问输入一个点,要求这个点在多少条线段上出现过; 最基本的解法当然就是读一个点,就把所有线段比一下,看看在不在线段中; 每次询问都要把n条线段查一次,那么m次询问,就要运算m*n次,复杂度就是O(m*n) 这道题m和n都是30000,那么计算量达到了10^9;而计算机1秒的计算量大约是10^8的数量级,所以这种方法无论怎么优化都是超时 —– 因为n条线段是固定的,所以某种程度上说每次都把n条线段查一遍有大量的重复和浪费; 线段树就是可以解决这类问题的数据结构 举例说明:已知线段[2,5] [4,6] [0,7];求点2,4,7分别出现了多少次''' '''http://hzwer.com/670.html 下面我写的不清楚就继续看上面这个url,又是一个不懂感觉很神秘,懂了感觉很平凡的东西.发明的 人还是吊. 这个网址上面的题目和代码,从他的分析可以看出 用线段树把原来O(mn) 的算法变成了O(mlog30000+nlog30000) 的算法,m和n也都取成3万, 那么我们有左边是10亿右边是几十万,这就是线段树的快捷地方,他快捷的本质是其实不用每一次都 重新问一个数字是否在一个线段里面,而只需要把线段的信息都一次储存好,然后直接询问每一个 要找的数字即可''' #下面开始实现这个问题 #每一个节点 class jiedian: def __init__(self,start,end): self.start,self.end=start,end self.left,self.right=None,None self.count=0#在这个题目里面作为计数的工具使用 #创立这个二叉树,我们问题里面start就是0,end就是30000,因为是线段 #所以需要包含端点,0是自然数. def build(start,end): if start>end: return None root=jiedian(start,end) if start==end: return root root.left=build(start,(start+end)//2) root.right=build((start+end)//2+1,end) return root #下面我们就开始创立我们的简单例子里面[0,7]这个线段树. a=build(0,7) #下面我们写一个把一个区间插入到线段树里面的函数,然后把对应的拆分的count+1 #对于插入的区间要分类讨论,比如我插入一个[-99,0]的这种不合逻辑的也需要加强程序鲁邦性. def charu(root,start,end):#用函数的封装root来实现迭代 if start<root.start: start=root.start if end >root.end: end=root.end if root.start==start and root.end==end: root.count+=1 return if root.start>end: return if root.end<start: return if end<=root.left.end: charu(root.left,start,end) if start>=root.left.end+1: charu(root.right,start,end) ##charu(a,-99,99) ##print (a.count)#经过实验这里面成功输出1这个数字.基本上讨论了所有情况. #下面写查询代码即可,相类似.因为这个题目只是每一次找一个数字不是区间所以比上面更容易一点. def chaxun(root,obj): if obj>root.end: return 0 if obj<root.start: return 0 if obj==root.start==root.end: return root.count if obj<=(root.start+root.end)//2: return chaxun(root.left,obj)+root.count if obj>=(root.start+root.end)//2+1: return chaxun(root.right,obj)+root.count #下面开始重头插入和查询 charu(a,-99,0) charu(a,-99,0) charu(a,-99,0) charu(a,-99,0) charu(a,-99,1) answer=chaxun(a,1) print (answer)
2.数组树
##http://www.cnblogs.com/mycapple/archive/2012/08/09/2630430.html '''下面我们继续搞树状数组,也就是数组树''' '''传统数组(共n个元素)的元素修改和连续元素求和的复杂度分别为O(1)和O(n)。树状数组通过将线性结构转换成伪树状结构(线性结构只能逐个扫描元素,而树状结构可以实现跳跃 式扫描),使得修改和求和复杂度均为O(lgn),大大提高了整体效率。''' '''这个东西很神秘,数学上很深. 比如一个数组有8个元素记作a1,....a8 那么做一个新数组c,要求这个数组能加出a1到an的任何一个sum n从1到8 并且这个数组的生成最快,做的运算最少来得到这个数组c''' '''这个数组是 c1=a1 c2=a1+a2 c3=a3 c4=a1+a2+a3+a4 c5=a5 c6=a5+a6 c7=a7 c8=a1+a2+a3+a4+a5+a6+a7+a8''' '''对于这个c数组如何找到很神秘''' '''https://www.cnblogs.com/hsd-/p/6139376.html''' '''上面这个微博里面的图形很形象表现出来了, 我保存到了2.png'''#ps:从这里面说明我想做的ide带图片的多重要,不然神他妈能说清楚. '''也就是上面这个链接从上往下数的第五个picture,说的很明白了,图里面你从上往下看, 看这个树,他的第一个元素就是c数组里面需要的元素.真心形象.也保证了这个树的每一个列都取 一个,因为 我们只取最北的那一个就行了''' '''那么问题来了,改如何证明?感觉还是一个图论的问题,还是从树上入手 1.首先这种取法能保证任何a1加到an都能分解成c里面的和,这很显然,因为 从树这个图能看出来任何的求和,也就是最下面一排的取一个a1到am,都可以分解c里面的和''' '''2.还需要证明这种c的算法最节约时间的,首先2分是显然的思路,3分或者更高显然速度慢, 所以我们只需要讨论图里面为什么c不选其他的点就行了.那么只需要讨论一个,比如c4就可以了 ,我们假设c4不取到最北的点,比如c4取为他下面一个点也就是c4=a3+a4, 这样显然我们如果其他点都不变的话,a1,a1+a2,a1+a2+a3的计算次数 都没变化,但是a1+a2+a3+a4的计算次数多一次. 再看后面a1+a2+a3+a4+a5也是要多一次,归纳法显然后面的嘉禾都多一次, 所以时间复杂度加了O(N),所以变差了,再用归纳法对c4,c5...做归纳,就得到这种算法是最快 的结论,证毕.当然这只是一个初步证明,话说我就喜欢骗自己,当不会证明时候也非要写一种伪证明 来骗自己,否则不敢用.总怕有反例,然而骗自己证明了就随便用...总感觉不证明的东西,用起来 总保证不了是最好的算法,不够完美.''' '''代码实现都是很多数学推导利用二进制来进行拆分,写了也记不住,只写到理解算法的层面了.'''
3.堆的简单建立
'''堆的定义: 堆是一种特殊的树形数据结构,每个节点都有一个值, 通常我们所说的堆的数据结构指的是二叉树。堆的特点是根节点的值最大(或者最小)''' '''那么我们为什么要用堆而不用list呢,因为堆的插入查找,特别是查找前n个大的元素, 这些操作都是log(N)的,而list是O(N)的''' '''heapq这个模块是最小堆的实现 heap = [] #创建了一个空堆 heappush(heap,item) #往堆中插入一条新的值 item = heappop(heap) #从堆中弹出最小值 item = heap[0] #查看堆中最小值,不弹出 heapify(x) #以线性时间讲一个列表转化为堆 #!!然后像数组一样用下表来访问就行了,下面是2个更封装的方法 nlargest(n, iterable, key=None) Find the n largest elements in a dataset. Equivalent to: sorted(iterable, key=key, reverse=True)[:n] nsmallest(n, iterable, key=None) Find the n smallest elements in a dataset. ''' '''用上面这些命令来做堆就够了,当然是从实用方面讲''' from heapq import * a=[1,23,2,13,2,4,32,432,4,2] heapify(a) print (a)#这时候打印就知道了,a还是一个list的类型,但是他已经是一个 #heap排列的数组了.本质上还是heap. heappush(a,9999) print (a[3]) #访问第四小 print (a[-1])#访问最大. print (type(a)) print (nsmallest(3,a))#打印前3个最小的 #堆的缺陷:他不是稳定的算法.堆里面元素的波动太大了,都是块的移动. '''下面进行改进把他改成稳定的算法''' b = [(1,1),(1,2),(1,-1),(1,-2131),(2,34234),(543,4534),(1,1342),(1,5461),(1,1342),(1,5461),(1,1342),(1,5461),] shuchu=nsmallest(len(b),b,key=lambda x:x[0]) print (shuchu) #经过实验python自带的堆排序也是稳定的.anyway,其实加一个索引就能让一个不稳定的排序变稳定. #一个方法是建一个字典,然后扫一次list,遇到相同的才储存进去,也就是按照第一个元素去掉只有一次的. #那么这个字典也就记录了第一个元素有重复的那些数据,然后堆排序之后,再从字典里面换就行了. #但是这个建立字典速度很慢.先放着 '''下面手动写堆''' '''做大根堆然后输出升序排列''' '''https://www.cnblogs.com/chengxiao/p/6129630.html''' def adjust(heap,heapsize,root):#root是这个数组其中一个角标 left=2*root+1#首先得到左右的坐标 right=left+1 #需要判断left是否超过了数组长度,也就是是否有 #左右kid larger=root if left<heapsize and heap[larger]<heap[left]: #larger表示变动的index larger=left if right<heapsize and heap[larger]<heap[right]: larger=right if larger!=root: heap[larger],heap[root]=heap[root],heap[larger] adjust(heap,heapsize,larger)#本质就是这里面只需要adjust一个,而不用adjust两个.做了二分所以是logn #那么为什么只需要一个adjust呢,因为下面sort函数是从下往上建立的 #每一次你adjust如果这个根子叔的root没变那么他是不用adjust的,因为下面层已经 #在之前的for循环里面拍好了 #上面这2步要递归因为破坏了下面的堆 def sort(heap): #倒着拍好堆,为什么要倒着拍.因为要让大的数一直上去, #所以要从下往上升才行. for i in range(len(heap)): j=len(heap)//2-1-i#最后一个非叶子节点开始,也就是for所有的有叶子的节点逆着走 if j<0: break adjust(heap,len(heap),j) #下面就是出数了,首先交换堆顶和堆尾 for i in range(len(heap)): j=len(heap)-i-1 if j<0: break heap[0],heap[j]=heap[j],heap[0] adjust(heap,j,0) return heap import copy #下面做测试: import random a=range(10000) a=list(a) random.shuffle(a) print (sort(a)[:100])
4.并查集
# -*- coding: utf-8 -*- '''并查集(Union-Find)算法详解''' ##原始地址下面,写的很好 '''https://www.cnblogs.com/learnbydoing/p/6896472.html?utm_source=itdadao&utm_medium=referral''' class QuickFind(object): id=[]#n给多少就产生多少个id,从0到n-1这n个id count=0 #下面函数就是生成id而已 def __init__(self,n): self.count = n i=0 while i<n: self.id.append(i) i+=1 #判断2个id是否相连的函数 def connected(self,p,q): return self.find(p) == self.find(q) def find(self,p): return self.id[p] #下面就是并查集的核心代码!union函数 #union函数理解:把p,q这2个index对应的点进行相连,那么就等价于 def union(self,p,q): idp = self.find(p)#这地方需要先记录一下self.find(p)因为一会儿要复制改变数组,这个等式右边会变化. if not self.connected(p,q): for i in range(len(self.id)): if self.id[i]==idp: # 将p所在组内的所有节点的id都设为q的当前id,显然每一次union的复杂度是O(N). #算法也不是很快,但是也基本上没有更快的了.log(N),感觉好像存在.每一次用二分方法来查找 #替代这个for循环. #即表示把所有跟p相连的都改成q的id, #这个步奏很叼,可以把union(p,q) 等效于union(q,p).证明很显然,因为你把所有是p的都改成q,跟把所有是q的都改成p是一样的. self.id[i] = self.id[q] self.count -= 1 #count来输出最后这个set分成多少个不相交的集合. # -*- coding: utf-8 -*- qf = QuickFind(10) print ("initial id list is %s" % (",").join(str(x) for x in qf.id)) list = [ (4,3), (3,8), (6,5), (9,4), (2,1), (8,9), (5,0), (7,2), (6,1), (1,0), (6,7) ] for k in list: p = k[0] q = k[1] #把p,q进行union操作 qf.union(p,q) print ("%d and %d is connected? %s" % (p,q,str(qf.connected(p,q) ))) print ("final id list is %s" % (",").join(str(x) for x in qf.id)) print ("count of components is: %d" % qf.count) #从下面打印可以看出来相同的元素对应的id也是相同的.就是通过union函数实现的这个功能. print (qf.id)
5.二叉树遍历
#二叉树的各种遍历的递归实现: #深度:前序,后序,中序.和广度优先遍历.这么一共4中方法. class node(object): def __init__(self,data=None,left=None,right=None): self.data=data self.left=left self.right=right #深度遍历里面的前序遍历:把root放前面,所以遍历顺序是root,left,right def front_depth(root): print (root.data) if root.left!=None: front_depth(root.left) if root.right!=None: front_depth(root.right) aa=node(4) bb=node(5) a=node(2,aa,bb) b=node(3) root=node(1,a,b) front_depth(root) #成功输出12453 #其他后序,中序都类似.所以再写一个广度优先 print ('**********************') def wide(root): #用数组来实现 if type(root)!=list: root=[root] b=[] if type(root)==list: b=[] for i in root: print (i.data) for i in root: if i.left!=None: b.append(i.left) if i.right!=None: b.append(i.right) if b!=[]: wide(b) wide(root) #输出了12345
6.加入添加,删除操作的堆排序
'''堆的定义: 堆是一种特殊的树形数据结构,每个节点都有一个值, 通常我们所说的堆的数据结构指的是二叉树。堆的特点是根节点的值最大(或者最小)''' '''那么我们为什么要用堆而不用list呢,因为堆的插入查找,特别是查找前n个大的元素, 这些操作都是log(N)的,而list是O(N)的''' '''heapq这个模块是最小堆的实现 heap = [] #创建了一个空堆 heappush(heap,item) #往堆中插入一条新的值 item = heappop(heap) #从堆中弹出最小值 item = heap[0] #查看堆中最小值,不弹出 heapify(x) #以线性时间讲一个列表转化为堆 #!!然后像数组一样用下表来访问就行了,下面是2个更封装的方法 nlargest(n, iterable, key=None) Find the n largest elements in a dataset. Equivalent to: sorted(iterable, key=key, reverse=True)[:n] nsmallest(n, iterable, key=None) Find the n smallest elements in a dataset. ''' '''用上面这些命令来做堆就够了,当然是从实用方面讲''' from heapq import * a=[1,23,2,13,2,4,32,432,4,2] heapify(a) print (a)#这时候打印就知道了,a还是一个list的类型,但是他已经是一个 #heap排列的数组了.本质上还是heap. heappush(a,9999) print (a[3]) #访问第四小 print (a[-1])#访问最大. print (type(a)) print (nsmallest(3,a))#打印前3个最小的 #堆的缺陷:他不是稳定的算法.堆里面元素的波动太大了,都是块的移动. '''下面进行改进把他改成稳定的算法''' b = [(1,1),(1,2),(1,-1),(1,-2131),(2,34234),(543,4534),(1,1342),(1,5461),(1,1342),(1,5461),(1,1342),(1,5461),] shuchu=nsmallest(len(b),b,key=lambda x:x[0]) print (shuchu) #经过实验python自带的堆排序也是稳定的.anyway,其实加一个索引就能让一个不稳定的排序变稳定. #一个方法是建一个字典,然后扫一次list,遇到相同的才储存进去,也就是按照第一个元素去掉只有一次的. #那么这个字典也就记录了第一个元素有重复的那些数据,然后堆排序之后,再从字典里面换就行了. #但是这个建立字典速度很慢.先放着 '''下面手动写堆''' '''做大根堆然后输出升序排列''' '''https://www.cnblogs.com/chengxiao/p/6129630.html''' def adjust(heap,heapsize,root):#root是这个数组其中一个角标 left=2*root+1#首先得到左右的坐标 right=left+1 #需要判断left是否超过了数组长度,也就是是否有 #左右kid larger=root if left<heapsize and heap[larger]<heap[left]: #larger表示变动的index larger=left if right<heapsize and heap[larger]<heap[right]: larger=right if larger!=root: heap[larger],heap[root]=heap[root],heap[larger] adjust(heap,heapsize,larger)#本质就是这里面只需要adjust一个,而不用adjust两个.做了二分所以是logn #那么为什么只需要一个adjust呢,因为下面sort函数是从下往上建立的 #每一次你adjust如果这个根子叔的root没变那么他是不用adjust的,因为下面层已经 #在之前的for循环里面拍好了 #上面这2步要递归因为破坏了下面的堆 def sort(heap): #倒着拍好堆,为什么要倒着拍.因为要让大的数一直上去, #所以要从下往上升才行. for i in range(len(heap)): j=len(heap)//2-1-i#最后一个非叶子节点开始,也就是for所有的有叶子的节点逆着走 if j<0: break adjust(heap,len(heap),j) #下面就是出数了,首先交换堆顶和堆尾 for i in range(len(heap)): j=len(heap)-i-1 if j<0: break heap[0],heap[j]=heap[j],heap[0] adjust(heap,j,0) return heap #继续加入一个新功能:往堆里面加入一个数,然后保持堆. #思路:显然要把他放到最后面一个.然后从最后一个非叶子节点开始调整堆即可.原因就是为了让这个数参与所有的比较 #从而保证调整完堆里面各个位置都是正确的.并且你入过放到root上也放不上去啊,因为是一个满二叉树哪有位置给你放. #只有最后的叶子能放.#很容易,把上面代码贴过来就差不多了. def input_(heap,num): heap.append(num) for i in range(len(heap)): j=len(heap)//2-1-i#最后一个非叶子节点开始,也就是for所有的有叶子的节点逆着走 if j<0: break adjust(heap,len(heap),j) return heap #堆里面删除一个数也一样. def delete(heap,num): tmpheap=[] for i in range(len(heap)): if heap[i]!=num: tmpheap.append(heap[i]) print (tmpheap) heap=tmpheap for i in range(len(heap)): j=len(heap)//2-1-i#最后一个非叶子节点开始,也就是for所有的有叶子的节点逆着走 if j<0: break adjust(heap,len(heap),j) print (heap) return heap import copy #下面做测试: import random a=range(100) a=list(a) random.shuffle(a) a=input_(a,-1) a=delete(a,-1) print (sort(a)[:10])
7.堆排序最强版,可以直接一个数组中输出最大的n个数
'''堆的定义: 堆是一种特殊的树形数据结构,每个节点都有一个值, 通常我们所说的堆的数据结构指的是二叉树。堆的特点是根节点的值最大(或者最小)''' '''那么我们为什么要用堆而不用list呢,因为堆的插入查找,特别是查找前n个大的元素, 这些操作都是log(N)的,而list是O(N)的''' '''heapq这个模块是最小堆的实现 heap = [] #创建了一个空堆 heappush(heap,item) #往堆中插入一条新的值 item = heappop(heap) #从堆中弹出最小值 item = heap[0] #查看堆中最小值,不弹出 heapify(x) #以线性时间讲一个列表转化为堆 #!!然后像数组一样用下表来访问就行了,下面是2个更封装的方法 nlargest(n, iterable, key=None) Find the n largest elements in a dataset. Equivalent to: sorted(iterable, key=key, reverse=True)[:n] nsmallest(n, iterable, key=None) Find the n smallest elements in a dataset. ''' '''用上面这些命令来做堆就够了,当然是从实用方面讲''' from heapq import * a=[1,23,2,13,2,4,32,432,4,2] heapify(a) print (a)#这时候打印就知道了,a还是一个list的类型,但是他已经是一个 #heap排列的数组了.本质上还是heap. heappush(a,9999) print (a[3]) #访问第四小 print (a[-1])#访问最大. print (type(a)) print (nsmallest(3,a))#打印前3个最小的 #堆的缺陷:他不是稳定的算法.堆里面元素的波动太大了,都是块的移动. '''下面进行改进把他改成稳定的算法''' b = [(1,1),(1,2),(1,-1),(1,-2131),(2,34234),(543,4534),(1,1342),(1,5461),(1,1342),(1,5461),(1,1342),(1,5461),] shuchu=nsmallest(len(b),b,key=lambda x:x[0]) print (shuchu) #经过实验python自带的堆排序也是稳定的.anyway,其实加一个索引就能让一个不稳定的排序变稳定. #一个方法是建一个字典,然后扫一次list,遇到相同的才储存进去,也就是按照第一个元素去掉只有一次的. #那么这个字典也就记录了第一个元素有重复的那些数据,然后堆排序之后,再从字典里面换就行了. #但是这个建立字典速度很慢.先放着 '''下面手动写堆''' '''做大根堆然后输出升序排列''' '''https://www.cnblogs.com/chengxiao/p/6129630.html''' def adjust(heap,heapsize,root):#root是这个数组其中一个角标 left=2*root+1#首先得到左右的坐标 right=left+1 #需要判断left是否超过了数组长度,也就是是否有 #左右kid larger=root if left<heapsize and heap[larger]<heap[left]: #larger表示变动的index larger=left if right<heapsize and heap[larger]<heap[right]: larger=right if larger!=root: heap[larger],heap[root]=heap[root],heap[larger] adjust(heap,heapsize,larger)#本质就是这里面只需要adjust一个,而不用adjust两个.做了二分所以是logn #那么为什么只需要一个adjust呢,因为下面sort函数是从下往上建立的 #每一次你adjust如果这个根子叔的root没变那么他是不用adjust的,因为下面层已经 #在之前的for循环里面拍好了 #上面这2步要递归因为破坏了下面的堆 def sort(heap): #倒着拍好堆,为什么要倒着拍.因为要让大的数一直上去, #所以要从下往上升才行. for i in range(len(heap)): j=len(heap)//2-1-i#最后一个非叶子节点开始,也就是for所有的有叶子的节点逆着走 if j<0: break adjust(heap,len(heap),j) #下面就是出数了,首先交换堆顶和堆尾 for i in range(len(heap)): j=len(heap)-i-1 if j<0: break heap[0],heap[j]=heap[j],heap[0] adjust(heap,j,0) return heap #继续加入一个新功能:往堆里面加入一个数,然后保持堆. #思路:显然要把他放到最后面一个.然后从最后一个非叶子节点开始调整堆即可.原因就是为了让这个数参与所有的比较 #从而保证调整完堆里面各个位置都是正确的.并且你入过放到root上也放不上去啊,因为是一个满二叉树哪有位置给你放. #只有最后的叶子能放.#很容易,把上面代码贴过来就差不多了. def input_(heap,num): heap.append(num) for i in range(len(heap)): j=len(heap)//2-1-i#最后一个非叶子节点开始,也就是for所有的有叶子的节点逆着走 if j<0: break adjust(heap,len(heap),j) return heap #堆里面删除一个数也一样. def delete(heap,num): tmpheap=[] for i in range(len(heap)): if heap[i]!=num: tmpheap.append(heap[i]) heap=tmpheap for i in range(len(heap)): j=len(heap)//2-1-i#最后一个非叶子节点开始,也就是for所有的有叶子的节点逆着走 if j<0: break adjust(heap,len(heap),j) return heap #最后再加一个max_multi函数,输出前multi个大的数. def max_multi(heap,multi): #倒着拍好堆,为什么要倒着拍.因为要让大的数一直上去, #所以要从下往上升才行. for i in range(len(heap)): j=len(heap)//2-1-i#最后一个非叶子节点开始,也就是for所有的有叶子的节点逆着走 if j<0: break adjust(heap,len(heap),j) #下面就是出数了,首先交换堆顶和堆尾,治理面不是全排列,而是只输出multi这么多个就行了. for i in range(multi): j=len(heap)-i-1 if j<0: break heap[0],heap[j]=heap[j],heap[0] adjust(heap,j,0) return heap[-1:len(heap)-multi-1:-1] import copy #下面做测试: import random a=range(1000) a=list(a) random.shuffle(a) a=input_(a,-1) a=delete(a,-1) print (sort(a)[:10]) #输出前n个最大的 print ('输出前n个最大的') print (max_multi(a,10))
8.优先队列
'''一个最大优先队列支持下列操作: Insert(S,x)把元素x插入集合S中 Maximum(S)返回S中具有最大关键字的元素 Extract-Max(S)去掉并且返回S中具有最大关键字的元素 Increase-Key(S,x,k)将原来元素x的关键字的值增加到k。这里k>x''' '''恰好下面代码实现的也是最大堆''' #下面就在代码的最后天上优先队列的实现. '''堆的定义: 堆是一种特殊的树形数据结构,每个节点都有一个值, 通常我们所说的堆的数据结构指的是二叉树。堆的特点是根节点的值最大(或者最小)''' '''那么我们为什么要用堆而不用list呢,因为堆的插入查找,特别是查找前n个大的元素, 这些操作都是log(N)的,而list是O(N)的''' '''heapq这个模块是最小堆的实现 heap = [] #创建了一个空堆 heappush(heap,item) #往堆中插入一条新的值 item = heappop(heap) #从堆中弹出最小值 item = heap[0] #查看堆中最小值,不弹出 heapify(x) #以线性时间讲一个列表转化为堆 #!!然后像数组一样用下表来访问就行了,下面是2个更封装的方法 nlargest(n, iterable, key=None) Find the n largest elements in a dataset. Equivalent to: sorted(iterable, key=key, reverse=True)[:n] nsmallest(n, iterable, key=None) Find the n smallest elements in a dataset. ''' '''用上面这些命令来做堆就够了,当然是从实用方面讲''' from heapq import * a=[1,23,2,13,2,4,32,432,4,2] heapify(a) print (a)#这时候打印就知道了,a还是一个list的类型,但是他已经是一个 #heap排列的数组了.本质上还是heap. heappush(a,9999) print (a[3]) #访问第四小 print (a[-1])#访问最大. print (type(a)) print (nsmallest(3,a))#打印前3个最小的 #堆的缺陷:他不是稳定的算法.堆里面元素的波动太大了,都是块的移动. '''下面进行改进把他改成稳定的算法''' b = [(1,1),(1,2),(1,-1),(1,-2131),(2,34234),(543,4534),(1,1342),(1,5461),(1,1342),(1,5461),(1,1342),(1,5461),] shuchu=nsmallest(len(b),b,key=lambda x:x[0]) print (shuchu) #经过实验python自带的堆排序也是稳定的.anyway,其实加一个索引就能让一个不稳定的排序变稳定. #一个方法是建一个字典,然后扫一次list,遇到相同的才储存进去,也就是按照第一个元素去掉只有一次的. #那么这个字典也就记录了第一个元素有重复的那些数据,然后堆排序之后,再从字典里面换就行了. #但是这个建立字典速度很慢.先放着 '''下面手动写堆''' ########注意我们下面手动写的堆是大根堆!!!!!!!!!! '''做大根堆然后输出升序排列''' '''https://www.cnblogs.com/chengxiao/p/6129630.html''' def adjust(heap,heapsize,root):#root是这个数组其中一个角标 left=2*root+1#首先得到左右的坐标 right=left+1 #需要判断left是否超过了数组长度,也就是是否有 #左右kid larger=root if left<heapsize and heap[larger]<heap[left]: #larger表示变动的index larger=left if right<heapsize and heap[larger]<heap[right]: larger=right if larger!=root: heap[larger],heap[root]=heap[root],heap[larger] adjust(heap,heapsize,larger)#本质就是这里面只需要adjust一个,而不用adjust两个.做了二分所以是logn #那么为什么只需要一个adjust呢,因为下面sort函数是从下往上建立的 #每一次你adjust如果这个根子叔的root没变那么他是不用adjust的,因为下面层已经 #在之前的for循环里面拍好了 #上面这2步要递归因为破坏了下面的堆 def sort(heap): #倒着拍好堆,为什么要倒着拍.因为要让大的数一直上去, #所以要从下往上升才行. for i in range(len(heap)): j=len(heap)//2-1-i#最后一个非叶子节点开始,也就是for所有的有叶子的节点逆着走 if j<0: break adjust(heap,len(heap),j) #下面就是出数了,首先交换堆顶和堆尾 for i in range(len(heap)): j=len(heap)-i-1 if j<0: break heap[0],heap[j]=heap[j],heap[0] adjust(heap,j,0) return heap #继续加入一个新功能:往堆里面加入一个数,然后保持堆. #思路:显然要把他放到最后面一个.然后从最后一个非叶子节点开始调整堆即可.原因就是为了让这个数参与所有的比较 #从而保证调整完堆里面各个位置都是正确的.并且你入过放到root上也放不上去啊,因为是一个满二叉树哪有位置给你放. #只有最后的叶子能放.#很容易,把上面代码贴过来就差不多了. def input_(heap,num): heap.append(num) for i in range(len(heap)): j=len(heap)//2-1-i#最后一个非叶子节点开始,也就是for所有的有叶子的节点逆着走 if j<0: break adjust(heap,len(heap),j) return heap #堆里面删除一个数也一样. def delete(heap,num): tmpheap=[] for i in range(len(heap)): if heap[i]!=num: tmpheap.append(heap[i]) heap=tmpheap for i in range(len(heap)): j=len(heap)//2-1-i#最后一个非叶子节点开始,也就是for所有的有叶子的节点逆着走 if j<0: break adjust(heap,len(heap),j) return heap #最后再加一个max_multi函数,输出前multi个大的数. def max_multi(heap,multi): #倒着拍好堆,为什么要倒着拍.因为要让大的数一直上去, #所以要从下往上升才行. for i in range(len(heap)): j=len(heap)//2-1-i#最后一个非叶子节点开始,也就是for所有的有叶子的节点逆着走 if j<0: break adjust(heap,len(heap),j) #下面就是出数了,首先交换堆顶和堆尾,治理面不是全排列,而是只输出multi这么多个就行了. for i in range(multi): j=len(heap)-i-1 if j<0: break heap[0],heap[j]=heap[j],heap[0] adjust(heap,j,0) return heap[-1:len(heap)-multi-1:-1] import copy #下面做测试: import random a=range(3) a=list(a) random.shuffle(a) '''一个最大优先队列支持下列操作: Insert(S,x)把元素x插入集合S中 Maximum(S)返回S中具有最大关键字的元素 Extract-Max(S)去掉并且返回S中具有最大关键字的元素 Increase-Key(S,x,k)将原来元素x的关键字的值增加到k。这里k>x的原关键字的值''' '''恰好下面代码实现的也是最大堆''' #下面就在代码的最后天上优先队列的实现. #首先是建立最大有限队列. def creat(heap): for i in range(len(heap)): j=len(heap)//2-1-i#最后一个非叶子节点开始,也就是for所有的有叶子的节点逆着走 if j<0: break adjust(heap,len(heap),j) return heap def Insert(S,x): return input_(S,x) def Maximum(heap): return heap[0] def ExtractMax(heap): a=heap[0] heap[0]=heap[len(heap)-1] adjust(heap,len(heap)-1,0) heap.pop() #注意这里面不能写heap=heap[:-1].这涉及到list的复制拷贝和原地址修改的区别问题.我 #另写一个总结来写这个问题,今天偶然遇到了.这里面只能用pop print (heap) return heap[0] print (creat(a)) print (Insert(a,3)) print (Maximum(a)) ExtractMax(a) print (a) def Increase_Key(heap,x,k):#说白了就是把heap[x]这个值增加到k,然后维护这个堆. heap[x]=k for i in range (min(len(heap)//2-1,x),-1,-1): adjust(heap,len(heap),i) return heap Increase_Key(a,0,-99999) print (a)#效果还不错,都出来了. #最后写一下最大优先队列的应用,他的使用是电脑里面的任务分配器,给一些程序设置优先级, #当一个程序结束了,就等价于ExtractMax一个程序,之后需要再调用下面的最大优先级的程序,就是 #继续ExtractMax,然后调整优先级就是Increase_Key即可. #这个东西非常牛逼的地方是这些所有操作都是log(N)的复杂度,如果用其他的比如快速排序什么的都是Nlog(N) #因为他们不能维护这个数组里面东西大小的关系.比如你快排排好了,然后Increase一下,之后立马又需要快拍一次. #而堆这个东西不用排序,因为他已经有相对顺序了,还是logN复杂度.
9.基数排序,桶排序
'''没什么人用的排序算法,都是一些特俗东西,设计还算巧妙''' '''计数排序: 比如有4455676,那么就开一个长度为8的数组a, a[0]表示0出现的次数,所以这个数组结果是00002221, 然后显然再O(N)把数据生成出来即可''' def count_sort(data,max):#需要实现知道上界才好用 a=[0]*(max+1) b=[] for i in range(len(data)): a[data[i]]+=1 for i in range(len(a)): for j in range(a[i]): b.append(i) return b print (count_sort([1,5,5,7,8,9,0],9))#太简单了.
'''桶排序:针对大数据的排序,非常重要!!!!!!''' '''思路就是先把数据进行切分,假设数据在一个区间上是均匀分布的,那么比如先弄10个桶代表这些小区间, 然后每一个桶内部都分好了,之后再拼起来即可''' def bucket_sort(data,max,min): num_of_bucket=3 interval=(max-min)/num_of_bucket a=[] #建立桶: c=[] for i in range((num_of_bucket)): c.append([]) for i in range((num_of_bucket)+1): a.append(min+i*interval) print (a) for i in range(len(data)): for j in range(1,len(a)): if data[i]==a[0]: c[0].append(data[i]) break if data[i]<=a[j]: c[j-1].append(data[i]) break for i in range(len(c)): c[i]=sorted(c[i]) return c import numpy as np a=np.random.rand(30) d=bucket_sort(a,1,0) print (d)#搞定桶排序
10.'''一个利用1.5N的时间同时找到数组里面最大值和最小值的算法'''
'''一个利用1.5N的时间同时找到数组里面最大值和最小值的算法''' '''思想就是做一个对(ai,ai+1),把他里面大的跟tmpbig比,小的跟tmpsmall比,所以用3次比了2个元素.''' def max_and_min(a): if len(a)==1: return a[0],a[0] else: tmpbig=max(a[0],a[1]) tmpsmall=min(a[0],a[1]) for i in range(2,len(a)-1,2): if a[i]<=a[i+1]: tmpbig=max(tmpbig,a[i+1]) tmpsmall=min(tmpsmall,a[i]) if a[i]>a[i+1]: tmpbig=max(tmpbig,a[i]) tmpsmall=min(tmpsmall,a[i+1]) return tmpbig,tmpsmall print (max_and_min([1,4,65,87,45,4,54,5,45,45]))
11.随机快速排序
'''随机快速快速排序,本质就是在选择分点位a[0]之前,先把数组进行一次 洗牌,随机抽一个index与a[0]交换即可.让a[0]随机起来''' '''我最新理解是,给数组头和尾两个指针, 然后分别向中间跑,如果发现逆序就交换,一直交换到指针相碰就停止就写好了fenge函数''' #关键就是这个分割函数的写法 import random def fenge(a): left=0 right=len(a)-1 #改成随机快拍 randnum=random.randint(left,right) a[0],a[randnum]=a[randnum],a[0] tmp=a[0]#tmp就是分割的点的值 for i in range(len(a)): if left==right: #主体思想是2重循环来写,然后每一个循环都先加入break条件. break while 1: if left==right: break if a[right]<tmp: #如果有反序就交换过来 a[right],a[left]=a[left],a[right] break else: right-=1 while 1: #更上面完全类似了. if left==right: break if a[left]>tmp: a[right],a[left]=a[left],a[right] break else: left+=1 return a,left def sort(a): if len(a)==0: return [] if len(a)==2: if a[0]>a[1]: return [a[1],a[0]] else: return a if len(a)==1: return a a,left=fenge(a) #[a[left]]把一个数变成数组 return sort(a[:left])+[a[left]]+sort(a[left+1:]) a=[34,57,4,101,5,97,99,234,3423423] import random a=range(1,1000) a=list(a) random.shuffle(a) print (sort(a)[:100])#百万级别还是有点慢大概10s不到的样子
12.真心难写的队列实现,直接用数组模拟最好
'''队列的本质就是循环数组,这里面要说一下,一定要注意 是队列,还是双向队列,还是优先队列.优先队列有最大优先队列,和最小优先队列, 我刚写过,优先队列不是队列而是堆!!!!!!!!!!!!!!! 而单向队列和双向队列才是队列,本质就是循环数组实现,再加入俩个指针head和end''' #先弄单项队列: #其实直接用python list里面的方法pop和append也实现了出队和入队的功能. #下面假设我闲的蛋疼要自己写一个input output class queue(): def __init__(self,a,max_len=4):#写一个默认长度为10的队列 self.head=len(a)%max_len t=-1%max_len self.data=a for i in range(len(a),max_len): a.append(None) self.tail=t self.max_len=max_len def input(self,num): if len(self.data)!=0 and self.head==(self.tail+1)%self.max_len and self.data[self.head-1]!=None :#注意这2个input和output条件一样需要加一个[0]元素 #判断.注意这个len要写前面用短路判断才行,不然后面的index0会报错 print ('error,queue is full') return self.data[self.tail]=num self.tail-=1 self.tail%=self.max_len def output(self): if len(self.data)==0 or self.head==(self.tail+1)%self.max_len and self.data[self.head-1]==None : print ('error,queue is empty') return self.data[self.head-1]=None self.head-=1 self.head%=self.max_len d=queue([1,23]) d.input(5) print (d.data) d.output() print (9999999999) print (d.data) d.output() print (d.data) d.output() print (d.data) d.output() print (d.data) d.output() print ('232222222222') c=queue([]) print (c.data) print (c.head) print (c.tail) c.output() #自己手动写queue好难,边界条件太多.改的我都吐了.
13.堆继续修改
'''堆的定义: 堆是一种特殊的树形数据结构,每个节点都有一个值, 通常我们所说的堆的数据结构指的是二叉树。堆的特点是根节点的值最大(或者最小)''' '''那么我们为什么要用堆而不用list呢,因为堆的插入查找,特别是查找前n个大的元素, 这些操作都是log(N)的,而list是O(N)的''' '''heapq这个模块是最小堆的实现 heap = [] #创建了一个空堆 heappush(heap,item) #往堆中插入一条新的值 item = heappop(heap) #从堆中弹出最小值 item = heap[0] #查看堆中最小值,不弹出 heapify(x) #以线性时间讲一个列表转化为堆 #!!然后像数组一样用下表来访问就行了,下面是2个更封装的方法 nlargest(n, iterable, key=None) Find the n largest elements in a dataset. Equivalent to: sorted(iterable, key=key, reverse=True)[:n] nsmallest(n, iterable, key=None) Find the n smallest elements in a dataset. ''' '''用上面这些命令来做堆就够了,当然是从实用方面讲''' from heapq import * a=[1,23,2,13,2,4,32,432,4,2] heapify(a) print (a)#这时候打印就知道了,a还是一个list的类型,但是他已经是一个 #heap排列的数组了.本质上还是heap. heappush(a,9999) print (a[3]) #访问第四小 print (a[-1])#访问最大. print (type(a)) print (nsmallest(3,a))#打印前3个最小的 #堆的缺陷:他不是稳定的算法.堆里面元素的波动太大了,都是块的移动. '''下面进行改进把他改成稳定的算法''' b = [(1,1),(1,2),(1,-1),(1,-2131),(2,34234),(543,4534),(1,1342),(1,5461),(1,1342),(1,5461),(1,1342),(1,5461),] shuchu=nsmallest(len(b),b,key=lambda x:x[0]) print (shuchu) #经过实验python自带的堆排序也是稳定的.anyway,其实加一个索引就能让一个不稳定的排序变稳定. #一个方法是建一个字典,然后扫一次list,遇到相同的才储存进去,也就是按照第一个元素去掉只有一次的. #那么这个字典也就记录了第一个元素有重复的那些数据,然后堆排序之后,再从字典里面换就行了. #但是这个建立字典速度很慢.先放着 '''下面手动写堆''' '''做大根堆然后输出升序排列''' '''https://www.cnblogs.com/chengxiao/p/6129630.html''' def adjust(heap,heapsize,root):#root是这个数组其中一个角标 left=2*root+1#首先得到左右的坐标 right=left+1 #需要判断left是否超过了数组长度,也就是是否有 #左右kid larger=root if left<heapsize and heap[larger]<heap[left]: #larger表示变动的index larger=left if right<heapsize and heap[larger]<heap[right]: larger=right if larger!=root: heap[larger],heap[root]=heap[root],heap[larger] adjust(heap,heapsize,larger)#本质就是这里面只需要adjust一个,而不用adjust两个.做了二分所以是logn #那么为什么只需要一个adjust呢,因为下面sort函数是从下往上建立的 #每一次你adjust如果这个根子叔的root没变那么他是不用adjust的,因为下面层已经 #在之前的for循环里面拍好了 #上面这2步要递归因为破坏了下面的堆 def sort(heap): #倒着拍好堆,为什么要倒着拍.因为要让大的数一直上去, #所以要从下往上升才行. for i in range(len(heap)): j=len(heap)//2-1-i#最后一个非叶子节点开始,也就是for所有的有叶子的节点逆着走 if j<0: break adjust(heap,len(heap),j) #下面就是出数了,首先交换堆顶和堆尾 for i in range(len(heap)): j=len(heap)-i-1 if j<0: break heap[0],heap[j]=heap[j],heap[0] adjust(heap,j,0) return heap #继续加入一个新功能:往堆里面加入一个数,然后保持堆. #思路:显然要把他放到最后面一个.然后从最后一个非叶子节点开始调整堆即可.原因就是为了让这个数参与所有的比较 #从而保证调整完堆里面各个位置都是正确的.并且你入过放到root上也放不上去啊,因为是一个满二叉树哪有位置给你放. #只有最后的叶子能放.#很容易,把上面代码贴过来就差不多了. def input_(heap,num): heap.append(num) for i in range(len(heap)): j=len(heap)//2-1-i#最后一个非叶子节点开始,也就是for所有的有叶子的节点逆着走 if j<0: break adjust(heap,len(heap),j) return heap #堆里面删除一个数也一样. def delete_all(heap,num): tmpheap=[] for i in range(len(heap)): if heap[i]!=num: tmpheap.append(heap[i]) heap=tmpheap for i in range(len(heap)): j=len(heap)//2-1-i#最后一个非叶子节点开始,也就是for所有的有叶子的节点逆着走 if j<0: break adjust(heap,len(heap),j) return heap #实际上,堆真正能用的是删除堆顶元素,上面写的删除这种东西效率是O(N),太慢了,完全没有意义用堆 def pop(heap): a=heap[0] heap[0],heap[-1]=heap[-1],heap[0] adjust(heap,len(heap)-1,0) heap=heap.pop() print (heap) return a #最后再加一个max_multi函数,输出前multi个大的数. def max_multi(heap,multi): #倒着拍好堆,为什么要倒着拍.因为要让大的数一直上去, #所以要从下往上升才行. for i in range(len(heap)): j=len(heap)//2-1-i#最后一个非叶子节点开始,也就是for所有的有叶子的节点逆着走 if j<0: break adjust(heap,len(heap),j) #下面就是出数了,首先交换堆顶和堆尾,治理面不是全排列,而是只输出multi这么多个就行了. for i in range(multi): j=len(heap)-i-1 if j<0: break heap[0],heap[j]=heap[j],heap[0] adjust(heap,j,0) return heap[-1:len(heap)-multi-1:-1] def creat(heap):#建立一个大根堆: for i in range(len(heap)): j=len(heap)//2-1-i#最后一个非叶子节点开始,也就是for所有的有叶子的节点逆着走 if j<0: break adjust(heap,len(heap),j) return heap def delete_only_once(heap,num_tobe_deledted): pass import copy #下面做测试: import random a=range(10) a=list(a) random.shuffle(a) a=creat(a) print ('tanchu') print (pop(a)) print (a)
看知乎上面说,如果要删除堆中任意一个元素的话,只需要做一个hash表来储存每个值对应的index就行