目录
- 前置知识
文章: Python算法-概念篇
- 排序
- 冒泡排序
- 选择排序
- 插入排序
- 谢尔排序
- 归并排序
- 快速排序
- 查找
- 顺序查找(已排序的表/未排序的表)
- 二分查找
- 常见基本算法
- 最大公约数
- 斐波那契数列
- 判断素数
- 变位词
- 栈
- 括号匹配
- 前中后缀表达式
- 进制转换
- 队列
- 热土豆问题(约瑟夫问题)
- 回文序列
- 递归
- 数列求和
- 汉诺塔
- 探索迷宫
- 递归画树
- 递归画螺旋曲线
- 递归画三角形
- 优化/动态规划
- 找零兑换
- 博物馆大盗
- 树
- 表达式解析/求值/恢复原表达式
- 图
- 词梯问题
- 骑士周游/Warnsdorff改进
- 拓扑排序
- 强连通分支
- 最短路径
- 最小生成树
- 图深度遍历
- 其他
- 旅行商问题
1.排序
- 冒泡排序
def bubbleSort(alist):
for passnum in range(len(alist) - 1, 0, -1):
# 比较还未排序的余下的列表
for i in range(passnum):
# 整个列表所有数比较,最大的放最后
if alist[i] > alist[i + 1]:
temp = alist[i]
alist[i] = alist[i + 1]
alist[i + 1] = temp
def shortBubbleSort(alist):
exchanges = True
passnum = len(alist) - 1
while passnum > 0 and exchanges:
exchanges = False
# 比较还未排序的余下的列表
for i in range(passnum):
# 整个列表所有数比较,最大的放最后
if alist[i] > alist[i + 1]:
# 如果所有数都<后一个,则说明已有序,不用继续比较
exchanges = True
temp = alist[i]
alist[i] = alist[i + 1]
alist[i + 1] = temp
passnum = passnum - 1
- 选择排序
def selectionSort(alist):
for fillslot in range(len(alist) - 1, 0, -1):
positionOfMax = 0
# 未排序的全表比较,选出最大的记录下索引
for location in range(1, fillslot + 1):
if alist[location] > alist[positionOfMax]:
positionOfMax = location
# 把最大的数和当前未排序部分的最后一个数进行交换
temp = alist[fillslot]
alist[fillslot] = alist[positionOfMax]
alist[positionOfMax] = temp
- 插入排序
def insertionSort(alist):
# 循环逐步扩大有序表
for index in range(1, len(alist)):
currentvalue = alist[index]
position = index
# 一段范围内,当前值<前面某值(向前对比),前面最大的某值最终赋给当前值的位置,并逐步扩大有序的范围
while position > 0 and alist[position - 1] > currentvalue:
alist[position] = alist[position - 1]
position = position - 1
# 最大值赋给当前值位置后,当前值放到最大值的位置
alist[position] = currentvalue
- 谢尔排序
def shellSort(alist):
sublistcount = len(alist) // 2
while sublistcount > 0:
# 间隔划分为子列表,对每个子列表分别插入排序
for startposition in range(sublistcount):
gapInsertionSort(alist, startposition, sublistcount)
print("After increments of size", sublistcount, "The list is", alist)
sublistcount = sublistcount // 2
def gapInsertionSort(alist, start, gap):
for i in range(start + gap, len(alist), gap):
currentvalue = alist[i]
position = i
# 向前比较
while position >= gap and alist[position - gap] > currentvalue:
alist[position] = alist[position - gap]
position = position - gap
alist[position] = currentvalue
- 归并排序
def mergeSort(alist):
print("Splitting ", alist)
if len(alist) > 1:
# 拆分左半右半
mid = len(alist) // 2
lefthalf = alist[:mid]
righthalf = alist[mid:]
# 递归归并排序
mergeSort(lefthalf)
mergeSort(righthalf)
i, j, k = 0, 0, 0
# 拉链对比
while i < len(lefthalf) and j < len(righthalf):
# 左小,左数加入总表
if lefthalf[i] < righthalf[j]:
alist[k] = lefthalf[i]
i = i + 1
# 右小,右数加入总表
else:
alist[k] = righthalf[j]
j = j + 1
# 判断完一次后,总表索引向后移一格
k = k + 1
# 右表比完之后,左表还有遗留,逐个加入总表
while i < len(lefthalf):
alist[k] = lefthalf[i]
i = i + 1
k = k + 1
# 左表比完之后,右表还有遗留,逐个加入总表
while j < len(righthalf):
alist[k] = righthalf[j]
j = j + 1
k = k + 1
print("Merging ", alist)
- 快速排序
def quickSort(alist):
quickSortHelper(alist, 0, len(alist) - 1)
def quickSortHelper(alist, first, last):
if first < last:
# 右指针
splitpoint = partition(alist, first, last)
# 根据中值分两半的左半部分递归
quickSortHelper(alist, first, splitpoint - 1)
# 根据中值分两半的右半部分递归
quickSortHelper(alist, splitpoint + 1, last)
def partition(alist, first, last):
# 将第一个值设置为中值
pivotvalue = alist[first]
# 左指针
leftmark = first + 1
# 右指针
rightmark = last
done = False
while not done:
# 左指针在右指针左边,左指针位置处的值<=中值
while leftmark <= rightmark and \
alist[leftmark] <= pivotvalue:
leftmark = leftmark + 1
# 左指针在右指针左左边,左指针位置处的值>=中值
while alist[rightmark] >= pivotvalue and \
rightmark >= leftmark:
rightmark = rightmark - 1
# 左指针在右指针右边
if rightmark < leftmark:
done = True
# 左指针在右指针左左边,左右指针处的值互换
else:
temp = alist[leftmark]
alist[leftmark] = alist[rightmark]
alist[rightmark] = temp
# 右指针处的值和中值互换
temp = alist[first]
alist[first] = alist[rightmark]
alist[rightmark] = temp
return rightmark
2.查找
- 顺序查找(已排序的表/未排序的表)
def sequentialSearch(alist, item):
"""
未排序的表
"""
pos = 0
found = False
while pos < len(alist) and not found:
if alist[pos] == item:
found = True
else:
pos = pos+1
return found
def orderedSequentialSearch(alist, item):
"""
已排序的表
"""
pos = 0
found = False
stop = False
while pos < len(alist) and not found and not stop:
if alist[pos] == item:
found = True
else:
# 如果列表中某个值直接大于目标值,因为是有序表,说明后面不会再有相等的了,直接跳查找失败
if alist[pos] > item:
stop = True
else:
pos = pos+1
return found
- 二分查找
def binarySearch(alist, item):
first = 0
last = len(alist) - 1
found = False
while first <= last and not found:
# 中间值 = (头+尾)/2
midpoint = (first + last) // 2
if alist[midpoint] == item:
found = True
else:
# 目标值<中间值,在前部找
if item < alist[midpoint]:
last = midpoint - 1
# 目标值>中间值,在后部找
else:
first = midpoint + 1
return found
def binarySearch2(alist, item):
"""
递归 二分查找
"""
if len(alist) == 0:
return False
else:
midpoint = len(alist) // 2
if alist[midpoint] == item:
return True
else:
# 目标值<中间值,在前部找
if item < alist[midpoint]:
return binarySearch2(alist[:midpoint], item)
# 目标值>中间值,在后部找
else:
return binarySearch2(alist[midpoint + 1:], item)
3.常见基本算法
- 最大公约数
def hcf(x, y):
# 获取最小值
if x > y:
small = y
else:
small = x
for i in range(1,small + 1):
if((x % i == 0) and (y % i == 0)):
hcf = i
return hcf
- 斐波那契数列
def printFibonacci(n):
"""
n为数列的数字个数
斐波那契数列0,1,1,2,3,5,8,13,21...
f(n)=f(n-1)+f(n-2)
"""
lis = [0, 1]
a, b = lis
for i in range(n - 2):
a, b = b, a + b
lis.append(b)
return lis
def printFibonacci2(n):
"""
递归斐波那契数列
"""
def recursion(n):
if n == 0: return 0
if n == 1: return 1
return recursion(n - 1) + recursion(n - 2)
lis = []
for i in range(n):
lis.append(recursion(i))
return lis
- 判断素数
def checkPrime(nums):
"""
从2~sqrt(n)+1判断
"""
ranges = range(2, int(sqrt(nums) + 1))
for i in ranges:
if nums % i == 0:
return False
return True
def checkPrime2(nums):
"""
从2~sqrt(n)判断,只需要判断6x+1,6x+5
"""
flag = True
if nums == 2 or nums == 3:
return flag
if nums % 6 != 1 and nums % 6 != 5:
flag = False
return flag
for i in range(5, int(sqrt(nums)) + 2, 6):
if nums % i == 0 or nums % (i + 2) == 0:
flag = False
return flag
return flag
- 变位词
def checkAnagrams(wordA, wordB):
"""
变位词
"""
word_dictA, word_dictB = {}, {}
for i in wordA:
word_dictA[i] = word_dictA.get(i, 0) + 1
for j in wordB:
word_dictB[j] = word_dictB.get(j, 0) + 1
return word_dictA == word_dictB
4.栈
- 括号匹配
# 括号匹配检查
def expressionCheck(exp):
"""
括号匹配检查,不包含数字符号等
"""
exp_dict = {'(': ')', '[': ']', '{': '}'}
s = Stack()
flag = True
for i in exp:
# 如果是左括号
if i in '([{':
s.push(i)
# 如果是右括号
else:
# 栈空则说明不匹配
if s.isEmpty():
flag = False
break
# 栈不空看括号类型是否匹配
else:
top = s.pop()
if not exp_dict[top] == i:
flag = False
break
return flag and s.isEmpty()
- 前中后缀表达式
# 中缀转后缀
def expressionConversion(infixexpr):
prec = {"*": 3, "/": 3, "+": 2, "-": 2, "(": 1}
opStack = Stack()
postfixList = []
tokenList = infixexpr.split()
for token in tokenList:
# 字符数字直接加入list
if token in "ABCDEFGHIJKLMNOPQRSTUVWXYZ" or token in "0123456789":
postfixList.append(token)
# 左括号压入栈
elif token == '(':
opStack.push(token)
# 右括号
elif token == ')':
# 弹出栈顶符号
topToken = opStack.pop()
# 栈顶非左括号
while topToken != '(':
# 栈顶元素加入list
postfixList.append(topToken)
# 继续从栈顶拿出元素直到是左括号位置
topToken = opStack.pop()
# 运算符号
else:
# 栈顶运算符号优先级>当前运算符号,栈顶运算符号出栈并加入list
while not opStack.isEmpty() and prec[opStack.peek()] >= prec[token]:
postfixList.append(opStack.pop())
# 当前运算符号加入栈
opStack.push(token)
# 栈不空则把栈剩下的全部加到list中
while not opStack.isEmpty():
postfixList.append(opStack.pop())
return " ".join(postfixList)
# 表达式求值
def expressionEvaluation(postfixExpr):
operandStack = Stack()
tokenList = postfixExpr.split()
for token in tokenList:
# 数字则压入栈
if token in "0123456789":
operandStack.push(int(token))
# 非数字
else:
# 先出栈的是操作数2
operand2 = operandStack.pop()
# 后出栈的是操作数1
operand1 = operandStack.pop()
# 运算
result = doMath(token, operand1, operand2)
# 运算结果压入栈
operandStack.push(result)
return operandStack.pop()
def doMath(op, op1, op2):
if op == "*":
return op1 * op2
elif op == "/":
return op1 / op2
elif op == "+":
return op1 + op2
else:
return op1 - op2
- 进制转换
def baseConversion(nums, base):
"""
迭代版 进制转换:10进制~任意(2-16)进制
"""
if base not in range(2, 17):
print('ERROR!Base can only in 2-16')
return
digits = '0123456789ABCDEF'
s = Stack()
n = nums
rst = ''
while n:
q, r = divmod(n, base)
# 余数压入栈
s.push(r)
# 商继续运算
n = q
# 出栈并映射到0~F
for i in range(s.size()):
rst += digits[s.pop()]
return rst
def baseConversion2(nums, base):
"""
递归版 进制转换:10进制~任意(2-16)进制
"""
digits = '0123456789ABCDEF'
if nums < base:
return digits[nums]
else:
return baseConversion2(nums // base, base) + digits[nums % base]
5.队列
- 热土豆问题(约瑟夫问题)
def hotPotato(namelist, num):
simqueue = Queue()
# 全部加入队列
for name in namelist:
simqueue.enqueue(name)
# 循环传递土豆,直到队列仅一个
while simqueue.size() > 1:
# 队首出,队尾进,循环num次
for i in range(num):
simqueue.enqueue(simqueue.dequeue())
# 每隔num次,从队列出去一个
simqueue.dequeue()
# 返回最后一个人
return simqueue.dequeue()
- 回文序列
def palchecker(aString):
stillEqual = True
chardeque = Deque()
# 把字符串一个个加入双端队列队末
for ch in aString:
chardeque.addRear(ch)
# 双端队列大小>1且目前保持两端相等时
while chardeque.size() > 1 and stillEqual:
# 双端队列头弹出
first = chardeque.removeFront()
# 双端队列尾弹出
last = chardeque.removeRear()
# 判断是否头尾相等
if first != last:
stillEqual = False
return stillEqual
6.递归
- 数列求和
def sumArray(lis):
"""
数组求和
"""
if len(lis) == 1:
return lis[0]
else:
return lis[0] + sumArray(lis[1:])
- 汉诺塔
def hanoiTower(n):
"""
开始柱->中间柱->目标柱
"""
steps = []
def move(n, A, B, C):
"""
A:fromPlace(开始柱)
B:withPlace(中间柱)
C:toPlace(目标柱)
"""
if n >= 1:
# 开始柱移到中间柱
move(n - 1, A, C, B)
# 记录移动步骤
steps.append(f'{A}->{C}')
# 中间柱移到目标柱
move(n - 1, B, A, C)
move(n, '#1', '#2', '#3')
return steps
7.优化/动态规划
- 找零兑换
def dpMakeChange(coinValueList, change, minCoins, coinsUsed):
# 从1开始到change逐个计算最少硬币数
for cents in range(change + 1):
# 初始最大值
coinCount = cents
# 初始化新加硬币
newCoin = 1
# 循环各个面值
for j in [c for c in coinValueList if c <= cents]:
# 判断这个面值是否优于之前的结果(更少硬币)
if minCoins[cents - j] + 1 < coinCount:
# 记录当前这种配置的硬币数
coinCount = minCoins[cents - j] + 1
# 当前配置的硬币面值
newCoin = j
# 记录得到当前最少硬币数
minCoins[cents] = coinCount
# 本步骤加的硬币面值
coinsUsed[cents] = newCoin
# 返回最后一个结果
return minCoins[change]
def printCoins(coinsUsed, change):
coin = change
while coin > 0:
thisCoin = coinsUsed[coin]
print(thisCoin)
coin = coin - thisCoin
- 博物馆大盗
def museumRobber(treasure, maxbag):
# 初始化各种商品重量组合的最大价值
m = {(i, w): 0 for i in range(len(treasure)) for w in range(maxbag + 1)}
for i in range(1, len(treasure)):
for w in range(1, maxbag + 1):
# 装不下第i个商品
if treasure[i]['w'] > w:
# 不装第i个
m[(i, w)] = m[(i - 1, w)]
# 装得下第i个
else:
# 判断不装第i和装第i个哪种价值更大
m[(i, w)] = max(m[(i - 1, w)],m[(i - 1, w - treasure[i]['w'])] + treasure[i]['v'])
return m
m = {}
def museumRobber2(tr, w):
"""
迭代方法
"""
if tr == set() or w == 0:
m[(tuple(tr), w)] = 0
return 0
elif (tuple(tr), w) in m:
return m[(tuple(tr), w)]
else:
vmax = 0
for t in tr:
if t[0] <= w:
# 逐个从集合中去掉某个商品,递归调用选出所有价值中最大值
v = museumRobber2(tr - {t}, w - t[0]) + t[1]
vmax = max(vmax, v)
m[(tuple(tr), w)] = vmax
return vmax
8.树
- 表达式解析/求值/恢复原表达式
# 表达式树后序遍历
def postordereval(tree):
opers = {'+': operator.add, '-': operator.sub, '*': operator.mul, '/': operator.truediv}
res1 = None
res2 = None
if tree:
res1 = postordereval(tree.getLeftChild())
res2 = postordereval(tree.getRightChild())
if res1 and res2:
return opers[tree.getRootVal()](res1, res2)
else:
return tree.getRootVal()
def buildParseTree(fpexp):
fplist = fpexp.split()
pStack = Stack()
eTree = BinaryTree('')
pStack.push(eTree)
currentTree = eTree
for i in fplist:
# 如果是左括号,当前节点添加左子节点,新节点设为当前节点
if i == '(':
currentTree.insertLeft('')
pStack.push(currentTree)
currentTree = currentTree.getLeftChild()
# 如果是数字,赋值给当前节点,其父节点设为当前节点
elif i not in ['+', '-', '*', '/', ')']:
currentTree.setRootVal(int(i))
parent = pStack.pop()
currentTree = parent
# 如果是运算符,赋给当前节点,并给当前节点加右子节点,新节点设为当前节点
elif i in ['+', '-', '*', '/']:
currentTree.setRootVal(i)
currentTree.insertRight('')
pStack.push(currentTree)
currentTree = currentTree.getRightChild()
# 如果是右括号,父节点设为当前节点
elif i == ')':
currentTree = pStack.pop()
else:
raise ValueError
return eTree
9.图
- 词梯问题
def wordproc():
f = open("rawwords.txt", "r")
wl = []
for aline in f:
l = aline.split()
wl.append(l[0])
of = open("fourletterwords.txt", "w")
for w in wl:
of.write(w + '\n')
def buildGraph(wordFile):
d = {}
g = Graph()
wfile = open(wordFile, 'r')
# create buckets of words that differ by one letter
# 如果是4字母单词,属于4个桶
for line in wfile:
word = line[:-1]
for i in range(len(word)):
bucket = word[:i] + '_' + word[i + 1:]
if bucket in d:
d[bucket].append(word)
else:
d[bucket] = [word]
# 给同一个桶的单词增加边和顶点
for bucket in d.keys():
for word1 in d[bucket]:
for word2 in d[bucket]:
if word1 != word2:
g.addEdge(word1, word2)
return g
# 广度优先遍历
def bfs(g, start):
start.setDistance(0)
start.setPred(None)
vertQueue = Queue()
vertQueue.enqueue(start)
while vertQueue.size() > 0:
# 队首作为当前顶点
currentVert = vertQueue.dequeue()
# 遍历邻接节点
for nbr in currentVert.getConnections():
if nbr.getColor() == 'white':
nbr.setColor('gray')
nbr.setDistance(currentVert.getDistance() + 1)
nbr.setPred(currentVert)
vertQueue.enqueue(nbr)
currentVert.setColor('black')
# 回途追溯函数
def traverse(y):
x = y
while x.getPred():
print(x.getId())
x = x.getPred()
print(x.getId())
if __name__ == '__main__':
# 构建单词关系图
wordgraph = buildGraph("fourletterwords.txt")
# 广度优先搜索
bfs(wordgraph, wordgraph.getVertex('FOOL'))
# 遍历找词路径
traverse(wordgraph.getVertex('SAGE'))
# traverse(wordgraph.getVertex('COOL'))
- 骑士周游/Warnsdorff改进
def genLegalMoves(x, y, bdSize):
newMoves = []
moveOffsets = [(-1, -2), (-1, 2), (-2, -1), (-2, 1),
(1, -2), (1, 2), (2, -1), (2, 1)]
for i in moveOffsets:
newX = x + i[0]
newY = y + i[1]
if legalCoord(newX, bdSize) and legalCoord(newY, bdSize):
newMoves.append((newX, newY))
return newMoves
def legalCoord(x, bdSize):
if 0 <= x < bdSize:
return True
else:
return False
def knightGraph(bdSize):
ktGraph = Graph()
for row in range(bdSize):
for col in range(bdSize):
nodeId = posToNodeId(row, col, bdSize)
newPositions = genLegalMoves(row, col, bdSize)
for e in newPositions:
nid = posToNodeId(e[0], e[1], bdSize)
ktGraph.addEdge(nodeId, nid)
return ktGraph
def knightTour(n, path, u, limit):
# n=层次,path=路径,u=当前节点,limit=搜索总深度
u.setColor('gray')
# 当前节点加入路径
path.append(u)
if n < limit:
# 对所有合法移动逐一深入
nbrList = list(u.getConnections())
i = 0
done = False
while i < len(nbrList) and not done:
# 深入未经过的顶点
if nbrList[i].getColor() == 'white':
# 层次加一,递归深入
done = knightTour(n + 1, path, nbrList[i], limit)
i = i + 1
# 无法完成总深度,回溯,试本层下一个顶点
if not done: # prepare to backtrack
path.pop()
u.setColor('white')
else:
done = True
return done
def posToNodeId(row, col, bdSize):
return row * bdSize + col
# Warnsdorff改进
def orderByAvail(n):
resList = []
for v in n.getConnections():
if v.getColor() == 'white':
c = 0
for w in v.getConnections():
if w.getColor() == 'white':
c = c + 1
resList.append((c, v))
resList.sort(key=lambda x: x[0])
return [y[1] for y in resList]
def knightTourBetter(n, path, u, limit): # use order by available function
u.setColor('gray')
path.append(u)
if n < limit:
# 具有最少合法移动目标的格子优先搜索
nbrList = orderByAvail(u)
i = 0
done = False
while i < len(nbrList) and not done:
if nbrList[i].getColor() == 'white':
done = knightTour(n + 1, path, nbrList[i], limit)
i = i + 1
if not done: # prepare to backtrack
path.pop()
u.setColor('white')
else:
done = True
return done
if __name__ == '__main__':
kg = knightGraph(5) # five by five solution
thepath = []
start = kg.getVertex(4)
knightTourBetter(0, thepath, start, 24)
for v in thepath:
print(v.getId())
- 最短路径
def dijkstra(aGraph, start):
# 对所有顶点建堆,形成优先队列
pq = PriorityQueue()
start.setDistance(0)
pq.buildHeap([(v.getDistance(), v) for v in aGraph])
while not pq.isEmpty():
# 优先队列出队
currentVert = pq.delMin()
for nextVert in currentVert.getConnections():
newDist = currentVert.getDistance() + currentVert.getWeight(nextVert)
# 修改出队顶点所邻接顶点的dist,并逐个重排队列
if newDist < nextVert.getDistance():
nextVert.setDistance(newDist)
nextVert.setPred(currentVert)
pq.decreaseKey(nextVert, newDist)
- 最小生成树
def prim(G,start):
pq = PriorityQueue()
for v in G:
v.setDistance(sys.maxsize)
v.setPred(None)
start.setDistance(0)
pq.buildHeap([(v.getDistance(), v) for v in G])
while not pq.isEmpty():
currentVert = pq.delMin()
for nextVert in currentVert.getConnections():
newCost = currentVert.getWeight(nextVert)
if nextVert in pq and newCost< nextVert.getDistance():
nextVert.setDistance(newCost)
nextVert.setPred(currentVert)
pq.decreaseKey(nextVert, newCost)
- 图深度遍历
class dfsgraph(Graph):
def __init__(self):
super().__init__()
self.time = 0
def dfs(self):
# 颜色初始化
for aVertex in self:
aVertex.setColor('white')
aVertex.setPred(-1)
# 如果还有未包括的顶点则建森林
for aVertex in self:
if aVertex.getColor() == 'white':
self.dfsvisit(aVertex)
def dfsvisit(self, startVertex):
startVertex.setColor('gray')
# 算法的步数
self.time += 1
startVertex.setDiscovery(self.time)
# 深度优先递归访问
for nextVertex in startVertex.getConnections():
if nextVertex.getColor() == 'white':
nextVertex.setPred(startVertex)
self.dfsvisit(nextVertex)
startVertex.setColor('black')
self.time += 1
startVertex.setFinish(self.time)
10.其他
- 旅行商问题
解决方法:近似(KNN) / 二叉树