python 数据结构与算法
1 python常见数据结构性能
1.1 List
1.1.1 安索引取值和赋值
list最常用的操作是:按索引取值和赋值(v=a[i],a[i]=v),这两个操作执行时间与列表大小无关,均为O(1)。
1.1.2 列表append和__add__()
list.addend(v),执行时间是O(1)。
list0 = list1 + [v],执行时间是O(n+k),其中k是被加的列表长度。
1.1.3 使用timeit模块测试执行时间
使用timeit模块对函数计时:
1.1.4 List基本操作的大O数量级
1.2 Dict
1.2.1 dict数据类型
字典是根据key找到对应的value,最常用的取值get和赋值set,其性能都是O(1)。另外一个常用的操作判断字典中是否存在某个key (in),这个性能也是O(1)。
2 线性结构 Linear Structure
线性结构是一种有序数据项的集合,其中每个数据项都有唯一的前驱和后继(除了第一个和最后一个)。
2.1 栈Stack
栈是一种有次序的数据项集合,在栈中,数据项的加入和移除都仅发生在同一端,这一端叫做“栈顶top”,另一端叫做“栈底base”。
栈是一种后进先出LIFO:Last in First out,这是一种基于数据项保存时间的次序,时间越短的离栈顶越近,而时间越长的离栈底越近。
2.1.1 抽象数据类型Stack
抽象数据类型“栈”是一个有次序的数据集,每个数据项仅从“栈顶”一端加入到数据集中、从数据集中移除,栈具有后进先出LIFO的特性。
2.1.2 Stack的操作如下
stack():创建一个空栈,不包含任何数据项。
push(item):将item加入栈顶,无返回值。
pop():将栈顶的数据项移除,并返回,栈被修改。
peek():“窥视”栈顶数据项,返回栈顶的数据。
isEmpty():返回是否是空栈。
size():返回栈中有多少个数据项。
class Stack(object):
def __init__(self):
self.stack = []
def push(self, item):
self.stack.append(item)
def pop(self):
if not self.is_empty():
return self.stack.pop()
def peek(self):
if not self.is_empty():
return self.stack[self.size()-1]
def is_empty(self):
return len(self.stack) == 0
def size(self):
return len(self.stack)
2.1.3 栈的应用1:简单括号匹配
2.1.3.1 圆括号匹配
括号必须遵循“平衡”原则,即每个开括号必须有一个比括号对应。
从左到右扫描括号,最新打开的左括号,应该匹配最先遇到的右括号。这样,第一个左括号就应该匹配最后一个右括号,这种次序反转的识别,正好符合栈的特性。
流程图如下:
from data_structure.Stack.stack import Stack
def brackets_valid(expression):
stack = Stack()
for item in expression:
if item == "(":
stack.push(item)
elif item == ")":
if stack.is_empty():
return False
else:
stack.pop()
return stack.is_empty()
if __name__ == '__main__':
print(brackets_valid("()()()"))
print(brackets_valid("(()())(()"))
print(brackets_valid("((5+6)*(4+3))+((10-9)"))
2.1.3.2 通用括号匹配
实际应用中,会遇到更多种括号,比如:[], {},()。
from data_structure.Stack.stack import Stack
def brackets_valid(expression):
stack = Stack()
mapping = {
")": "(",
"]": "[",
"}": "{"
}
for item in expression:
if item in "([{":
stack.push(item)
elif item in ")]}":
if stack.is_empty():
return False
else:
if stack.peek() != mapping[item]:
return False
else:
stack.pop()
return stack.is_empty()
if __name__ == '__main__':
print(brackets_valid("()()()"))
print(brackets_valid("(()())(()"))
print(brackets_valid("((5+6)*(4+3))+((10-9)"))
print(brackets_valid("{{([][])}()}"))
print(brackets_valid("[[{{(())}}]]"))
print(brackets_valid("[][][](){}"))
print(brackets_valid("([)}"))
print(brackets_valid("((()]))"))
print(brackets_valid("[{()]"))
2.1.4 栈的应用2:进制转换
进制:指用多少字符来表示整数。十进制是0-9十个数字字符,二进制是0、1两个字符。
例如:十进制233对应的二进制是11101001,具体算法如下:
233 = 2102 + 3101 + 3100
11101001 = 127 + 126 + 125 + 024 + 123 + 022 +021 + 1*20
2.1.4.1 十进制转换二进制
常见的十进制转换二进制采用的是**“除2求余数”的算法。如下:35的二进制是100011。在除2求余的过程中,得到的余数是从低到高的次序,而输出则是从高到低,所以需要一个栈来反转次序**。
from data_structure.Stack.stack import Stack
def convert(num):
if not isinstance(num, int):
return False
stack = Stack()
while num:
num, remainder = divmod(num, 2)
stack.push(remainder)
result = ""
while not stack.is_empty():
result += str(stack.pop())
return result
if __name__ == '__main__':
print(f"35的二进制数是{convert(35)}")
2.1.4.2 十进制转换任意进制
将“除2求余”改为“除N求余”就可以将上面的算法扩展为任意进制转换。
十进制233对应八进制351,十六进制是E9。
主要区别是:
二进制:0-1
十进制:0-9
八进制:0-7
十六进制:0-9,A-F
from data_structure.Stack.stack import Stack
def convert(num, unit):
if not isinstance(num, int):
return False
dights = "0123456789ABCDEF"
stack = Stack()
while num:
num, remainder = divmod(num, unit)
stack.push(remainder)
result = ""
while not stack.is_empty():
result += str(dights[stack.pop()])
return result
if __name__ == '__main__':
print(f"35的二进制数是{convert(35, 2)}")
print(f"233的八进制数是{convert(233, 8)}")
print(f"233的十六进制数是{convert(233, 16)}")
2.1.5 栈的应用3:表达式转换
2.1.5.1 中缀表达式
操作符位于两个操作数之间的表示法,称为“中缀”表达式。例如:A+B。
2.1.5.2 全括号中缀表达式
计算机处理时最好避免复杂的优先级(乘除优先于加减、括号优先级)规则,能明确规定所有的计算顺序是最好的。因此,引入全括号表达式: ((A+B*C)+D)
2.1.5.3 前缀和后缀表达式
前缀表达式:将操作符移到操作数前面,即:+AB。
后缀表达式:将操作符移到操作数后面,即:AB+。
在前缀和后缀表达式中,操作符次序完全决定了运算的次序,不再混淆。
2.1.5.4 中缀表达式转换为前缀和后缀形式
2.1.5.5 通用的中缀转后缀算法
中缀表达式A+BC,对应的后缀表达式是 ABC+。其中,操作数ABC的顺序没有改变,操作符的出现顺序在后缀表达式中反转了。由于*的优先级比+高,所以后缀表达式中操作符的出现顺序与运算次序一致。
算法流程:
import string
from data_structure.Stack.stack import Stack
def convert_postfix(expression):
result = ""
oper_stack = Stack()
priority = {
"(": 0,
"+": 1,
"-": 1,
"*": 2,
"/": 2
}
for item in expression:
if item in string.ascii_letters or item in string.digits:
result += item
elif item == "(":
oper_stack.push(item)
elif item == ")":
while oper_stack.peek() != "(":
result += oper_stack.pop()
else:
oper_stack.pop()
elif item in "+-*/":
while oper_stack.peek() and \
priority[oper_stack.peek()] >= priority[item]:
result += oper_stack.pop()
else:
oper_stack.push(item)
while not oper_stack.is_empty():
result += oper_stack.pop()
return result
if __name__ == '__main__':
print(convert_postfix("A+B*C"))
print(convert_postfix("(A+B)*C"))
2.1.5.6 后缀表达式求值
对于后缀表达式从左到右扫描,由于操作符在操作数后面,所以要暂存操作数,在遇到操作符的时候再将暂存的两个操作数进行实际的计算。
例如: ABC*+,先扫描到了ABC暂存到栈中,扫描到*的时候,从栈中弹出2个操作数做乘法,做完之后再存入栈中;继续扫描到+,从栈中弹出2个操作数做加法。
注意:对于-/操作符,弹出的两个操作数顺序很重要,先弹出的是右操作数,后弹出的是左操作数。
算法流程:
import string
from data_structure.Stack.stack import Stack
from data_structure.Stack.infix_to_postfix import convert_postfix
def calculate(expression):
stack = Stack()
for item in expression:
if item in string.digits:
stack.push(item)
elif item in "+-*/":
first = stack.pop()
second = stack.pop()
stack.push(str(eval(second + item + first)))
return stack.pop()
if __name__ == '__main__':
print(calculate(convert_postfix("1+2*3")))
print(calculate(convert_postfix("(1+2)*3")))
2.2 队列
队列是一种有次序的数据集合,其特征是新数据项的添加总发生在一端(尾端rear),数据项的移除总发生在另一端(首端front),这种次序原则称为FIFO:first in first out。
当数据项加入对列,首先出现在队尾,随着队首数据项的移除,它主键接近队首。
2.2.1 抽象数据类型Queue
抽象数据类型Queue是一个有次序的数据集合。 数据仅允许在尾端添加,在首端删除,满足FIFO的操作次序。
2.2.1.1 Queue的操作
Queue():创建一个空队列对象,返回值为Queue对象;
enqueue(item):将数据项item添加到队尾,无返回值;
dequeue(item):从队首移除数据项,返回值为队首数据项,队列被修改;
isEmpty():返回是否空队列;
size():返回对了中数据项的个数。
class Queue(object):
def __init__(self):
self.queue = []
def enqueue(self, item):
# enqueue 复杂度为O(n)
self.queue.insert(0, item)
def dequeue(self):
# dequeue 复杂度为O(1)
if not self.is_empty():
return self.queue.pop()
def is_empty(self):
return len(self.queue) == 0
def size(self):
return len(self.queue)
2.2.1.2 队列应用1:热土豆问题(约瑟夫问题)
约瑟夫问题可以通过队列来解决:
- 将所有的人名放入队列中;
- 确定报数num,循环num次将队首的人出队再入队;循环结束后,移除队首的人;
- 直到最终队列中只剩1个人。
from data_structure.Queue.queue import Queue
def josef_quest(names, num):
if not isinstance(names, list) or len(names) == 0:
return
queue = Queue()
# 1. 所有人入队
for name in names:
queue.enqueue(name)
# 2. 循环num次,将队首人移出再添加到队尾。 循环结束后,将队首的人移除
while queue.size() > 1:
for i in range(num):
queue.enqueue(queue.dequeue())
queue.dequeue()
# 3. 返回剩余的1个人
return queue.dequeue()
if __name__ == '__main__':
print(josef_quest(["A", "B", "C", "D", "E", "F", "G"], 7))
2.3 双端队列
双端队列Deque是一种有次序的数据集。跟队列相似,其两端可以称作“首”“尾”端;但是双端队列首尾都可以移除或添加数据项。
2.3.1 抽象数据类型Deque
Deque():创建一个空双端队列;
add_front(item):将item加入队首;
add_rear(item):将item加入队尾;
remove_front():从队首移除数据项,返回值移除的数据项;
remove_rear():从队尾移除数据项,返回值为移除的数据项;
is_empty():返回deque是否为空;
size():返回deque中包含数据项的个数。
class Deque(object):
def __init__(self):
self.deque = []
def add_front(self, item):
# 时间复杂度O(n)
self.deque.insert(0, item)
def add_rear(self, item):
# 时间复杂度O(1)
self.deque.append(item)
def remove_front(self):
# 时间复杂度O(n)
if not self.is_empty():
return self.deque.pop(0)
def remove_rear(self):
# 时间复杂度O(1)
if not self.is_empty():
return self.deque.pop()
def is_empty(self):
return self.size() == 0
def size(self):
return len(self.deque)
if __name__ == '__main__':
d = Deque()
print(d.is_empty())
print(d.add_rear(4))
print(d.add_rear("dog"))
print(d.add_front("cat"))
print(d.add_front(True))
print(d.size())
print(d.is_empty())
print(d.add_rear(8.4))
print(d.remove_rear())
print(d.remove_front())
2.3.2 双端队列应用1:回文词
使用双端队列很容易解决回文词问题:
- 将词加入双端队列;
- 从双端队列两边分别移除字符,并判断是否相等。直到最红deque中剩下0个或1个字符。
from data_structure.Deque.deque import Deque
def pal_check(expression):
deque = Deque()
for i in expression:
deque.add_rear(i)
while deque.size() not in [0, 1]:
if deque.remove_front() != deque.remove_rear():
return False
else:
return True
if __name__ == '__main__':
print(pal_check("abccba"))
print(pal_check("abcba"))
print(pal_check("abcdba"))
2.4 链表
2.4.1 无序表抽象数据类型
2.4.1.1 列表List
一种数据项按照相对位置存放的数据集。数据项指按照存放位置来索引,没有其他关系。
2.4.1.2 无序表List的操作:
List():创建一个空列表;
add(item):添加一个数据项到列表中,假设item原先不存在于列表中;
remove(item):从列表中移除item;
search(item):在列表中查找item,返回布尔值;
is_empty():返回列表是否为空;
size():返回列表包含了多少数据项;
append(item):添加item到末尾,假设原先不存在item;
index(item):返回item的索引。
insert(pos, item):将数据项插入到位置pos,假设item原先不存在,并且列表有足够多的数据项能够插入;
pop():移除末尾的数据项,并返回数据
pop(pos):移除位置为pos的数据项,假设存在pos位置的数据项。
2.4.1.3 采用链表实现无序表
class Node(object):
def __init__(self, init_data):
self.data = init_data
self.next = None
def get_data(self):
return self.data
def get_next(self):
return self.next
def set_data(self, data):
self.data = data
def set_next(self, node):
self.next = node
from data_structure.LinkedList.node import Node
class UnsortedLinkedList(object):
def __init__(self):
self.head = None
# 队首添加,添加在队首最简单,如果添加在队尾需要遍历整个链表
def add(self, item):
node = Node(item)
# 1. 先将新node的下一个节点设置为self.head
node.set_next(self.head)
# 2. 再将self.head指向新加入的节点
self.head = node
def remove(self, data):
"""
根据传入的data,找到具体的node并删除。
:param data: 要删除的data
:return:
"""
current = self.head
previous = self.head
while current is not None:
if current.get_data() == data:
previous.set_next(current.get_next())
break
previous = current
current = current.get_next()
def search(self, data):
"""
根据传入的data,遍历链表查找是否有节点data等于传入的data,并返回是否找到。
:param data:
:return: Boolean
"""
current = self.head
while current is not None:
if current.get_data() == data:
return True
current = current.get_next()
else:
return False
def is_empty(self):
return self.size() == 0
def size(self):
count = 0
current = self.head
while current is not None:
count += 1
current = current.get_next()
return count
def append(self, item):
"""
链表尾部添加node
:param item: 要新增的节点data
:return:
"""
node = Node(item)
current = self.head
# 1.空链表
if self.is_empty():
self.head = node
# 2.非空链表
# 这里要拿到最后一个node,所以判断条件是 current.get_next() is not None
while current.get_next() is not None:
current = current.get_next()
else:
current.set_next(node)
def index(self, data):
"""
传入data,遍历链表,查找是否有node的data等于传入的data,
如果有返回下标,没有返回-1
:param data:
:return:
"""
index = 0
current = self.head
while current is not None:
if current.get_data() == data:
return index
current = current.get_next()
return -1
def insert(self, pos, data):
"""
:param pos: pos大于等于链表长度时添加在尾部,小于0时添加在首部
:param data: 要插入的data
:return:
"""
# 1. pos 大于等于列表长度时添加到对尾
if pos >= self.size():
self.append(data)
# 2. pos 小于等于0时,添加到队首
elif pos <= 0:
self.add(data)
else:
index = 0
current = self.head
node = Node(data)
while current is not None:
if index == pos:
node.set_next(current.get_next())
current.set_next(node)
break
index += 1
current = current.get_next()
def pop(self, pos=None):
"""
:param pos: 指定位置删除,pos大于等于链表长度或None代表删除最后一个节点;
pos小于等于0代表删除第一个节点
:return: 返回被删除的节点data
"""
previous = self.head
current = self.head
index = 0
data = None
if self.is_empty():
return None
if pos < 0:
data = self.head.get_data()
self.head.set_next(self.head.get_next())
return data
while current.get_next() is not None:
if index == pos:
previous.set_next(current.get_next())
data = current.get_data()
break
index += 1
previous = current
current = current.get_next()
else:
# 走到这里时,current是最后一个node,previous是倒数第二个node,
# index指向最后一个node下标;如果这时 pos>=index,就删除最后一个node
if pos >= index:
previous.set_next(current.get_next())
data = current.get_data()
return data
2.4.2 有序表抽象数据类型
有序表是一种数据项依照某些可比性质来决定在列表中的位置,按照顺序排列。
2.4.2.1 有序表操作如下
2.4.2.2 有序表实现
from data_structure.LinkedList.node import Node
class SortedLinkedList(object):
def __init__(self):
self.head = None
def add(self, data):
"""
:param data: 要插入的data
:return:
"""
node = Node(data)
if self.head is None:
self.head = node
return
elif data < self.head.get_data():
node.set_next(self.head)
self.head = node
else:
current = self.head
previous = self.head
while current is not None:
if data < current.get_data():
previous.set_next(node)
node.set_next(current)
break
previous = current
current = current.get_next()
def remove(self, data):
"""
根据传入的data,找到具体的node并删除。
:param data: 要删除的data
:return:
"""
current = self.head
previous = self.head
while current is not None:
if current.get_data() == data:
previous.set_next(current.get_next())
break
previous = current
current = current.get_next()
def search(self, data):
"""
根据传入的data,遍历链表查找是否有节点data等于传入的data,并返回是否找到。
:param data:
:return: Boolean
"""
current = self.head
while current is not None:
if current.get_data() == data:
return True
elif current.get_data() > data:
return False
else:
current = current.get_next()
else:
return False
def is_empty(self):
return self.size() == 0
def size(self):
count = 0
current = self.head
while current is not None:
count += 1
current = current.get_next()
return count
def index(self, data):
"""
传入data,遍历链表,查找是否有node的data等于传入的data,
如果有返回下标,没有返回-1
:param data:
:return:
"""
index = 0
current = self.head
while current is not None:
if current.get_data() == data:
return index
elif current.get_data() > data:
return -1
else:
current = current.get_next()
return -1
def pop(self, pos=None):
"""
:param pos: 指定位置删除,pos大于等于链表长度或None代表删除最后一个节点;
pos小于等于0代表删除第一个节点
:return: 返回被删除的节点data
"""
previous = self.head
current = self.head
index = 0
data = None
if self.is_empty():
return None
if pos < 0:
data = self.head.get_data()
self.head.set_next(self.head.get_next())
return data
while current.get_next() is not None:
if index == pos:
previous.set_next(current.get_next())
data = current.get_data()
break
index += 1
previous = current
current = current.get_next()
else:
# 走到这里时,current是最后一个node,previous是倒数第二个node,
# index指向最后一个node下标;如果这时 pos>=index,就删除最后一个node
if pos >= index:
previous.set_next(current.get_next())
data = current.get_data()
return data
2.4.2.3 链表实现的算法分析
3 递归
3.1 什么是递归 Recursion
3.2 初识递归
问题:给定一个列表,返回所有数的和。要求不能用for或while循环。
3.3 递归程序如何被执行
3.4 递归三定律 ※※※
前面数列求和问题套用递归三定律如下:
3.5 递归应用
3.5.1 整数转换为任意进制
def convert(num, unit):
convert_str = "0123456789ABCDEF"
if num < unit:
return convert_str[num]
num, remainder = divmod(num, unit)
return convert(num, unit) + str(remainder)
if __name__ == '__main__':
print(f"35的二进制数是{convert(35, 2)}")
print(f"233的八进制数是{convert(233, 8)}")
print(f"233的十六进制数是{convert(233, 16)}")
3.6 递归调用的实现
3.6.1 python递归深度限制
4 分治策略与递归
4.1 优化问题和贪心策略
4.1.1 贪心策略Greedy Method
4.1.2 贪心策略解决找零兑换问题
def change(change_money, coin_value_list):
for unit in coin_value_list:
if change_money >= unit:
consult, remainder = divmod(change_money, unit)
if remainder != 0:
# 减小规模 + 递归调用
return "%s*%s+" % (consult, unit) + change(remainder, coin_value_list)
else:
# 递归结束条件
return "%s*%s" % (consult, unit)
if __name__ == '__main__':
print(change(63, [25, 21, 10, 5, 1]))