λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°

μΉ΄ν…Œκ³ λ¦¬ μ—†μŒ

[μ•Œκ³ λ¦¬μ¦˜] 트라이(Trie) μ•Œκ³ λ¦¬μ¦˜

πŸ’‘νŠΈλΌμ΄(Trie) μ•Œκ³ λ¦¬μ¦˜μ΄λž€?

트라이(Trie)

  • λ¬Έμžμ—΄μ„ μ €μž₯ν•˜κ³ , λΉ λ₯΄κ²Œ νƒμƒ‰ν•˜κΈ° μœ„ν•œ 트리 ν˜•νƒœμ˜ 자료ꡬ쑰
  • 접두사λ₯Ό κΈ°μ€€μœΌλ‘œ 탐색
  • λ¬Έμžμ—΄ λ°°μ—΄μ—μ„œ 각 λ¬Έμžμ—΄ 별 문자λ₯Ό 일일이 νƒμƒ‰ν•˜λŠ” 것보닀 탐색 속도가 빠름
  • 각 λ…Έλ“œμ—μ„œ μžμ‹μ— λŒ€ν•œ 포인터λ₯Ό λ°°μ—΄λ‘œ μ €μž₯ν•œλ‹€λŠ” μ μ—μ„œ λ©”λͺ¨λ¦¬λ₯Ό 더 μ‚¬μš©

 

트라이 ꡬ쑰

λ…Έλ“œ ꡬ성

class Node{
    //λ…Έλ“œμ˜ μžμ‹ λ…Έλ“œλ“€μ„ μ €μž₯
    HashMap<Character, Node> child;
    //이 λ…Έλ“œκ°€ λ‹¨μ–΄μ˜ 끝인지 μ €μž₯
    boolean endOfWord;
}

λ©”μ†Œλ“œ ꡬ성

μ‚½μž…(insert)

“TO” μ‚½μž…

  1. 루트 λ…Έλ“œμ— T μžμ‹ μ—†μœΌλ―€λ‘œ 생성
  2. T λ…Έλ“œμ— O μžμ‹ μ—†μœΌλ―€λ‘œ 생성
  3. O λ…Έλ“œ 이후 문자 λ‹¨μœ„ 탐색 λλ‚¬μœΌλ―€λ‘œ endOfWord = true μ„€μ •

“TA” μ‚½μž…

  1. 루트 λ…Έλ“œμ— T μžμ‹ μžˆμœΌλ―€λ‘œ T μžμ‹μœΌλ‘œ 이동
  2. T λ…Έλ“œμ— A μžμ‹ μ—†μœΌλ―€λ‘œ 생성
  3. A λ…Έλ“œ 이후 문자 탐색 λλ‚¬μœΌλ―€λ‘œ endOfWord = true μ„€μ •

“CAT” μ‚½μž…

  1. 루트 λ…Έλ“œμ— C μžμ‹ μ—†μœΌλ―€λ‘œ 생성
  2. C λ…Έλ“œμ— A μžμ‹ μ—†μœΌλ―€λ‘œ 생성
  3. A λ…Έλ“œμ— T μžμ‹ μ—†μœΌλ―€λ‘œ 생성
  4. T λ…Έλ“œ 이후 문자 탐색 λλ‚¬μœΌλ―€λ‘œ endOfWord = true μ„€μ •

“TREE” μ‚½μž…

  1. 루트 λ…Έλ“œμ— T μžμ‹ μžˆμœΌλ―€λ‘œ T μžμ‹μœΌλ‘œ 이동
  2. T λ…Έλ“œμ— R μžμ‹ μ—†μœΌλ―€λ‘œ 생성
  3. R λ…Έλ“œμ— E μžμ‹ μ—†μœΌλ―€λ‘œ 생성
  4. E λ…Έλ“œμ— E μžμ‹ μ—†μœΌλ―€λ‘œ 생성
  5. E λ…Έλ“œ 이후 문자 탐색 λλ‚¬μœΌλ―€λ‘œ endOfWord = true μ„€μ •

 

탐색(search)

“TRE” 탐색

  1. 루트 λ…Έλ“œμ— T λ…Έλ“œ μžˆμœΌλ―€λ‘œ 이동
  2. T λ…Έλ“œμ— R μžμ‹ μžˆμœΌλ―€λ‘œ 이동
  3. R λ…Έλ“œμ— E μžμ‹ μžˆμœΌλ―€λ‘œ 이동
  4. E λ…Έλ“œμ—μ„œ endOfWord λŠ” false μ΄λ―€λ‘œ false 리턴

“TAKE” 탐색

  1. 루트 λ…Έλ“œμ— T λ…Έλ“œ μžˆμœΌλ―€λ‘œ 이동
  2. T λ…Έλ“œμ— A μžμ‹ μžˆμœΌλ―€λ‘œ 이동
  3. A λ…Έλ“œμ— K μžμ‹ μ—†μœΌλ―€λ‘œ false 리턴

μ‚­μ œ(delete)

“TA” μ‚­μ œ

  1. “TA” 탐색
  2. 맨 λ§ˆμ§€λ§‰ 문자 “A”의 endOfWordλ₯Ό false 둜 λ³€κ²½
  3. “A”에 μžμ‹ λ…Έλ“œκ°€ μžˆλŠ”μ§€ 확인. 있으면 return, μ—†μœΌλ©΄ μ‚­μ œ ν›„ T λ…Έλ“œλ‘œ 이동
  4. “T”에 μžμ‹ λ…Έλ“œκ°€ μžˆλŠ”μ§€ 확인. 있으면 return, μ—†μœΌλ©΄ μ‚­μ œ ν›„ 루트 λ…Έλ“œλ‘œ 이동

κ΅¬ν˜„

Node 클래슀

class Node{
    //λ…Έλ“œμ˜ μžμ‹ λ…Έλ“œλ“€μ„ μ €μž₯
    HashMap<Character, Node> child;
    //이 λ…Έλ“œκ°€ λ‹¨μ–΄μ˜ 끝인지 μ €μž₯
    boolean endOfWord;
    
    public Node() {
        this.child = new HashMap<>();
        this.endOfWord = false;
    }
}

Trie 클래슀

public class Trie {
    Node root;
    
    public Trie(){
        this.root = new Node();
    }

    public void insert(String str); //μ‚½μž…
    boolean search(String str); //탐색
    public boolean delete(String str);//μ‚­μ œ
        
}

insert λ©”μ„œλ“œ

public void insert(String str){
    //μ‹œμž‘ λ…Έλ“œλ₯Ό λ£¨νŠΈλ…Έλ“œλ‘œ μ„€μ •
    Node node = this.root;

		// λ¬Έμžμ—΄μ˜ 문자 λ‹¨μœ„λ‘œ 탐색
    for(int i=0;i<str.length(); i++){
        char c = str.charAt(i);
        //  ν•΄λ‹Ή λ¬Έμžκ°€ ν˜„μž¬ λ…Έλ“œμ˜ μžμ‹λ…Έλ“œ 쀑에 μžˆλŠ”μ§€ 확인
        node.child.putIfAbsent(c,new Node());
        // μ—†μœΌλ©΄ 생성 ν›„ μžμ‹λ…Έλ“œλ‘œ 이동
        node = node.child.get(c);
    }
    // λ¬Έμžμ—΄ 탐색 ν›„ λ§ˆμ§€λ§‰ λ¬Έμžκ°€ λ‹¨μ–΄μ˜ λμž„μ„ λͺ…μ‹œ
    node.endOfWord = true;
}

search λ©”μ„œλ“œ

public boolean search(String str){
    Node node = this.root;

    //λ¬Έμžμ—΄μ„ 문자 λ‹¨μœ„λ‘œ 탐색
    for(int i=0; i<str.length(); i++){
            char c = str.charAt(i);

            // μžμ‹ λ…Έλ“œμ— μ°ΎλŠ” λ¬Έμžκ°€ μ—†μœΌλ©΄ false 리턴
            if(!node.child.containsKey(c)) return false;

            // ν•΄λ‹Ή λ…Έλ“œκ°€ 있으면 μžμ‹ λ…Έλ“œλ‘œ 이동
            node = node.child.get(c);
    }
    // λ§ˆμ§€λ§‰ λ…Έλ“œ 도달 μ‹œ, endOfWord λ°˜ν™˜
    return node.endOfWord;
}

delete λ©”μ„œλ“œ

public boolean delete(String str) {
    boolean result = delete(this.root, str, 0);
    return result;
}

private boolean delete(Node node, String str, int idx) {
    char c = str.charAt(idx);

    // μ§€μ›Œμ•Ό ν•  λ¬Έμžκ°€ μžμ‹λ…Έλ“œμ— μ—†μœΌλ©΄ λ¬Έμžμ—΄ μ—†μ–΄μ„œ μ‚­μ œ μ‹€νŒ¨, false 리턴
    if(!node.child.containsKey(c)){
            return false;
    }

    Node cur = node.child.get(c);
    idx++;
    // λ¬Έμžμ—΄ 길이 만큼 νƒμƒ‰ν•˜μ—¬ 도달
    if(idx == str.length()){
            // λ¬Έμžμ—΄ 끝이 endOfWord κ°€ μ•„λ‹ˆλ©΄ λ¬Έμžμ—΄ μ—†μ–΄μ„œ μ‚­μ œ μ‹€νŒ¨, false 리턴
            if(!cur.endOfword){
                     return false;
            }
            // λ¬Έμžμ—΄ 끝의 endOfWordλ₯Ό false둜 λ³€κ²½
            cur.endOfWord = false;

            // μ§€μš°λ €λŠ” λ¬Έμžμ—΄μ˜ 뒀에 λ‹€λ₯Έ μžμ‹μ΄ μ—†λ‹€λ©΄ ν˜„μž¬ λ…Έλ“œμ—μ„œ μžμ‹λ…Έλ“œ μ‚­μ œ
    // λ¬Έμžμ—΄ 뒀에 λ‹€λ₯Έ μžμ‹μ΄ μžˆλ‹€λ©΄ 탐색 λλ‚˜κ³  λŒμ•„κ°€κΈ°
    if(cur.child.isEmpty()){
        node.child.remove(c);
    }
    }
    else {
            // μ‚­μ œ μ‹€νŒ¨
            if(!this.delete(cur, str, idx)){
                    return false;
            }

            // trueλ₯Ό λ°˜ν™˜λ°›μ•˜κ³ , ν˜„μž¬ λ…Έλ“œμ˜ μžμ‹λ…Έλ“œκ°€ λΉ„μ–΄μžˆμœΌλ©΄ ν˜„μž¬ λ…Έλ“œλ₯Ό λΆ€λͺ¨μ˜ μžμ‹μ—μ„œ μ‚­μ œ
    if(!cur.endOfWord && cur.child.isEmpty()){
        node.child.remove(c);
    }

    }

    return true;
		
}

 

전체 μ½”λ“œ

더보기
class Node{
    //λ…Έλ“œμ˜ μžμ‹ λ…Έλ“œλ“€μ„ μ €μž₯
    HashMap<Character, Node> child;
    //이 λ…Έλ“œκ°€ λ‹¨μ–΄μ˜ 끝인지 μ €μž₯
    boolean endOfWord;
    
    public Node() {
        this.child = new HashMap<>();
        this.endOfWord = false;
    }
}

public class Trie {
    Node root;
    
    public Trie(){
        this.root = new Node();
    }

    public void insert(String str){
    //μ‹œμž‘ λ…Έλ“œλ₯Ό λ£¨νŠΈλ…Έλ“œλ‘œ μ„€μ •
        Node node = this.root;

            // λ¬Έμžμ—΄μ˜ 문자 λ‹¨μœ„λ‘œ 탐색
        for(int i=0;i<str.length(); i++){
            char c = str.charAt(i);
            //  ν•΄λ‹Ή λ¬Έμžκ°€ ν˜„μž¬ λ…Έλ“œμ˜ μžμ‹λ…Έλ“œ 쀑에 μžˆλŠ”μ§€ 확인
            node.child.putIfAbsent(c,new Node());
            // μ—†μœΌλ©΄ 생성 ν›„ μžμ‹λ…Έλ“œλ‘œ 이동
            node = node.child.get(c);
        }
        // λ¬Έμžμ—΄ 탐색 ν›„ λ§ˆμ§€λ§‰ λ¬Έμžκ°€ λ‹¨μ–΄μ˜ λμž„μ„ λͺ…μ‹œ
        node.endOfWord = true;
    }
    
    
    public boolean search(String str){
		Node node = this.root;
		
		//λ¬Έμžμ—΄μ„ 문자 λ‹¨μœ„λ‘œ 탐색
		for(int i=0; i<str.length(); i++){
            char c = str.charAt(i);

            // μžμ‹ λ…Έλ“œμ— μ°ΎλŠ” λ¬Έμžκ°€ μ—†μœΌλ©΄ false 리턴
            if(!node.child.containsKey(c)) return false;

            // ν•΄λ‹Ή λ…Έλ“œκ°€ 있으면 μžμ‹ λ…Έλ“œλ‘œ 이동
            node = node.child.get(c);
		}
		// λ§ˆμ§€λ§‰ λ…Έλ“œ 도달 μ‹œ, endOfWord λ°˜ν™˜
		return node.endOfWord;
	}
    
    
    public boolean delete(String str) {
		boolean result = delete(this.root, str, 0);
		return result;
	}

	private boolean delete(Node node, String str, int idx) {
		char c = str.charAt(idx);
		
		// μ§€μ›Œμ•Ό ν•  λ¬Έμžκ°€ μžμ‹λ…Έλ“œμ— μ—†μœΌλ©΄ λ¬Έμžμ—΄ μ—†μ–΄μ„œ μ‚­μ œ μ‹€νŒ¨, false 리턴
		if(!node.child.containsKey(c)){
				return false;
		}
		
		Node cur = node.child.get(c);
		idx++;
		// λ¬Έμžμ—΄ 길이 만큼 νƒμƒ‰ν•˜μ—¬ 도달
		if(idx == str.length()){
            // λ¬Έμžμ—΄ 끝이 endOfWord κ°€ μ•„λ‹ˆλ©΄ λ¬Έμžμ—΄ μ—†μ–΄μ„œ μ‚­μ œ μ‹€νŒ¨, false 리턴
            if(!cur.endOfword){
                     return false;
            }
            // λ¬Έμžμ—΄ 끝의 endOfWordλ₯Ό false둜 λ³€κ²½
            cur.endOfWord = false;
				
			// μ§€μš°λ €λŠ” λ¬Έμžμ—΄μ˜ 뒀에 λ‹€λ₯Έ μžμ‹μ΄ μ—†λ‹€λ©΄ ν˜„μž¬ λ…Έλ“œμ—μ„œ μžμ‹λ…Έλ“œ μ‚­μ œ
            // λ¬Έμžμ—΄ 뒀에 λ‹€λ₯Έ μžμ‹μ΄ μžˆλ‹€λ©΄ 탐색 λλ‚˜κ³  λŒμ•„κ°€κΈ°
            if(cur.child.isEmpty()){
                node.child.remove(c);
            }
        }
        else {
            // μ‚­μ œ μ‹€νŒ¨
            if(!this.delete(cur, str, idx)){
                    return false;
            }

            // trueλ₯Ό λ°˜ν™˜λ°›μ•˜κ³ , ν˜„μž¬ λ…Έλ“œμ˜ μžμ‹λ…Έλ“œκ°€ λΉ„μ–΄μžˆμœΌλ©΄ ν˜„μž¬ λ…Έλ“œλ₯Ό λΆ€λͺ¨μ˜ μžμ‹μ—μ„œ μ‚­μ œ
            if(!cur.endOfWord && cur.child.isEmpty()){
                node.child.remove(c);
            }
		
		}
		
		return true;
		
	}
    
}