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

คลาส TrieNode: แทรกและค้นหา

สร้าง TrieNode ที่มีพจนานุกรม children และแฟล็ก is_end นำการแทรกและการค้นหาแบบตรงทั้งหมดไปใช้งาน และวิเคราะห์ว่าแต่ละการดำเนินการใช้เวลา O(m) เมื่อ m คือความยาวคำ

คลาส TrieNode: แทรกและค้นหา เป็นบทเรียน DSA Interview Prep ฟรีบน CoddyKit นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน DSA Interview Prep และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส DSA Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน

Trie คืออะไร

Trie (ต้นไม้คำนำหน้า) เป็นโครงสร้างข้อมูลรูปต้นไม้ที่แต่ละโหนดแทนอักขระหนึ่งตัว คำต่าง ๆ ถูกจัดเก็บโดยเชื่อมอักขระจากรากไปยังใบ รากแทนสตริงว่าง เส้นทางแต่ละเส้นจากรากไปยังโหนด is_end = True จะแสดงเป็นคำที่จัดเก็บไว้ Trie เหมาะอย่างยิ่งสำหรับ การสืบค้นตามคำนำหน้า เช่น autocomplete การตรวจการสะกด และการกำหนดเส้นทาง IP โดยทำงานได้ดีกว่าตารางแฮชสำหรับกรณีเหล่านี้

การออกแบบคลาส TrieNode

TrieNode มีฟิลด์สองรายการ ได้แก่ children — พจนานุกรมที่จับคู่อักขระกับ TrieNode ของโหนดลูก และ is_end — ค่าบูลีนที่ระบุว่าโหนดนี้เป็นจุดสิ้นสุดของคำที่จัดเก็บไว้หรือไม่ การใช้พจนานุกรมแทนอาร์เรย์อักขระ 26 ตำแหน่งแบบตายตัวทำให้รองรับชุดอักขระใด ๆ ได้ทั่วไป และประหยัดหน่วยความจำสำหรับ Trie ที่มีข้อมูลเบาบาง โหนดแต่ละโหนดใน Trie แทนตำแหน่งอักขระหนึ่งตำแหน่งในคำที่อยู่ด้านล่างโหนดนั้นโดยพอดี

class TrieNode:
    def __init__(self):
        self.children = {}  # char -> TrieNode
        self.is_end = False  # True if a word ends here

class Trie:
    def __init__(self):
        self.root = TrieNode()
    
    def __repr__(self):
        return f'Trie(root with {len(self.root.children)} children)'

t = Trie()
print(t)  # Trie(root with 0 children)

การดำเนินการ insert

หากต้องการ insert คำ ให้เริ่มเดินจากราก และสร้าง TrieNode ใหม่สำหรับอักขระแต่ละตัวที่ยังไม่มีอยู่ในโหนดปัจจุบัน หลังจากประมวลผลอักขระทั้งหมดแล้ว ให้กำหนดโหนดสุดท้ายเป็นจุดสิ้นสุดของคำ การ insert คำว่า 'แอปเปิล' และ 'แอป' จะสร้างเส้นทางร่วมตามอักขระของคำแรก โดยโหนดสุดท้ายของ 'แอปเปิล' และโหนดปลายของ 'แอป' จะถูกทำเครื่องหมายว่าเป็นจุดสิ้นสุดของคำที่เกี่ยวข้อง

class TrieNode:
    def __init__(self):
        self.children = {}
        self.is_end = False

class Trie:
    def __init__(self):
        self.root = TrieNode()
    
    def insert(self, word):
        node = self.root
        for char in word:
            if char not in node.children:
                node.children[char] = TrieNode()
            node = node.children[char]
        node.is_end = True

t = Trie()
t.insert('apple')
t.insert('app')
print('Inserted apple and app')
print('app is_end:', t.root.children['a'].children['p'].children['p'].is_end)

การดำเนินการ search

หากต้องการค้นหาคำแบบตรงทั้งหมด ให้เดินไปตาม Trie โดยใช้อักขระแต่ละตัว หากอักขระตัวใดไม่มีอยู่ในโหนดปัจจุบัน ให้ส่งกลับค่าเท็จ หากพบอักขระครบทั้งหมด ให้ส่งกลับ node.is_end ซึ่งจะเป็นค่าจริงก็ต่อเมื่อมีคำสิ้นสุดตรงตำแหน่งนี้ ไม่ใช่เพียงมีคำนำหน้ามาถึงตำแหน่งนี้ ความแตกต่างระหว่าง 'มีคำนำหน้าอยู่' กับ 'มีคำตรงทั้งหมดอยู่' เป็นสิ่งสำคัญและมักถูกนำไปทดสอบ

class TrieNode:
    def __init__(self):
        self.children = {}
        self.is_end = False

class Trie:
    def __init__(self):
        self.root = TrieNode()
    
    def insert(self, word):
        node = self.root
        for c in word:
            if c not in node.children:
                node.children[c] = TrieNode()
            node = node.children[c]
        node.is_end = True
    
    def search(self, word):
        node = self.root
        for c in word:
            if c not in node.children:
                return False
            node = node.children[c]
        return node.is_end  # must be a complete word

t = Trie()
t.insert('apple')
print(t.search('apple'))   # True
print(t.search('app'))     # False (app not inserted)
print(t.search('orange'))  # False

การขึ้นต้นด้วย (การค้นหาคำนำหน้า)

เมธอด starts_with ตรวจสอบว่ามีคำที่ insert ไว้คำใดขึ้นต้นด้วยคำนำหน้าที่กำหนดหรือไม่ เมธอดนี้เดินตามเส้นทางเดียวกับ search แต่แทนที่จะตรวจสอบ is_end จะส่งกลับค่าจริงทันทีที่เดินตามอักขระของคำนำหน้าครบทั้งหมด ซึ่งหมายความว่าเส้นทางของคำนำหน้านั้นมีอยู่ใน Trie

class TrieNode:
    def __init__(self):
        self.children = {}
        self.is_end = False

class Trie:
    def __init__(self):
        self.root = TrieNode()
    
    def insert(self, word):
        node = self.root
        for c in word:
            if c not in node.children:
                node.children[c] = TrieNode()
            node = node.children[c]
        node.is_end = True
    
    def search(self, word):
        node = self.root
        for c in word:
            if c not in node.children: return False
            node = node.children[c]
        return node.is_end
    
    def starts_with(self, prefix):
        node = self.root
        for c in prefix:
            if c not in node.children: return False
            node = node.children[c]
        return True  # prefix path exists

t = Trie()
t.insert('apple')
print(t.starts_with('app'))   # True
print(t.starts_with('ape'))   # False
print(t.search('app'))         # False (not inserted)

ความซับซ้อนด้านเวลาและพื้นที่

การดำเนินการแต่ละอย่างของ Trie ได้แก่ insert, search และ starts_with ใช้เวลา O(m) โดย m คือความยาวของคำ เพราะเราเดินผ่านโหนดมากที่สุด m โหนด ส่วนพื้นที่ใช้ O(ALPHABET_SIZE × N × M) โดย N คือจำนวนคำ และ M คือความยาวคำโดยเฉลี่ย ในทางปฏิบัติ คำนำหน้าที่ใช้ร่วมกันช่วยลดการใช้พื้นที่ได้มาก พจนานุกรม children ที่อาศัยตารางแฮชใช้พื้นที่น้อยกว่าอาร์เรย์ 26 ตำแหน่งแบบตายตัวสำหรับ Trie ที่มีข้อมูลเบาบาง แต่แลกกับค่าใช้จ่ายคงที่ต่อการค้นหาแต่ละครั้งที่สูงขึ้นเล็กน้อย

การใช้อาร์เรย์แทนพจนานุกรม

สำหรับอักษรภาษาอังกฤษตัวพิมพ์เล็กเท่านั้น ให้ใช้อาร์เรย์ขนาดคงที่ children = [None] * 26 โดยใช้ดัชนี ord(c) - ord('a') วิธีนี้เร็วกว่า เพราะการค้นหาโหนดลูกใช้เวลา O(1) เมื่อเทียบกับตารางแฮช และมีผังการใช้หน่วยความจำที่คาดเดาได้ ควรใช้แบบพจนานุกรมเมื่อชุดอักขระมีขนาดใหญ่หรือไม่ทราบแน่ชัด เช่น Unicode และใช้แบบอาร์เรย์สำหรับโจทย์การแข่งขันที่มีเฉพาะอักษรภาษาอังกฤษตัวพิมพ์เล็ก

class TrieNodeArray:
    def __init__(self):
        self.children = [None] * 26
        self.is_end = False

class TrieArray:
    def __init__(self):
        self.root = TrieNodeArray()
    
    def insert(self, word):
        node = self.root
        for c in word:
            idx = ord(c) - ord('a')
            if node.children[idx] is None:
                node.children[idx] = TrieNodeArray()
            node = node.children[idx]
        node.is_end = True
    
    def search(self, word):
        node = self.root
        for c in word:
            idx = ord(c) - ord('a')
            if node.children[idx] is None: return False
            node = node.children[idx]
        return node.is_end

t = TrieArray()
t.insert('cat')
print(t.search('cat'))  # True
print(t.search('car'))  # False

การดำเนินการลบ

การลบจาก Trie ต้องรองรับสามกรณี ได้แก่ (1) ไม่มีคำอยู่ — ไม่ต้องทำอะไร (2) มีคำอยู่แต่คำนั้นเป็นคำนำหน้าของอีกคำ — ยกเลิกการตั้งค่า is_end เท่านั้น (3) มีคำอยู่และคำนั้นไม่ใช่คำนำหน้า — ลบโหนดจากล่างขึ้นบน และหยุดเมื่อโหนดมีโหนดลูกอื่นหรือเป็นจุดสิ้นสุดของอีกคำหนึ่ง การลบมักไม่ค่อยถูกทดสอบในการสัมภาษณ์ แต่ควรทำความเข้าใจแนวคิดนี้ไว้

การนับคำด้วยคำนำหน้า

เพิ่มฟิลด์ count ให้แต่ละโหนด และเพิ่มค่าฟิลด์นี้ทุกครั้งที่มีการเดินผ่านโหนดระหว่าง insert หากต้องการนับคำที่มีคำนำหน้าที่กำหนด ให้เดินไปยังโหนดปลายของคำนำหน้าแล้วส่งกลับค่าการนับ วิธีนี้ทำให้การสืบค้น autocomplete ใช้เวลา O(m) โดยไม่ต้องเดินผ่านโหนดลูกทั้งหมด และเป็นส่วนขยายที่มีประโยชน์สำหรับระบบ autocomplete ในโลกจริง

class TrieNodeCount:
    def __init__(self):
        self.children = {}
        self.is_end = False
        self.count = 0  # words passing through this node

class TrieCount:
    def __init__(self):
        self.root = TrieNodeCount()
    
    def insert(self, word):
        node = self.root
        for c in word:
            if c not in node.children:
                node.children[c] = TrieNodeCount()
            node = node.children[c]
            node.count += 1  # increment on each level
        node.is_end = True
    
    def count_with_prefix(self, prefix):
        node = self.root
        for c in prefix:
            if c not in node.children: return 0
            node = node.children[c]
        return node.count

t = TrieCount()
for w in ['apple','app','application','apply']:
    t.insert(w)
print(t.count_with_prefix('app'))   # 4
print(t.count_with_prefix('appl'))  # 3

การเปรียบเทียบ Trie กับตารางแฮช

ตารางแฮช สามารถค้นหาแบบตรงทั้งหมดได้ในเวลาเฉลี่ย O(m) แต่ไม่สามารถตอบการสืบค้นคำนำหน้าได้อย่างมีประสิทธิภาพ เพราะต้องตรวจสอบคีย์ทั้งหมด Trie ตอบการสืบค้นคำนำหน้าได้ในเวลา O(p) โดย p คือความยาวคำนำหน้า จัดกลุ่มคำที่ใช้คำนำหน้าร่วมกันโดยธรรมชาติ และไม่จำเป็นต้องใช้การแฮช ควรใช้ Trie เมื่อมีการสืบค้นคำนำหน้าบ่อย ต้องการ autocomplete หรือการตรวจการสะกด และควรใช้ตารางแฮชเมื่อจำเป็นต้องค้นหาแบบตรงทั้งหมดเท่านั้น

Trie ในระบบจริง

การใช้งาน Trie ในโลกจริง ได้แก่ autocomplete (คำแนะนำการค้นหาของ Google) เครื่องมือตรวจการสะกด (ค้นหาคำที่ตรงกันมากที่สุด) การกำหนดเส้นทาง IP (การจับคู่คำนำหน้าที่ยาวที่สุดในเราเตอร์) ข้อความคาดเดาแบบ T9 (แยกความหมายของอักขระ) และ ตัวแก้ไข DNS (ค้นหาชื่อโดเมนตามลำดับชั้น) ในแต่ละกรณี การแลกเปลี่ยนระหว่างเวลา O(m) ต่อการดำเนินการกับพื้นที่ O(ALPHABET × จำนวนโหนด) ของ Trie ทำให้ Trie เป็นเครื่องมือที่เหมาะสมสำหรับการค้นหาที่รวดเร็วและรองรับคำนำหน้าในระบบขนาดใหญ่

ตรวจสอบความเข้าใจ

ทดสอบความเข้าใจแนวคิดโครงสร้างข้อมูล & อัลกอริทึม — การเตรียมตัวสัมภาษณ์การเขียนโปรแกรมจากบทเรียนนี้

สรุปบทเรียน

ในบทเรียนนี้ คุณได้เรียนรู้ว่า TrieNode มีพจนานุกรมโหนดลูกและค่าบูลีนที่ระบุจุดสิ้นสุด insert จะเดินผ่านอักขระทีละตัว สร้างโหนดเมื่อจำเป็น และกำหนดจุดสิ้นสุดเมื่อถึงท้ายคำ และ search จะตรวจสอบจุดสิ้นสุด ขณะที่ starts_with ตรวจสอบเพียงว่าเส้นทางคำนำหน้ามีอยู่หรือไม่ ต่อไปเราจะเพิ่ม autocomplete ตามคำนำหน้าและศึกษาเมธอด starts_with ในรายละเอียดมากขึ้น

คำถามที่พบบ่อย

บทเรียน “คลาส TrieNode: แทรกและค้นหา” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “คลาส TrieNode: แทรกและค้นหา” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส DSA Interview Prep ให้อัปเกรดเป็น CoddyKit PRO คอร์ส DSA Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “คลาส TrieNode: แทรกและค้นหา”

สร้าง TrieNode ที่มีพจนานุกรม children และแฟล็ก is_end นำการแทรกและการค้นหาแบบตรงทั้งหมดไปใช้งาน และวิเคราะห์ว่าแต่ละการดำเนินการใช้เวลา O(m) เมื่อ m คือความยาวคำ คุณปฏิบัติ DSA Interview Prep ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน DSA Interview Prep หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน DSA Interview Prep บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน

บทเรียน “คลาส TrieNode: แทรกและค้นหา” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน DSA Interview Prep นี้ได้ไหม

ได้ บทเรียน DSA Interview Prep ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

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

  1. คลาส TrieNode: แทรกและค้นหา
  2. การค้นหาคำนำหน้าและ Starts-With
  3. การค้นหาแบบไวลด์การ์ดและนิพจน์ประจำใน Trie
  4. การค้นหาคำ II: Trie + การย้อนกลับบนกริด
← กลับไปที่ DSA Interview Prep