0Pricing
Coding Interview Prep · บทเรียน

การรวมตามอันดับและขอบเขตอินเวอร์สแอกเคอร์มันน์

เพิ่มการรวมตามอันดับเพื่อรักษาให้ต้นไม้แบนราบ และทำความเข้าใจว่าเหตุใดการเพิ่มประสิทธิภาพร่วมกันจึงให้เวลาเฉลี่ยตัดจำหน่าย 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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. DSU กับการบีบอัดเส้นทาง
  2. การรวมตามอันดับและขอบเขตอินเวอร์สแอกเคอร์มันน์
  3. การเชื่อมโยงเกินจำเป็นและการตรวจจับวงจร
  4. การรวมบัญชีและองค์ประกอบเชื่อมโยง
← กลับไปที่ Coding Interview Prep