การรวมตามอันดับและขอบเขตอินเวอร์สแอกเคอร์มันน์
เพิ่มการรวมตามอันดับเพื่อรักษาให้ต้นไม้แบนราบ และทำความเข้าใจว่าเหตุใดการเพิ่มประสิทธิภาพร่วมกันจึงให้เวลาเฉลี่ยตัดจำหน่าย O(alpha(n)) ซึ่งแทบจะคงที่
การรวมตามอันดับและขอบเขตอินเวอร์สแอกเคอร์มันน์ เป็นบทเรียน Coding Interview Prep ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Coding Interview Prep และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Coding Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน
เหตุใดต้นไม้จึงสูงขึ้นเมื่อไม่มีอันดับ
การบีบอัดเส้นทางแบบธรรมดาจะป้องกันไม่ให้ต้นไม้สูง หลังจากมีการเดินผ่าน แต่ระหว่างการดำเนินการ union ในช่วงแรก เรายังคงสร้างต้นไม้สูงได้หากให้รากของต้นไม้ที่ใหญ่กว่าชี้ไปอยู่ใต้ต้นไม้ที่เล็กกว่าเสมอ union ตามอันดับ แก้ปัญหานี้โดยติดตาม ขอบเขตบนของความสูงต้นไม้ (อันดับ) และเชื่อมต้นไม้ที่ตื้นกว่าไว้ใต้ต้นไม้ที่ลึกกว่าเสมอ
อันดับไม่ใช่ความสูงที่แท้จริง — การบีบอัดเส้นทางอาจลดความสูงให้ต่ำกว่าอันดับได้ — แต่เป็นขอบเขตบน การรักษาต้นไม้ที่ลึกกว่าไว้เป็นรากใหม่ทำให้แน่ใจว่าอันดับจะเพิ่มขึ้นเฉพาะเมื่อรวมต้นไม้ที่มีอันดับเท่ากัน ดังนั้นอันดับสูงสุดจึงถูกจำกัดไว้ที่ O(log n)
class DSU:
def __init__(self, n):
self.parent = list(range(n))
self.rank = [0] * n # initially all trees have rank 0
def find(self, x):
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x]) # path compression
return self.parent[x]
def union(self, x, y):
px, py = self.find(x), self.find(y)
if px == py:
return False
# Attach lower-rank tree under higher-rank tree
if self.rank[px] < self.rank[py]:
px, py = py, px
self.parent[py] = px
if self.rank[px] == self.rank[py]:
self.rank[px] += 1 # only increases when ranks are equal
return Trueสามกรณีของ union ตามอันดับ
เมื่อรวมองค์ประกอบสององค์ประกอบที่มีรากเป็น px และ py จะมีสามกรณีตามอันดับของรากดังนี้:
- rank[px] > rank[py]: เชื่อม py ไว้ใต้ px — อันดับของ px ไม่เปลี่ยนแปลง
- rank[px] < rank[py]: เชื่อม px ไว้ใต้ py — อันดับของ py ไม่เปลี่ยนแปลง
- rank[px] == rank[py]: เชื่อม py ไว้ใต้ px (หรือกลับกัน) — อันดับของรากใหม่เพิ่มขึ้น 1
อันดับจะเพิ่มขึ้นเฉพาะกรณีที่อันดับเท่ากันเท่านั้น นั่นหมายความว่าต้นไม้ที่มีอันดับ n ต้องมีโหนดอย่างน้อย 2^n โหนด ดังนั้นอันดับสูงสุดจึงเป็น O(log n) ทำให้เส้นทางของ find สั้นแม้ไม่มีการบีบอัดเส้นทาง
# Illustrating rank behaviour with 8 nodes
dsu_parent = list(range(8))
dsu_rank = [0] * 8
def find(x):
while dsu_parent[x] != x:
x = dsu_parent[x]
return x
def union(x, y):
px, py = find(x), find(y)
if px == py: return
if dsu_rank[px] < dsu_rank[py]:
px, py = py, px
dsu_parent[py] = px
if dsu_rank[px] == dsu_rank[py]:
dsu_rank[px] += 1
# Build balanced tree step by step
union(0,1); union(2,3); union(4,5); union(6,7)
union(0,2); union(4,6)
union(0,4)
print('Ranks:', dsu_rank) # max rank <= log2(8) = 3
print('Root of all:', find(0))การบีบอัดเส้นทางร่วมกับ union ตามอันดับ
เมื่อใช้ทั้ง การบีบอัดเส้นทาง และ union ตามอันดับ ร่วมกัน เวลาแบบเฉลี่ยตัดจำหน่ายต่อการดำเนินการจะลดลงเป็น O(alpha(n)) — ฟังก์ชันแอคเคอร์มันน์ผกผัน สำหรับขนาดอินพุตที่ใช้งานได้จริง (ไม่เกิน 2^65536) alpha(n) มีค่าไม่เกิน 4 ซึ่งถือว่าเป็นเวลาคงที่ในทางปฏิบัติ
การบีบอัดเส้นทางทำให้ต้นไม้แบนลงจากล่างขึ้นบนหลังการเดินผ่าน ขณะที่ union ตามอันดับป้องกันไม่ให้ต้นไม้สูงขึ้นจากบนลงล่างระหว่างการรวม ทั้งสองวิธีจึงเสริมกัน: อันดับจะจำกัดความลึกเริ่มต้น ส่วนการบีบอัดจะกำจัดความลึกนั้นหลังการเดินผ่านครั้งแรก
class OptimalDSU:
def __init__(self, n):
self.parent = list(range(n))
self.rank = [0] * n
def find(self, x): # path compression
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x])
return self.parent[x]
def union(self, x, y): # union by rank
px, py = self.find(x), self.find(y)
if px == py:
return False
if self.rank[px] < self.rank[py]:
px, py = py, px
self.parent[py] = px
if self.rank[px] == self.rank[py]:
self.rank[px] += 1
return True
dsu = OptimalDSU(1000)
import random; random.seed(42)
for _ in range(5000):
dsu.union(random.randint(0,999), random.randint(0,999))
print('Max rank reached:', max(dsu.rank)) # stays very smallทำความเข้าใจฟังก์ชันแอคเคอร์มันน์ผกผัน
ฟังก์ชันแอคเคอร์มันน์ A(m, n) เติบโตอย่างรวดเร็วเป็นพิเศษ — เร็วกว่าฟังก์ชันเวียนเกิดพื้นฐานใด ๆ ส่วนฟังก์ชันผกผันของมันคือ alpha(n) ซึ่งนิยามให้เป็นค่า m ที่น้อยที่สุดที่ทำให้ A(m, m) >= n เนื่องจากฟังก์ชันแอคเคอร์มันน์เติบโตอย่างรวดเร็วมาก alpha(n) จึงเติบโตช้าอย่างแทบจินตนาการไม่ออก
สำหรับ n = 10^80 (จำนวนอะตอมในเอกภพที่สังเกตได้) alpha(n) ยังคงมีค่าเพียง 4 นี่คือเหตุผลที่ DSU ซึ่งใช้การเพิ่มประสิทธิภาพทั้งสองแบบถูกถือว่ามี เวลาคงที่ในทางปฏิบัติ สำหรับการใช้งานจริงทุกกรณี คุณจะไม่มีวันพบปัญหาจริงที่มีขนาดใหญ่พอให้ alpha(n) มากกว่า 5
# Showing how slowly alpha(n) grows
# alpha(n) = smallest m such that A(m,m) >= n
# A(0,n) = n+1
# A(1,n) = n+2
# A(2,n) = 2n+3
# A(3,n) = 2^(n+3) - 3
# A(4,4) = 2^(2^(2^(2^2))) - 3 which is astronomically large
alpha_thresholds = {
1: 'n=1',
2: 'n up to 3',
3: 'n up to about 2048',
4: 'n up to 10^19728 (far beyond atoms in universe)',
5: 'essentially unreachable in practice',
}
for k, v in alpha_thresholds.items():
print(f'alpha(n)={k}: {v}')
print('\nConclusion: DSU operations are effectively O(1) for all real inputs.')อันดับกับขนาด: ควรใช้แบบใด
ทางเลือกอื่นนอกเหนือจาก union ตามอันดับ คือ union ตามขนาด: ให้เชื่อมต้นไม้ที่มีขนาดเล็กกว่าไว้ใต้ต้นไม้ที่มีขนาดใหญ่กว่าเสมอ ทั้งสองวิธีรับประกันความสูง O(log n) เท่ากัน union ตามขนาดมักทำความเข้าใจได้ง่ายกว่า เพราะขนาดเป็นจำนวนที่นับได้จริง ขณะที่อันดับเป็นขอบเขตบนซึ่งอาจไม่สะท้อนความสูงที่แท้จริงหลังการบีบอัด
ในการสัมภาษณ์ ทั้งสองวิธีถือว่ายอมรับได้ union ตามขนาดมีประโยชน์เพิ่มเติมคือทำให้ได้ขนาดขององค์ประกอบโดยไม่ต้องคำนวณเพิ่ม ซึ่งเป็นสิ่งที่หลายปัญหาต้องการ union ตามอันดับมีความสง่างามในเชิงทฤษฎีมากกว่าเล็กน้อย และสอดคล้องกับบทพิสูจน์ดั้งเดิมของทาร์จันเกี่ยวกับขอบเขตของฟังก์ชันแอคเคอร์มันน์ผกผัน
class DSUBySize:
def __init__(self, n):
self.parent = list(range(n))
self.size = [1] * n
def find(self, x):
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x])
return self.parent[x]
def union(self, x, y):
px, py = self.find(x), self.find(y)
if px == py:
return False
if self.size[px] < self.size[py]:
px, py = py, px # always attach smaller under larger
self.parent[py] = px
self.size[px] += self.size[py]
return True
dsu = DSUBySize(8)
for u, v in [(0,1),(2,3),(0,2),(4,5),(6,7),(4,6),(0,4)]:
dsu.union(u, v)
print('Size of giant component:', dsu.size[dsu.find(0)])โครงร่างบทพิสูจน์: เหตุใดอันดับจึงคงอยู่ที่ O(log n)
เราสามารถพิสูจน์โดยอุปนัยได้ว่า ต้นไม้ DSU ที่มีอันดับ r จะมีโหนดอย่างน้อย 2^r โหนด กรณีฐาน: อันดับ 0 หมายถึงมีโหนดเดียว (2^0 = 1) ขั้นอุปนัย: อันดับ r จะเพิ่มขึ้นก็ต่อเมื่อต้นไม้สองต้นที่มีอันดับ r-1 เท่ากันถูกนำมารวมกัน ตามสมมติฐานอุปนัย แต่ละต้นย่อยมีโหนดอย่างน้อย 2^(r-1) โหนด ดังนั้นต้นไม้ที่รวมแล้วจะมีโหนดอย่างน้อย 2 × 2^(r-1) = 2^r โหนด
เนื่องจากต้นไม้ที่มีอันดับ r มีโหนดอย่างน้อย 2^r โหนด และเรามีโหนดทั้งหมด n โหนด อันดับสูงสุดจึงไม่เกิน log₂(n) นั่นหมายความว่า find ที่ไม่มีการบีบอัดเส้นทางใช้เวลา O(log n) และเมื่อมีการบีบอัดเส้นทาง ต้นทุนแบบเฉลี่ยตัดจำหน่ายจะลดลงไปได้อีกมาก
# Verify the 2^rank lower bound empirically
class DSU:
def __init__(self, n):
self.parent = list(range(n))
self.rank = [0] * n
self.size = [1] * n
def find(self, x):
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x])
return self.parent[x]
def union(self, x, y):
px, py = self.find(x), self.find(y)
if px == py: return
if self.rank[px] < self.rank[py]: px, py = py, px
self.parent[py] = px
self.size[px] += self.size[py]
if self.rank[px] == self.rank[py]: self.rank[px] += 1
n = 32
dsu = DSU(n)
for i in range(n - 1): dsu.union(i, i + 1)
for root in range(n):
if dsu.find(root) == root:
r = dsu.rank[root]
print(f'Root {root}: rank={r}, size={dsu.size[root]}, 2^rank={2**r}')แม่แบบ DSU สำหรับการเขียนโปรแกรมแข่งขัน
ในการเขียนโปรแกรมแข่งขันและการสัมภาษณ์ คุณต้องการแม่แบบ DSU ที่ผ่านการทดสอบมาอย่างดี กระชับ ถูกต้อง และรองรับกรณีขอบทั้งหมด แม่แบบด้านล่างใช้ การลดครึ่งของเส้นทาง (การบีบอัดแบบเที่ยวเดียว) ร่วมกับ union ตามขนาด ซึ่งเป็นการผสมผสานที่พิมพ์ได้รวดเร็ว เข้าใจง่าย และหลีกเลี่ยงการเรียกแบบเวียนเกิดโดยสิ้นเชิง
ให้เริ่มต้น parent[i] = i และ size[i] = 1 เสมอ โปรดจำไว้ว่าหลังเรียก find แล้ว size ของรากจะสะท้อนขนาดขององค์ประกอบทั้งหมด อย่าใช้ size[x] โดยตรง — ให้เรียก size[find(x)] เสมอ
class DSU:
def __init__(self, n):
self.p = list(range(n))
self.sz = [1] * n
def find(self, x):
while self.p[x] != x:
self.p[x] = self.p[self.p[x]] # path halving
x = self.p[x]
return x
def union(self, x, y):
x, y = self.find(x), self.find(y)
if x == y: return False
if self.sz[x] < self.sz[y]: x, y = y, x
self.p[y] = x
self.sz[x] += self.sz[y]
return True
def same(self, x, y): return self.find(x) == self.find(y)
def size(self, x): return self.sz[self.find(x)]
# Usage
dsu = DSU(10)
dsu.union(0, 5)
dsu.union(5, 9)
print(dsu.same(0, 9)) # True
print(dsu.size(0)) # 3เมื่อ DSU ไม่เพียงพอ
DSU รองรับการรวมเซต แต่ ไม่รองรับการแยกเซตกลับออกเป็นสองเซต หากปัญหาต้องการทั้งการรวมและการแยกกลุ่ม คุณต้องใช้โครงสร้างอื่น (เช่น ต้นไม้ตัดการเชื่อมโยง) นอกจากนี้ DSU ยังไม่ได้เก็บสมาชิกของแต่ละกลุ่มไว้โดยอัตโนมัติ — คุณต้องมีรายการการเชื่อมโยงหรือพจนานุกรมเพิ่มเติมสำหรับการนี้
ยิ่งไปกว่านั้น DSU มาตรฐานไม่รองรับ เส้นเชื่อมถ่วงน้ำหนัก หากไม่มีการปรับแก้ (DSU แบบมีน้ำหนักเป็นรูปแบบขั้นสูงกว่า) สำหรับปัญหาอย่างการหาเส้นทางที่มีค่าใช้จ่ายต่ำที่สุดระหว่างโหนดที่เชื่อมต่อกัน ควรใช้ดิจิกสตราหรือ BFS มากกว่า การตระหนักถึงขอบเขตของ DSU จะช่วยป้องกันการนำไปใช้ผิดกรณี
# DSU is perfect for: connected-components, cycle detection,
# Kruskal's MST, accounts-merge, number-of-provinces
# DSU is NOT suitable for:
# - Splitting/removing edges from a group
# - Finding the actual path between two nodes
# - Storing all members of a group efficiently
# - Directed graphs (without modification)
# Example of storing group members alongside DSU
from collections import defaultdict
class DSUWithMembers:
def __init__(self, n):
self.p = list(range(n))
self.members = defaultdict(set)
for i in range(n): self.members[i].add(i)
def find(self, x):
while self.p[x] != x: self.p[x] = self.p[self.p[x]]; x = self.p[x]
return x
def union(self, x, y):
px, py = self.find(x), self.find(y)
if px == py: return
self.members[px] |= self.members[py]
del self.members[py]
self.p[py] = pxการเปรียบเทียบ DSU กับ BFS/DFS สำหรับการเชื่อมต่อ
ทั้ง BFS/DFS และ DSU สามารถแก้คำถามเกี่ยวกับการเชื่อมต่อแบบคงที่ได้ แต่มีจุดเด่นแตกต่างกัน BFS/DFS ใช้เวลา O(V + E) และสามารถค้นหาเส้นทางจริงระหว่างโหนดได้ ส่วน DSU ตอบคำถามเกี่ยวกับการเชื่อมต่อจำนวนมากบนชุดเส้นเชื่อมที่เพิ่มขึ้นทีละน้อย โดยใช้เวลาเกือบ O(1) ต่อคำถาม — จึงเหมาะอย่างยิ่งกับอัลกอริทึมแบบ ออนไลน์ ที่เส้นเชื่อมเข้ามาทีละเส้น
หากคุณได้รับเส้นเชื่อมทั้งหมดตั้งแต่ต้นและต้องการเพียงตรวจสอบการเชื่อมต่อ ทั้งสองวิธีใช้ได้ หากเส้นเชื่อมเข้ามาแบบพลวัตและคุณต้องตอบคำถามการเชื่อมต่อระหว่างการเพิ่มเส้นเชื่อมใหม่แต่ละเส้น DSU จะเหมาะสมกว่าอย่างชัดเจน สำหรับปัญหาที่ต้องการเส้นทางสั้นที่สุดด้วย ให้เลือกใช้ BFS
# Comparing DSU vs BFS for 1000 nodes, 2000 edges
# After all edges given => BFS works fine
# But with online edge arrival + interleaved queries => DSU shines
from collections import deque
def bfs_connected(graph, src, dst, n):
visited = set([src])
q = deque([src])
while q:
node = q.popleft()
if node == dst: return True
for nb in graph.get(node, []):
if nb not in visited:
visited.add(nb); q.append(nb)
return False
# DSU for same query:
# dsu.same(src, dst) -- O(alpha(n)) amortised
# BFS for same query:
# O(V + E) every time -- not suitable for repeated queries
print('DSU is preferred for repeated connectivity queries.')
print('BFS/DFS is preferred when you also need the actual path.')ฝึกปฏิบัติ: ต้นไม้ทอดข้ามต่ำสุดด้วย DSU
อัลกอริทึมของ Kruskal สำหรับต้นไม้ทอดข้ามต่ำสุดใช้ DSU โดยตรง โดยเรียงลำดับเส้นเชื่อมทั้งหมดตามน้ำหนัก จากนั้นเพิ่มเส้นเชื่อมแต่ละเส้นแบบละโมบ หากปลายทั้งสองอยู่ในองค์ประกอบที่แตกต่างกัน (ไม่มีวัฏจักร) DSU ช่วยตรวจสอบวัฏจักรได้ในเวลาเกือบ O(1) ผลลัพธ์คือ MST ที่มีเส้นเชื่อม n-1 เส้น
นี่เป็นตัวอย่างคลาสสิกที่แสดงพลังของ DSU: การตรวจสอบวัฏจักรแบบพื้นฐานที่ใช้เวลา O(E × V) ถูกเปลี่ยนเป็นกระบวนการที่ใช้เวลา O(E × alpha(n)) เมื่อรวมการเรียงลำดับด้วยเวลา E log E เวลารวมของ Kruskal คือ O(E log E) และการดำเนินการของ DSU รวดเร็วมากจนแทบไม่มีนัยสำคัญเมื่อเทียบกับการเรียงลำดับ
def kruskal(n, edges):
edges.sort(key=lambda e: e[2]) # sort by weight
parent = list(range(n))
rank = [0] * n
def find(x):
if parent[x] != x:
parent[x] = find(parent[x])
return parent[x]
def union(x, y):
px, py = find(x), find(y)
if px == py: return False
if rank[px] < rank[py]: px, py = py, px
parent[py] = px
if rank[px] == rank[py]: rank[px] += 1
return True
mst_weight = 0
mst_edges = []
for u, v, w in edges:
if union(u, v):
mst_weight += w
mst_edges.append((u, v, w))
return mst_weight, mst_edges
edges = [(0,1,4),(0,2,3),(1,2,1),(1,3,2),(2,3,5)]
w, e = kruskal(4, edges)
print('MST weight:', w) # 6: edges (1,2,1)+(1,3,2)+(0,2,3)
print('MST edges:', e)DSU พร้อม rollback: การเชื่อมต่อแบบออฟไลน์
DSU มาตรฐานไม่รองรับการดำเนินการยกเลิก อย่างไรก็ตาม DSU พร้อม rollback (เรียกอีกอย่างว่า DSU พร้อมประวัติ) รองรับ โดยแทนที่จะใช้การบีบอัดเส้นทาง (ซึ่งยกเลิกได้ยาก) ให้ใช้เฉพาะการ union ตามอันดับ และบันทึกการ union แต่ละครั้งไว้ในสแตก หากต้องการ rollback ให้ pop จากสแตก แล้วคืนค่าโหนดแม่และอันดับ วิธีนี้ทำให้สามารถแก้ปัญหาการเชื่อมต่อแบบพลวัตออฟไลน์ ซึ่งอาจมีการเพิ่มและลบเส้นเชื่อมได้
แม้รูปแบบขั้นสูงนี้จะไม่ค่อยพบในการสัมภาษณ์ทั่วไป แต่ก็แสดงให้เห็นว่าการ union ตามอันดับคือค่าคงที่สำคัญ ไม่ใช่การบีบอัดเส้นทาง หากไม่มีการบีบอัดเส้นทาง การดำเนินการ find แต่ละครั้งจะใช้เวลา O(log n) และเมื่อใช้ rollback การดำเนินการบนสแตกจะใช้เวลา O(1) ดังนั้นเวลาต่อการดำเนินการโดยรวมจึงเป็น O(log n) แทนที่จะเป็น O(alpha(n))
class DSUWithRollback:
def __init__(self, n):
self.parent = list(range(n))
self.rank = [0] * n
self.history = [] # stack of (node, old_parent, node2, old_rank)
def find(self, x): # NO path compression (cannot undo)
while self.parent[x] != x:
x = self.parent[x]
return x
def union(self, x, y):
px, py = self.find(x), self.find(y)
if px == py: return False
if self.rank[px] < self.rank[py]: px, py = py, px
# Record state before modifying
self.history.append((py, self.parent[py], px, self.rank[px]))
self.parent[py] = px
if self.rank[px] == self.rank[py]: self.rank[px] += 1
return True
def rollback(self):
py, old_par_py, px, old_rank_px = self.history.pop()
self.parent[py] = old_par_py
self.rank[px] = old_rank_px
dsu = DSUWithRollback(5)
dsu.union(0, 1); dsu.union(1, 2)
print('0 and 2 connected:', dsu.find(0) == dsu.find(2)) # True
dsu.rollback()
print('After rollback:', dsu.find(0) == dsu.find(2)) # Falseตรวจสอบความเข้าใจอย่างรวดเร็ว
ทดสอบความเข้าใจของคุณเกี่ยวกับแนวคิดโครงสร้างข้อมูล & อัลกอริทึม — การเตรียมตัวสัมภาษณ์การเขียนโปรแกรมจากบทเรียนนี้
สรุปบทเรียน
ในบทเรียนนี้ คุณได้เรียนรู้ว่า การ union ตามอันดับจะนำต้นไม้ที่ตื้นกว่าไปไว้ใต้ต้นไม้ที่ลึกกว่าเสมอ อันดับจะเพิ่มขึ้นเฉพาะเมื่อรวมต้นไม้ที่มีอันดับเท่ากัน ทำให้ความสูงของต้นไม้อยู่ที่ O(log n) และ การผสานการบีบอัดเส้นทางเข้ากับการ union ตามอันดับทำให้ได้เวลาแบบเฉลี่ยสะสม O(alpha(n)) ซึ่งในทางปฏิบัติถือเป็นเวลาคงที่ บทถัดไป เราจะใช้ DSU ที่มีประสิทธิภาพสูงสุดกับปัญหาเส้นเชื่อมเกินและการตรวจจับวัฏจักรในกราฟ
คำถามที่พบบ่อย
บทเรียน “การรวมตามอันดับและขอบเขตอินเวอร์สแอกเคอร์มันน์” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “การรวมตามอันดับและขอบเขตอินเวอร์สแอกเคอร์มันน์” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Coding Interview Prep ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Coding Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “การรวมตามอันดับและขอบเขตอินเวอร์สแอกเคอร์มันน์”
เพิ่มการรวมตามอันดับเพื่อรักษาให้ต้นไม้แบนราบ และทำความเข้าใจว่าเหตุใดการเพิ่มประสิทธิภาพร่วมกันจึงให้เวลาเฉลี่ยตัดจำหน่าย O(alpha(n)) ซึ่งแทบจะคงที่ คุณปฏิบัติ Coding Interview Prep ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Coding Interview Prep หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน Coding Interview Prep บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน
บทเรียน “การรวมตามอันดับและขอบเขตอินเวอร์สแอกเคอร์มันน์” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน Coding Interview Prep นี้ได้ไหม
ได้ บทเรียน Coding Interview Prep ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- DSU กับการบีบอัดเส้นทาง
- การรวมตามอันดับและขอบเขตอินเวอร์สแอกเคอร์มันน์
- การเชื่อมโยงเกินจำเป็นและการตรวจจับวงจร
- การรวมบัญชีและองค์ประกอบเชื่อมโยง