引言
主要讲解在 oj 的面试背景下,如何处理数据结构。
文章目录
2. 双链表
3. 栈与队列(先入后入与先入先出)
4. 单调栈,单调队列
5. trie 字典树
6. 并查集
7. 堆
1. 单链表——数组模拟单链表
因为动态链表比较复杂,每次需要重建的时间成本比较大。因此在题目中一般采用数组模拟静态链表的操作。
定义:e[index]
表示编号为index的节点的数值,ne[index]
表示编号为index的节点的子节点,每次最后还需要index++
表示生成新的数组位置。
e = [0]*100000
ne = [0]*100000
index = 0
head = -1
N = int(input())
def add_to_head(x): # 头插链表,链表的尾部是-1
# 注意设置全局变量
global e, index, head, ne
e[index] = x
ne[index] = head
head = index
index += 1
def insert(k, x): # 插入到第k个后面
global e, index,ne
e[index] = x
ne[index] = ne[k]
ne[k] = index
index += 1
def delete(k): # 删除第K个插入的数后面的数
global ne
ne[k] = ne[ne[k]]
for i in range(N):
# 注意这里的读入方法
s, *p = input().split()
if s == 'H':
# int这个函数只能操作字符型
add_to_head(int(p[0]))
elif s == 'I':
k, x = map(int, p)
insert(k-1, x)
elif s == 'D':
k = int(p[0])
if k == 0:
head = ne[head]
else:
delete(k-1)
while head != -1:
print(e[head], end=' ')
head = ne[head]
2. 双向链表
N = int(input())
e = [0]*100000
r = [0]*100000
l = [0]*100000
index = 2
head = 0
tail = 1
# 构建一个队首一个队尾
r[head] = tail
l[tail] = head
def insert(k, x): # 插入k在后侧
global index, e, l, r
e[index] = x
r[index] = r[k]
l[index] = k
l[r[k]] = index
r[k] = index
index += 1
def delete(k): # 删除第k个数后面的数字
global r, l
l[r[k]] = l[k]
r[l[k]] = r[k]
for i in range(N):
s, *p = input().split()
if s == 'L':
insert(0, int(p[0]))
elif s == 'R':
insert(l[1], int(p[0]))
elif s == 'IL':
k, x = map(int, p)
insert(l[k+1],x)
elif s == 'IR':
k, x = map(int, p)
insert(k+1, x)
elif s == 'D':
delete(int(p[0])+1)
head = r[head]
while head != 1:
print(e[head], end=' ')
head = r[head]
3. Trie 字典树
class Trie:
def __init__(self):
"""
Initialize your data structure here.
"""
self.dic = {}
def insert(self, word: str) -> None:
"""
Inserts a word into the trie.
"""
## 一定要另外调用字典
dic = self.dic
for i in word:
if not i in dic:
dic[i] = {}
dic = dic[i]
dic['end'] = True
def search(self, word: str) -> bool:
"""
Returns if the word is in the trie.
"""
dic = self.dic
for i in word:
if not i in dic:
return False
dic = dic[i]
if 'end' in dic:
return True
return False
def startsWith(self, prefix: str) -> bool:
"""
Returns if there is any word in the trie that starts with the given prefix.
"""
dic = self.dic
for i in prefix:
if not i in dic:
return False
dic = dic[i]
return True
4. 堆(小顶堆)
需要支持的操作:
- 插入一个数
- 求集合中的最小值
- 删除最小值
- 删除任意一个元素
- 修改任意一个元素
之前写过的手撕堆
我发现还是把这个存储堆的序列从1开始编号方便,这样左节点是2i
,右节点是2i+1
;查询父节点时i//2
。
1. 子函数 down
变大了堆顶的元素,下移。每次和两个子节点中小的那个交换位置,直到两个子节点都大于等于。
def down(x): # 时间复杂度是logn
child = 2*x
while child <= size:
if child+1 <= size and heap[child+1]<heap[child]:
child += 1
if heap[child] < heap[x]:
heap[x], heap[child] = heap[child], heap[x]
x = child
child *= 2
else:
break
2. 子函数 up
减小了某个叶节点,上移。每次和父节点进行比较,如果父节点大于自己,交换。
3. 操作:
- 插入:堆尾插入,依次上浮。
size+= 1, heap[size] = c, up(c)
- 求最小值:返回堆首,注意我们这里堆从1开始索引的。
heap[1]
- 删除最小值:把整个堆的最后一个元素覆盖堆顶元素,然后再下沉。
heap[1] = heap[size], hize--, down(1)
- 删除任意元素:
heap[k] = heap[size], size--, down(k), up(k)
- 修改某元素:
heap[k] = num, down(k), up(k)
- 建堆:
O
(
n
)
O(n)
O(n)的时间复杂度,保证了每个儿子都是堆了。
for i in range(n//2, 0, -1) down(i)
5. 并查集
专门的并查集的讲解参考,可以参考 。这里在总结一下并查集的子函数结构。
1. 子函数 new
生成新的并查集编号,一般我习惯用字典。
def __init__(self):
self.fathernode = {}
self.val = {}
self.size = {}
# 初始化节点
def new_UF(self, node):
# 当节点已经存在时
if node in self.fathernode:
return
self.fathernode[node] = node
self.val[node] = 1.0 # 数值
self.size[node] = 1 # 规模
2. 子函数 findfather
寻找每个节点的父节点,这里可以用递归也可以用迭代。递归会更简单一些。
# -------------------------------递归------------------------------
# 查询父节点 同时进行值的更新
# 使用递归 同时对路径进行压缩 将每个节点连接到与父节点的关系
def getfather(self, node):
if self.fathernode[node] == node:
return node
# 递归得到父节点
fa = self.getfather(self.fathernode[node])
# 递归的更新当前节点得值 前父节点关系值 * 更新后得父节点值
self.val[node] = self.val[self.fathernode[node]] * self.val[node]
# 将节点的父节点更新为最父节点
self.fathernode[node] = fa
return fa
# -----------------------------迭代--------------------------------
def findfather(self, node):
while self.fathernode[node] != node:
self.fathernode[node] = self.fathernode[self.fathernode[node]]
node = self.fathernode[node]
return node
3. 子函数 union
实现合并,一般方法就是合并父节点。
def union(self, node1, node2):
node1fa = self.findfather(node1)
node2fa = self.findfather(node2)
if node1fa == node2fa:
return
self.count += 1
size_node1 = self.nodesize[node1fa]
size_node2 = self.nodesize[node2fa]
if size_node1>size_node2:
self.fathernode[node2fa] = node1fa
self.nodesize[node1fa] += size_node2
else:
self.fathernode[node1fa] = node2fa
self.nodesize[node2fa] += size_node1
例题包括:食物链
思路是使用一个并查集,然后我们维护了每个节点到根节点的距离。保证维护并查集中,每个节点到根节点的距离,定义距离为0,1,2,3 , 1吃0, 2吃1, 0吃2且与1一类。
## 利用并查集解决问题
# 维护并查集中,每个节点到根节点的距离,定义距离为0,1,2,3
# 1吃0, 2吃1, 0吃2且与1一类
N, M = map(int, input().split())
p = {}
d = {}
def new(x):
p[x] = x
d[x] = 0
def find(x):
if x == p[x]:
return x
t = find(p[x]) # 先对px进行完路径压缩,这样可以维护好d[px]
d[x] += d[p[x]] # d[px]表示了p[x]到根节点的距离,在加上x到px的距离
p[x] = t
return p[x]
def union(x,y,t):
fx = find(x)
fy = find(y)
p[fx] = fy # 把x连接到y上
add = (d[y]-d[x]+t)# 因为(d[x]+?-d[y]-1)%3 == 0,所以?=d[y]+1-d[x],这里的问号是d[fx]距离d[fy]的距离。
d[fx] = add
ans = 0
for i in range(M):
a,x,y = map(int,input().split())
if x>N or y>N:
ans += 1
#print(x,y)
else:
if x not in p:
new(x)
if y not in p:
new(y)
fx = find(x)
fy = find(y)
if a == 1:
if fx == fy:
if (d[x] - d[y])%3 != 0:
ans += 1 # 同类矛盾
else:
union(x,y, 0)
elif a == 2:
if fx == fy:
if (d[x]-d[y]-1)%3 != 0: # 因为x吃y,所以x比y大1的距离
ans += 1
else:
union(x, y, 1)
print(ans)