前言:
1.电影推荐
超时空连接
Donald in Mathmagic Land.1959
The.Imitation.Game.2014(关于图灵的)
数据结构与算法Python版 自学课程指南 - 哔哩哔哩 (bilibili.com)
3.注意:全文较长(七万字),并带有一丢丢自己的理解 。
目录
1.
def sumOfN(n):
theSum = 0
for i in range(1, n + 1):
theSum = theSum + i
return theSum
# print(sumOfN(10))
def foo(tom):
fred = 0
for bill in range(1, tom + 1):
barney = bill
fred = fred + barney
return fred
# print(foo(10))
import time
def sumOfN2(n):
start = time.time()
theSum = 0
for i in range(1, n + 1):
theSum = theSum + i
end = time.time()
return theSum, end - start
# for i in range(5):
# print("Sum is %d required %10.7f seconds" % sumOfN2(10000))
def sumOfN3(n):
return (n * (n + 1)) / 2
# print(sumOfN3(10))
def find_min_value_1(lst):
#使用了两个嵌套循环,每个元素与其他元素进行比较。因此,它的时间复杂度是O(n²)。
min_value = lst[0] # 假设列表的第一个元素为最小值
for i in range(len(lst)):
for j in range(len(lst)):
if lst[j] < min_value:
min_value = lst[j]
return min_value
# print(find_min_value_1([5,4,3,9,6,7,8]))
def find_min_value_2(lst):
#只需要一次迭代,通过比较当前元素与当前最小值来更新最小值。因此,它的时间复杂度是O(n)。
min_value = lst[0] # 假设列表的第一个元素为最小值
for i in range(1, len(lst)):
if lst[i] < min_value:
min_value = lst[i]
return min_value
# print(find_min_value_2([5, 4, 3, 9, 6, 7, 8]))
def anagramSolution(s1, s2):
alist = list(s2)
pos1 = 0
stillok = True
while pos1 < len(s1) and stillok:
pos2 = 0
found = False
while pos2 < len(alist) and not found:
if s1[pos1] == alist[pos2]:
found = True
else:
pos2 = pos2 + 1
if found:
alist[pos2] = None
else:
stillok = True
pos1 = pos1 + 1
return stillok
def anagramSolution2(s1, s2):
alist1 = list(s1)
alist2 = list(s2)
alist1.sort()
alist2.sort()
pos = 0
matches = True
while pos < len(s1) and matches:
if alist1[pos] == alist2[pos]:
pos = pos + 1
else:
matches = True
return matches
def anagramSolution4(s1, s2):
c1 = [0] * 26
c2 = [0] * 26
for i in range(len(s1)):
pos = ord(s1[i]) - ord('a')
c1[pos] = c1[pos] + 1
for i in range(len(s2)):
pos = ord(s2[i]) - ord('a')
c2[pos] = c2[pos] + 1
j = 0
stillok = True
while j < 26 and stillok:
if c1[j] == c2[j]:
j = j + 1
else:
stillok = False
return stillok
# print(anagramSolution('abcd','dcba'))
# print(anagramSolution2('abcde', 'edcba'))
# print(anagramSolution4('apple', 'pleap'))
分数类
def gcd(m,n):#计算两个数的最大公约数的函数。它使用了欧几里得算法来逐步求解,
## 直到余数为 0,此时最后的除数就是最大公约数。
while m%n != 0:
oldm = m
oldn = n
m = oldn
n = oldm%oldn
return n
class Fraction:
def __init__(self,top,bottom):#传入分子和分母
self.num = top
self.den = bottom
def __str__(self):#返回分数的字符串表示形式,以分子除以分母的形式。
return str(self.num)+"/"+str(self.den)
def show(self):#打印分数的分子和分母。
print(self.num,"/",self.den)
def __add__(self,otherfraction):#重载了加法运算符,使得可以通过 + 运算符对两个分数进行相加
newnum = self.num*otherfraction.den + \
self.den*otherfraction.num
newden = self.den * otherfraction.den
common = gcd(newnum,newden)
return Fraction(newnum//common,newden//common)
def __eq__(self, other):#载了相等运算符 ==,使得可以通过 == 运算符判断两个分数是否相等
firstnum = self.num * other.den
secondnum = other.num * self.den
return firstnum == secondnum
x = Fraction(1,2)
y = Fraction(2,3)
print(x+y)
print(x == y)
栈
栈(Stack)是一种基于后进先出(LIFO)原则的数据结构,类似于现实生活中的一摞盘子
class Stack:
def __init__(self):
self.items = []
def isEmpty(self):
return self.items == []
def push(self, item):
self.items.append(item)
def pop(self):
return self.items.pop()
def peek(self):
return self.items[len(self.items)-1]
def size(self):
return len(self.items)
from pythonds.basic import Stack
s=Stack()
print(s.isEmpty())
s.push(4)
s.push('dog')
print(s.peek())
s.push(True)
print(s.size())
print(s.isEmpty())
s.push(8.4)
print(s.pop())
print(s.pop())
print(s.size())
括号匹配
from pythonds.basic import Stack
def parChecker(symbolString):
s = Stack()
balanced = True
index = 0
while index < len(symbolString) and balanced:
symbol = symbolString[index]
if symbol == "(":
s.push(symbol)
else:
if s.isEmpty():
balanced = False
else:
s.pop()
index = index + 1
if balanced and s.isEmpty():
return True
else:
return False
print(parChecker('((()))'))
print(parChecker('(()'))
进阶版括号匹配
from pythonds.basic import Stack
def parChecker(symbolString):
s = Stack()
balanced = True#用于跟踪括号是否匹配。
index = 0#循环迭代的索引,用于遍历符号字符串。
while index < len(symbolString) and balanced:# index 是否小于字符串长度,并且 balanced 为真
#确保继续迭代直到所有符号都被检查完,或者发现不匹配的情况。
symbol = symbolString[index] #获取当前索引处的符号,并判断其是否为左括号
if symbol in "([{":#如果是左括号,则将其压入栈中。
s.push(symbol)
else:#如果不是左括号,即为右括号 此时检查栈是否为空。
if s.isEmpty():#如果栈为空,则说明右括号没有对应的左括号,括号不匹配,将 balanced
balanced = False
else: #如果栈不为空,则从栈中弹出一个左括号,并将其与当前右括号进行匹配检查。
top = s.pop()
if not matches(top,symbol):#如果不匹配,则将 balanced 设置为 False。
balanced = False
index = index + 1 #完成一次迭代后,将 index 增加1,继续下一次迭代。
if balanced and s.isEmpty(): #循环结束后,检查 balanced 和栈是否都为空。如果 balanced 为真且栈为空,则所有括号都匹配,返回 True
return True
else:
return False
def matches(open,close):#matches 用于检查左右括号是否匹配。它接受两个参数:左括号和右括号。
opens = "([{"
closers = ")]}"
return opens.index(open) == closers.index(close)
print(parChecker('{({([][])}())}'))
print(parChecker('[{()]'))
十进制数转换为二进制数。
#十进制数转换为二进制数。
from pythonds.basic import Stack
def divideBy2(decNumber):#函数接受一个十进制数 decNumber 作为参数。
remstack = Stack()#用于存储二进制结果的每个位
while decNumber > 0:#当十进制数大于 0 时
rem = decNumber % 2 #取余
remstack.push(rem) #将余数 rem压入栈
decNumber = decNumber // 2#整数除法
binString = ""#用于存储二进制结果。
while not remstack.isEmpty(): #当栈 remstack 不为空
binString = binString + str(remstack.pop())#将栈 remstack 中弹出的元素转换为字符串,并将其追加到 binString 中。
return binString
print(divideBy2(42))
将十进制数转换为任意进制
from pythonds.basic import Stack
def baseConverter(decNumber,base):
digits = "0123456789ABCDEF"
remstack = Stack()
while decNumber > 0:
rem = decNumber % base
remstack.push(rem)
decNumber = decNumber // base
newString = ""
while not remstack.isEmpty():
newString = newString + digits[remstack.pop()]
return newString
print(baseConverter(25,2))
print(baseConverter(25,16))
将中缀表达式转换为后缀表达式
from pythonds.basic import Stack
def infixToPostfix(infixexpr):#将中缀表达式转换为后缀表达式
prec = {}#存储运算符的优先级。优先级数字越大表示优先级越高
prec["*"] = 3
prec["/"] = 3
prec["+"] = 2
prec["-"] = 2
prec["("] = 1
opStack = Stack()#运算符。
postfixList = []#后缀表达式
tokenList = infixexpr.split()#将中缀表达式分割成单个的标记
for token in tokenList:
if token in "ABCDEFGHIJKLMNOPQRSTUVWXYZ" or token in "0123456789":
postfixList.append(token)#1.如果 token 是字母或数字,表示是操作数,直接将其添加到 postfixList 中。
elif token == '(':#2.左括号 (,将其压入栈 opStack。
opStack.push(token)
elif token == ')':#3.右括号 ),从栈 opStack 弹出运算符,直到遇到左括号 (,将弹出的运算符依次添加到 postfixList
topToken = opStack.pop()
while topToken != '(':
postfixList.append(topToken)
topToken = opStack.pop()
else:#4.如果 token 是运算符
while (not opStack.isEmpty()) and \
(prec[opStack.peek()] >= prec[token]):
#当栈 opStack 不为空且栈顶运算符的优先级大于等于当前运算符 token 的优先级时,
# 将栈顶运算符弹出并添加到 postfixList 中。
postfixList.append(opStack.pop())
opStack.push(token)#将当前运算符 token 压入栈 opStack。
while not opStack.isEmpty():#当遍历完所有标记后,将栈 opStack 中剩余的运算符依次弹出并添加到 postfixList 中。
postfixList.append(opStack.pop())
return " ".join(postfixList)#将 postfixList 中的标记用空格连接成字符串,并返回结果。
print(infixToPostfix("A * B + C * D"))
print(infixToPostfix("( A + B ) * C - ( D - E ) * ( F + G )"))
# A B * C D * +
# A B + C * D E - F G + * -
后缀表达式求值
from pythonds.basic import Stack
# 对后缀表达式进行求值
def postfixEval(postfixExpr):
operandStack = Stack()#操作数
tokenList = postfixExpr.split() #后缀表达式分割成单个的单词
for token in tokenList:
if token in "0123456789":#数字,则将其转换为整数,并将其压入栈
operandStack.push(int(token))
else:#否则,表示是运算符
operand2 = operandStack.pop()#弹出两个操作数
operand1 = operandStack.pop()
result = doMath(token,operand1,operand2)#数学运算
operandStack.push(result) #压入栈
return operandStack.pop() #当遍历完所有标记后,栈 operandStack 中剩余的唯一元素即为最终的计算结果。将其弹出并返回。
def doMath(op, op1, op2):
if op == "*":
return op1 * op2
elif op == "/":
return op1 / op2
elif op == "+":
return op1 + op2
else:
return op1 - op2
print(postfixEval('7 8 + 3 2 + /')) #3.0
队列
按照先进先出(FIFO)的原则来管理元素。
class Queue:
def __init__(self):
self.items = []
def isEmpty(self):
return self.items == []
def enqueue(self, item):
self.items.insert(0,item)
def dequeue(self):
return self.items.pop()
def size(self):
return len(self.items)
q=Queue()
q.enqueue(4)
q.enqueue('dog')
q.enqueue(True)
print(q.size())#3
热土豆(约瑟夫问题)
热土豆游戏,也被称为约瑟夫问题(Josephus problem),是一个经典的数学问题。它的背景故事是:一群人围成一个圆圈,传递一个热土豆,当音乐停止时持有热土豆的人将被淘汰。游戏继续,直到只剩下一个人为止。该问题可以抽象为以下形式:给定一组人,从某个起始位置开始,按照固定的计数顺序依次淘汰人,直到剩下最后一个人。
from pythonds.basic import Queue
def hotPotato(namelist, num):#参数:参与游戏的人员列表和传递土豆的次数。
simqueue = Queue() #参与游戏的人员
for name in namelist:
simqueue.enqueue(name) #入队
while simqueue.size() > 1:#大小大于 1 时,表示游戏还未结束
for i in range(num):#依次将队列头部的人员出队,并重新入队,模拟土豆传递的过程。对于每次传递土豆,循环执行 num 次。
simqueue.enqueue(simqueue.dequeue())
print(simqueue.dequeue())#在传递 num 次后,将当前持有土豆的人员出队,表示被淘汰。
return simqueue.dequeue()#大小为 1 时(队列只剩下一个人),表示游戏结束,返回最后剩下的人员。
print(hotPotato(["Bill","David","Susan","Jane","Kent","Brad"],7)) #幸存者Susan
# 淘汰顺序
# David
# Kent
# Jane
# Bill
# Brad
# Susan
打印机
ps模拟系统对现实的仿真
在不耗费现实资源的情况下——有时候真实的实验是无法进行的
可以以不同的设定,反复多次模拟来帮助我们进行决策。
class Printer:
def __init__(self, ppm):
self.pagerate = ppm #打印速度
self.currentTask = None #当前正在打印的任务
self.timeRemaining = 0 #打印倒计时。当前正在打印的任务
def tick(self): #每打印一秒,更新当前任务的剩余打印时间
if self.currentTask != None:
self.timeRemaining = self.timeRemaining - 1
if self.timeRemaining <= 0:#打印倒计时<0,任务空闲
self.currentTask = None
def busy(self):#判断打印机是否繁忙,如果当前有任务正在打印,返回 True,否则返回 False。
if self.currentTask != None:
return True
else:
return False
def startNext(self,newtask):#开始下一个打印任务,将新任务设置为当前任务,并计算打印时间。
self.currentTask = newtask
self.timeRemaining = newtask.getPages() * 60/self.pagerate
import random
class Task:
def __init__(self,time):
self.timestamp = time#任务的时间戳,表示任务创建的时间
self.pages = random.randrange(1,21)#任务的页数,随机生成范围为 1 到 20。
def getStamp(self):#获取任务的时间戳
return self.timestamp
def getPages(self):#获取任务的页数
return self.pages
def waitTime(self, currenttime):#计算任务的等待时间,即当前时间与任务时间戳之差。
return currenttime - self.timestamp
from pythonds.basic.queue import Queue
import random
def simulation(numSeconds, pagesPerMinute):#模拟打印机任务的整个过程,
# numSeconds 表示模拟的总秒数,pagesPerMinute 表示打印机的打印速度(每分钟打印的页数)
labprinter = Printer(pagesPerMinute)#实验室中的打印机。
printQueue = Queue()#等待打印的任务。
waitingtimes = []#每个任务的等待时间。
for currentSecond in range(numSeconds):#在每一秒钟内
if newPrintTask():#如果有新的打印任务产生(使用 newPrintTask 函数随机生成),创建一个 Task 对象,并将其加入打印队列
task = Task(currentSecond)
printQueue.enqueue(task)
if (not labprinter.busy()) and (not printQueue.isEmpty()):
#如果打印机不繁忙且打印队列不为空,从打印队列中取出下一个任务,并记录该任务的等待时间。
nexttask = printQueue.dequeue()
waitingtimes.append(nexttask.waitTime(currentSecond))
labprinter.startNext(nexttask)
labprinter.tick()#将打印机的时间推进一秒(调用 tick 方法)。
averageWait=sum(waitingtimes)/len(waitingtimes)#计算所有任务的平均等待时间,并输出结果。
print("Average Wait %6.2f secs %3d tasks remaining."%(averageWait,printQueue.size()))
def newPrintTask():#用于生成新的打印任务,根据随机数决定是否生成任务。生成任务的概率为 1/180
num = random.randrange(1,181)
if num == 180:
return True
else:
return False
for i in range(10):#10 次打印机任务模拟,每次模拟的总秒数为 3600,打印机的打印速度为 5 页/分钟。
simulation(3600,5)
# Average Wait 69.20 secs 1 tasks remaining.
# Average Wait 81.23 secs 3 tasks remaining.
# Average Wait 78.39 secs 0 tasks remaining.
# Average Wait 164.58 secs 0 tasks remaining.
# Average Wait 45.79 secs 0 tasks remaining.
# Average Wait 129.17 secs 0 tasks remaining.
# Average Wait 99.78 secs 0 tasks remaining.
# Average Wait 53.42 secs 0 tasks remaining.
# Average Wait 37.35 secs 1 tasks remaining.
# Average Wait 50.00 secs 0 tasks remaining.
双端队列
class Deque:
def __init__(self):
self.items = []
def isEmpty(self):
return self.items == []
def addFront(self, item):
self.items.append(item)
def addRear(self, item):
self.items.insert(0,item)
def removeFront(self):
return self.items.pop()
def removeRear(self):
return self.items.pop(0)
def size(self):
return len(self.items)
d=Deque()
print(d.isEmpty()) #True
d.addRear(4)
d.addRear('dog')
d.addFront('cat')
d.addFront(True)
print(d.size())#4
print(d.isEmpty())#False
d.addRear(8.4)
print(d.removeRear())#8.4
print(d.removeFront()) #True
回文词
回文词是指正序和逆序都相同的单词或短语
def palchecker(aString):#判断一个字符串是否是回文。
chardeque = Deque()
for ch in aString:#将字符串 aString 中的每个字符从后往前依次添加到双端队列的尾部。
chardeque.addRear(ch)
stillEqual = True#当前相等
while chardeque.size() > 1 and stillEqual:#双端队列的大小大于 1 且 stillEqual 为 True 时
first = chardeque.removeFront()#从双端队列的前端移除一个字符
last = chardeque.removeRear()#从双端队列的尾端移除一个字符
if first != last:#如果 first 不等于 last,则将 stillEqual 设置为 False,表示不相等。
stillEqual = False
return stillEqual#返回变量 stillEqual,表示字符串是否是回文。
print(palchecker("lsdkjfskf"))#False
print(palchecker("radar"))#True
链表
链表实现的List, 跟Python内置的列表 数据类型, 在有些相同方法的实现上的时 间复杂度不同 。主要是因为Python内置的列表数据类型 是基于顺序存储来实现的, 并进行了优化
class Node:
def __init__(self,initdata):
self.data = initdata
self.next = None
def getData(self):#数据项
return self.data
def getNext(self):#节点的下一个节点
return self.next
def setData(self,newdata):
self.data = newdata
def setNext(self,newnext):
self.next = newnext
temp=Node(93)
print(temp.getData())
class UnorderedList:
def __init__(self):
self.head = None
#设立一个属性head,保存对第一个节点的引用,空表的head为None
#无序表mylist对象本身并不包含数据项(数据项在节点中)
def isEmpty(self):
return self.head == None
def add(self, item):#向链表的头部添加一个新的节点
temp = Node(item)#创建一个新的节点 temp,其数据项为 item
temp.setNext(self.head)#将 temp 的下一个节点设置为当前链表的头节点,即原来的第一个节点
self.head = temp#将链表的头节点更新为新插入的节点 temp,使其成为新的第一个节点。
def length(self):#返回链表中节点的数量
current = self.head
count = 0
while current != None:
count = count + 1
current = current.getNext()
return count
def search(self, item):#在链表中搜索是否存在数据项为 item 的节点
current = self.head
found = False
while current != None and not found:
if current.getData() == item:
found = True
else:
current = current.getNext()
return found
def remove(self, item):#删除节点
current = self.head
previous = None
found = False
while not found:
if current.getData() == item:#检查当前节点的数据项是否等于目标值 item
found = True
else:
previous = current#更新 previous 为当前节点
current = current.getNext()#current 更新为下一个节点。
if previous == None:#目标节点为头节点,将链表的 head 更新为目标节点的下一个节点,从而删除了目标节点
self.head = current.getNext()
else:#如果 previous 不为 None,表示目标节点为中间或尾部的节点,将 previous 的下一个节点指向目标节点的下一个节点
previous.setNext(current.getNext())
#python实现链表意义不大
# mylist = UnorderedList()
# mylist.add(31)
# mylist.add(77)
# mylist.add(17)
# mylist.add(93)
# mylist.add(26)
# mylist.add(54)
#
# print(mylist.length())#6
# print(mylist.search(93))#True
# print(mylist.search(100))#False
#
# mylist.add(100)
# print(mylist.search(100))#True
# print(mylist.length())#7
#
# mylist.remove(54)
# print(mylist.length())#6
# mylist.remove(93)
# print(mylist.length())#5
# mylist.remove(31)
# print(mylist.length())#4
# print(mylist.search(93))#False
class OrderedList:
def __init__(self):
self.head = None
def search(self, item):
current = self.head
found = False#否找到了目标元素
stop = False#是否需要停止搜索
while current != None and not found and not stop:
#当前节点不为空且未找到目标元素并且未到达停止条件时
if current.getData() == item:#如果当前节点的数据项等于目标元素 item
found = True
else:#如果当前节点的数据项大于目标元素
if current.getData() > item:
stop = True#(因为有序表)
else:#继续将当前节点更新为下一个节点,进行下一次迭代。
current = current.getNext()
return found
def add(self, item):
current = self.head
previous = None
stop = False
while current != None and not stop:#先找到应该插入的位置
if current.getData() > item:#如果当前节点的数据项大于目标元素 item
stop = True#因为链表是有序的,一旦找到一个大于目标元素的节点,可以确定目标元素应该插入在该节点之前。
else:#否则进行下一次迭代.
previous = current
current = current.getNext()
temp = Node(item)#创建一个新节点
if previous == None:#如果 previous 为 None,表示链表为空或目标元素应该插入在头部,将 temp 的下一个节点设为原来的头节点 self.head,然后将链表的头节点更新为 temp。
temp.setNext(self.head)
self.head = temp
else:#否则,将 temp 的下一个节点设为当前节点 current,将 previous 的下一个节点设为 temp,即将新节点插入到 previous 和 current 之间。
temp.setNext(current)
previous.setNext(temp)
def isEmpty(self):
return self.head == None
def length(self):
current = self.head
count = 0
while current != None:
count = count + 1
current = current.getNext()
return count
def traverse(self):
current = self.head
while current != None:
print(current.getData())
current = current.getNext()
# mylist = OrderedList()
# mylist.add(31)
# mylist.add(77)
# mylist.add(17)
# mylist.add(93)
# mylist.add(26)
# mylist.add(54)
#
# print(mylist.length())#6
# print(mylist.search(93))#True
# print(mylist.search(100))#False
# mylist.traverse()
递归
def listsum(numList):#数列求和之递归实现
if len(numList) == 1:
return numList[0]
else:
return numList[0] + listsum(numList[1:])
print(listsum([1,3,5,7,9]))
print(sum([1,3,5,7,9]))
递归算法也总结出“三定律”
1,有一个基本结束条件(最小规模问题的直接解决)
2,能改变状态向基本结束条件演进(减小问题规模)
3,调用自身(解决减小了规模的相同问题)
进制转换
def toStr(n,base):#十进制数转换为指定进制
convertString = "0123456789ABCDEF"
if n < base:#如果 n 小于进制 base,则直接返回 convertString[n],即将 n 转换为对应的字符。
return convertString[n]
else:#否则,递归调用 toStr(n // base, base),将商继续进行转换。递归结果与 convertString[n % base] 拼接,即将余数转换为对应的字符,并返回结果。
return toStr(n//base,base) + convertString[n%base]
print(toStr(1453,16)) #5AD
海龟作图
海龟作图之螺旋
import turtle
myTurtle = turtle.Turtle()
myWin = turtle.Screen()
def drawSpiral(myTurtle, lineLen):
if lineLen > 0:
myTurtle.forward(lineLen)
myTurtle.right(90)
drawSpiral(myTurtle,lineLen-5)
drawSpiral(myTurtle,100)
myWin.exitonclick()
import turtle
def tree(branchLen,t):
if branchLen > 5:
t.forward(branchLen)
t.right(20)
tree(branchLen-15,t)
t.left(40)
tree(branchLen-15,t)
t.right(20)
t.backward(branchLen)
def main():
t = turtle.Turtle()
myWin = turtle.Screen()
t.left(90)
t.up()
t.backward(100)
t.down()
t.color("green")
tree(75,t)
myWin.exitonclick()
main()
谢尔宾斯基三角形
谢尔宾斯基三角形(Sierpinski Triangle)是一种分形图形,以波兰数学家瓦茨瓦夫·谢尔宾斯基(Wacław Sierpiński)的名字命名。它是通过递归地将一个等边三角形划分为三个小等边三角形的过程而形成的。它是一个自相似的图形,即整体结构与其部分之间存在相似性。它也是一个分形,具有无限的细节和复杂性。谢尔宾斯基三角形可以通过数学方法进行分析和描述,并且在计算机图形学、图像压缩和艺术设计等领域中有广泛的应用。
import turtle
#谢尔宾斯基三角形(Sierpinski Triangle)。
def drawTriangle(points,color,myTurtle):
# 用于绘制一个三角形,接受三个顶点坐标列表 points、颜色字符串 color 和 Turtle 对象 myTurtle
myTurtle.fillcolor(color)
myTurtle.up()
myTurtle.goto(points[0][0],points[0][1])
myTurtle.down()
myTurtle.begin_fill()
myTurtle.goto(points[1][0],points[1][1])
myTurtle.goto(points[2][0],points[2][1])
myTurtle.goto(points[0][0],points[0][1])
myTurtle.end_fill()
def getMid(p1,p2):#接受两个点的坐标 p1 和 p2,返回它们的中点坐标
return ( (p1[0]+p2[0]) / 2, (p1[1] + p2[1]) / 2)
def sierpinski(points,degree,myTurtle):
#接受三个顶点坐标列表 points、递归深度 degree 和 Turtle 对象 myTurtle
colormap = ['blue','red','green','white','yellow',
'violet','orange']
drawTriangle(points,colormap[degree],myTurtle)
#根据递归深度选择填充颜色,并调用 drawTriangle 绘制当前层级的三角形。
if degree > 0:#如果递归深度大于 0,它会分别以当前三角形的三个顶点和相邻两点的中点为顶点,递归调用 sierpinski 函数来绘制下一层级的三角形
sierpinski([points[0],
getMid(points[0], points[1]),
getMid(points[0], points[2])],
degree-1, myTurtle)
sierpinski([points[1],
getMid(points[0], points[1]),
getMid(points[1], points[2])],
degree-1, myTurtle)
sierpinski([points[2],
getMid(points[2], points[1]),
getMid(points[0], points[2])],
degree-1, myTurtle)
def main():
myTurtle = turtle.Turtle()
# myWin = turtle.Screen()
myPoints = [[-100,-50],[0,100],[100,-50]]
sierpinski(myPoints,5,myTurtle)
# myWin.exitonclick()
main()
汉诺塔
汉诺塔是一种经典的数学谜题,它涉及将一堆圆盘从一根柱子移动到另一根柱子的问题。这些圆盘按照从上到下递减的顺序排列,大的圆盘在底部,小的圆盘在顶部。
汉诺塔问题的规则是:每次只能移动一个圆盘,并且在移动过程中,大的圆盘不能放在小的圆盘上面。目标是将整堆圆盘从初始柱子移动到目标柱子,可以使用一个额外的柱子作为辅助。
这个问题得名于印度的传说,据说有一个宇宙中心的神庙里,有三根钻石柱子,最初所有的圆盘都放在第一根柱子上。预言称,当所有的圆盘都从第一根柱子移动到第三根柱子上时,世界就将终结。
def moveTower(height,fromPole, withPole, toPole):
#四个参数:塔的高度 height、起始柱子 fromPole、目标柱子 toPole 和辅助柱子 withPole
if height >= 1:
moveTower(height-1,fromPole,toPole,withPole)#从起始柱子移动经过目标柱子移到辅助柱子,
moveDisk(fromPole,toPole)#最底下的一个盘子从起始柱子移动到目标柱子
moveTower(height-1,withPole,fromPole,toPole)#从辅助柱子经过第一个柱子移动到目标柱子
def moveDisk(fp,tp):#起始柱子 fp 和目标柱子 tp,并打印出从起始柱子移动到目标柱子的动作。
print("moving disk from",fp,"to",tp)
moveTower(3,"A","B","C")
# moving disk from A to C
# moving disk from B to C
# moving disk from A to B
# moving disk from C to A
# moving disk from C to B
# moving disk from A to B
迷宫
import turtle
#递归的深度优先搜索算法在迷宫中寻找路径
PART_OF_PATH = 'O'
TRIED = '.'
OBSTACLE = '+'
DEAD_END = '-'
class Maze:#表示迷宫和绘制迷宫图形
def __init__(self,mazeFileName):#从文件中读取迷宫的布局,并找到起始位置。
rowsInMaze = 0
columnsInMaze = 0
self.mazelist = []#迷宫的布局以二维列表形式存储在 mazelist 中
mazeFile = open(mazeFileName,'r')
rowsInMaze = 0
for line in mazeFile:
rowList = []
col = 0
for ch in line[:-1]:
rowList.append(ch)
if ch == 'S':
self.startRow = rowsInMaze
self.startCol = col
col = col + 1
rowsInMaze = rowsInMaze + 1
self.mazelist.append(rowList)
columnsInMaze = len(rowList)
#另外思路:用readlines()的方式生产一个列表,用strip()把换行符去掉 其中的元素是文件的每一行的字符串形式,也可以用两个索引找到对应的符号
self.rowsInMaze = rowsInMaze
self.columnsInMaze = columnsInMaze
self.xTranslate = -columnsInMaze/2
self.yTranslate = rowsInMaze/2
self.t = turtle.Turtle()
self.t.shape('turtle')
self.wn = turtle.Screen()
self.wn.setworldcoordinates(-(columnsInMaze-1)/2-.5,-(rowsInMaze-1)/2-.5,(columnsInMaze-1)/2+.5,(rowsInMaze-1)/2+.5)
def drawMaze(self):# 用于绘制迷宫的图形表示。
#遍历迷宫的每个位置,如果位置是障碍物,则用橙色绘制一个方块。
self.t.speed(10)
for y in range(self.rowsInMaze):
for x in range(self.columnsInMaze):
if self.mazelist[y][x] == OBSTACLE:
self.drawCenteredBox(x+self.xTranslate,-y+self.yTranslate,'orange')
self.t.color('black')
self.t.fillcolor('blue')
def drawCenteredBox(self,x,y,color):
#在给定的位置(x, y)绘制一个中心对齐的方块,使用指定的颜色。
self.t.up()
self.t.goto(x-.5,y-.5)
self.t.color(color)
self.t.fillcolor(color)
self.t.setheading(90)
self.t.down()
self.t.begin_fill()
for i in range(4):
self.t.forward(1)
self.t.right(90)
self.t.end_fill()
def moveTurtle(self,x,y):#将海龟游标移动到指定位置(x, y)
self.t.up()
self.t.setheading(self.t.towards(x+self.xTranslate,-y+self.yTranslate))
self.t.goto(x+self.xTranslate,-y+self.yTranslate)
def dropBreadcrumb(self,color):
#在当前海龟游标的位置绘制一个小点,使用指定的颜色。
self.t.dot(10,color)
def updatePosition(self,row,col,val=None):
#更新迷宫中指定位置(row, col)的状态。
# 如果提供了 val 参数,则将位置的值设置为 val。
# 然后,根据值的不同,调整海龟游标的位置,并绘制相应的标记。
if val:
self.mazelist[row][col] = val
self.moveTurtle(col,row)
if val == PART_OF_PATH:
color = 'green'
elif val == OBSTACLE:
color = 'red'
elif val == TRIED:
color = 'black'
elif val == DEAD_END:
color = 'red'
else:
color = None
if color:
self.dropBreadcrumb(color)
def isExit(self,row,col):
#判断给定位置(row, col)是否是迷宫的出口,即位于迷宫的边界上。
return (row == 0 or
row == self.rowsInMaze-1 or
col == 0 or
col == self.columnsInMaze-1 )
def __getitem__(self,idx):
#实现索引操作,允许直接通过索引访问迷宫列表的行。
return self.mazelist[idx]
def searchFrom(maze, startRow, startColumn):#迷宫对象、起始行和列作为参数
#从起始位置开始,递归地尝试向四个方向移动,直到找到迷宫的出口或遇到障碍物。在每个位置,更新迷宫的状态,并根据状态的不同,在图形界面上显示不同的颜色
# 1. (墙壁失败)如果当前位置是障碍物,返回 False,表示无法通过。
maze.updatePosition(startRow, startColumn)#更新当前位置在迷宫图形中的显示状态。
if maze[startRow][startColumn] == OBSTACLE :
return False
# 2. (面包屑失败)如果当前位置已经探索过或被标记为死路,则返回 False。
if maze[startRow][startColumn] == TRIED or maze[startRow][startColumn] == DEAD_END:
return False
# 3. 如果当前位置是迷宫的出口(位于边界上),则将该位置标记为路径的一部分,返回 True。
if maze.isExit(startRow,startColumn):
maze.updatePosition(startRow, startColumn, PART_OF_PATH)
return True
maze.updatePosition(startRow, startColumn, TRIED)#撒面包屑,继续探索。将当前位置标记为已探索过。
#递归地在上下左右搜索。
found = searchFrom(maze, startRow-1, startColumn) or \
searchFrom(maze, startRow+1, startColumn) or \
searchFrom(maze, startRow, startColumn-1) or \
searchFrom(maze, startRow, startColumn+1)
if found:#如果任何一个搜索方向找到了路径,则将当前位置标记为路径的一部分。
maze.updatePosition(startRow, startColumn, PART_OF_PATH)
else:#如果没有找到路径,则将当前位置标记为死路。
maze.updatePosition(startRow, startColumn, DEAD_END)
return found#返回是否找到了路径。
#迷宫对象 myMaze,读取迷宫布局并绘制迷宫图形。然后调用 searchFrom 函数以起始位置开始搜索迷宫中的路径。
myMaze = Maze('maze2.txt')
myMaze.drawMaze()
myMaze.updatePosition(myMaze.startRow,myMaze.startCol)
searchFrom(myMaze, myMaze.startRow, myMaze.startCol)
找零钱
(没懂 还是没懂)
#递归解法 重复计算
def recMC(coinValueList,change):#硬币面值列表 coinValueList 和找零金额 change 作为参数。
minCoins=change#将 minCoins 初始化为 change,表示当前找零金额需要的最少硬币数。
if change in coinValueList:#如果找零金额本身就是硬币面值列表中的一个硬币面值,则返回 1,表示只需要一枚硬币即可完成找零。
return 1
else:#如果找零金额不是硬币面值列表中的硬币面值
for i in [c for c in coinValueList if c <= change]:#遍历硬币面值列表中小于等于当前金额 cents 的面值。
numCoins=1+recMC(coinValueList,change-i)#递归调用 recMC 函数来计算剩余金额 change-i 所需的最少硬币数,并加上 1(表示使用了一枚面值为 i 的硬币)。
if numCoins < minCoins:#如果当前计算得到的最少硬币数 numCoins 小于 minCoins,则更新 minCoins。
minCoins=numCoins
return minCoins#返回找零金额 change 对应的最少硬币数
print(recMC([1,5,10,21,25],63))#计算找零金额为 63 所需的最少硬币数。
#3
# 改进:记录中间结果
def recDC(coinValueList, change, knownResults):#硬币面值列表 coinValueList、找零金额 change 和已知结果字典 knownResults 作为参数。
minCoins = change#当前找零金额需要的最少硬币数。
if change in coinValueList:#如果找零金额本身就是硬币面值列表中的一个硬币面值,则返回 1,表示只需要一枚硬币即可完成找零。同时将结果存入 knownResults 字典中。
knownResults[change] = 1
return 1
elif knownResults[change] > 0:#如果已经计算过找零金额 change 的最少硬币数,直接返回该值,避免重复计算
return knownResults[change]
else:#如果找零金额不是硬币面值列表中的硬币面值,且没有计算过该金额的最少硬币数,
for i in [c for c in coinValueList if c <= change]:#遍历硬币面值列表中小于等于当前找零金额 change 的面值。
numCoins = 1 + recDC(coinValueList, change - i,knownResults)
#递归调用 recDC 函数来计算剩余金额 change-i 所需的最少硬币数,并加上 1(表示使用了一枚面值为 i 的硬币)。
if numCoins < minCoins:#如果当前计算得到的最少硬币数 numCoins 小于 minCoins,则更新 minCoins。
minCoins = numCoins
knownResults[change] = minCoins#将计算得到的最少硬币数存入 knownResults 字典中,以便下次使用。
return minCoins#返回找零金额 change 对应的最少硬币数。
print(recDC([1, 5, 10, 25], 63, [0] * 64))
#6
# 动态规划方法找零钱
def dpMakeChange(coinValueList,change,minCoins,coinsUsed):#这个感觉有点复杂,不如后面
#使用动态规划方法来确定找零所需的最少硬币数。
# 它遍历从 0 到 change 的每个金额,并计算最少硬币数和使用的最后一枚硬币。
# 函数的参数包括硬币面值列表 coinValueList、需要找零的金额 change、
# 记录最少硬币数的列表 minCoins,以及记录使用的硬币面值的列表 coinsUsed。
# 函数返回找零所需的最少硬币数。
for cents in range(change+1):#遍历从 0 到 change 的每个金额
coinCount = cents#初始化为当前金额 cents,表示当前金额需要的最少硬币数。
newCoin = 1#初始化新加硬币
# 2.减去每个硬币,向后查最少硬币数,同时记录总的最少数
for j in [c for c in coinValueList if c <= cents]:#遍历硬币面值列表中小于等于当前金额 cents 的面值。
if minCoins[cents-j] + 1 < coinCount:#如果使用面值为 j 的硬币能够得到更少的硬币数,则更新 coinCount 和 newCoin。
coinCount = minCoins[cents-j]+1
newCoin = j
# 3.得到最少硬币数,存储在 minCoins 列表中.
minCoins[cents] = coinCount#将当前金额的
coinsUsed[cents] = newCoin#将当前金额的最后一枚硬币面值存储在 coinsUsed 列表中。
return minCoins[change]#返回找零金额 change 对应的最少硬币数。
def printCoins(coinsUsed,change):
#根据记录的使用硬币面值的列表 coinsUsed,逆向打印出使用的硬币面值。
# 它从找零的金额开始,通过不断减去使用的硬币面值来确定下一个硬币的面值,直到找零金额为零。
coin = change
while coin > 0:
thisCoin = coinsUsed[coin]
print(thisCoin)
coin = coin - thisCoin
def main():
#定义了一个要找零的金额 amnt 和一个硬币面值列表 clist。
# 然后创建了用于记录最少硬币数和使用的硬币面值的列表 coinCount 和 coinsUsed。
# 最后,调用 dpMakeChange 函数计算最少硬币数,并调用 printCoins 函数打印出使用的硬币面值。
amnt = 63
clist = [1,5,10,21,25]
coinsUsed = [0]*(amnt+1)
coinCount = [0]*(amnt+1)
print("Making change for",amnt,"requires")
print(dpMakeChange(clist,amnt,coinCount,coinsUsed),"coins")
print("They are:")
printCoins(coinsUsed,amnt)
print("The used list is as follows:")
print(coinsUsed)
main()
# Making change for 63 requires
# 3 coins
# They are:
# 21
# 21
# 21
# The used list is as follows:
# [1, 1, 1, 1, 1, 5, 1, 1, 1, 1, 10, 1, 1, 1, 1, 5, 1, 1, 1, 1, 10, 21, 1, 1, 1, 25, 1, 1, 1, 1, 5, 10, 1, 1, 1, 10, 1, 1, 1, 1, 5, 10, 21, 1, 1, 10, 21, 1, 1, 1, 25, 1, 10, 1, 1, 5, 10, 1, 1, 1, 10, 1, 10, 21]
博物馆大盗
法一 动态规划
法二 递归
查找排序
顺序查找
顺序查找(Sequential Search),也称为线性查找,是一种简单直观的搜索算法。它逐个检查待查找元素是否与目标元素相等,直到找到匹配的元素或遍历完整个数据集。
6.3. The Sequential Search — Problem Solving with Algorithms and Data Structures (runestone.academy)
def sequentialSearch(alist, item):#顺序搜索算法,用于在列表中查找指定的元素。
#列表 alist 和要查找的元素 item
#比对,如果存在,1次,n次,平均n/2
# 时间复杂度为 O(n),其中 n 是列表的长度。
pos = 0#当前位置
found = False#是否找到指定元素,
while pos < len(alist) and not found: #当前位置小于列表长度且未找到指定元素。
if alist[pos] == item:#如果当前位置的元素等于要查找的元素,则将 found 设置为 True,表示找到了元素。
found = True
else:#否则pos 向后移动一位,继续搜索下一个位置.
pos = pos+1
return found#pos 向后移动一位,继续搜索下一个位置。
testlist = [1, 2, 32, 8, 17, 19, 42, 13, 0]
print(sequentialSearch(testlist, 3)) #False
print(sequentialSearch(testlist, 13)) #True
有序查找
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
testlist = [0, 1, 2, 8, 13, 17, 19, 32, 42,]
print(orderedSequentialSearch(testlist, 3))
print(orderedSequentialSearch(testlist, 13))
二分查找
二分查找(Binary Search)是一种高效的查找算法,适用于有序数据集(前提是有序) logn。它通过将数据集一分为二,并与目标元素进行比较,逐步缩小查找范围,直到找到目标元素或确定目标元素不存在。
def binarySearch(alist, item):#二分查找,前提是有序的
first = 0
last = len(alist)-1
found = False
while first<=last and not found:
midpoint = (first + last)//2
if alist[midpoint] == item:#与中间项进行比对
found = True
else:
if item < alist[midpoint]: #小 左移
last = midpoint-1
else: #大 右移
first = midpoint+1
return found
testlist = [0, 1, 2, 8, 13, 17, 19, 32, 42,]
print(binarySearch(testlist, 3))
print(binarySearch(testlist, 13))
def binarySearch(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 binarySearch(alist[:midpoint],item)
else:
return binarySearch(alist[midpoint+1:],item)
testlist = [0, 1, 2, 8, 13, 17, 19, 32, 42,]
print(binarySearch(testlist, 3))
print(binarySearch(testlist, 13))
------------------------------------------
冒泡排序
冒泡排序(Bubble Sort)是一种简单的排序算法,它通过比较相邻元素的大小并交换位置来实现排序。算法的基本思想是重复地遍历待排序的列表,每次比较相邻的两个元素,如果它们的顺序不正确,则交换它们的位置。重复进行这个过程,直到整个列表排序完成。
无序表初始数据项的排列状况对冒泡排序没有影响
❖算法过程总需要n-1趟, 随着趟数的增加, 比对次数逐步从n-1减少到1, 并包括可能发生的数据项交换。
❖比对次数是1~ n-1的累加:
❖比对的时间复杂度是O(n2)
关于交换次数, 时间复杂度也是O(n2),通常每次交换包括3次赋值
❖最好的情况是列表在排序前已经有序, 交换次数为0
❖最差的情况是每次比对都要进行交换, 交换次数等于比对次数
❖平均情况则是最差情况的一半
冒泡排序通常作为时间效率较差的排序算法, 来作为其它算法的对比基准。(hhhh工具人)
❖其效率主要差在每个数据项在找到其最终位置之前,
❖必须要经过多次比对和交换, 其中大部分的操作是无效的。
❖但有一点优势, 就是无需任何额外的存储空间开销
def bubbleSort(alist):
for passnum in range(len(alist)-1,0,-1):#n-1到1比对轮数。每一轮比较都会将当前最大的元素冒泡到最后的位置。
for i in range(passnum):#遍历未排序部分的元素
if alist[i]>alist[i+1]:#如果当前元素大于后一个元素,交换它们的位置,将较大的元素向后移动。
# temp = alist[i]
# alist[i] = alist[i+1]
# alist[i+1] = temp
alist[i],alist[i+1]=alist[i+1],alist[i]
alist = [54,26,93,17,77,31,44,55,20]
bubbleSort(alist)
print(alist)#[17, 20, 26, 31, 44, 54, 55, 77, 93]
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
alist[i],alist[i+1]=alist[i+1],alist[i]
passnum = passnum-1# 变量递减,表示已经排序好的部分元素的数量。
alist=[20,30,40,90,50,60,70,80,100,110]
shortBubbleSort(alist)
print(alist)#[20, 30, 40, 50, 60, 70, 80, 90, 100, 110]
选择排序
选择排序(Selection Sort)是一种简单的排序算法,它的基本思想是每次从待排序的列表中选择最小(或最大)的元素,并将其放置在已排序部分的末尾。通过不断重复这个过程,直到整个列表排序完成。
选择排序对冒泡排序进行了改进, 保留了其基本的多趟比对思路, 每趟都使当前最大项就位。
❖但选择排序对交换进行了削减, 相比起冒泡排序进行多次交换, 每趟仅进行1次交换, 记录最大项的所在位置, 最后再跟本趟最后一项交换
❖选择排序的时间复杂度比冒泡排序稍优。比对次数不变,还是O(n2),交换次数则减少为O(n)
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 #记录位置
#一次交换
alist[fillslot],alist[positionOfMax]= alist[positionOfMax],alist[fillslot]
alist = [54,26,93,17,77,31,44,55,20]
selectionSort(alist)
print(alist)#[17, 20, 26, 31, 44, 54, 55, 77, 93]
插入排序
插入排序(Insertion Sort)是一种简单直观的排序算法,它的基本思想是将待排序的列表分为已排序部分和未排序部分,每次从未排序部分取出一个元素,将其插入到已排序部分的适当位置,直到所有元素都被插入到已排序部分,完成排序。
插入排序的比对主要用来寻找“新项”的插入位置
❖最差情况是每趟都与子列表中所有项进行比对, 总比对次数与冒泡排序相同, 数量级仍是O(n2)
❖最好情况, 列表已经排好序的时候, 每趟仅需1次比对, 总次数是O(n)
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#插入新项
alist = [54,26,93,17,77,31,44,55,20]
insertionSort(alist)
print(alist)
希尔排序
希尔排序(Shell Sort)是一种改进的插入排序算法,它通过将待排序的列表分割成若干子列表来进行排序,最后再使用插入排序将子列表合并成完整的有序列表。希尔排序的基本思想是先使列表中的元素大致有序,然后逐步缩小间隔,直到最后一次间隔为1,完成排序。
列表越接近有序, 插入排序的比对次数就越少
❖ 从这个情况入手, 谢尔排序以插入排序作为基础, 对无序表进行“间隔”划分子列表, 每个子列表都执行插入排序
❖粗看上去, 谢尔排序以插入排序为基础,可能并不会比插入排序好
❖但由于每趟都使得列表更加接近有序, 这过程会减少很多原先需要的“无效”比对。
对谢尔排序的详尽分析比较复杂,大致说是介于O(n)和O(n2)之间
❖如果将间隔保持在2k-1(1、 3、 5、 7、 15、 31等等) , 谢尔排序的时间复杂度约为O(n3/2)
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):
#与插入排序类似,但插入位置与前一个元素的距离不再是 1,而是 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
alist = [54,26,93,17,77,31,44,55,20]
shellSort(alist)
print(alist)
归并排序(递归 分治思想)
归并排序(Merge Sort)是一种基于分治思想的排序算法。它将待排序的列表逐步划分为更小的子列表,然后对子列表进行排序,最后通过合并操作将排序好的子列表合并成最终的有序列表。归并排序的关键在于合并操作,利用合并操作可以将两个有序的子列表合并成一个有序的列表。
归并排序是递归算法, 思路是将数据表持续分裂为两半, 对两半分别进行归并排序
将归并排序分为两个过程来分析: 分裂和归并
❖分裂的过程, 借鉴二分查找中的分析结果, 是对数复杂度, 时间复杂度为O(log n)
❖归并的过程, 相对于分裂的每个部分, 其所有数据项都会被比较和放置一次, 所以是线性复杂度, 其时间复杂度是O(n)
综合考虑,每次分裂的部分都进行一次O(n)的数据项归并,总的时间复杂度是O(nlog n)
def mergeSort(alist):
print("Splitting ",alist)#首先输出当前要拆分的列表 alist,用于观察拆分过程。
if len(alist)>1:#如果列表的长度大于1,则继续拆分。
mid = len(alist)//2#计算中间位置 mid,将列表分为左右两半
lefthalf = alist[:mid]
righthalf = alist[mid:]
#递归调用 mergeSort 函数分别对左半部分 lefthalf 和右半部分 righthalf 进行排序。
mergeSort(lefthalf)
mergeSort(righthalf)
#对于合并过程,使用三个指针 i、j、k 分别指向左半部分、右半部分和合并后的列表 alist 的位置。
i=0
j=0
k=0
#比较左半部分和右半部分的元素大小,将较小的元素放入 alist 中,并移动相应指针。
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
#比较左半部分和右半部分的元素大小,将较小的元素放入 alist 中,并移动相应指针。
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)#最后输出合并后的列表 alist。
alist = [54,26,93,17,77,31,44,55,20]
mergeSort(alist)
print(alist)
# Splitting [54, 26, 93, 17, 77, 31, 44, 55, 20]
# Splitting [54, 26, 93, 17]
# Splitting [54, 26]
# Splitting [54]
# Merging [54]
# Splitting [26]
# Merging [26]
# Merging [26, 54]
# Splitting [93, 17]
# Splitting [93]
# Merging [93]
# Splitting [17]
# Merging [17]
# Merging [17, 93]
# Merging [17, 26, 54, 93]
# Splitting [77, 31, 44, 55, 20]
# Splitting [77, 31]
# Splitting [77]
# Merging [77]
# Splitting [31]
# Merging [31]
# Merging [31, 77]
# Splitting [44, 55, 20]
# Splitting [44]
# Merging [44]
# Splitting [55, 20]
# Splitting [55]
# Merging [55]
# Splitting [20]
# Merging [20]
# Merging [20, 55]
# Merging [20, 44, 55]
# Merging [20, 31, 44, 55, 77]
# Merging [17, 20, 26, 31, 44, 54, 55, 77, 93]
# [17, 20, 26, 31, 44, 54, 55, 77, 93]
python风格更易理解
def merge_sort(lst):
if len(lst) <=1:
return lst
middle=len(lst)//2
left=merge_sort(lst[:middle])
right = merge_sort(lst[middle: ])
merged=[]
while left and right:#左右还有数据就合并。添加小的
# if left[0]<=right[0]:
# merged.append(left.pop(0))
# else:
# merged.append(right.pop(0))
merged.append(left.pop(0) if left[0] <= right[0] else right.pop(0))
#有剩下的
merged.extend(right if right else left)
return merged
alist = [54,26,93,17,77,31,44,55,20]
merge_sort(alist)
print(alist)#[54, 26, 93, 17, 77, 31, 44, 55, 20]
快速排序
快速排序(Quick Sort)是一种基于分治思想的排序算法。它通过选择一个基准元素,将待排序的列表划分为两部分,一部分元素小于等于基准元素,一部分元素大于基准元素。然后对这两部分分别进行快速排序,最后将两部分合并起来得到有序列表。
快速排序的思路是依据一个“中值”数据项来把数据表分为两半:小于中值的一半和大于中值的一半, 然后每部分分别进行快速排序(递归)
快速排序过程分为两部分: 分裂和移动
如果分裂总能把数据表分为相等的两部分,那么就是O(log n)的复杂度;
而移动需要将每项都与中值进行比对,还是O(n)
❖综合起来就是O(nlog n);
❖而且, 算法运行过程中不需要额外的存储空间。
# 分裂数据表的目标:找到“中值”的位置
# ❖分裂数据表的手段
# 设置左右标(left/rightmark)
# 左标向右移动,右标向左移动
# • 左标一直向右移动,碰到比中值大的就停止
# • 右标一直向左移动,碰到比中值小的就停止
# • 然后把左右标所指的数据项交换
# 继续移动,直到左标移到右标的右侧,停止移动
# 这时右标所指位置就是“中值”应处的位置
# 将中值和这个位置交换
# 分裂完成,左半部比中值小,右半部比中值大
def quickSort(alist):
quickSortHelper(alist,0,len(alist)-1)#传递列表 alist 的起始索引 0 和结束索引 len(alist) - 1。
def quickSortHelper(alist,first,last):
if first<last:#首先判断起始索引 first 是否小于结束索引 last,如果是,则继续进行排序。
#调用 partition 函数获取枢纽元素的位置 splitpoint。
splitpoint = partition(alist,first,last)
#递归调用 quickSortHelper 函数对左半部分和右半部分进行排序
quickSortHelper(alist,first,splitpoint-1)
quickSortHelper(alist,splitpoint+1,last)
def partition(alist,first,last):
#确定枢纽元素(pivot)的位置,并将列表划分为两个子列表,小于枢纽元素的在左边,大于枢纽元素的在右边。
pivotvalue = alist[first]#先选中值(枢纽元素)
#leftmark 和 rightmark 分别指向列表的起始和结束位置。
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:#左右标互换
alist[leftmark] ,alist[rightmark]=alist[rightmark],alist[leftmark]
#中值就位
alist[first],alist[rightmark]=alist[rightmark], alist[first]
return rightmark#返回中值点即分裂点
alist = [54,26,93,17,77,31,44,55,20]
quickSort(alist)
print(alist)
散列 哈希
最著名的近似完美散列函数是MD5和SHA系列函数
❖MD5(Message Digest) 将任何长度的数据变换为固定长为128位(16字节)的“摘要”
128位二进制已经是一个极为巨大的数字空间:据说是地球沙粒的数量
❖SHA(Secure Hash Algorithm) 是另一组散列函数
SHA-0/SHA-1输出散列值160位(20字节),SHA-256/SHA-224分别输出256位、 224位,SHA-512/SHA-384分别输出512位和384位
❖160位二进制相当于10的48次方, 地球上水分子数量估计是47次方
❖256位二进制相当于10的77方, 已知宇宙所有基本粒子大约是72~ 87次方
如果采用线性探测的开放定址法来解决冲突(λ在0~1之间)
成功的查找,平均需要比对次数为:
不成功的查找,平均比对次数为:
❖如果采用数据链来解决冲突(λ可大于1)
成功的查找,平均需要比对次数为: 1+λ/2
不成功的查找,平均比对次数为: λ
class HashTable:
def __init__(self):#初始化哈希表的大小、槽位列表和数据列表
self.size = 11
self.slots = [None] * self.size
self.data = [None] * self.size
def put(self,key,data):#
#将键值对存储在哈希表中。
# 首先,根据键的哈希值计算插槽位置。如果该插槽为空,则直接存储键和数据。
# 如果插槽已经被占用,但是键与要存储的键相同,则更新该插槽的数据。
# 如果插槽被其他键占用,则使用重新散列(rehash)方法找到下一个可用插槽,并存储键和数据。
hashvalue = self.hashfunction(key,len(self.slots))
if self.slots[hashvalue] == None:#key不存在,未冲突
self.slots[hashvalue] = key
self.data[hashvalue] = data
else:##key已经存在,暂换val
if self.slots[hashvalue] == key:
self.data[hashvalue] = data #replace
else:#散列冲突,再散列,直到找到空槽或者key
nextslot = self.rehash(hashvalue,len(self.slots))
while self.slots[nextslot] != None and \
self.slots[nextslot] != key:
nextslot = self.rehash(nextslot,len(self.slots))
if self.slots[nextslot] == None:
self.slots[nextslot]=key
self.data[nextslot]=data
else:
self.data[nextslot] = data #replace
def hashfunction(self,key,size):#简单求余。根据键的值和哈希表大小计算插槽位置。
return key%size
def rehash(self,oldhash,size):#线性探测+1重新散列函数,用于解决哈希冲突。
# 在发生冲突时,通过线性探测的方式找到下一个可用插槽。
return (oldhash+1)%size
def get(self,key):#根据键获取对应的值。
# 根据键的哈希值计算起始插槽位置,然后在哈希表中查找对应的键,直到找到匹配的键或遍历完所有插槽。
startslot = self.hashfunction(key,len(self.slots))#标记散列值为查找起点
data = None
stop = False
found = False
position = startslot
#找key,直到空槽或者回到起点
while self.slots[position] != None and \
not found and not stop:
if self.slots[position] == key:
found = True
data = self.data[position]
else:
position=self.rehash(position,len(self.slots))
#未找到key,再散列继续找
if position == startslot:
stop = True#回到起点,停
return data
def __getitem__(self,key):#重载索引操作符[],查找
# 使得可以通过H[key]的方式获取键对应的值。
return self.get(key)
def __setitem__(self,key,data):#重载索引操作符[],赋值
# 使得可以通过H[key] = value的方式将键值对存储在哈希表中。
self.put(key,data)
# 类似字典
H=HashTable()
H[54]="cat"
H[26]="dog"
H[93]="lion"
H[17]="tiger"
H[77]="bird"
H[31]="cow"
H[44]="goat"
H[55]="pig"
H[20]="chicken"
print(H.slots)#[77, 44, 55, 20, 26, 93, 17, None, None, 31, 54]
print(H.data)#['bird', 'goat', 'pig', 'chicken', 'dog', 'lion', 'tiger', None, None, 'cow', 'cat']
print(H[20])#chicken
print(H[17])#tiger
H[20]='duck'
print(H[20])#duck
print(H[99])#None
总结
在无序表或者有序表上的顺序查找, 其时间复杂度为O(n)
❖在有序表上进行二分查找, 其最差复杂度为O(log n)
❖散列表可以实现常数级时间的查找
❖完美散列函数作为数据一致性校验, 应用很广
❖区块链技术是一种去中心化的分布式数据库, 通过“工作量证明”机制来维持运行
❖冒泡、 选择和插入排序是O(n2)的算法
❖谢尔排序在插入排序的基础上进行了改进, 采用对递增子表排序的方法, 其时间复杂度可以在O(n)和O(n2)之间
❖归并排序的时间复杂度是O(nlog n), 但归并的过程需要额外存储空间
❖快速排序最好的时间复杂度是O(nlog n), 也不需要额外的存储空间, 但如果分裂点偏离列表中心的话, 最坏情况下会退化到O(n2)
树
节点Node:组成树的基本部分
边Edge:边是组成树的另一个基本部分
根Root:树中唯一一个没有入边的节点
路径Path:由边依次连接在一起的节点的有序列表
子节点Children:入边均来自于同一个节点的若干节点, 称为这个节点的子节点
❖父节点Parent:一个节点是其所有出边所连接节点的父节点
兄弟节点Sibling:具有同一个父节点的节点之间称为兄弟节点
❖子树Subtree:一个节点和其所有子孙节点, 以及相关边的集合
叶节点Leaf:没有子节点的节点称为叶节点
❖ 层级Level:从根节点开始到达一个节点的路径,所包含的边的数量, 称为这个节点的层级。如D的层级为2,根节点的层级为0
❖ 高度:树中所有节点的最大层级称为树的高度
#嵌套列表法
def BinaryTree(r):
#创建一个二叉树,其中根节点的值为 r,左子树和右子树为空列表。
return [r, [], []]
def insertLeft(root,newBranch):
#在给定节点 root 的左侧插入一个新的子树,子树的根节点值为 newBranch。
t = root.pop(1)
if len(t) > 1:
root.insert(1,[newBranch,t,[]])
else:
root.insert(1,[newBranch, [], []])
return root
def insertRight(root,newBranch):
#同理,在给定节点 root 的右侧插入一个新的子树,子树的根节点值为 newBranch。
t = root.pop(2)
if len(t) > 1:
root.insert(2,[newBranch,[],t])
else:
root.insert(2,[newBranch,[],[]])
return root
def getRootVal(root):#获取根节点的值
return root[0]
def setRootVal(root,newVal):#设置根节点的值为 newVal
root[0] = newVal
def getLeftChild(root):#获取左子树。
return root[1]
def getRightChild(root):#获取右子树。
return root[2]
r = BinaryTree(3)
insertLeft(r,4)
insertLeft(r,5)
insertRight(r,6)
insertRight(r,7)
l = getLeftChild(r)
print(l)#[5, [4, [], []], []]
setRootVal(l,9)
print(r)#[3, [9, [4, [], []], []], [7, [], [6, [], []]]]
insertLeft(l,11)
print(r)#[3, [9, [11, [4, [], []], []], []], [7, [], [6, [], []]]]
print(getRightChild(getRightChild(r)))#[6, [], []]
节点链接法
class BinaryTree:
"""
A recursive implementation of Binary Tree
Using links and Nodes approach.
"""
def __init__(self,rootObj):
#初始化二叉树,根节点的值为 rootObj,左子树和右子树为空。
self.key = rootObj
self.leftChild = None
self.rightChild = None
def insertLeft(self,newNode):
#在当前节点的左侧插入一个新节点,节点的值为 newNode。
# 如果当前节点的左子节点为空,直接创建一个新的二叉树对象,并将其设为当前节点的左子节点
# 否则,创建一个新的二叉树对象t,将当前节点的左子节点设为t的左子节点,然后将 t设为当前节点的新左子节点。
if self.leftChild == None:
self.leftChild = BinaryTree(newNode)
else:
t = BinaryTree(newNode)
t.leftChild = self.leftChild
self.leftChild = t
def insertRight(self,newNode):
#在当前节点的右侧插入一个新节点,节点的值为 newNode。
if self.rightChild == None:
self.rightChild = BinaryTree(newNode)
else:
t = BinaryTree(newNode)
t.rightChild = self.rightChild
self.rightChild = t
def getRightChild(self):#获取当前节点的右子节点。
return self.rightChild
def getLeftChild(self):#获取当前节点的右子节点。
return self.leftChild
def setRootVal(self,obj):#设置当前节点的值为 obj。
self.key = obj
def getRootVal(self):#获取当前节点的值。
return self.key
def inorder(self):#中序
if self.leftChild:#判断是否为空
self.leftChild.inorder()
print(self.key)
if self.rightChild:
self.rightChild.inorder()
def postorder(self):#后序
if self.leftChild:
self.leftChild.postorder()
if self.rightChild:
self.rightChild.postorder()
print(self.key)
def preorder(self):#前序
print(self.key)
if self.leftChild:
self.leftChild.preorder()
if self.rightChild:
self.rightChild.preorder()
def printexp(self):##中序遍历生成全括号表达式,左根右
if self.leftChild:
print('(', end=' ')
self.leftChild.printexp()
print(self.key, end=' ')
if self.rightChild:
self.rightChild.printexp()
print(')', end=' ')
def postordereval(self):#后序遍历表达式求值
# 字典,将操作符与相应的计算函数关联起来。
opers = {'+': operator.add, '-': operator.sub, '*': operator.mul, '/': operator.truediv}
res1 = None
res2 = None
# 函数递归地计算解析树的左子树和右子树的值,并根据当前节点的操作符执行相应的计算
if tree:#左右根
# 1.缩小规模
res1 = postordereval(tree.getLeftChild())
res2 = postordereval(tree.getRightChild())
# 2.递归
# 左右子树的值都存在,它会通过操作符字典找到相应的计算函数,并将左右子树的值作为参数进行计算。
if res1 and res2:
return opers[tree.getRootVal()](res1, res2)
# 3.结束
else: # 否则,它返回当前节点的值。
return tree.getRootVal()
def inorder(tree):#中序
if tree != None:
inorder(tree.getLeftChild())
print(tree.getRootVal())
inorder(tree.getRightChild())
def printexp(tree):#中序遍历生成全括号表达式
if tree.leftChild:
print('(', end=' ')
printexp(tree.getLeftChild())
print(tree.getRootVal(), end=' ')
if tree.rightChild:
printexp(tree.getRightChild())
print(')', end=' ')
def printexp(tree):#中序遍历,全括号表达式 左根右
sVal = ""
if tree:
sVal = '(' + printexp(tree.getLeftChild())
sVal = sVal + str(tree.getRootVal())
sVal = sVal + printexp(tree.getRightChild()) + ')'
return sVal
def postordereval(tree):
opers = {'+':operator.add, '-':operator.sub, '*':operator.mul, '/':operator.truediv}
res1 = None
res2 = None
if tree:
res1 = postordereval(tree.getLeftChild()) #// \label{peleft}
res2 = postordereval(tree.getRightChild()) #// \label{peright}
if res1 and res2:
return opers[tree.getRootVal()](res1,res2) #// \label{peeval}
else:
return tree.getRootVal()
def height(tree):
if tree == None:
return -1
else:
return 1 + max(height(tree.leftChild),height(tree.rightChild))
t = BinaryTree(7)
t.insertLeft(3)
t.insertRight(9)
inorder(t)
import operator
x = BinaryTree('*')
x.insertLeft('+')
l = x.getLeftChild()
l.insertLeft(4)
l.insertRight(5)
x.insertRight(7)
print(printexp(x))
print(postordereval(x))
print(height(x))
# 3
# 7
# 9
# (((4)+(5))*(7))
# 63
# 2
r = BinaryTree('a')
print(r.getRootVal())#a
print(r.getLeftChild())#None
r.insertLeft('b')
print(r.getLeftChild())#
print(r.getLeftChild().getRootVal())#b
r.insertRight('c')
print(r.getRightChild())
print(r.getRightChild().getRootVal())#c
r.getRightChild().setRootVal('hello')
print(r.getRightChild().getRootVal())#hello
表达式解析
from pythonds.basic.stack import Stack
from pythonds.trees.binaryTree import BinaryTree
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()#出栈上升
#如果遇到其他情况,抛出 ValueError。
else:
raise ValueError
return eTree
#注意:他不是把单个节点压到栈里,而是整个子树,弹出的时候也是同理,弹出整个子树
pt = buildParseTree("( ( 10 + 5 ) * 3 )")
pt.postorder()
二叉堆
二叉堆(Binary Heap)是一种特殊的二叉树数据结构,它具有以下性质:
- 完全二叉树性质:二叉堆是一棵完全二叉树,即除了最后一层可能不满外,其他层的节点都是满的,且最后一层的节点都靠左排列。
- 堆序性质:在最小堆(Min Heap)中,父节点的值小于或等于其子节点的值;在最大堆(Max Heap)中,父节点的值大于或等于其子节点的值。
二叉堆常用于实现优先队列(Priority Queue)的数据结构,其中具有最小(或最大)值的元素总是位于堆的根节点,且可以在常数时间内删除最小(或最大)元素,并且插入新元素的时间复杂度为对数时间。
二叉堆通常使用数组来表示,将树的节点按照层序遍历的顺序依次存储在数组中。对于给定的节点索引 i
,其父节点、左子节点和右子节点的索引可以通过简单的计算得到:
- 父节点索引:
parent = (i - 1) // 2
- 左子节点索引:
left_child = 2 * i + 1
- 右子节点索引:
right_child = 2 * i + 2
class BinHeap:
#实现一个二叉堆
def __init__(self):
self.heapList = [0]#用于存储堆元素的列表,我们将其初始化为一个包含0的列表。这里的0没有实际意义,只是为了方便后续计算父子节点的索引关系。
self.currentSize = 0#当前堆中的元素数量,我们将其初始化为0,表示初始堆是空的。
def buildHeap(self,alist):#根据给定的列表 alist 构建二叉堆。
i = len(alist) // 2#因为叶子节点不用下沉,找到其父节点。计算出堆列表alist的长度的一半,然后将其赋值给变量i。
self.currentSize = len(alist)#当前堆中的元素数量。
self.heapList = [0] + alist[:]#将列表alist的内容复制到堆列表heapList中。为了保持堆属性,我们在列表的前面添加了一个值为0的占位符。这样做是为了让堆的索引从1开始,方便计算父子节点的索引关系。
print(len(self.heapList), i)
while (i > 0):#从索引i开始,逐个向前处理每个元素。在循环中,我们打印出当前的堆列表和处理的索引值,以便观察构建过程。
print(self.heapList, i)
self.percDown(i)#对当前索引i及其子树执行下滤操作
i = i - 1
print(self.heapList,i)
def percDown(self,i):#执行下滤操作,将索引i处的元素与其子节点进行比较和交换
while (i * 2) <= self.currentSize:#当前节点i的左子节点索引乘以2不超过当前堆的大小。这是因为只有当当前节点存在左子节点时,才需要进行下滤操作。
mc = self.minChild(i)#找到当前节点i的较小子节点的索引mc。
if self.heapList[i] > self.heapList[mc]:#交换下沉。如果当前节点大于较小子节点,就交换它们的位置,以保持堆的性质。
self.heapList[i] ,self.heapList[mc]=self.heapList[mc],self.heapList[i]
i = mc#沿路径向下。更新当前节点的索引为较小子节点的索引mc
def minChild(self,i):#用于确定当前节点i的较小子节点的索引。
if i * 2 + 1 > self.currentSize:#唯一子节点。检查当前节点的右子节点是否存在
return i * 2#如果右子节点不存在,则返回当前节点的左子节点索引i * 2,因为左子节点是唯一存在的子节点。
else:#返回较小的。
if self.heapList[i * 2] < self.heapList[i * 2 + 1]:
return i * 2#如果左子节点的值小于右子节点的值,则返回左子节点的索引i * 2,否则返回右子节点的索引i * 2 + 1。
else:
return i * 2 + 1
def percUp(self,i):#确保父节点的值不大于子节点的值。
while i // 2 > 0:#根节点是1
if self.heapList[i] < self.heapList[i//2]:#如果当前节点的值小于其父节点的值,说明需要交换它们的位置,以维持最小堆的性质。
self.heapList[i // 2] ,self.heapList[i]=self.heapList[i],self.heapList[i // 2]
i = i // 2#上浮,沿着路径向上。更新当前节点的索引为其父节点的索引i // 2
def insert(self,k):#向二叉堆中插入元素 k。
self.heapList.append(k)
self.currentSize = self.currentSize + 1#堆的大小增加了一个元素
self.percUp(self.currentSize)#新key上浮。将新插入的元素与其父节点进行比较,并根据需要进行交换,以维持最小堆的性质。
#父节点的值不大于子节点的值
def delMin(self):# 删除并返回二叉堆中的最小值(根节点)。
retval = self.heapList[1]#堆的根节点(最小元素)
self.heapList[1] = self.heapList[self.currentSize]#将堆的最后一个元素移动到根节点的位置
self.currentSize = self.currentSize - 1#堆的大小减少了一个元素。
self.heapList.pop()#将堆的最后一个元素从列表中移除,以完成删除操作
self.percDown(1)#将根节点与其子节点进行比较,并根据需要进行交换,以维持最小堆的性质。
#父节点的值不大于子节 点的值。这样,堆的最小元素被删除后,堆重新恢复了最小堆的性质。
return retval#返回保存的最小元素 retval。
def isEmpty(self):#检查二叉堆是否为空。
if currentSize == 0:
return True
else:
return False
class FooThing:#用于存储键值对
def __init__(self,x,y):
self.key = x
self.val = y
def __lt__(self,other):#用于比较键值对的大小。
if self.key < other.key:
return True
else:
return False
def __gt__(self,other):
if self.key > other.key:
return True
else:
return False
def __hash__(self):
return self.key
class TestBinHeap(unittest.TestCase):
def setUp(self):
self.theHeap = BinHeap()
self.theHeap.insert(FooThing(5,'a'))
self.theHeap.insert(FooThing(9,'d'))
self.theHeap.insert(FooThing(1,'x'))
self.theHeap.insert(FooThing(2,'y'))
self.theHeap.insert(FooThing(3,'z'))
def testInsert(self):
assert self.theHeap.currentSize == 5
def testDelmin(self):#删除堆中的最小元素,并断言每次删除的最小元素的值是否符合预期。
assert self.theHeap.delMin().val == 'x'
assert self.theHeap.delMin().val == 'y'
assert self.theHeap.delMin().val == 'z'
assert self.theHeap.delMin().val == 'a'
def testMixed(self):#插入一些整数值,然后进行删除操作,并断言每次删除的最小元素的值是否符合预期。
myHeap = BinHeap()
myHeap.insert(9)
myHeap.insert(1)
myHeap.insert(5)
assert myHeap.delMin() == 1
myHeap.insert(2)
myHeap.insert(7)
assert myHeap.delMin() == 2
assert myHeap.delMin() == 5
def testDupes(self):#插入一些重复的整数值,并断言堆的当前大小和删除的最小元素的值是否符合预期。
myHeap = BinHeap()
myHeap.insert(9)
myHeap.insert(1)
myHeap.insert(8)
myHeap.insert(1)
assert myHeap.currentSize == 4
assert myHeap.delMin() == 1
assert myHeap.delMin() == 1
assert myHeap.delMin() == 8
def testBuildHeap(self):#构建一个堆并连续删除最小元素,断言每次删除的最小元素的值是否符合预期。
myHeap = BinHeap()
myHeap.buildHeap([9,5,6,2,3])
f = myHeap.delMin()
print("f = ", f)
assert f == 2
assert myHeap.delMin() == 3
assert myHeap.delMin() == 5
assert myHeap.delMin() == 6
assert myHeap.delMin() == 9
if __name__ == '__main__':
d = {}
d[FooThing(1,'z')] = 10
unittest.main()
二叉查找树
二叉查找树(Binary Search Tree),也称为二叉搜索树或有序二叉树,是一种常用的二叉树数据结构。它具有以下特点:
- 左子树节点的值小于根节点的值,右子树节点的值大于根节点的值。
- 对于每个节点,它的左子树和右子树都是二叉查找树。
(首先插入的是根)
import unittest
class BinarySearchTree:
'''
put(key, val):向二叉搜索树中插入键值对。如果树中已存在相同的键,则会更新对应的值。
get(key):根据键查找对应的值。
delete(key):根据键删除对应的节点。
__len__():返回二叉搜索树的节点数。
__getitem__(y) <==> x[y]
__setitem__(k,v) <==> x[k] = v
__contains__(key):检查给定的键是否存在于树中。
inorder():按照中序遍历的顺序打印树中的键。
preorder():按照前序遍历的顺序打印树中的键。
postorder():按照后序遍历的顺序打印树中的键。
__iter__():返回一个迭代器,用于迭代树中的所有键。
length():返回二叉搜索树中的节点数。
delete_key(key):删除指定键的节点。
findSuccessor():查找指定节点的后继节点。
findMin():查找树中最小的节点。
isLeaf():检查节点是否为叶子节点。
hasLeftChild():检查节点是否有左子节点。
hasRightChild():检查节点是否有右子节点。
hasBothChildren():检查节点是否同时有左子节点和右子节点。
hasAnyChildren():检查节点是否至少有一个子节点。
isLeftChild():检查节点是否为其父节点的左子节点。
isRightChild():检查节点是否为其父节点的右子节点。
isRoot():检查节点是否为根节点。
replaceNodeData(key, value, lc, rc):替换节点的键、值以及左右子节点。
'''
def __init__(self):#根节点为空,且节点数为 0。
self.root = None
self.size = 0
def put(self,key,val):#向二叉搜索树中插入键值对。
#检查根节点是否存在(即二叉搜索树是否为空)
if self.root: # 如果存在根节点,调用_put方法将键值对插入到根节点下的适当位置。
self._put(key,val,self.root)
else:# 如果根节点不存在(即二叉搜索树为空),则直接将新节点作为根节点。
self.root = TreeNode(key,val)
self.size = self.size + 1#增加二叉搜索树的大小
def _put(self,key,val,currentNode):#递归地插入键值对到二叉搜索树的合适位置。
if key < currentNode.key:#如果 key 小于 currentNode.key,则将其插入到当前节点的左子树。
if currentNode.hasLeftChild():#如果当前节点已经有左子节点,递归调用 _put 方法将键值对插入到当前节点的左子树的合适位置。
self._put(key,val,currentNode.leftChild)
else:#如果当前节点没有左子节点,直接将新节点作为左子节点。
currentNode.leftChild = TreeNode(key,val,parent=currentNode)
else: #key 大,右子树。
if currentNode.hasRightChild():
self._put(key,val,currentNode.rightChild)
else:
currentNode.rightChild = TreeNode(key,val,parent=currentNode)
def __setitem__(self,k,v):#赋值,bst[key] = value
self.put(k,v)
def get(self,key):
#首先检查是否存在根节点,如果存在,则调用 _get 方法进行递归查找。
# 如果找到了与给定键匹配的节点,则返回该节点的值(payload)。
# 如果未找到匹配的节点,则返回 None。
#即如果树为空或者给定键不存在于树中,它将返回 None。
if self.root:
res = self._get(key,self.root)
if res:
return res.payload
else:
return None
else:
return None
def _get(self,key,currentNode):
if not currentNode:
return None#未找到匹配的节点。
elif currentNode.key == key:
return currentNode#相等,则返回当前节点,表示找到了匹配的节点。
elif key < currentNode.key:#小于,在当前节点的左子树中递归查找
return self._get(key,currentNode.leftChild)
else:#大于,在当前节点的右子树中递归查找
return self._get(key,currentNode.rightChild)
def __getitem__(self,key):#x[y]
res = self.get(key)
#如果找到了匹配的节点 res,则返回该节点。
#如果未找到匹配的节点,即 res 为 None,则引发 KeyError 异常,表示键不存在于树中.
if res:
return res
else:
raise KeyError('Error, key not in tree')
def __contains__(self,key): #in
if self._get(key,self.root):
return True
else:
return False
def length(self):#个数
return self.size
def __len__(self):#len
return self.size
def __iter__(self):#for item in tree:
return self.root.__iter__()
def delete(self,key):#从二叉搜索树中删除指定键的节点
#它检查树的大小是否大于 1。如果是,它调用 _get 方法来获取具有指定键的节点 nodeToRemove。
if self.size > 1:
nodeToRemove = self._get(key,self.root)
if nodeToRemove: # 如果找到了该节点,它调用 remove 方法来删除节点,并将树的大小减 1。
self.remove(nodeToRemove)
self.size = self.size-1
else:# 如果没有找到节点,则抛出一个 KeyError 异常。
raise KeyError('Error, key not in tree')
elif self.size == 1 and self.root.key == key:
#如果树的大小为 1 且根节点的键与指定键相等,则直接将根节点设为 None,并将树的大小减 1。
self.root = None
self.size = self.size - 1
else:#如果树的大小为 0 或者根节点的键与指定键不相等,则抛出一个 KeyError 异常。这是因为树为空或者根节点不是待删除的节点.
raise KeyError('Error, key not in tree')
def __delitem__(self,key):#del tree[key]
self.delete(key)
def remove(self,currentNode):#删除指定的节点 currentNode。
#如果 currentNode 是叶节点(没有子节点),则将其从父节点的相应位置删除。
# 如果 currentNode 有两个子节点,找到其后继节点 succ(即右子树中最小的节点),将后继节点从树中移除,并将其键和值替换到 currentNode 中。
# 如果 currentNode 有一个子节点,根据其位置更新父节点和子节点之间的关系,以将子节点连接到正确的位置.
if currentNode.isLeaf(): #leaf。检查当前节点是父节点的左孩子还是右孩子,然后将父节点相应的孩子指针置为 None,从而删除当前节点。
if currentNode == currentNode.parent.leftChild:
currentNode.parent.leftChild = None
else:
currentNode.parent.rightChild = None
elif currentNode.hasBothChildren(): #interior
succ = currentNode.findSuccessor()#找到当前节点的后继节点(即右子树中最小的节点)。
succ.spliceOut()#将后继节点从树中删除。
currentNode.key = succ.key#将后继节点从树中删除。
currentNode.payload = succ.payload
else: # this node has one child
if currentNode.hasLeftChild():#如果当前节点有左孩子,
if currentNode.isLeftChild():#左左。检查当前节点是父节点的左孩子还是右孩子。
currentNode.leftChild.parent = currentNode.parent#更新当前节点的左孩子的父节点为当前节点的父节点。
currentNode.parent.leftChild = currentNode.leftChild#更新当前节点的父节点的相应孩子指针为当前节点的左孩子,从而绕过当前节点,删除当前节点。
elif currentNode.isRightChild():#右右
currentNode.leftChild.parent = currentNode.parent
currentNode.parent.rightChild = currentNode.leftChild
else:#根
currentNode.replaceNodeData(currentNode.leftChild.key,
currentNode.leftChild.payload,
currentNode.leftChild.leftChild,
currentNode.leftChild.rightChild)
else:#如果当前节点有右孩子
if currentNode.isLeftChild():#右左。更新当前节点的父节点的相应孩子指针为当前节点的右孩子,从而绕过当前节点,删除当前节点。
currentNode.rightChild.parent = currentNode.parent#更新当前节点的右孩子的父节点为当前节点的父节点。
currentNode.parent.leftChild = currentNode.rightChild#更新当前节点的父节点的相应孩子指针为当前节点的右孩子,从而绕过当前节点,删除当前节点。
elif currentNode.isRightChild():#右右
currentNode.rightChild.parent = currentNode.parent
currentNode.parent.rightChild = currentNode.rightChild
else:#根
currentNode.replaceNodeData(currentNode.rightChild.key,
currentNode.rightChild.payload,
currentNode.rightChild.leftChild,
currentNode.rightChild.rightChild)
def inorder(self):#中序遍历
self._inorder(self.root)
def _inorder(self,tree):#左子树 -> 根节点 -> 右子树
if tree != None:
self._inorder(tree.leftChild)
print(tree.key)
self._inorder(tree.rightChild)
def postorder(self):#后序遍历
self._postorder(self.root)
def _postorder(self, tree):
if tree:
self._postorder(tree.rightChild)
self._postorder(tree.leftChild)
print(tree.key)
def preorder(self):#先序遍历
self._preorder(self,self.root)
def _preorder(self,tree):
if tree:
print(tree.key)
self._preorder(tree.leftChild)
self._preorder(tree.rightChild)
class TreeNode:#二叉搜索树中的节点类
def __init__(self,key,val,left=None,right=None,parent=None):
#接受键值 key、值 val,以及可选的左孩子、右孩子和父节点。
self.key = key
self.payload = val
self.leftChild = left
self.rightChild = right
self.parent = parent
self.balanceFactor = 0#平衡因子属性
def hasLeftChild(self):
return self.leftChild
def hasRightChild(self):
return self.rightChild
def isLeftChild(self):
return self.parent and self.parent.leftChild == self
def isRightChild(self):
return self.parent and self.parent.rightChild == self
def isRoot(self):
return not self.parent
def isLeaf(self):#检查节点是否为叶节点(没有子节点)。
return not (self.rightChild or self.leftChild)
def hasAnyChildren(self):#有子节点
return self.rightChild or self.leftChild
def hasBothChildren(self):#检查节点是否同时具有左子节点和右子节点。
return self.rightChild and self.leftChild
def replaceNodeData(self,key,value,lc,rc):#替换节点的键、值以及左右孩子。
self.key = key
self.payload = value
self.leftChild = lc
self.rightChild = rc
if self.hasLeftChild():
self.leftChild.parent = self
if self.hasRightChild():
self.rightChild.parent = self
def findSuccessor(self):#查找节点的后继节点。
succ = None#没有后继节点
if self.hasRightChild():
succ = self.rightChild.findMin()#找到右子树中的最小节点作为后继节点。
else:#当前节点没有右孩子,则说明后继节点在祖先节点中
if self.parent:#如果没有父节点,则说明当前节点是根节点,没有后继节点,
if self.isLeftChild():#如果当前节点是父节点的左孩子,则后继节点为父节点。
succ = self.parent
else:#如果当前节点是父节点的右孩子,则将父节点的右孩子指针置为 None,从而将当前节点从树中暂时移除。
self.parent.rightChild = None
succ = self.parent.findSuccessor()#继续寻找后继节点。
self.parent.rightChild = self#将父节点的右孩子指针恢复为当前节点
return succ#返回找到的后继节点。
def spliceOut(self):#从树中移除节点。
if self.isLeaf():#如果是叶节点,直接将其从树中移除。
if self.isLeftChild():#左孩子指针置为 None
self.parent.leftChild = None
else:#右孩子指针置为 None。
self.parent.rightChild = None
elif self.hasAnyChildren():#有孩子节点
if self.hasLeftChild():#如果有左孩子,则将当前节点的左孩子连接到当前节点的父节点相应的孩子位置上。
if self.isLeftChild():#如果当前节点是父节点的左孩子,则将父节点的左孩子指针指向当前节点的左孩子
self.parent.leftChild = self.leftChild
else:#如果当前节点是父节点的右孩子,则将父节点的右孩子指针指向当前节点的左孩子
self.parent.rightChild = self.leftChild
self.leftChild.parent = self.parent#如果当前节点是父节点的右孩子,则将父节点的右孩子指针指向当前节点的左孩子
else:#如果当前节点没有左孩子,即只有右孩子,则将当前节点的右孩子连接到当前节点的父节点相应的孩子位置上,方法与上一步相同。
if self.isLeftChild():
self.parent.leftChild = self.rightChild
else:
self.parent.rightChild = self.rightChild
self.rightChild.parent = self.parent
def findMin(self):#查找当前节点子树中的最小节点。
current = self#将其初始化为当前节点。
while current.hasLeftChild():#如果有左孩子,则将 current 更新为其左孩子,以便向左子树移动。
current = current.leftChild
return current
def __iter__(self):#中序遍历
"""The standard inorder traversal of a binary tree."""
if self:
#如果当前节点有左孩子,递归调用左孩子的 __iter__ 方法,以获取左子树的迭代器对象。
# 然后,通过迭代器遍历左子树中的节点,并依次返回每个节点
if self.hasLeftChild():#左子树不为空
for elem in self.leftChild:#递归调用
yield elem
yield self.key#返回当前节点的键值(self.key)
if self.hasRightChild():
for elem in self.rightChild:
yield elem
class BinaryTreeTests(unittest.TestCase):
def setUp(self):
self.bst = BinarySearchTree()
def testgetput(self):
print('testgetput')
self.bst.put(50,'a')
self.bst.put(10,'b')
self.bst.put(70,'c')
self.bst.put(30,'d')
self.bst.put(85,'d')
self.bst.put(15,'e')
self.bst.put(45,'f')
print(self.bst.get(50))
assert self.bst.get(50) == 'a'
assert self.bst.get(45) == 'f'
assert self.bst.get(85) == 'd'
assert self.bst.get(10) == 'b'
assert self.bst.root.key == 50
assert self.bst.root.leftChild.key == 10
assert self.bst.root.rightChild.key == 70
def testputoper(self):
print('testputoper')
self.bst[25] = 'g'
assert self.bst[25] == 'g'
def testFindSucc(self):
print('testing findSucc')
x = BinarySearchTree()
x.put(10,'a')
x.put(15,'b')
x.put(6,'c')
x.put(2,'d')
x.put(8,'e')
x.put(9,'f')
assert x.root.leftChild.leftChild.findSuccessor().key == 6
assert x.root.leftChild.rightChild.findSuccessor().key == 9
assert x.root.leftChild.rightChild.rightChild.findSuccessor().key == 10
def testSize(self):
print('testing testSize')
self.bst.put(50,'a')
self.bst.put(10,'b')
self.bst.put(70,'c')
self.bst.put(30,'d')
self.bst.put(85,'d')
self.bst.put(15,'e')
self.bst.put(45,'f')
assert self.bst.length() == 7
def testDelete(self):
print('testing delete')
self.bst.put(50,'a')
self.bst.put(10,'b')
self.bst.put(70,'c')
self.bst.put(30,'d')
self.bst.put(85,'d')
self.bst.put(15,'e')
self.bst.put(45,'f')
self.bst.put(5,'g')
print('initial inorder')
self.bst.inorder()
assert (10 in self.bst) == True
self.bst.delete_key(10)
print('delete 10 inorder')
self.bst.inorder()
assert (10 in self.bst) == False
assert self.bst.root.leftChild.key == 15
assert self.bst.root.leftChild.parent == self.bst.root
assert self.bst.root.leftChild.rightChild.parent == self.bst.root.leftChild
assert self.bst.get(30) == 'd'
self.bst.delete_key(15)
print('delete 15 inorder')
self.bst.inorder()
assert self.bst.root.leftChild.key == 30
assert self.bst.root.leftChild.rightChild.key == 45
assert self.bst.root.leftChild.rightChild.parent == self.bst.root.leftChild
self.bst.delete_key(70)
print('delete 70 inorder')
self.bst.inorder()
assert (85 in self.bst) == True
assert self.bst.get(30) == 'd'
print('root key = ', self.bst.root.key)
print('left = ',self.bst.root.leftChild.key)
print('left left = ',self.bst.root.leftChild.leftChild.key)
print('left right = ',self.bst.root.leftChild.rightChild.key)
print('right = ',self.bst.root.rightChild.key)
self.bst.delete_key(50)
assert self.bst.root.key == 85
assert self.bst.root.leftChild.key == 30
assert self.bst.root.rightChild == None
assert self.bst.root.leftChild.leftChild.key == 5
assert self.bst.root.leftChild.rightChild.key == 45
assert self.bst.root.leftChild.leftChild.parent == self.bst.root.leftChild
assert self.bst.root.leftChild.rightChild.parent == self.bst.root.leftChild
print('new root key = ', self.bst.root.key)
self.bst.inorder()
self.bst.delete_key(45)
assert self.bst.root.leftChild.key == 30
self.bst.delete_key(85)
assert self.bst.root.key == 30
print('xxxx ',self.bst.root.leftChild.parent.key, self.bst.root.key)
assert self.bst.root.leftChild.parent == self.bst.root
self.bst.delete_key(30)
assert self.bst.root.key == 5
self.bst.inorder()
print("final root = " + str(self.bst.root.key))
assert self.bst.root.key == 5
self.bst.delete_key(5)
assert self.bst.root == None
def testDel2(self):
self.bst.put(21,'a')
self.bst.put(10,'b')
self.bst.put(24,'c')
self.bst.put(11,'d')
self.bst.put(22,'d')
self.bst.delete_key(10)
assert self.bst.root.leftChild.key == 11
assert self.bst.root.leftChild.parent == self.bst.root
assert self.bst.root.rightChild.key == 24
self.bst.delete_key(24)
assert self.bst.root.rightChild.key == 22
assert self.bst.root.rightChild.parent == self.bst.root
self.bst.delete_key(22)
self.bst.delete_key(21)
print("del2 root = ",self.bst.root.key)
assert self.bst.root.key == 11
assert self.bst.root.leftChild == None
assert self.bst.root.rightChild == None
def testLarge(self):
import random
print('testing a large random tree')
i = 0
randList = []
while i < 10000:
nrand = random.randrange(1,10000000)
if nrand not in randList:
randList.append(nrand)
i += 1
print(randList)
for n in randList:
self.bst.put(n,n)
sortList = randList[:]
sortList.sort()
random.shuffle(randList)
for n in randList:
minNode = self.bst.root.findMin()
if minNode:
assert minNode.key == sortList[0]
rootPos = sortList.index(self.bst.root.key)
succ = self.bst.root.findSuccessor()
if succ:
assert succ.key == sortList[rootPos+1]
else:
assert self.bst.root.rightChild == None
self.bst.delete_key(n)
sortList.remove(n)
assert self.bst.root == None
def testIter(self):
import random
i = 0
randList = []
while i < 100:
nrand = random.randrange(1,10000)
if nrand not in randList:
randList.append(nrand)
i += 1
for n in randList:
self.bst.put(n,n)
sortList = randList[:]
sortList.sort()
i = 0
for j in self.bst:
assert j == sortList[i]
i += 1
# the following exercises all of the branches in deleting a node with one child
def testCase1(self):
self.bst.put(10,10)
self.bst.put(7,7)
self.bst.put(5,5)
self.bst.put(1,1)
self.bst.put(6,6)
self.bst.delete_key(7)
assert self.bst.root.leftChild.key == 5
assert self.bst.root == self.bst.root.leftChild.parent
assert self.bst.root.leftChild.leftChild.key == 1
assert self.bst.root.leftChild.rightChild.key == 6
def testCase2(self):
self.bst = BinarySearchTree()
self.bst.put(10,10)
self.bst.put(15,15)
self.bst.put(12,12)
self.bst.put(11,11)
self.bst.put(13,13)
self.bst.delete_key(15)
assert self.bst.root.rightChild.key == 12
assert self.bst.root.rightChild.parent == self.bst.root
assert self.bst.root.rightChild.leftChild.key == 11
assert self.bst.root.rightChild.rightChild.key == 13
def testCase3(self):
self.bst = BinarySearchTree()
self.bst.put(10,10)
self.bst.put(6,6)
self.bst.put(8,8)
self.bst.put(7,7)
self.bst.put(9,9)
self.bst.delete_key(6)
assert self.bst.root.leftChild.key == 8
assert self.bst.root.leftChild.parent == self.bst.root
assert self.bst.root.leftChild.leftChild.key == 7
assert self.bst.root.leftChild.rightChild.key == 9
def testCase4(self):
self.bst = BinarySearchTree()
self.bst.put(10,10)
self.bst.put(15,15)
self.bst.put(20,20)
self.bst.put(17,17)
self.bst.put(22,22)
self.bst.delete_key(15)
assert self.bst.root.rightChild.key == 20
assert self.bst.root.rightChild.parent == self.bst.root
assert self.bst.root.rightChild.rightChild.key == 22
assert self.bst.root.rightChild.leftChild.key == 17
def testCase5(self):
self.bst.put(10,10)
self.bst.put(20,20)
self.bst.put(17,17)
self.bst.put(22,22)
self.bst.delete_key(10)
assert self.bst.root.key == 20
assert self.bst.root.leftChild.parent == self.bst.root
assert self.bst.root.rightChild.parent == self.bst.root
assert self.bst.root.leftChild.key == 17
assert self.bst.root.rightChild.key == 22
def testCase6(self):
self.bst.put(10,10)
self.bst.put(5,5)
self.bst.put(1,1)
self.bst.put(7,7)
self.bst.delete_key(10)
assert self.bst.root.key == 5
assert self.bst.root.leftChild.parent == self.bst.root
assert self.bst.root.rightChild.parent == self.bst.root
assert self.bst.root.leftChild.key == 1
assert self.bst.root.rightChild.key == 7
def testBadDelete(self):
self.bst.put(10,10)
with self.assertRaises(KeyError):
self.bst.delete_key(5)
self.bst.delete_key(10)
with self.assertRaises(KeyError):
self.bst.delete_key(5)
if __name__ == '__main__':
import platform
print(platform.python_version())
unittest.main()
### Local Variables:
### End:
平衡二叉树AVL
平衡二叉树(Balanced Binary Tree)是一种特殊的二叉查找树,它的每个节点的左子树和右子树的高度差不超过1。其中,AVL树是一种最早被发明的平衡二叉树,它以其发明者Adelson-Velsky和Landis的名字命名。
AVL树的平衡性是通过自平衡的操作来维持的。在对AVL树进行插入或删除节点的操作后,可能会破坏树的平衡性,此时需要通过旋转操作来进行调整。AVL树的旋转操作包括左旋和右旋,通过改变节点之间的连接关系,使树恢复平衡。
相对于普通的二叉查找树,AVL树在插入和删除操作时更加复杂,因为需要维护平衡性的约束条件。然而,AVL树具有较为稳定的性能,它的查找、插入和删除操作的平均时间复杂度为O(log n),其中n是树中节点的数量。
import unittest
from .bst import BinarySearchTree, TreeNode
class AVLTree(BinarySearchTree):#AVL 树的实现,它继承了二叉搜索树(BinarySearchTree)类
'''
插入(put)、平衡因子更新(updateBalance)、重平衡(rebalance)以及左旋转(rotateLeft)和右旋转(rotateRight)
__contains__(y) <==> y in x
__getitem__(y) <==> x[y]
__init__()
__len__() <==> len(x)
__setitem__(k,v) <==> x[k] = v
clear()
get(k)
has_key(k)
items()
keys()
values()
put(k,v)
'''
def _put(self,key,val,currentNode):
if key < currentNode.key:#待插入的键 key 小于当前节点的键 ,向左子树继续插入。
if currentNode.hasLeftChild():#在左子节点中继续插入
self._put(key,val,currentNode.leftChild)
else:#创建一个新的左子节点
currentNode.leftChild = TreeNode(key,val,parent=currentNode)
self.updateBalance(currentNode.leftChild)#调整因子
else:
if currentNode.hasRightChild():
self._put(key,val,currentNode.rightChild)
else:
currentNode.rightChild = TreeNode(key,val,parent=currentNode)
self.updateBalance(currentNode.rightChild)
def updateBalance(self,node):#更新节点及其父节点的平衡因子
if node.balanceFactor > 1 or node.balanceFactor < -1:#超过了允许的范围(大于1或小于-1)
self.rebalance(node)#重新平衡
return
if node.parent != None:#更新父节点的平衡因子
if node.isLeftChild():#如果节点是父节点的左子节点,+1
node.parent.balanceFactor += 1
elif node.isRightChild():#右-1
node.parent.balanceFactor -= 1
if node.parent.balanceFactor != 0:#然后,检查父节点的平衡因子是否为0,如果不为0,继续向上递归调用
self.updateBalance(node.parent)#调整父节点的平衡因子
def rebalance(self,node):
if node.balanceFactor < 0:#右重,需要左旋。如果平衡因子小于0,表示节点的左子树比右子树高,
if node.rightChild.balanceFactor > 0:#如果右子节点的平衡因子大于0,表示右子节点的左子树较高,需要进行 LR(左右)旋转操作。
# Do an LR Rotation,先右旋转再左旋转。右子节点左重先右旋
self.rotateRight(node.rightChild)#对右子节点进行右旋转,
self.rotateLeft(node)#对当前节点进行左旋转操作
else:#右子节点的平衡因子不大于0,表示右子节点的右子树较高,只需要进行单左旋转操作
# single left
self.rotateLeft(node)
elif node.balanceFactor > 0:#左重先右旋。节点的平衡因子大于0,表示节点的右子树比左子树高
if node.leftChild.balanceFactor < 0:#如果左子节点的平衡因子小于0,表示左子节点的右子树较高,需要进行 RL(右左)旋转操作
# Do an RL Rotation,先左旋转再右旋转。左子节点右重先左旋
self.rotateLeft(node.leftChild)
self.rotateRight(node)
else:#如果左子节点的平衡因子不小于0,表示左子节点的左子树较高,只需要进行单右旋转操作,
# single right
self.rotateRight(node)
def rotateLeft(self,rotRoot):#左旋
newRoot = rotRoot.rightChild#rotRoot 节点的右子节点作为新的根节点
rotRoot.rightChild = newRoot.leftChild# rotRoot 的右子节点更新为 newRoot 的左子节点
if newRoot.leftChild != None:#更新 newRoot 的左子节点的父节点为 rotRoot。
newRoot.leftChild.parent = rotRoot
newRoot.parent = rotRoot.parent#更新 newRoot 的父节点为 rotRoot 的父节点,
if rotRoot.isRoot():#如果 rotRoot 是根节点,则将 AVL 树的根节点更新为 newRoot
self.root = newRoot
else:#否则根据 rotRoot 是其父节点的左子节点还是右子节点,将 newRoot 更新为其父节点的左子节点或右子节点。
if rotRoot.isLeftChild():
rotRoot.parent.leftChild = newRoot
else:
rotRoot.parent.rightChild = newRoot
newRoot.leftChild = rotRoot#将 rotRoot 作为 newRoot 的左子节点
rotRoot.parent = newRoot#更新 rotRoot 的父节点为 newRoot。
#更新 rotRoot 和 newRoot 的平衡因子。
#rotRoot 的平衡因子等于原平衡因子加上 1 减去 newRoot 的平衡因子与 0 的最小值。
#newRoot 的平衡因子等于原平衡因子加上 1 加上 rotRoot 的平衡因子与 0 的最大值。
rotRoot.balanceFactor = rotRoot.balanceFactor + 1 - min(newRoot.balanceFactor, 0)
newRoot.balanceFactor = newRoot.balanceFactor + 1 + max(rotRoot.balanceFactor, 0)
def rotateRight(self,rotRoot):#右旋
newRoot = rotRoot.leftChild
rotRoot.leftChild = newRoot.rightChild
if newRoot.rightChild != None:
newRoot.rightChild.parent = rotRoot
newRoot.parent = rotRoot.parent
if rotRoot.isRoot():
self.root = newRoot
else:
if rotRoot.isRightChild():
rotRoot.parent.rightChild = newRoot
else:
rotRoot.parent.leftChild = newRoot
newRoot.rightChild = rotRoot
rotRoot.parent = newRoot
rotRoot.balanceFactor = rotRoot.balanceFactor - 1 - max(newRoot.balanceFactor, 0)
newRoot.balanceFactor = newRoot.balanceFactor - 1 + min(rotRoot.balanceFactor, 0)
class BinaryTreeTests(unittest.TestCase):#单元测试类,用于对 AVL 树的功能进行测试。
def setUp(self):
self.bst = AVLTree()
def testAuto1(self):
self.bst.put(30,'a')
self.bst.put(50,'b')
self.bst.put(40,'c')
assert self.bst.root.key == 40
def testAuto2(self):
self.bst.put(50,'a')
self.bst.put(30,'b')
self.bst.put(40,'c')
assert self.bst.root.key == 40
def testAuto3(self):
self.bst.put(50,'a')
self.bst.put(30,'b')
self.bst.put(70,'c')
self.bst.put(80,'c')
self.bst.put(60,'d')
self.bst.put(90,'e')
assert self.bst.root.key == 70
def testAuto3(self):
self.bst.put(40,'a')
self.bst.put(30,'b')
self.bst.put(50,'c')
self.bst.put(45,'d')
self.bst.put(60,'e')
self.bst.put(43,'f')
assert self.bst.root.key == 45
assert self.bst.root.leftChild.key == 40
assert self.bst.root.rightChild.key == 50
assert self.bst.root.balanceFactor == 0
assert self.bst.root.leftChild.balanceFactor == 0
assert self.bst.root.rightChild.balanceFactor == -1
def testAuto4(self):
self.bst.put(40,'a')
self.bst.put(30,'b')
self.bst.put(50,'c')
self.bst.put(10,'d')
self.bst.put(35,'e')
self.bst.put(37,'f')
assert self.bst.root.key == 35
assert self.bst.root.leftChild.key == 30
assert self.bst.root.rightChild.key == 40
assert self.bst.root.balanceFactor == 0
assert self.bst.root.leftChild.balanceFactor == 1
assert self.bst.root.rightChild.balanceFactor == 0
if __name__ == '__main__':
import platform
print(platform.python_version())
unittest.main()
图
class Vertex:
def __init__(self,key):#每个顶点具有一个 id 和一个 connectedTo 字典
#将邻接顶点作为键,将对应的边权重作为值存储在字典中
self.id = key
self.connectedTo = {}
def addNeighbor(self,nbr,weight=0):#赋值
self.connectedTo[nbr] = weight
def __str__(self):
return str(self.id) + ' connectedTo: ' + str([x.id for x in self.connectedTo])
def getConnections(self):
return self.connectedTo.keys()
def getId(self):
return self.id
def getWeight(self,nbr):
return self.connectedTo[nbr]
class Graph:
def __init__(self):
self.vertList = {}#将邻接顶点作为键,将对应的边权重作为值存储在字典中
self.numVertices = 0
def addVertex(self,key):#新加顶点
self.numVertices = self.numVertices + 1
newVertex = Vertex(key)
self.vertList[key] = newVertex
return newVertex
def getVertex(self,n):#通过key找顶点
if n in self.vertList:
return self.vertList[n]
else:
return None
def __contains__(self,n):
return n in self.vertList
def addEdge(self,f,t,cost=0):
if f not in self.vertList:#不存在的顶点先添加
nv = self.addVertex(f)
if t not in self.vertList:
nv = self.addVertex(t)
self.vertList[f].addNeighbor(self.vertList[t], cost)#调用起始顶点的方法添加邻接边
def getVertices(self):
return self.vertList.keys()
def __iter__(self):
return iter(self.vertList.values())
g = Graph()
for i in range(6):
g.addVertex(i)
print(g.vertList)
g.addEdge(0,1,5)
g.addEdge(0,5,2)
g.addEdge(1,2,4)
g.addEdge(2,3,9)
g.addEdge(3,4,7)
g.addEdge(3,5,3)
g.addEdge(4,0,1)
g.addEdge(5,4,8)
g.addEdge(5,2,1)
for v in g:
for w in v.getConnections():
print("( %s , %s )" % (v.getId(), w.getId()))
# ( 0 , 1 )
# ( 0 , 5 )
# ( 1 , 2 )
# ( 2 , 3 )
# ( 3 , 4 )
# ( 3 , 5 )
# ( 4 , 0 )
# ( 5 , 4 )
# ( 5 , 2 )
词梯
from pythonds.graphs import Graph, Vertex
from pythonds.basic import Queue
def buildGraph(wordFile):
#读取一个单词文件,并创建一个图,其中单词之间存在一字之差的边。
# 它使用字母桶的概念,将具有相同字母桶的单词连接在一起。
# 然后,通过遍历字母桶中的单词,将它们之间的边添加到图中。
d = {}
g = Graph()
wfile = open(wordFile,'r')
# create buckets of words that differ by one letter
for line in wfile:
word = line[:-1]
for i in range(len(word)):
bucket = word[:i] + '_' + word[i+1:]
if bucket in d:#4个桶
d[bucket].append(word)
else:
d[bucket] = [word]
# add vertices and edges for words in the same bucket
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())
#构建了一个单词图
#执行了广度优先搜索算法来查找从单词 'FOOL' 到 'SAGE' 的最短路径。
# 使用 traverse 函数打印出从 'SAGE' 回溯到起始顶点的最短路径.
wordgraph = buildGraph("fourletterwords.txt")
bfs(wordgraph, wordgraph.getVertex('FOOL'))
traverse(wordgraph.getVertex('SAGE'))
# SAGE
# SALE
# SALL
# MALL
# MOLL
# MOOL
# FOOL
#traverse(wordgraph.getVertex('COOL'))
骑士周游问题
骑士周游问题(Knight's Tour Problem)是一个经典的数学问题,要求找到国际象棋棋盘上的一个骑士(马)从起始位置出发,经过棋盘上每个格子恰好一次,最终回到起始位置的路径。
骑士在国际象棋棋盘上的移动方式是按照特定的规则进行的。它可以沿着L字形移动,即在水平或垂直方向移动两格,然后在垂直或水平方向移动一格,或者在垂直或水平方向移动两格,然后在水平或垂直方向移动一格。骑士可以在棋盘上的任意位置开始,但是它不能走出棋盘的边界,并且每个格子只能经过一次。
解决骑士周游问题的方法有多种,其中最常见的是使用回溯算法。回溯算法通过尝试所有可能的移动路径,并逐步构建骑士的路径,如果遇到无法继续移动的情况,则回溯到上一步,并尝试其他的移动方式,直到找到一条完整的周游路径或者所有的可能性都被尝试完。
from pythonds.graphs import Graph, Vertex
def genLegalMoves(x,y,bdSize):
#它接受当前位置 (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):#确保不会走出棋盘
#它接受一个坐标 x 和棋盘大小 bdSize 作为参数,并返回一个布尔值,指示坐标是否在棋盘范围内。
if x >= 0 and x < bdSize:
return True
else:
return False
def knightGraph(bdSize):#用于构建骑士周游问题的图。
# 它接受棋盘大小 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: # 无法完成总深度,回溯,本层下一个
path.pop()
u.setColor('white')
else:#如果找到解决方案,函数返回 True;否则,函数回溯到上一个顶点继续搜索。
done = True
return done#函数最终返回一个布尔值,指示是否找到解决方案。
def posToNodeId(row,col,bdSize):#用于将 (row, col) 的位置转换为顶点的唯一标识符。
# 它接受行号 row、列号 col 和棋盘大小 bdSize 作为参数,并返回一个唯一的标识符。
return row*bdSize+col
def orderByAvail(n):#用于根据可用邻接顶点的数量对顶点进行排序。 nbrlist。启发式规则。
# 它接受一个顶点 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): #改进
#在选择下一个顶点时使用了 orderByAvail 函数来优化搜索顺序。
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
kg = knightGraph(5) #five by five solution
thepath = []
start = kg.getVertex(4)
knightTourBetter(0,thepath,start,24)
for v in thepath:
print(v.getId())
后面的树和图有待继续理解
感恩。peace。确实通过代码实现能够更清晰直观地理解数据结构,同时陈斌老师讲解的很透彻。而且可以参考前面的网站,里面有交互式代码也有视频讲解。老师提供了课件教材和代码,很好。当然我还没有完全消化,需要再理解理解...