00-让刚访问的点靠近你.py 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. #!/usr/bin/env python3
  2. """课前零食:一个可查看 zig、zig-zig、zig-zag 的小型 Splay 集合。"""
  3. from __future__ import annotations
  4. from dataclasses import dataclass
  5. @dataclass
  6. class Node:
  7. key: int
  8. left: Node | None = None
  9. right: Node | None = None
  10. parent: Node | None = None
  11. size: int = 1
  12. def size(node: Node | None) -> int:
  13. return node.size if node else 0
  14. class SplaySet:
  15. def __init__(self) -> None:
  16. self.root: Node | None = None
  17. def pull(self, node: Node | None) -> None:
  18. if node:
  19. node.size = 1 + size(node.left) + size(node.right)
  20. def pull_to_root(self, node: Node | None) -> None:
  21. while node:
  22. self.pull(node)
  23. node = node.parent
  24. def rotate(self, x: Node, log: list[str]) -> None:
  25. p = x.parent
  26. if p is None:
  27. return
  28. g = p.parent
  29. if x is p.left:
  30. middle = x.right
  31. x.right = p
  32. p.left = middle
  33. direction = "右旋"
  34. else:
  35. middle = x.left
  36. x.left = p
  37. p.right = middle
  38. direction = "左旋"
  39. if middle:
  40. middle.parent = p
  41. p.parent = x
  42. x.parent = g
  43. if g is None:
  44. self.root = x
  45. elif p is g.left:
  46. g.left = x
  47. else:
  48. g.right = x
  49. self.pull(p)
  50. self.pull(x)
  51. log.append(f"{direction}:让 {x.key} 绕 {p.key} 上来。")
  52. def is_right(self, node: Node) -> bool:
  53. return node.parent is not None and node.parent.right is node
  54. def splay(self, x: Node, goal: Node | None, log: list[str]) -> None:
  55. while x.parent is not goal:
  56. p = x.parent
  57. if p is None:
  58. break
  59. g = p.parent
  60. if g is goal:
  61. log.append("zig:父亲已是目标父亲。")
  62. self.rotate(x, log)
  63. elif self.is_right(x) == self.is_right(p):
  64. log.append("zig-zig:祖孙同向,先旋父亲再旋当前点。")
  65. self.rotate(p, log)
  66. self.rotate(x, log)
  67. else:
  68. log.append("zig-zag:祖孙异向,当前点连续旋两次。")
  69. self.rotate(x, log)
  70. self.rotate(x, log)
  71. def insert_plain(self, key: int) -> None:
  72. """仅用于造出固定的初始 BST;不触发 Splay。"""
  73. if self.root is None:
  74. self.root = Node(key)
  75. return
  76. current = self.root
  77. while True:
  78. if key < current.key:
  79. if current.left is None:
  80. current.left = Node(key=key, parent=current)
  81. self.pull_to_root(current)
  82. return
  83. current = current.left
  84. else:
  85. if current.right is None:
  86. current.right = Node(key=key, parent=current)
  87. self.pull_to_root(current)
  88. return
  89. current = current.right
  90. def locate(self, key: int) -> tuple[Node | None, Node | None]:
  91. current = self.root
  92. last: Node | None = None
  93. while current:
  94. last = current
  95. if key == current.key:
  96. return current, last
  97. current = current.left if key < current.key else current.right
  98. return None, last
  99. def find(self, key: int) -> tuple[bool, list[str]]:
  100. node, last = self.locate(key)
  101. log: list[str] = []
  102. if node:
  103. log.append(f"找到 {key},开始把它伸到根。")
  104. self.splay(node, None, log)
  105. return True, log
  106. if last:
  107. log.append(f"没找到 {key},但把最后访问的 {last.key} 伸到根。")
  108. self.splay(last, None, log)
  109. return False, log
  110. def add(self, key: int) -> list[str]:
  111. if self.root is None:
  112. self.root = Node(key)
  113. return [f"空树插入 {key},它就是根。"]
  114. node, last = self.locate(key)
  115. log: list[str] = []
  116. if node:
  117. log.append(f"{key} 已存在,仍把它伸到根。")
  118. self.splay(node, None, log)
  119. return log
  120. assert last is not None
  121. fresh = Node(key=key, parent=last)
  122. if key < last.key:
  123. last.left = fresh
  124. else:
  125. last.right = fresh
  126. self.pull_to_root(last)
  127. log.append(f"把 {key} 当普通 BST 新叶插入,再 Splay。")
  128. self.splay(fresh, None, log)
  129. return log
  130. def delete(self, key: int) -> tuple[bool, list[str]]:
  131. found, log = self.find(key)
  132. if not found:
  133. return False, log
  134. assert self.root is not None and self.root.key == key
  135. left, right = self.root.left, self.root.right
  136. if left:
  137. left.parent = None
  138. if right:
  139. right.parent = None
  140. self.root.left = self.root.right = None
  141. if left is None:
  142. self.root = right
  143. log.append("左树为空,右树直接成为新根。")
  144. return True, log
  145. self.root = left
  146. maximum = left
  147. while maximum.right:
  148. maximum = maximum.right
  149. log.append(f"左树最大点是 {maximum.key};在左树内把它伸根后接右树。")
  150. self.splay(maximum, None, log)
  151. assert self.root is maximum
  152. self.root.right = right
  153. if right:
  154. right.parent = self.root
  155. self.pull(self.root)
  156. return True, log
  157. def inorder(self) -> list[int]:
  158. def dfs(node: Node | None) -> list[int]:
  159. if node is None:
  160. return []
  161. return dfs(node.left) + [node.key] + dfs(node.right)
  162. return dfs(self.root)
  163. def show_tree(self, node: Node | None = None, prefix: str = "", side: str = "根") -> None:
  164. if node is None and not prefix:
  165. node = self.root
  166. if node is None:
  167. return
  168. self.show_tree(node.right, prefix + " ", "右")
  169. parent = node.parent.key if node.parent else "-"
  170. print(f"{prefix}{side}: {node.key} [parent={parent}, size={node.size}]")
  171. self.show_tree(node.left, prefix + " ", "左")
  172. def check(self) -> tuple[bool, str]:
  173. def dfs(node: Node | None, low: int | None, high: int | None, parent: Node | None) -> tuple[bool, int, str]:
  174. if node is None:
  175. return True, 0, ""
  176. if node.parent is not parent:
  177. return False, 0, f"{node.key} 的 parent 指针不对"
  178. if (low is not None and node.key <= low) or (high is not None and node.key >= high):
  179. return False, 0, f"{node.key} 的 BST 范围不对"
  180. ok_l, left_size, why = dfs(node.left, low, node.key, node)
  181. if not ok_l:
  182. return False, 0, why
  183. ok_r, right_size, why = dfs(node.right, node.key, high, node)
  184. if not ok_r:
  185. return False, 0, why
  186. if node.size != left_size + 1 + right_size:
  187. return False, 0, f"{node.key} 的 size 应为 {left_size + 1 + right_size}"
  188. return True, node.size, ""
  189. ok, _, why = dfs(self.root, None, None, None)
  190. return ok, why or "BST、parent、size 均正确"
  191. def print_log(log: list[str]) -> None:
  192. for line in log:
  193. print(" ", line)
  194. def warmup() -> None:
  195. print("=== 任务 0:先预测 7 怎样上来 ===")
  196. print("初始树根是 10,5 在左,7 是 5 的右儿子。")
  197. try:
  198. input("请写下它是 zig-zig 还是 zig-zag,再按回车: ")
  199. except EOFError:
  200. pass
  201. print("核对:7 相对 5 向右,5 相对 10 向左,是 zig-zag。")
  202. def main() -> None:
  203. tree = SplaySet()
  204. for key in (10, 5, 15, 3, 7, 12, 18):
  205. tree.insert_plain(key)
  206. print("Splay 基本操作小实验室:固定初始 BST,不使用随机数。")
  207. warmup()
  208. tree.show_tree()
  209. print("中序:", tree.inorder())
  210. print("\n命令:find 7;add 6;del 10;show;check;q。")
  211. while True:
  212. try:
  213. raw = input("\n> ").strip()
  214. except EOFError:
  215. raw = "q"
  216. if not raw or raw in {"q", "quit", "exit"}:
  217. return
  218. parts = raw.split()
  219. try:
  220. if parts == ["show"]:
  221. tree.show_tree()
  222. print("中序:", tree.inorder())
  223. elif parts == ["check"]:
  224. ok, message = tree.check()
  225. print("通过:" if ok else "失败:", message)
  226. elif len(parts) == 2 and parts[0] == "find":
  227. found, log = tree.find(int(parts[1]))
  228. print_log(log)
  229. print("找到。" if found else "未找到。", "当前根:", tree.root.key if tree.root else "空")
  230. elif len(parts) == 2 and parts[0] == "add":
  231. print_log(tree.add(int(parts[1])))
  232. print("当前根:", tree.root.key if tree.root else "空")
  233. elif len(parts) == 2 and parts[0] == "del":
  234. deleted, log = tree.delete(int(parts[1]))
  235. print_log(log)
  236. print("已删除。" if deleted else "没有该键。")
  237. else:
  238. raise ValueError
  239. except ValueError:
  240. print("小提示:命令示例是 find 7、add 6、del 10。")
  241. if __name__ == "__main__":
  242. main()