概述
数据结构与算法框架:
本人在b站的《剑指offer》讲解视频(比较通俗易懂的类型,当时代码能力一般,欢迎交流和批评指正):
https://www.bilibili.com/video/BV145411x76D
https://www.bilibili.com/video/BV1oi4y1G7eJ
1.while True 必须和break结合,无异常时执行try,异常时执行except,break退出循环。
while True:
try:
do something
except:
break
2.raw_input()与input()
python2
存在raw_input()和input()两个函数
raw_input()将所有输入作为字符串看待,并且返回字符串类型
input()只用于数字的输入,返回所输入数字类型
python3
只存在input()函数,接收任意类型的输入,并且将输入默认为字符串类型处理,返回字符串类型,相当于python2的raw_input().
3.赋值a=b时,如果开辟新地址,b变化不会影响a;如果没有开辟新地址,则a、b做相同变化。
(1)可变对象、不可变对象:
可变对象:址传递,改变值不改变地址。(列表、字典、集合)
不可变对象:值传递,改变值必须改变地址。(数字、字符串、元组)
(2)赋值、深拷贝和浅拷贝的区别:
(深、浅拷贝分析的是可变对象情形下的的地址)
对于可变对象类型 List、Dictionary、Set,举例:列表alist和a:
import copy
a=[1,[2,3]]
alist=a #赋值
alist=copy.copy(a) #浅拷贝
alist=copy.deepcopy(a) #深拷贝
alist.append(1)
alist[0]=2
1)赋值:地址a和alist地址一样;改变alist,a作相同变化,。
2)copy.copy( )浅拷贝: 父对象开辟新地址,子对象地址不变。
3)copy.deepcopy( )深拷贝:父对象和子对象都开辟新地址。
4.链表
单向链表节点的表示:
class Node(object): #单向链表节点类
def __init__(self,data,next = None):
self.data = data
self.next = next
节点实例化:
node=Node('A',Node('B',Node('C')))
整体:
5.位运算:
a=60,b=13。
n小于0时,用补码表示:
if n < 0:
n = n & 0xffffffff
6.算数运算符:
注意:python3中, / 返回float,// 返回int
7.python3中,range()用法和列表切片经常搞混:
(1)range()返回的不是列表,而是一个包含索引的对象:
range()是一个函数,用的是括号和逗号。
for i in range(n): #顺着取索引,0~n-1
for i in range(n-1,-1,-1) #倒着取索引,n-1~0
(2)列表切片:
切片是取列表,用的是中括号和冒号。
list = [1, 2, 3, 4, 5, 6, 7 ]
list2[1:5] #[2, 3, 4, 5]
8.用sys模块输入:
A、用input()输入:
a=int(input()) #数字
b=list(map(int,input().split())) #数字元素的列表
print(a,b)
B、用sys模块输入:
import sys
(1)控制台单个数字输入:
a=int(sys.stdin.readline().strip())
(2)把这一行用空格分开的数字,变为列表:
b=list(map(int,sys.stdin.readline().strip().split()))
(3)指定行数 输入多行数据 返回二维list:
c=[]
for i in range(a):
cc=list(map(int,sys.stdin.readline().strip().split()))
c.append(cc)
print(c)
(4)不指定行数 输入多行数据 返回二维list:
d=[]
while True:
try:
dd=list(map(int,sys.stdin.readline().strip().split()))
if not dd:
break
d.append(dd)
except:
break
print(d)
9.子结构和子树的区别:
子树的意思是:
包含了一个结点,就得包含这个结点下的所有节点,一棵大小为n的二叉树有n个子树,就是分别以每个结点为根的子树。
子结构的意思是:
包含了一个结点,可以只取左子树或者右子树,或者都不取。
10、列表:
(1)插入:
.append(a) #在末尾加上元素a
.insert(index,a) #在指定位置index处加入元素a
.extend([1,2]) #加入新列表
a=a+b #直接加法
(2)删除:
.pop() #删除末尾元素
切片 #列表删除元素还可以进行切片操作。
11.a=[ ]或a=0或a=None,
都可代表False的意思,当not a 时相当于True。
13.树的创建与各种遍历:
(BFS,DFS==前,中,后)(非递归方式)
#树的遍历
class Node ():
def __init__(self,data,left=None,right=None): #也是值在前面,指针在后面,默认为None
self.data=data
self.left=left
self.right=right
class Tree():
def __init__(self,base):
self.base=base
def bfs(self,node):
queue=[]
queue.append(node)
while queue:
temp=queue.pop(0)
print(temp.data, end=' ')
left=temp.left
right=temp.right
if left:
queue.append(left)
if right:
queue.append(right)
def fro(self,node): #前序遍历==DFS
if not node:
return
stack=[]
stack.append(node)
while stack:
temp=stack.pop()
print(temp.data, end=' ')
right=temp.right
left=temp.left
if right:
stack.append(right)
if left:
stack.append(left)
def mid(self,node): #左-根-右,左节点放在栈的最右边
if not node:
return
stack = []
while stack or node:
while node:
stack.append(node)
node = node.left
node = stack.pop()
print(node.data,end=' ')
node = node.right
def beh(self,node):
if not node:
return
stack = []
while stack or node:
while node:
stack.append(node)
node = node.left if node.left else node.right
node = stack.pop()
print(node.data,end=' ')
if stack and stack[-1].left == node:
node = stack[-1].right
else:
node = None
base = Node(1,Node(2, Node(4), Node(5)), Node(3, Node(6), Node(7))) # 树的根节点
tree = Tree(base) # 树
print('BFS')
tree.bfs(tree.base)
print('n')
print('前序遍历==DFS')
tree.fro(tree.base)
print('n')
print('中序遍历')
tree.mid(tree.base)
print('n')
print('后序遍历')
tree.beh(tree.base)
print('n')
13、图的BFS、DFS:
# 定义无向图结构(相当于字典,键是一个节点,值是列表(储存相关联的所有节点))
graph = {
"A": ["B","C"],
"B": ["A", "C", "D"],
"C": ["A", "B", "D","E"],
"D": ["B", "C", "E","F"],
"E": ["C", "D"],
"F": ["D"],
}
def BFS(graph,vertex):
# 使用列表作为队列
queue = []
# 将首个节点添加到队列中
queue.append(vertex)
# 使用集合来存放已访问过的节点
looked = set()
# 将首个节点添加到集合中表示已访问
looked.add(vertex)
# 当队列不为空时进行遍历
while(len(queue)>0):
# 从队列头部取出一个节点并查询该节点的相邻节点
temp = queue.pop(0)
nodes = graph[temp]
# 遍历该节点的所有相邻节点
for w in nodes:
# 判断节点是否存在于已访问集合中,即是否已被访问过
if w not in looked:
# 若未被访问,则添加到队列中,同时添加到已访问集合中,表示已被访问
queue.append(w)
looked.add(w)
print(temp,end=' ')
def DFS(graph,vertex):
# 使用列表作为栈
stack = []
# 将首个元素添加到队列中
stack.append(vertex)
# 使用集合来存放已访问过的节点
looked = set()
# 将首个节点添加到集合中表示已访问
looked.add(vertex)
# 当队列不为空时进行遍历
while len(stack)>0:
# 从栈尾取出一个节点并查询该节点的相邻节点
temp = stack.pop()
nodes = graph[temp]
# 遍历该节点的所有相邻节点
for w in nodes:
# 判断节点是否存在于已访问集合中,即是否已被访问过
if w not in looked:
# 若未被访问,则添加到栈中,同时添加到已访问集合中,表示已被访问
stack.append(w)
looked.add(w)
print(temp,end=' ')
# 由于无向图无根节点,则需要手动传入首个节点,此处以"A"为例
print("BFS",end=" ")
BFS(graph,"A")
print("")
print("DFS",end=" ")
DFS(graph,"A")
BFS A B C D E F
DFS A C E D F B
14.二叉搜索树:
又称为二叉排序树(二叉查找树),它或许是一棵空树,或许是具有以下性质的二叉树:
1.若它的左子树不为空,则左子树上所有的节点的值小于根节点的值
2.若它的右子树不为空,则右子树上所有的节点的值都大于根节点的值
3.它的左右子树也分别是二叉搜索树
#中序遍历可以按顺序排列
15.排序函数:
sorted(iterable, cmp=None, key=None, reverse=False)
lambda函数:
add = lambda x, y : x+y
add(1,2) # 结果为3
17.itertools.permutations(ss)
输出字符串排序的不同方法,每个方法一个组合,集合成一个非常规对象,有重复的
import itertools
class Solution:
def Permutation(self, ss):
# write code here
if not ss:
return []
return sorted(list(set(map(''.join, itertools.permutations(ss)))))
#map(''.join,A) 转化为字符串组成的对象
#set() 返回无重复元素集,降重;可以看作不能重复的集合,也可看做set()对象。
#list() 转化为列表
#sorted() 排序
18.max( )函数、min( )函数:
max(x,y,z):返回x,y,z中最大元素。
max([1,1,2]):返回[1,1,2]中最大元素。(列表)
19.次数问题:创建字典,以元素为键,次数为值。
d={}
numbers=[1,1,2,2,2,3,3,3]
for i in numbers:
try:
dict[i]+=1
except:
dict[i]=1
pop( )、get()、del
对字典排序用sorted排序:(列表既可用sort也可用sorted)
a={1:2,3:4,5:1}
b=dict(sorted(a.items(),key=lambda b:b[1]))
print(b)
20.考试执行时,多用try-except语句避免异常:
try-except
try:
fun()
do something1
expect:
do something2
while-True-try-except(解决处理多个case)(最后再加,避免无法调试)
while True:
try:
a=sys.stdin.readline().strip()
if not a:
break
fun()
except:
break
21.sorted( )和sort( )的小区别:
#sort()改变了a,且不能赋值给b。
a=[1,4,3,2]
a.sort()
print(a)
#sorted()未改变a,改变后的对象赋值给b。
a=[1,4,3,2]
b=sorted(a)
print(a,b)
22.tab与空格的问题:
(1)tab与空格不能混用:同一列不能一个用tab,一个用空格。(pycharm里处理过的,所以只要对齐,就不用担心)
(2)建议缩进都用4个空格的长度(考试时一定要检查)
23.python中,{集合}不支持索引。
24.题义有时候很迷,直接看输入描述、输出描述,看的还明白些。
25.tuple可以作为字典的键,list不能。
26.索引函数
(1)字符串找索引函数:find、rfind
s.find('a'):返回s中a的最小索引
s.rfind('a'):返回s中a的最大索引
(2)列表索引函数:index
list.index(a):返回list中a的最小索引
27.collections库
import collections
d = collections.OrderedDict() #有序字典(输出顺序与添加顺序有关//无序字典无关)
a = collections.Counter(b) #计数器,Counter类型,加dict变成计数字典
28.if和elif的区别:
if:会一直遍历完所有的if,不管你想判断的条件有没有遍历到,他都会继续执行完所有的if。
elif :走到符合查询条件的语句后,后面所有的elif和else就不会再被执行。
29.五大算法思想(最优解算法):
(1)贪心算法:
在对问题求解时,总是作出在当前看来是最好的选择。(一件事情分为很多步,每步都做最好的选择)(局部最优>>全局最优,必须无后效性)
(2)动态规划算法:
每次决策依赖于当前状态,又随即引起 ‘状态的转移’。一个‘决策序列’就是在变化的状态中产生出来的,所以,这种多阶段最优化决策解决问题的过程就称为动态规划。(经分解后得到的子问题往往不是互相独立的,即下一个子阶段的求解是建立在上一个子阶段的解的基础上,进行进一步的求解)
(3)分治法:
分治法的设计思想是:将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。
(4)DFS(深度优先搜索):
(回溯法=DFS+剪枝)
在包含问题的所有解的解空间树中,按照深度优先搜索的策略,从根结点出发深度探索解空间树。当探索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯。(其实回溯法就是对隐式图的深度优先搜索算法)。
(5)BFS(广度优先搜索、分支限界法):
类似于回溯法,也是一种在问题的解空间树T上搜索问题解的算法。但在一般情况下,分支限界法与回溯法的求解目标不同。回溯法的求解目标是找出T中满足约束条件的所有解,而分支限界法的求解目标则是找出满足约束条件的一个解,或是在满足约束条件的解中找出使某一目标函数值达到极大或极小的解,即在某种意义下的最优解。
30.图:
32.哈希表(散列表):
哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表或哈希表。具体表现为: 存储位置 = f(key)
33.堆:
34.if-elif-else形成一套,只执行第一个满足条件的;后面再遇到if就是第二套:
输出:
35.round(x,2) #把x换为两位小数的浮点型
36.平衡二叉树(AVL):
定义:
一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
平衡树的判定:
#遍历每个结点,借助一个获取树深度的递归函数,根据该结点的左右子树高度差判断是否平衡,然后递归地对左右子树进行判断。
class Solution:
def IsBalanced_Solution(self, pRoot):
# write code here
if pRoot == None:
return True
if abs(self.TreeDepth(pRoot.left)-self.TreeDepth(pRoot.right)) > 1:
return False
return self.IsBalanced_Solution(pRoot.left) and self.IsBalanced_Solution(pRoot.right)
def TreeDepth(self, pRoot): #计算树的深度
# write code here
if pRoot == None:
return 0
nLeft = self.TreeDepth(pRoot.left) #左子树的深度
nRight = self.TreeDepth(pRoot.right)
return max(nLeft+1,nRight+1)
37.return可以返回一个判定语句,根据语句真实性选择True或False。
38.字符串:
(1)join() 方法用于将序列中的元素以指定的字符连接生成一个新的字符串
(把str插入序列元素之间)
str.join(sequence)
39.操作文件:
读取txt文件:
open()函数打开txt文件,返回 ‘file’ 类型;
file.readline( )方法 按照每一行划分,返回字符串组成的列表。
file = open('validation.txt','r')
number_list=file.readlines()
for i in range(len(number_list)):
number_list[i]=number_list[i].strip()
print(number_list)
40.操作文件夹:
读取文件夹,返回文件名组成的列表: #参数为路径,后面要有‘/’
a=os.listdir('velodyne/') #参数为路径,后面要有‘/’
print(a)
移动文件: #参数为文件名,要有文件名的后缀
shutil.move(src_path+number+'.bin',target_path+number+'.bin') #文件名
44、精心选择的数据结构可以带来更高的运行或者存储效率,数据结构往往同高效的检索算法和索引技术有关。
①队列:先入先出;单队列;双端队列。
②数组:最基本的数据结构;保存数据的个数在分配内存时是确定的;可以插入或删除数据。
③堆:一棵按顺序排列的完全二叉树。在存储时没有任何限制,可以访问任意节点。
最大堆:每个节点的值都大于等于它的孩子节点。
最小堆:每个节点的值都小于等于它的孩子节点。 对于下标为i的节点,它的子树的左节点的下标为2i,右节点为2i+1,父亲的节点下标为i/2(向下取整)。
④栈(stack):桶状线性结构;先进后出;只能在栈顶进行插入、删除操作。
⑤链表:在非连续的内存单元中保存数据;通过指针将各个内存单元链接在一起,最后一个节点的指针指向 NULL;不需要提前分配固定大小存储空间,当需要存储数据的时候分配一块内存并将这块内存插入链表中; 双链表;循环链表。
⑥树:
⑦图[G(V,E)]:有向图;无向图;图上的边或弧带有权则称为网;若任意两顶点都是连通的,则图就是连通图,有向则称为强连通图;无向图中连通且n个顶点n-1条边称为生成树;有向图中一顶点入度为0其余顶点入度为1的叫有向树。一个有向图由若干棵有向树构成生成森林。
45、如何判断单向链表中是否有环?
看快慢指针是否相遇。
46、哪种数据结构可以实现递归?
栈可以实现;递归需要保存正在计算的上下文, 等待当前计算完成后弹出,再继续计算, 只有栈先进后出的特性才能实现。
47、计算一个二叉树的最大距离有两个情况。
情况A: 路径经过左子树的最深节点,通过根节点,再到右子树的最深节点。
情况B: 路径不穿过根节点,而是左子树或右子树的最大距离路径,取其大者。 只需要计算这两个情况的路径距离,并取其大者,就是该二叉树的最大距离。
48、关于树:树的定义使用了递归的方式。
存储方式:
顺序存储→数组→满二叉树
链式存储→链表→其他二叉树
49、霍夫曼树(最优二叉树):一种带权路径长度最短的二叉树。
主要作用:数据压缩、缩短编码长度。
带权路径长度:WPL=(W1*L1+W2*L2+W3*L3+...+Wn*Ln),N个权值Wi(i=1,2,...n)构成一棵有N个叶结点的二叉树,相应的叶结点的路径长度为Li(i=1,2,...n)。可以证明霍夫曼树的WPL是最小的。
霍夫曼编码:C(2)+D(4)→T1(6)、B(5)+T1(6)→T2(11)、A(7)+T2(11)→霍夫曼树,算出霍夫曼树。然后从根节点出发,向左标记为0,向右标记为1,将字母串进行编码。
50.线索二叉树:若结点的左子树为空,则该结点的左孩子指针指向其前驱结点。
若结点的右子树为空,则该结点的右孩子指针指向其后继结点。
前驱节点:中序遍历前一个节点
后继节点:中序遍历后一个节点
51、
类变量:类名.变量名(定义时)(所有实例均可调用)
实例变量:self.变量名(定义时)(当前实例调用)
52、子类调用父类方法:
class 子类(父类):
def __init__(self,父类变量,父类变量,子类变量):
父类.__init__(self,父类变量,父类变量)
self.子类变量=子类变量
def 子类方法(self):
pass #这样,子类的实例就能用父类的方法了。
53.查找:
(1)二分查找(数组排好序,有重复,返回第一个):
def b_search(lst, val): #让left、right相遇,返回left
if not lst:
return -1
left = 0
right = len(lst) - 1
while left < right:
mid = (left+right) // 2
if lst[mid] >=val:
right=mid
else:
left = mid + 1
if lst[left]!=val:
return -1
return left
lst=[1,2,3,4,4,4,4,4,4,4,4,4,4,4,4]
print(b_search(lst,5))
(2)特别大的数据量,实现查找、排序:
a、位图法
位图法是我在编程珠玑上看到的一种比较新颖的方法,思路比较巧妙效率也很高。
使用场景举例:对2G的数据量进行排序,这是基本要求。
数据:1、每个数据不大于8亿;2、数据类型位int;3、每个数据最多重复一次。
内存:最多用200M的内存进行操作。
首先对占用的内存进行判断,每个数据不大于8亿,那么8亿是一个什么概念呢。
**
1 byte = 8 bit(位)
1024 byte = 8*1024 bit = 1k
1024 k = 8*1024*1024 bit = 1M = 8388608 bit
**
也就是1M=8388608位
而位图法的基本思想就是利用一位代表一个数字,例如3位上为1,则说明3在数据中出现过,若为0,则说明3在数据中没有出现过。所以当题目中出现每个数据最多重复一次这个条件时,我们可以考虑使用位图法来进行大数据排序。
那么假如使用位图法来进行这题的排序,内存占用多少呢。由题目知道每个数据不大于8亿,那么我们就需要8亿位,占用800000000/8388608=95M的空间,满足最多使用200M内存进行操作的条件,这也是这题能够使用位图法来解决的一个基础。
b、堆排序法
堆排序是4种平均时间复杂度为nlogn的排序方法之一,其优点在于当求M个数中的前n个最大数,和最小数的时候性能极好。所以当从海量数据中要找出前m个最大值或最小值,而对其他值没有要求时,使用堆排序法效果很好。
使用场景:从1亿个整数里找出100个最大的数
步骤:
(1)读取前100个数字,建立最大值堆。(这里采用堆排序将空间复杂度讲得很低,要排序1亿个数,但一次性只需读取100个数字,或者设置其他基数,不需要1次性读完所有数据,降低对内存要求)
(2)依次读取余下的数,与最大值堆作比较,维持最大值堆。可以每次读取的数量为一个磁盘页面,将每个页面的数据依次进堆比较,这样节省IO时间。
(3)将堆进行排序,即可得到100个有序最大值。
堆排序是一种常见的算法,但了解其的使用场景能够帮助我们更好的理解它。
c、较为通用的分治策略
分治策略师对常见复杂问题的一种万能的解决方法,虽然很多情况下,分治策略的解法都不是最优解,但是其通用性很强。分治法的核心就是将一个复杂的问题通过分解抽象成若干个简单的问题。
应用场景:10G的数据,在2G内存的单台机器上排序的算法
我的想法,这个场景既没有介绍数据是否有重复,也没有给出数据的范围,也不是求最大的个数。而通过分治虽然可能需要的io次数很多,但是对解决这个问题还是具有一定的可行性的。
步骤:
(1)从大数据中抽取样本,将需要排序的数据切分为多个样本数大致相等的区间,例如:1-100,101-300…
(2)将大数据文件切分为多个小数据文件,这里要考虑IO次数和硬件资源问题,例如可将小数据文件数设定为1G(要预留内存给执行时的程序使用)
(3)使用最优的算法对小数据文件的数据进行排序,将排序结果按照步骤1划分的区间进行存储
(4)对各个数据区间内的排序结果文件进行处理,最终每个区间得到一个排序结果的文件
(5)将各个区间的排序结果合并。通过分治将大数据变成小数据进行处理,再合并。
54.排序:
参考:https://blog.csdn.net/Dby_freedom/article/details/82154869
(1)冒泡排序:
时间复杂度为O(n2),空间复杂度为O(1) 。第一次把最大的冒泡到右边,第二次把第二大的冒泡到右边。
def bubble_sort(lst):
for j in range(len(lst)-1,0,-1): #每一次冒泡把最大的移到j的位置,一共n-1次
for i in range(j): #i不包括j的位置,i+1可以到j
if lst[i]>lst[i+1]:
lst[i],lst[i+1]=lst[i+1],lst[i]
return lst
lst=[3,2,1,4,5,6]
print(bubble_sort(lst))
(2)归并排序:
def merge_sort(lst):
if(len(lst) <= 1):
return lst
left = merge_sort(lst[:len(lst)//2])
right = merge_sort(lst[len(lst)//2:len(lst)])
result = []
while len(left) > 0 and len(right)> 0:
if( left[0] > right[0]):
result.append(right.pop(0))
else:
result.append(left.pop(0))
if(len(left)>0):
result.extend(merge_sort(left))
else:
result.extend(merge_sort(right))
return result
(3)插入排序:
(把未排序部分的第一个元素插入到排序部分合理的位置)
def insert_sort(ary):
for i in range(1, len(ary)):
key = i - 1
mark = ary[i] # 注: 必须将ary[i]赋值为mark,不能直接用ary[i]
while key >= 0 and ary[key] > mark:
ary[key+1] = ary[key]
key -= 1
ary[key+1] = mark
return ary
(4)希尔排序(不稳定):
def shell_sort(ary):
count = len(ary)
gap = round(count / 2)
# 双杠用于整除(向下取整),在python直接用 “/” 得到的永远是浮点数,
# 用round()得到四舍五入值
while gap >= 1:
for i in range(gap, count):
temp = ary[i]
j = i
while j - gap >= 0 and ary[j - gap] > temp: # 到这里与插入排序一样了
ary[j] = ary[j - gap]
j -= gap
ary[j] = temp
gap = round(gap / 2)
return ary
(5)堆排序(不稳定):
将待排序的序列构成一个大顶堆,这个时候整个序列的最大值就是堆顶的根节点,将它与末尾节点进行交换,然后末尾变成了最大值,然后剩余n-1个元素重新构成一个堆,这样得到这n个元素的次大值,反复进行以上操作便得到一个有序序列。
def heap_sort(ary):
n = len(ary)
first = int(n/2-1) #最后一个非叶子节点
for start in range(first,-1,-1): #构建最大堆
max_heapify(ary,start,n-1)
for end in range(n-1,0,-1): #堆排,将最大跟堆转换成有序数组
ary[end],ary[0] = ary[0], ary[end] #将根节点元素与最后叶子节点进行互换,取出最大根节点元素,对剩余节点重新构建最大堆
max_heapify(ary,0,end-1) #因为end上面取的是n-1,故而这里直接放end-1,相当于忽略了最后最大根节点元素ary[n-1]
return ary
#最大堆调整:将堆的末端子节点作调整,使得子节点永远小于父节点
#start为当前需要调整最大堆的位置,end为调整边界
def max_heapify(ary,start,end):
root = start
while True:
child = root * 2 + 1 #调整节点的子节点
if child > end:
break
if child + 1 <= end and ary[child] < ary[child+1]:
child = child + 1 #取较大的子节点
if ary[root] < ary[child]: #较大的子节点成为父节点
ary[root], ary[child] = ary[child], ary[root] #交换
root = child
else:
break
(6)选择排序(不稳定):
把未排序部分最小的(min)移动到排序部分的结尾。
(选择和冒泡有点像,都是把挑选出未排序部分的极值,移动到排序部分。
但是冒泡排序用的是冒泡的方式;选择排序用的是选择(逐一比较)的方式)
def select_sort(lst):
n = len(lst)
for i in range(0,n):
min = i #最小元素下标标记
for j in range(i+1,n):
if lst[j] < lst[min] :
min = j #找到最小值的下标
lst[min],lst[i] = lst[i],lst[min] #交换两者
return lst
(7)快速排序(不稳定):
def quick_sort(lst, start, end):
if start>=end:
return lst
left=start
right=end
mid=lst[left] #两个边界start、end,两个指针left、right;一个比较值mid
while left<right:
#右边比mid小的,和mid索引交换(此时mid索引为left);右边小于等于mid的,移动游标
while left<right and mid <=lst[right]:
right-=1
lst[left],lst[right]=lst[right],lst[left]
#左边比mid大的移到右边,和mid索引换(此时mid索引为right)
while left<right and mid>=lst[left]:
left+=1
lst[left],lst[right]=lst[right],lst[left]
#把mid索引重置为起始索引
mid=lst[left]
#对mid左右两部分分别快排,mid不要再包含进去
#不用再次切片,函数后两个参数就是切片
quick_sort(lst,start,left)
quick_sort(lst,left+1,end)
return lst
(8)top-K问题的解法:
a、局部淘汰法 -- 借助“冒泡排序”获取TopK
思路:(1)可以避免对所有数据进行排序,只排序部分;(2)冒泡排序是每一轮排序都会获得一个最大值,则K轮排序即可获得TopK。
时间复杂度空间复杂度:(1)时间复杂度:排序一轮是O(N),则K次排序总时间复杂度为:O(KN)。(2)空间复杂度:O(K),用来存放获得的topK,也可以O(1)遍历原数组的最后K个元素即可。
b、局部淘汰法 --"堆排序"获取TopK
思路:(1)堆:分为大顶堆(堆顶元素大于其他所有元素)和小顶堆(堆顶其他元素小于所有其他元素)。(2)我们使用小顶堆来实现。(3)取出K个元素放在另外的数组中,对这K个元素进行建堆。(4)然后循环从K下标位置遍历数据,只要元素大于堆顶,我们就将堆顶赋值为该元素,然后重新调整为小顶堆。(5)循环完毕后,K个元素的堆数组就是我们所需要的TopK。
时间复杂度与空间复杂度:(1)时间复杂度:每次对K个元素进行建堆,时间复杂度为:O(KlogK),加上N-K次的循环,则总时间复杂度为O((K+(N-K))logK),即O(NlogK),其中K为想要获取的TopK的数量N为总数据量。(2)空间复杂度:O(K),只需要新建一个K大小的数组用来存储topK即可。
c、分治法 -- 借助”快速排序“方法获取TopK
思路:(1)比如有10亿的数据,找处Top1000,我们先将10亿的数据分成1000份,每份100万条数据。(2)在每一份中找出对应的Top 1000,整合到一个数组中,得到100万条数据,这样过滤掉了999%%的数据。(3)使用快速排序对这100万条数据进行”一轮“排序,一轮排序之后指针的位置指向的数字假设为S,会将数组分为两部分,一部分大于S记作Si,一部分小于S记作Sj。(4)如果Si元素个数大于1000,我们对Si数组再进行一轮排序,再次将Si分成了Si和Sj。如果Si的元素小于1000,则我们需要在Sj中获取1000-count(Si)个元素的,也就是对Sj进行排序(5)如此递归下去即可获得TopK。
时间复杂度与空间复杂度:(1)时间复杂度:一份获取前TopK的时间复杂度:O((N/n)logK)。则所有份数为:O(NlogK),但是分治法我们会使用多核多机的资源,比如我们有S个线程同时处理。则时间复杂度为:O((N/S)logK)。之后进行快排序,一次的时间复杂度为:O(N),假设排序了M次之后得到结果,则时间复杂度为:O(MN)。所以 ,总时间复杂度大约为O(MN+(N/S)logK) 。(2)空间复杂度:需要每一份一个数组,则空间复杂度为O(N)。
55、哈希表:
Hash函数就是根据key计算出应该存储地址的位置id/index(就可得到value),而哈希表是基于哈希函数建立的一种查找表。
class HashTable:
def __init__(self, size):
self.elem = [None for i in range(size)] # 使用list数据结构作为哈希表元素保存方法
self.count = size # 最大表长
def hash(self, key):
return key % self.count # 散列函数采用除留余数法
def insert_hash(self, key, value):
"""插入关键字到哈希表内"""
address = self.hash(key) # 求散列地址
while self.elem[address]: # 当前位置已经有数据了,发生冲突。
address = (address + 1) % self.count # 线性探测下一地址是否可用
self.elem[address] = value # 没有冲突则直接保存。
def search_hash(self, key):
"""查找关键字,返回布尔值"""
star = address = self.hash(key)
while self.elem[address] != key:
address = (address + 1) % self.count
if not self.elem[address] or address == star: # 说明没找到或者循环到了开始的位置
return False
return True
解决哈希冲突:
a)开放定址法(用探查序列再搞一次)
为产生冲突的地址求得一个地址序列(),其中。其中m为表的长度,而增量有三种取值方法,根据三种探查序列划分:线性探测再散列,平方探测再散列,随即探测再散列。
b)链地址法(冲突时建立链表)
将所有Hash地址相同的记录都链接在同一链表中。
c)再Hash法(再哈希一次,直到不产生冲突)
同时构造多个不同的Hash函数,当产生冲突时,计算另一个Hash函数地址直到不再发生冲突为止。
d)建立公共溢出区
将Hash表分为基本表和溢出表,若是与基本表发生冲突,都放入溢出表。
56. 大顶堆怎么插入删除?
插入:
在一个大顶堆之后插入新的元素可能会破坏堆的结构,此时需要找到新插入节点的父节点,对堆进行自下而上的调整使其变成一个大顶堆。
删除:
将堆的最后一个元素填充到删除元素的位置,然后调整堆结构构造出新的大顶堆
57.堆栈区别?
一、空间分配区别:
1)栈(操作系统):由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。(类)
2)堆(操作系统): 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表。(实例)
二、缓存方式区别:
1)栈使用的是一级缓存,他们通常都是被调用时处于存储空间中,调用完毕立即释放;
2)堆是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收)。所以调用这些对象的速度要相对来得低一些。
堆:内存中,存储的是引用数据类型,引用数据类型无法确定大小,堆实际上是一个在内存中使用到内存中零散空间的链表结构的存储空间,堆的大小由引用类型的大小直接决定,引用类型的大小的变化直接影响到堆的变化
栈:是内存中存储值类型的,大小为2M,超出则会报错,内存溢出
三、数据结构区别:
堆(数据结构):堆可以被看成是一棵树,如:堆排序;
栈(数据结构):一种先进后出的数据结构。特点:先进后出,吃了吐。
58.栈溢出有哪些情况?
1)局部数组过大。当函数内部的数组过大时,有可能导致堆栈溢出。
2)递归调用层次太多。递归函数在运行时会执行压栈操作,当压栈次数太多时,也会导致堆栈溢出。
3)指针或数组越界。这种情况最常见,例如进行字符串拷贝,或处理用户输入等等。
59.递归和动态规划的异同:
用递归能解决的问题,一般都可以用动态规划来解决。
递归:
自顶向下,先解决大问题,再把大问题分解成小问题解决。
缺点:会重复计算相同的问题,相当耗时。
优点:不会记录每个问题的结果,所以内存消耗相对小。
动态规划:
自下向上,先解决小问题,再合并为解决大问题。
缺点:会记录每一个问题的结果,内存消耗较大。
优点:不会计算相同问题,时间消耗较小。
60.列表、元组、集合、字典:
最后
以上就是单身白开水为你收集整理的【笔试】python刷题笔记(基础)!数据结构与算法框架:本人在b站的《剑指offer》讲解视频(比较通俗易懂的类型,当时代码能力一般,欢迎交流和批评指正):1.while True 必须和break结合,无异常时执行try,异常时执行except,break退出循环。 2.raw_input()与input() 3.赋值a=b时,如果开辟新地址,b变化不会影响a;如果没有开辟新地址,则a、b做相同变化。 4.链表 5.位运算: 6.算数运算符: 7.python3中,range()用法和列表切的全部内容,希望文章能够帮你解决【笔试】python刷题笔记(基础)!数据结构与算法框架:本人在b站的《剑指offer》讲解视频(比较通俗易懂的类型,当时代码能力一般,欢迎交流和批评指正):1.while True 必须和break结合,无异常时执行try,异常时执行except,break退出循环。 2.raw_input()与input() 3.赋值a=b时,如果开辟新地址,b变化不会影响a;如果没有开辟新地址,则a、b做相同变化。 4.链表 5.位运算: 6.算数运算符: 7.python3中,range()用法和列表切所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复