数据结构与算法(python)(数据结构)
数据结构与算法(python)(数据结构)
文章目录
一、数据结构基本概念
数据结构是指相互之间存在着一种或多种关系的数据元素的集合和该集合中元素之间的关系组成。
简单来说,就是设计数据以何种方式组织并存储在计算机中。
比如:列表、集合和字典都是一种数据结构
N.Wirth:“程序=数据结构+算法”
数据的逻辑结构:数据元素之间的逻辑关系
分为:线性结构(元素存在一对一的相互关系):线性表、栈和队列、串;
非线性结构:集合、树结构、图结构,网状结构。
存储结构:也称物理结构,是指数据结构在计算机中的表示,包括数据元素的表示与关系的表示,
主要包括:顺序存储,链式存储,索引存储,散列存储
线性表的常见实现方式包括顺序存储结构(即数组)和链式存储结构(即链表)
二、线性结构
1.列表(顺序存储)
需要掌握:
(1)列表中元素是如何存储的?
(2)列表的基本操作:按下标查找,插入元素,删除元素…
(3)这些操作的时间复杂度是多少?
扩展:python的列表如何实现?
线性表是抽象的数据结构模型,列表是具体的编程实现。
列表中的元素在内存中是连续存储的。每个元素占据相同的内存空间,并且可以通过列表的起始地址和下标进行快速访问。对于长度为 𝑛的列表,每个元素可以通过其下标 𝑖直接访问,计算其内存地址为:地址=起始地址+i*元素大小
32位机器上,整数占4字节,一个地址占4个字节。
python实现:
Python 列表是基于动态数组实现的,具有连续存储、自动扩容的特点。
Python 列表能够存储不同类型的元素,这是通过存储对象引用(指针)来实现的。
基本操作:
1.按下标查找(索引访问)
操作: 通过下标访问列表中的元素,如 array[i] 时间复杂度: O(1)
2.插入元素
操作: 在列表的任意位置插入一个元素,如 array.insert(i, x)。
时间复杂度:在末尾插入: O(1)
原因: 如果列表末尾有空闲空间,直接在末尾插入即可,无需移动其他元素。
在中间或开头插入:O(n)
原因: 在开头或中间插入元素时,需要将插入位置之后的所有元素向后移动,以腾出空间。因此,最坏情况下需要移动n−1 个元素,时间复杂度为O(n)
3. 删除元素
操作: 从列表中删除元素,如 array.pop(i) 或 array.remove(x)。
删除末尾元素: O(1) 删除中间或开头元素:O(n)
4.遍历列表
操作: 依次访问列表中的每个元素。时间复杂度: O(n)
# 示例列表
lst = [1, 2, 3, 4, 5]
# 使用 for 循环遍历元素
for element in lst:
print(element)
# 示例列表
lst = [1, 2, 3, 4, 5]
#需要访问元素的下标,可以使用 range() 函数结合 len() 方法,通过下标进行遍历。
# 通过下标遍历列表
for i in range(len(lst)):
print(f"Index: {i}, Element: {lst[i]}")
# 使用 enumerate() 同时获取下标和元素
for index, element in enumerate(lst):
print(f"Index: {index}, Element: {element}")
# 使用列表推导式遍历并生成一个新的列表
squared_lst = [x**2 for x in lst]
print(squared_lst)
#尽管 for 循环更常用,while 循环也可以用于遍历列表,特别是在需要手动控制循环变量时
# 使用 while 循环遍历列表
i = 0
while i < len(lst):
print(lst[i])
i += 1
#map() 函数可以将列表中的每个元素应用一个函数,并返回一个迭代器,可以将其转换为列表或直接遍历。
# 使用 map() 函数遍历并应用函数
squared_lst = map(lambda x: x**2, lst)
# 转换为列表并打印
print(list(squared_lst))
5.更新元素
操作: 修改列表中的某个元素,如 array[i] = x。时间复杂度: O(1)
6.查找元素(线性搜索)
操作: 查找列表中是否包含某个元素,如 x in array。
时间复杂度: O(n)
原因: 需要遍历整个列表,直到找到目标元素或到达列表末尾,最坏情况下需要访问所有 𝑛 个元素。
2.栈
栈(Stack)是一种后进先出(LIFO,Last In First Out)的数据结构。这意味着最后插入栈中的元素会最先被取出。Python 本身没有专门的栈数据结构,但可以使用列表(list)或 collections.deque 来实现栈的功能。
栈的特点:
后进先出(LIFO): 栈的基本原则是后进先出,即最后加入栈的元素最先被移除。
单一操作端: 所有操作都在栈的顶端进行,无论是插入还是删除。
顺序访问: 栈只能访问位于栈顶的元素,不能直接访问其他元素。
栈有两个主要操作:压栈(push) 和 弹栈(pop)。压栈是将元素添加到栈的顶端,弹栈是从栈的顶端移除元素。
#栈的操作实现
class Stack:
def __init__(self):
# 初始化一个空栈
self.stack = []
def push(self,element):
self.stack.append(element) #压栈
def pop(self):
return self.stack.pop() #弹栈
def get_top(self):
if len(self.stack) > 0:
return self.stack[-1]
stack = Stack()
stack.push(10) # 栈: [10]
stack.push(20) # 栈: [10, 20]
stack.push(30) # 栈: [10, 20, 30]
# 查看栈顶元素
print("栈顶元素:", stack.get_top()) # 输出: 30
# 弹栈操作
print("弹出的元素:", stack.pop()) # 输出: 30
栈的应用,括号匹配问题,若栈为空,则匹配
#栈的应用:括号匹配问题
class Stack:
def __init__(self):
# 初始化一个空栈
self.stack = []
def push(self,element):
self.stack.append(element) #压栈
def pop(self):
return self.stack.pop() #弹栈
def get_top(self):
if len(self.stack) > 0:
return self.stack[-1]
else:
return None
#判断空列表
def is_empty(self):
return len(self.stack) == 0
def brace_match(s):
match = {'}':'{', ']':'[', ')':'('}
stack=Stack()
for ch in s:
if ch in {'(', '[', '{'}:
stack.push(ch)
elif: #ch in 右括号
if stack.is_empty():
return False
elif stack.get_top() == match[ch]
stack.pop()
else: #如果栈顶不匹配
return False
if stack.is_empty():
return True
else:
return False
3.队列
队列(Queue) 是一种线性数据结构,遵循先进先出(FIFO)的原则。可以将队列想象成排队的场景,最先排队的人最先被服务。
队列的特点:
先进先出(FIFO): 队列遵循先进先出的原则,第一个进入队列的元素最先被移除。
两个操作端: 队列在队尾插入元素,在队首移除元素,两个操作端分别负责不同的操作。
顺序访问: 队列只能访问队首的元素,不能直接访问其他元素。
队列有两个主要操作:入队(enqueue) 和 出队(dequeue)。入队是将元素添加到队尾,出队是从队首移除元素
虽然可以用列表来实现队列,但由于列表在队首插入和删除元素的效率较低(时间复杂度为O(n)),不推荐在生产环境中使用列表来实现队列。
环形队列
##环形队列实现
class Queue:
def __init__(self,size=100):
self.queue = [0 for _ in range(size)]
self.size=size
self.rear = 0 #队尾指针
self.front = 0 #队首指针
def push(self,element):
if not self.is_filed():
self.rear = (self.rear + 1) % self.size
self.queue[self.rear] = element
else:
raise IndexError("Queue is empty.")
def pop(self):
if not self.is_empty():
self.front = (self.front + 1) % self.size
return self.queue[self.front]
else:
raise IndexError("Queue is empty.")
def is_empty(self): #判断队空
return self.rear == self.front
def is_filed(self): #判断队满
return (self.rear+1)%self.size == self.front
q=Queue(5)
for i in range(4):
q.push(i)
print(q.pop())
q.push(4)
双向队列:双向队列的两端都至此进队和出队操作
在 Python 中,推荐使用 collections.deque 或 queue.Queue 来实现队列,因为它们在性能和功能上更适合队列的使用。
from collections import deque
q = deque([1,2,3], 5) #第二个参数,最大长度,队满自动出队
q.append(1) #队尾进队
q.popleft() #队首进队
q.appendleft(1) #队首进队
q.pop() #队尾出队
队列的应用场景
任务调度: 队列常用于任务调度器中,将任务按顺序排队执行。
广度优先搜索(BFS): 在图或树的广度优先搜索算法中,队列被用作辅助数据结构。
缓存系统: 队列可以用于实现缓存系统中的 FIFO 缓存策略。
多线程编程: 在多线程环境中,队列用于在线程间传递数据或任务
4.栈和队列的应用:迷宫问题.
给一个二维列表,表示迷宫(0表示通道,1表示围墙)。给出算法,求一条走出迷宫的路径。
栈——深度优先搜索
回溯法:从一个节点开始,任意找下一个能走的点,当找不到能走的点时,退回上一个点寻找是否有其他方向的点。
使用栈存储当前路径。
maze=[[1, 1, 1, 1, 1, 1, 1 ,1 ,1 ,1],
[1, 0, 0, 1, 0, 0, 0, 1, 0, 1],
[1, 0, 0, 1, 0, 0, 0, 1, 0, 1],
[1, 0, 0, 0, 0, 1, 1, 0, 0, 1],
[1, 0, 1, 1, 1, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 1, 0, 0, 0, 0, 1],
[1, 0, 1, 0, 0, 0, 1, 0, 0, 1],
[1, 0, 1, 1, 1, 0, 1, 1, 0, 1],
[1, 1, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]
dirs = [lambda x, y:(x+1, y),
lambda x, y:(x-1, y),
lambda x, y:(x, y-1),
lambda x, y:(x, y+1)]
def maze_path(x1,y1,x2,y2): #起点终点
stack = []
stack.append((x1, y1))
while(len(stack)>0):
curNode = stack[-1] #当前节点 正数从列表的开头开始计数,而负数从列表的末尾开始计数。 -1 表示获取列表 stack 中的最后一个元素
if curNode[0] == x2 and curNode[1] == y2:#在终点
for p in stack:
print(p)
return
#x,y 四个方向 上x-1,y;下 x+1,y ;左 x,y-1;;右 x,y+1
for dir in dirs:
nextNode = dir(curNode[0],curNode[1])
#如果下一个节点能走
if maze[nextNode[0]][nextNode[1]] == 0:
stack.append(nextNode)
maze[nextNode[0]][nextNode[1]] = 2 #2表示已经走过
break
else:
maze[nextNode[0]][nextNode[1]] = 2
stack.pop()
else:
print("没有路")
return False
maze_path(1, 1, 8, 8)
队列——广度优先搜索(最短路径)
思路:从一个节点开始,寻找所有接下来能继续走的点,继续不断寻找,直到找到出口。
使用队列存储当前正在考虑的节点。
def print_r(path):
curNode = path[-1]
realpath = []
while curNode[2] != -1:
# realpath.append((curNode[0],curNode[1]))
realpath.append(curNode[0:2])
curNode = path[curNode[2]]
realpath.append(curNode[0:2])#起点
realpath.reverse()
# print(realpath)
for node in realpath:
print(node)
def maze_path_queue(x1,y1,x2,y2):
queue = deque()
queue.append((x1, y1, -1))
path = []
while len(queue) > 0:
curNode = queue.popleft()
path.append(curNode)
if curNode[0] == x2 and curNode[1] == y2:#在终点
print_r(path)
return True
for dir in dirs:
nextNode = dir(curNode[0],curNode[1])
if maze[nextNode[0]][nextNode[1]] == 0:
queue.append((nextNode[0],nextNode[1],len(path) - 1)) #后续节点进队,记录哪个节点带来的
maze[nextNode[0]][nextNode[1]] = 2 #标记已经走过
else:
print("没有路")
return False
maze_path_queue(1, 1, 8, 8)
5.链表(链式存储)
链表(Linked List)是一种常见的线性数据结构,由一组节点(Node)组成,每个节点包含数据域和指针域。指针域指向下一个节点,因此链表中的元素在内存中并不是连续存储的,而是通过指针动态链接在一起的。
链表在插入和删除的操作上明显快于顺序表
链表内存可以更灵活分配,试利用链表重新实现栈和队列
链表这种链式存储的数据结构对数和图的结构有很大的启发性。
单链表的创建与遍历:
#创建链表
class Node:
def __init__(self,item):
self.item = item
self.next = None
#头插法
def create_linklist(li):
head = Node(li[0])
for element in li[1: ]:
node = Node(element)
node.next = head
head = node
return head
#尾插法
def create_linklist_tail(li):
head = Node(li[0])
tail=head
for element in li[1:]:
node = Node(element)
tail.next = node
tail = node
return head
def print_linklist(lk):
while lk:
print(lk.item,end=",")
lk = lk.next
lk = create_linklist([1,2,3])
print_linklist(lk)
lk = create_linklist_tail([1,2,3,5,9])
print_linklist(lk)
链表的基础操作:插入删除
插入操作:链表在头部插入的时间复杂度为 O(1),而在尾部和中间插入时通常需要 O(n) 的时间复杂度,因为需要遍历链表。
删除操作:删除头节点的时间复杂度为 O(1),而删除尾节点和中间节点的时间复杂度为 O(n)。如果位置已知,时间复杂度:O(1)
链表的动态性使得它在插入和删除方面比数组更灵活,但由于不能随机访问,操作效率在某些场景下较低。
#插入:
new_node.next = prev_node.next # 新节点的 next 指向 prev_node 的下一个节点
prev_node.next = new_node # prev_node 的 next 指向新节点
#删除
prev.next = cur.next # 前驱节点指向当前节点的下一个节点
cur = None
双链表:每个节点有两个指针:一个指向后一个节点,一个指向前一个节点。
#创建双链表
class Node:
def __init__(self,item):
self.item = item
self.next = None
self.prior = None
#插入:
p.next = curNode.next
curNode.next.prior = p
p.prior = curNode
curNode.next = p
#删除:
p = curNode.next
curNode.next = p.next
p.next.prior = curNode
del p
6.哈希表
哈希表:又称散列表,是一种线性表的存储结构。建立了关键字和存储地址的一种直接映射关系,由一个直接寻址和一个哈希函数组成。哈希函数h(k)将元素关键字k作为自变量,返回元素的存储下标。
直接寻址表:
查找速度快:直接寻址表的查找时间复杂度为 O(1),因为直接通过关键字作为索引,可以立即访问到对应的值。
结构简单:实现方式非常直接,通常通过数组来实现,无需复杂的数据结构。
插入和删除操作高效。空间浪费大:如果关键字的范围很大,但实际使用的关键字很少,直接寻址表会造成大量的内存浪费,因为必须为每个可能的关键字预留空间。
常见的哈希函数:除留余数法ℎ(𝑘)=𝑘mod𝑚;乘法哈希法*h(k)=m⋅((A⋅k)mod1)*平方取中法
例子:假设键 k=1234,则k2 =1522756,取中间的几位数 227 作为哈希值。
**哈希冲突:**指在哈希表中,两个或多个不同的键(Key)通过哈希函数映射到相同的哈希值(或存储位置)
解决哈希冲突:为产生冲突的关键字寻找下一个空的地址
1.开放寻址法:如果哈希函数返回的位置已经有值,则向后探查新的位置来存储这个值
1) 线性探查:如果位置i被占用,则探查i+1,i+2……
2)二次探查:如果位置i被占用,则探查i+12,i-12,i+22,i-22……
3)二度哈希:有n个哈希函数,当使用第一个哈希函数h1发生冲突时,尝试使用h2,h3……
2.拉链法:哈希表每个位置都连接一个链表,当发生冲突时,冲突的元素将被加到该位置链表的最后。
一个通过哈希函数来计算存储位置的数据结构,支持如下操作:
insert(key,value): 插入键值对
get(key): 如果存在键为key的键值对则返回其value,否则返回空值
delete(key): 删除键为key的键值对
通过自定义类,使用哈希函数和冲突处理方式来实现一个简单的哈希表。
使用链地址法的哈希表实现
class HashTable:
def __init__(self, size):
self.size = size
self.table = [[] for _ in range(size)] # 创建一个空的哈希表,每个槽位使用列表存储
def hash_function(self, key):
# 使用内置的 hash() 函数取模,得到索引
return hash(key) % self.size
def insert(self, key, value):
index = self.hash_function(key)
# 检查键是否已经存在,如果存在则更新值
for kvp in self.table[index]:
if kvp[0] == key:
kvp[1] = value
return
# 如果键不存在,则插入新的键值对
self.table[index].append([key, value])
def get(self, key):
index = self.hash_function(key)
# 遍历链表查找键
for kvp in self.table[index]:
if kvp[0] == key:
return kvp[1]
return None # 如果键不存在则返回 None
def delete(self, key):
index = self.hash_function(key)
# 遍历链表找到并删除键值对
for i, kvp in enumerate(self.table[index]):
if kvp[0] == key:
del self.table[index][i]
return True
return False # 如果键不存在
def display(self):
for i, slot in enumerate(self.table):
if slot:
print(f"Index {i}: {slot}")
# 创建一个哈希表对象
hash_table = HashTable(10)
# 插入键值对
hash_table.insert("name", "Alice")
hash_table.insert("age", 25)
hash_table.insert("city", "New York")
# 查找值
print(hash_table.get("name")) # 输出: Alice
print(hash_table.get("age")) # 输出: 25
# 删除键值对
hash_table.delete("age")
print(hash_table.get("age")) # 输出: None
# 显示整个哈希表
hash_table.display()
哈希表的应用:
在 Python 中,哈希表的典型实现是通过 字典(dict) 来完成的。字典是一种基于哈希表的数据结构,提供了平均时间复杂度为 O(1) 的查找、插入和删除操作。
# 创建一个字典
hash_table = {}
# 插入键值对
hash_table["name"] = "Alice"
hash_table["age"] = 25
hash_table["city"] = "New York"
# 查找一个值
print(hash_table["name"]) # 输出: Alice
# 删除一个键值对
del hash_table["age"]
# 检查某个键是否存在
if "age" in hash_table:
print("Age is in the hash table")
else:
print("Age is not in the hash table")
# 更新键的值
hash_table["city"] = "San Francisco"
# 遍历字典
for key, value in hash_table.items():
print(f"{key}: {value}")
MD5算法:密码学中常用的哈希函数,把任意长度的数据映射为128位的哈希值。(已经被破解)
应用:文件哈希值,文件完整性校验
SHA2算法:性质类似MD5,安全性高于MD5
三、树与二叉树
1.树
树是一种可以递归定义的数据结构。
树的基础概念:根节点、叶子节点、树的深度(高度)、树的度(节点最大度)、孩子节点/父节点、子树。
树的实例:模拟文件系统
# 树的实例:模拟文件系统
class Node:
def __init__(self,name, type='dir'):
self.name = name
self.type = type #"dir" or "file"
self.children = []
self.parent = None
def __str__(self):
return self.name
# n = Node("HELLO")
# N2 = Node("world")
# n.children.append(n2)
# n2.parent = n
class FileSystemTree:
def __init__(self):
self.root = Node("/")
self.now = self.root
def mkdir(self, name):#name 以/结尾
if name[-1] != "/":
name +="/"
node = Node(name)
self.now.children.append(node)
node.parent = self.now
def ls(self): #展示目录
return self.now.children
def cd (self, name): #切换目录
if name[-1] != "/":
name += "/"
if name =="../":#向上返回
self.now = self.now.parent
return
for child in self.now.children:
if child.name == name:
self.now = child
return
raise ValueError("invalid dir")
tree = FileSystemTree()
tree.mkdir("var/")
tree.mkdir("bin/")
tree.mkdir("user/")
tree.cd("bin/")
tree.mkdir("python/")
print(tree.ls())
2.二叉树
二叉树:度为2 的树
堆排序的时候写过顺序存储
二叉树的链式存储:将二叉树的节点定义为一个对象,节点之间通过类似链表的链接方式连接
#建立二叉树
class BiTreeNode:
def __init__(self,data):
self.data = data
self.lchild = None #左孩子
self.rchild = None #右孩子
a = BiTreeNode("A")
b = BiTreeNode("B")
c = BiTreeNode("C")
d = BiTreeNode("D")
e = BiTreeNode("E")
f = BiTreeNode("F")
g = BiTreeNode("G")
e.lchild = a
e.rchild = g
a.rchild = c
c.lchild = b
c.rchild = d
g.rchild = f
root = e
print(root.lchild.rchild.data)
**二叉树的遍历:**前序遍历、中序遍历、后序遍历、层次遍历
#二叉树的遍历
#前序遍历
# def pre_order(root):
# if root:
# print(root.data, end=',')
# pre_order(root.lchild)
# pre_order(root.rchild)
#/中序遍历
def in_order(root):
if root:
in_order(root.lchild)
print(root.data, end=',')
in_order(root.rchild)
#/后序遍历
def post_order(root):
if root:
post_order(root.lchild)
post_order(root.rchild)
print(root.data, end=',')
#层次遍历
def level_order(root):
queue = deque()
queue.append(root)
while len(queue) >0: #只要队不空
node = queue.popleft()
print(node.data, end=',')
if node.lchild:
queue.append(node.lchild)
if node.rchild:
queue.append(node.rchild)
level_order(root)
3.二叉搜索树
二叉搜索树是特殊的二叉树,满足以下性质:
节点值的比较性质:
对于任意一个节点,其左子树中所有节点的值都小于该节点的值。
对于任意一个节点,其右子树中所有节点的值都大于该节点的值。
递归结构:
左子树和右子树本身也是二叉搜索树。
二叉搜索树的操作:查询、插入、删除
中序序列输出升序
平均情况下,二叉搜索树进行搜索的时间复杂度O(lgn)
最坏情况下,二叉搜索树可能非常偏斜
#搜索二叉树操作
#插入
import random
class BitreeNode:
def __init__(self, data):
self.data = data
self.lchild = None
self.rchild = None
self.parent = None
class BST:
def __init__(self, li=None):
self.root = None
if li:
for val in li:
self.insert_no_rec(val)
def insert(self, node, val):#递归
if not node:#如果空
node = BitreeNode(val)
elif val < node.data:
node.lchild = self.insert(node.lchild, val)
node.lchild.parent = node
elif val > node.data:
node.rchild = self.insert(node.rchild, val)
node.rchild.parent = node
return node
def insert_no_rec(self,val): #不递归
p = self.root
if not p: #空树
self.root = BitreeNode(val)
return
while True:
if val < p.data:
if p.lchild:
p = p.lchild
else: #左子树不存在
p.lchild = BitreeNode(val)
p.lchild.parent = p
return
elif val > p.data:
if p.rchild:
p = p.rchild
else: #右子树不存在
p.rchild = BitreeNode(val)
p.rchild.parent = p
return
else:
return
# 中序遍历
def in_order(self, root):
if root:
self.in_order(root.lchild)
print(root.data, end=',')
self.in_order(root.rchild)
#查询
def query(self, node, val):
if not node:
return None
if node.data< val:
return self.query(node.rchild, val)
elif node.data > val:
return self.query(node,lchild, val)
else:
return node
def query_no_rec(self,val):
p = self.root
while p:
if p.data< val:
p=p.rchild
elif p.data >val:
p = p.lchild
else:
return p
return None
#删除
#1.如果要删除的节点是叶子节点:直接删除2.如果要删除的节点只有一个孩子,将此节点的父亲也孩子连接,然后删除该节点
#3.如果要删除的节点有两个孩子:将其右子树的最小节点(该节点最多有一个右孩子)删除,并替换当前节点
def __remove_node_1(self, node): #情况一,node是叶子节点
if not node.parent:
self.root = None
if node ==node.parent.lchild:#node是它父亲的左孩子
node.parent.lchild = None
else:#右孩子
node.parent.rchild = None
def __remove_node_21(self, node): #情况二,node只有一个左孩子
if not node.parent:
self.root = node.lchild
node.lchild.parent = None
elif node ==node.parent.lchild:
node.parent.lchild = node.lchild
node.lchild.parent = node.parent
else:
node.parent.rchild = node.lchild
node.lchild.parent = node.parent
def __remove_node_22(self, node): #情况二,node只有一个右孩子
if not node.parent:
self.root = node.rchild
node.rchild.parent = None
elif node == node.parent.lchild:
node.parent.lchild = node.rchild
node.rchild.parent = node.parent
else:
node.parent.rchild = node.rchild
node.rchild.parent = node.parent
def delete(self, val): #情况三
if self.root:#不是空树
node = self.query_no_rec(val)
if not node:#不存在
return False
if not node.lchild and not node.rchild: #1.叶子节点
self.__remove_node_1(node)#下划线表示私有的
elif not node.rchild: #2.1只有一个左孩子
self.__remove_node_21(node)
elif not node.lchild: # 2.2只有一个右孩子
self.__remove_node_22(node)
else: #3.两个孩子都有
min_node = node.rchild
while min_node.lchild:
min_node = min_node.lchild
node.data = min_node.data #删除min_node
if min_node.rchild:
self.__remove_node_22(min_node)
else:
self.__remove_node_1(min_node)
# tree = BST([4,6,7,9,2,1,3,5,8])
# tree.in_order(tree.root)
# print("")
# li = list(range(0, 500, 2))
# random.shuffle(li)
# tree= BST(li)
# print(tree.query_no_rec(3))
tree = BST([1,4,2,5,3,8,6,9,7])
tree.in_order(tree.root)
print("")
tree.delete(4)
tree.in_order(tree.root)
print("")
4.AVL树
针对二叉树的最坏情况,解决方案:1.随机化插入2.AVL树
AVL树是一棵自平衡的二叉搜索树。具有以下性质:
1.根的左右子树的高度之差的绝对值不能超过1
2.根的左右子树都是平衡二叉树(任何节点高度之差都小于1)
插入:插入一个节点可能会破坏AVL树的平衡,可以通过旋转操作来进行修正。
插入一个节点后,,只有从插入节点到根节点的路径上的节点的平衡可能被改变。需要找出第一个破坏了平衡条件的节点K。
不平衡的四种情况:
1.由于对K的左孩子的左子树插入导致的:右旋;
2.由于对K的右孩子的右子树插入导致的:左旋;
3.由于对K的右孩子的左子树插入导致的:右旋——左旋;
4.由于对K的左孩子的右子树插入导致的:左旋——右旋。
class TreeNode:
def __init__(self, key):
self.key = key
self.left = None
self.right = None
self.height = 1 # 节点的初始高度为1
class AVLTree:
# 获取节点的高度
def get_height(self, node):
if not node:
return 0
return node.height
# 获取节点的平衡因子
def get_balance(self, node):
if not node:
return 0
return self.get_height(node.left) - self.get_height(node.right)
# 右旋操作
def right_rotate(self, y):
x = y.left
T2 = x.right
# 执行旋转
x.right = y
y.left = T2
# 更新高度
y.height = 1 + max(self.get_height(y.left), self.get_height(y.right))
x.height = 1 + max(self.get_height(x.left), self.get_height(x.right))
return x
# 左旋操作
def left_rotate(self, x):
y = x.right
T2 = y.left
# 执行旋转
y.left = x
x.right = T2
# 更新高度
x.height = 1 + max(self.get_height(x.left), self.get_height(x.right))
y.height = 1 + max(self.get_height(y.left), self.get_height(y.right))
return y
# 插入节点
def insert(self, root, key):
# 1. 标准的BST插入
if not root:
return TreeNode(key)
elif key < root.key:
root.left = self.insert(root.left, key)
else:
root.right = self.insert(root.right, key)
# 2. 更新节点高度
root.height = 1 + max(self.get_height(root.left), self.get_height(root.right))
# 3. 检查平衡因子,并执行相应的旋转
balance = self.get_balance(root)
# 如果平衡因子大于1,说明左子树比右子树高,需要右旋
if balance > 1:
if key < root.left.key:
# 左左情况
return self.right_rotate(root)
else:
# 左右情况:先左旋,再右旋
root.left = self.left_rotate(root.left)
return self.right_rotate(root)
# 如果平衡因子小于-1,说明右子树比左子树高,需要左旋
if balance < -1:
if key > root.right.key:
# 右右情况
return self.left_rotate(root)
else:
# 右左情况:先右旋,再左旋
root.right = self.right_rotate(root.right)
return self.left_rotate(root)
return root
# 中序遍历AVL树
def inorder_traversal(self, root):
if not root:
return []
return self.inorder_traversal(root.left) + [root.key] + self.inorder_traversal(root.right)
if __name__ == "__main__":
avl = AVLTree()
root = None
# 插入节点
keys = [10, 20, 30, 40, 50, 25]
for key in keys:
root = avl.insert(root, key)
# 中序遍历结果
print("Inorder Traversal of the AVL Tree:")
print(avl.inorder_traversal(root))
5.B树
B树是一颗自平衡的多路搜索树,常用于数据库的索引。
总结
整理了基础的数据结构,包括列表、栈和队列、链表、哈希表和树的相关内容。
作者:芃芃舒