1. λͺ¨λ…Έν†€ μŠ€νƒ(Monotone stack) μ΄λž€?

μŠ€νƒ(Stack)은 ν•œμͺ½ λμ—μ„œλ§Œ 데이터λ₯Ό λ„£κ³  λΊ„ 수 μžˆλŠ” ν›„μž…μ„ μΆœ(LIFO:Last In, First Out) ν˜•νƒœμ˜ 자료ꡬ쑰 μž…λ‹ˆλ‹€.
즉, κ°€μž₯ λ¨Όμ € λ“€μ–΄μ˜¨ 데이터가 κ°€μž₯ λ§ˆμ§€λ§‰μ— λ‚˜κ°€κ³ , λ§ˆμ§€λ§‰μ— λ“€μ–΄μ˜¨ 데이터가 κ°€μž₯ λ¨Όμ € λ‚˜κ°€λŠ” κ΅¬μ‘°μž…λ‹ˆλ‹€.

 

  • λͺ¨λ…Έν†€ μŠ€νƒ(Monotone stack)은 νŠΉμ •ν•œ μˆœμ„œλ₯Ό μœ μ§€ν•˜λ©΄μ„œ 데이터λ₯Ό κ΄€λ¦¬ν•˜λŠ” μŠ€νƒ 자료ꡬ쑰 μž…λ‹ˆλ‹€.
    • Monotone은 μ¦κ°€ν•˜κ±°λ‚˜ κ°μ†Œν•˜λŠ” λ“±μ˜ μΌμ •ν•œ κ²½ν–₯이 λ³΄μž„μ„ μ˜λ―Έν•©λ‹ˆλ‹€.
  • λͺ¨λ…Έν†€ μŠ€νƒ(Monotone stack)은 μ¦κ°€ν•˜κ±°λ‚˜ κ°μ†Œν•˜λŠ” μΌμ •ν•œ κ²½ν–₯을 κ°€μ§„ μŠ€νƒμž…λ‹ˆλ‹€.
  • λͺ¨λ…Έν†€ μŠ€νƒ(Monotone stack)은 νŠΉμ • λ¬Έμ œμ— λŒ€ν•΄ μ‹œκ°„ λ³΅μž‘λ„λ₯Ό O(N)으둜 μ€„μ—¬μ£ΌλŠ” 맀우 효율적인 해결책을 μ œκ³΅ν•©λ‹ˆλ‹€.
    • λ‹€μŒ/이전 λ°μ΄ν„°μ˜ μ΅œλŒ“κ°’/μ΅œμ†Ÿκ°’μ„(Nearest Greater/Smaller Element) μ°ΎλŠ” λ¬Έμ œμ— μœ μš©ν•˜κ²Œ μ“°μž…λ‹ˆλ‹€.

 

2. λͺ¨λ…Έν†€ μŠ€νƒ(Monotone stack)의 μ’…λ₯˜ 및 κ΅¬ν˜„

μŠ€νƒμ€ LIFO(Last In, First Out) 자료 ꡬ쑰이기 λ•Œλ¬Έμ— λͺ¨λ…Έν†€ μŠ€νƒμ˜ μ΅œμƒμœ„ μš”μ†Œλ₯Ό μ œκ±°ν•˜λ©΄μ„œ 단쑰 증가/κ°μ†Œ μˆœμ„œλ₯Ό μœ μ§€ν•©λ‹ˆλ‹€. μƒˆλ‘œμš΄ μš”μ†ŒλŠ” 항상 μ΅œμƒμœ„μ— μΆ”κ°€λ©λ‹ˆλ‹€.

1) 단쑰 증가 μŠ€νƒ(Monotone Increasing Stack)

  • μŠ€νƒμ˜ μš”μ†Œλ“€μ΄ μ˜€λ¦„μ°¨μˆœμœΌλ‘œ μ •λ ¬λ˜λŠ” μŠ€νƒ
    • μ˜€λ¦„μ°¨μˆœμ„ μœ μ§€ν•œλ‹€λŠ” 것은 μŠ€νƒμ˜ μ•„λž˜μͺ½λΆ€ν„° μœ„μͺ½μœΌλ‘œ 올라갈수둝 값이 μ»€μ§„λ‹€λŠ” 의미
    • μŠ€νƒμ˜ μ΅œμƒμœ„μ—μ„œλΆ€ν„° μ‹œμž‘ν•˜μ—¬ μ•„λž˜λ‘œ λ‚΄λ €κ°ˆμˆ˜λ‘ 값이 μž‘μ•„μ§
10 (top)
5
2
1
  • μŠ€νƒμ˜ 맨 μœ„μ— μžˆλŠ” μš”μ†Œκ°€ 항상 κ·Έ μ•„λž˜μ— μžˆλŠ” μš”μ†Œλ³΄λ‹€ ν¬κ±°λ‚˜ 같은 경우λ₯Ό μœ μ§€ν•˜λŠ” μŠ€νƒ
    • μŠ€νƒμ— μƒˆλ‘œμš΄ μš”μ†Œλ₯Ό μΆ”κ°€ν•  λ•Œ, κ·Έ μš”μ†Œκ°€ μŠ€νƒμ˜ μ΅œμƒμœ„ μš”μ†Œλ³΄λ‹€ μž‘κ±°λ‚˜ 같을 λ•ŒκΉŒμ§€ κΈ°μ‘΄ μš”μ†Œλ₯Ό 제거
    • μ΄λ•Œ, 단쑰성을 μœ μ§€ν•΄μ•Όν•©λ‹ˆλ‹€. (μŠ€νƒμ— μžˆλŠ” μš”μ†Œλ“€μ΄ 항상 νŠΉμ • μˆœμ„œ(μ˜€λ¦„μ°¨μˆœ)λ₯Ό μœ μ§€)
    • 쀑볡 κ°’ ν—ˆμš© 여뢀에 따라 값이 같은 κ²½μš°μ— λŒ€ν•œ 제거 μ—¬λΆ€λ₯Ό μ •ν•˜λ©΄ λ©λ‹ˆλ‹€.
Deque<Integer> stack = new ArrayDeque<>();
for (int i=0; i<arr.length; i++) {
    // μŠ€νƒμ˜ μ΅œμƒμœ„ μš”μ†Œκ°€ ν˜„μž¬ 값보닀 ν¬κ±°λ‚˜ κ°™μœΌλ©΄ pop();
    while (!stack.isEmpty() && stack.peekFirst() >= arr[i]) {
    	stack.removeFirst();
    }
    // μŠ€νƒμ˜ μ΅œμƒμœ„ μš”μ†Œλ³΄λ‹€ μž‘μ„λ•ŒκΉŒμ§€ κΈ°μ‘΄ μš”μ†Œλ₯Ό μ œκ±°ν•˜κ³ , ν˜„μž¬ μš”μ†Œ μŠ€νƒμ— μΆ”κ°€
    stack.offerFirst(arr[i]);
}

2) 단쑰 κ°μ†Œ μŠ€νƒ(Monotone Decreasing Stack)

  • μŠ€νƒμ˜ μš”μ†Œλ“€μ΄ λ‚΄λ¦Όμ°¨μˆœμœΌλ‘œ μ •λ ¬λ˜λŠ” μŠ€νƒ
    • λ‚΄λ¦Όμ°¨μˆœμ„ μœ μ§€ν•œλ‹€λŠ” 것은 μŠ€νƒμ˜ μ•„λž˜μͺ½λΆ€ν„° μœ„μͺ½μœΌλ‘œ 올라갈수둝 값이 μž‘μ•„μ§„λ‹€λŠ” 의미
    • μŠ€νƒμ˜ μ΅œμƒμœ„μ—μ„œλΆ€ν„° μ‹œμž‘ν•˜μ—¬ μ•„λž˜λ‘œ 갈수둝 값이 컀짐
1 (top)
2
5
10
  • μŠ€νƒμ˜ 맨 μœ„μ— μžˆλŠ” μš”μ†Œκ°€ 항상 κ·Έ μ•„λž˜μ— μžˆλŠ” μš”μ†Œλ³΄λ‹€ μž‘κ±°λ‚˜ 같은 경우λ₯Ό μœ μ§€ν•˜λŠ” μŠ€νƒ
    • μŠ€νƒμ— μƒˆλ‘œμš΄ μš”μ†Œλ₯Ό μΆ”κ°€ν•  λ•Œ, κ·Έ μš”μ†Œκ°€ μŠ€νƒμ˜ μ΅œμƒμœ„ μš”μ†Œλ³΄λ‹€ ν¬κ±°λ‚˜ 같을 λ•ŒκΉŒμ§€ κΈ°μ‘΄ μš”μ†Œλ₯Ό 제거
    • μ΄λ•Œ, 단쑰성을 μœ μ§€ν•΄μ•Όν•©λ‹ˆλ‹€. (μŠ€νƒμ— μžˆλŠ” μš”μ†Œλ“€μ΄ νŠΉμ • μˆœμ„œ(λ‚΄λ¦Όμ°¨μˆœ)λ₯Ό μœ μ§€)
    • 쀑볡 κ°’ ν—ˆμš© 여뢀에 따라 값이 같은 κ²½μš°μ— λŒ€ν•œ 제거 μ—¬λΆ€λ₯Ό μ •ν•˜λ©΄ λ©λ‹ˆλ‹€.
Deque<Integer> stack = new ArrayDeque<>();
for (int i=0; i<arr.length; i++) {
    // μŠ€νƒμ˜ μ΅œμƒμœ„ μš”μ†Œκ°€ ν˜„μž¬ 값보닀 μž‘κ±°λ‚˜ κ°™μœΌλ©΄ pop();
    while (!stack.isEmpty() && stack.peekFirst() <= arr[i]) {
    	stack.removeFirst();
    }
    // μŠ€νƒμ˜ μ΅œμƒμœ„ μš”μ†Œλ³΄λ‹€ 클 λ•ŒκΉŒμ§€ κΈ°μ‘΄ μš”μ†Œλ₯Ό μ œκ±°ν•˜κ³ , ν˜„μž¬ μš”μ†Œ μŠ€νƒμ— μΆ”κ°€
    stack.offerFirst(arr[i]);
}

 

 

3. λͺ¨λ…Έν†€ μŠ€νƒ(Monotone stack) κ΅¬ν˜„ - μ˜ˆμ‹œλ‘œ 확인

개인적으둜 λͺ¨λ…Έν†€ μŠ€νƒμ˜ μ’…λ₯˜μ— λŒ€ν•΄ μ΄ν•΄ν•˜λŠ”λ°μ— 쑰금 μ‹œκ°„μ΄ κ±Έλ ΈμŠ΅λ‹ˆλ‹€. 단쑰 증가 μŠ€νƒκ³Ό 단쑰 κ°μ†Œ μŠ€νƒμ˜ μ •λ ¬ μˆœμ„œκ°€ μ–΄λ””λΆ€ν„° μ‹œμž‘ν•˜λŠ”μ§€μ— λŒ€ν•œ μ •ν™•ν•œ 이해λ₯Ό λͺ»ν•˜κ³  λ¬΄μž‘μ • μ΄ν•΄ν•˜λ €κ³  ν•˜λ‹€λ³΄λ‹ˆ ν—·κ°ˆλ ΈμŠ΅λ‹ˆλ‹€. 

κ·Έλž˜μ„œ λ§ˆμ§€λ§‰μœΌλ‘œ λ°°μ—΄ μ˜ˆμ‹œλ₯Ό μ •λ¦¬ν•˜λ©΄μ„œ 아직은 쑰금 ν—·κ°ˆλ¦¬λŠ” 뢀뢄을 μ •λ¦¬ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

public static void main(String[] args) {
    int[][] testArrays = {
        {1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
        {10, 9, 8, 7, 6, 5, 4, 3, 2, 1},
        {5, 5, 4, 4, 3, 3, 2, 2, 1, 1},
        {3, 6, 1, 9, 4, 7, 2, 8, 5, 10}
    };

    for (int[] arr : testArrays) {
        System.out.println("Monotonic Stack for array: " + Arrays.toString(arr));
        incresing(arr);
        decresing(arr);
        System.out.println();
    }
}

ν…ŒμŠ€νŠΈ 배열을 μœ„μ™€ 같이 4κ°€μ§€ μ •λ„λ‘œ 생성해두고 단쑰 증가 μŠ€νƒκ³Ό 단쑰 κ°μ†Œ μŠ€νƒμ„ 좜λ ₯ν•΄λ³΄μ•˜μŠ΅λ‹ˆλ‹€.

*각 μŠ€νƒμ€ 2번 λͺ©μ°¨μ˜ μ½”λ“œλ₯Ό μˆ˜ν–‰ν•΄μ„œ 생성

 

μœ„ μ½”λ“œμ— λŒ€ν•œ 좜λ ₯ κ²°κ³ΌλŠ” μ•„λž˜μ™€ κ°™μŠ΅λ‹ˆλ‹€.

Monotonic Stack for array: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Monotonic Increasing Stack: 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 
Monotonic Decreasing Stack: 10, 

Monotonic Stack for array: [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
Monotonic Increasing Stack: 1, 
Monotonic Decreasing Stack: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 

Monotonic Stack for array: [5, 5, 4, 4, 3, 3, 2, 2, 1, 1]
Monotonic Increasing Stack: 1, 
Monotonic Decreasing Stack: 1, 2, 3, 4, 5, 

Monotonic Stack for array: [3, 6, 1, 9, 4, 7, 2, 8, 5, 10]
Monotonic Increasing Stack: 10, 5, 2, 1, 
Monotonic Decreasing Stack: 10,

 

단쑰 증가 μŠ€νƒμ€ μš”μ†Œλ“€μ΄ μ˜€λ¦„μ°¨μˆœ(μŠ€νƒμ˜ μ•„λž˜λΆ€ν„° μ΅œμƒμœ„κΉŒμ§€)으둜 μ •λ ¬λ˜μ–΄ μžˆλŠ” μŠ€νƒ

단쑰 κ°μ†Œ μŠ€νƒμ€ μš”μ†Œλ“€μ΄ λ‚΄λ¦Όμ°¨μˆœ(μŠ€νƒμ˜ μ•„λž˜λΆ€ν„° μ΅œμƒμœ„κΉŒμ§€)으둜 μ •λ ¬λ˜μ–΄ μžˆλŠ” μŠ€νƒ

인 점을 머리속에 κΌ­ 담아두고 좜λ ₯ κ²°κ³Όλ₯Ό 확인해보면, μ œλŒ€λ‘œ 좜λ ₯λ˜λŠ” 것을 μ•Œ 수 μžˆμŠ΅λ‹ˆλ‹€.

 

μ½”λ“œ κ΅¬ν˜„μ‹œ 쀑볡 값은 ν—ˆμš©ν•˜μ§€ μ•Šμ•˜κΈ° λ•Œλ¬Έμ— 3번 μ˜ˆμ‹œμ²˜λŸΌ 쀑볡 값이 μ—†λŠ” 것을 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

λ§Œμ•½ 쀑볡을 ν—ˆμš©ν•œλ‹€λ©΄ μ•„λž˜μ™€ 같은 κ²°κ³Όκ°€ 좜λ ₯λ©λ‹ˆλ‹€.

Monotonic Stack for array: [5, 5, 4, 4, 3, 3, 2, 2, 1, 1]
Monotonic Increasing Stack: 1, 1, 
Monotonic Decreasing Stack: 1, 1, 2, 2, 3, 3, 4, 4, 5, 5,

 

단쑰 증가 μŠ€νƒ ν•¨μˆ˜λŠ” μ•„λž˜μ™€ κ°™μ„λ•Œ λ§ˆμ§€λ§‰ μ˜ˆμ‹œμΈ {3, 6, 1, 9, 4, 7, 2, 8, 5, 10} 에 λŒ€ν•œ 단쑰 증가 μŠ€νƒ 생성 과정을 ꡬ체적으둜 λ³΄κ² μŠ΅λ‹ˆλ‹€.

public static void incresing(int[] arr) {
    Deque<Integer> stack = new ArrayDeque<>();
    for (int i=0; i<arr.length; i++) {
        // μŠ€νƒμ˜ μ΅œμƒμœ„ μš”μ†Œκ°€ ν˜„μž¬ 값보닀 ν¬κ±°λ‚˜ κ°™μœΌλ©΄ pop();
        while (!stack.isEmpty() && stack.peekFirst() >= arr[i]) {
        	stack.removeFirst();
        }
        // μŠ€νƒμ˜ μ΅œμƒμœ„ μš”μ†Œλ³΄λ‹€ μž‘μ„λ•ŒκΉŒμ§€ κΈ°μ‘΄ μš”μ†Œλ₯Ό μ œκ±°ν•˜κ³ , ν˜„μž¬ μš”μ†Œ μŠ€νƒμ— μΆ”κ°€
        stack.offerFirst(arr[i]);
    }
}

 

  • λ°°μ—΄: {3, 6, 1, 9, 4, 7, 2, 8, 5, 10}
λ°°μ—΄ μš”μ†Œ μ„€λͺ… μŠ€νƒ
첫번째 μš”μ†Œ: 3 μŠ€νƒμ΄ λΉ„μ–΄μžˆμœΌλ―€λ‘œ μš”μ†Œ 3 μΆ”κ°€  [3]
λ‘λ²ˆμ§Έ μš”μ†Œ: 6 μŠ€νƒμ˜ 맨 μœ„ μš”μ†Œ(3)κ°€ 6보닀 μž‘μœΌλ―€λ‘œ 6을 μŠ€νƒμ— μΆ”κ°€ [6, 3]
μ„Έλ²ˆμ§Έ μš”μ†Œ: 1 μŠ€νƒμ˜ 맨 μœ„ μš”μ†Œ(6)κ°€ 1보닀 ν¬λ―€λ‘œ, 6을 μŠ€νƒμ—μ„œ 제거 [3]
μŠ€νƒμ˜ 맨 μœ„ μš”μ†Œ(3)κ°€ 1보닀 ν¬λ―€λ‘œ, 3을 μŠ€νƒμ—μ„œ 제거 [ ]
μŠ€νƒμ΄ λΉ„μ—ˆμœΌλ―€λ‘œ μš”μ†Œ 1 μΆ”κ°€ [1]
λ„€λ²ˆμ§Έ μš”μ†Œ: 9 μŠ€νƒμ˜ 맨 μœ„ μš”μ†Œ(1)κ°€ 9보닀 μž‘μœΌλ―€λ‘œ, 9λ₯Ό μŠ€νƒμ— μΆ”κ°€ [9, 1]
λ‹€μ„―λ²ˆμ§Έ μš”μ†Œ: 4 μŠ€νƒμ˜ 맨 μœ„ μš”μ†Œ(9)κ°€ 4보닀 ν¬λ―€λ‘œ, 9λ₯Ό μŠ€νƒμ—μ„œ 제거 [1]
μŠ€νƒμ˜ 맨 μœ„ μš”μ†Œ(1)κ°€ 4보닀 μž‘μœΌλ―€λ‘œ, 4λ₯Ό μŠ€νƒμ— μΆ”κ°€ [4, 1]
μ—¬μ„―λ²ˆμ§Έ μš”μ†Œ: 7 μŠ€νƒμ˜ 맨 μœ„ μš”μ†Œ(4)κ°€ 7보닀 μž‘μœΌλ―€λ‘œ, 7을 μŠ€νƒμ— μΆ”κ°€ [7, 4, 1]
일곱번째 μš”μ†Œ: 2 μŠ€νƒμ˜ 맨 μœ„ μš”μ†Œ(7)κ°€ 2보닀 ν¬λ―€λ‘œ, 7을 μŠ€νƒμ—μ„œ 제거 [4, 1]
μŠ€νƒμ˜ 맨 μœ„ μš”μ†Œ(4)κ°€ 2보닀 ν¬λ―€λ‘œ, 4λ₯Ό μŠ€νƒμ—μ„œ 제거 [1]
μŠ€νƒμ˜ 맨 μœ„ μš”μ†Œ(1)κ°€ 2보닀 μž‘μœΌλ―€λ‘œ, 2λ₯Ό μŠ€νƒμ— μΆ”κ°€ [2, 1]
μ—¬λŸλ²ˆμ§Έ μš”μ†Œ: 8 μŠ€νƒμ˜ 맨 μœ„ μš”μ†Œ(2)κ°€ 8보닀 μž‘μœΌλ―€λ‘œ, 8을 μŠ€νƒμ— μΆ”κ°€ [8, 2, 1]
μ•„ν™‰λ²ˆμ§Έ μš”μ†Œ: 5 μŠ€νƒμ˜ 맨 μœ„ μš”μ†Œ(8)κ°€ 5보닀 ν¬λ―€λ‘œ, 8을 μŠ€νƒμ—μ„œ 제거 [2, 1]
μŠ€νƒμ˜ 맨 μœ„ μš”μ†Œ(2)κ°€ 5보닀 μž‘μœΌλ―€λ‘œ, 5λ₯Ό μŠ€νƒμ— μΆ”κ°€ [5, 2, 1]
μ—΄λ²ˆμ§Έ μš”μ†Œ: 10 μŠ€νƒμ˜ 맨 μœ„ μš”μ†Œ(5)κ°€ 10보닀 μž‘μœΌλ―€λ‘œ, 10을 μŠ€νƒμ— μΆ”κ°€ [10, 5, 2, 1]

 

μ΅œμ’…μ μœΌλ‘œ μŠ€νƒμ„ 좜λ ₯ν•˜λ©΄ [10, 5, 2, 1] 이 좜λ ₯λ©λ‹ˆλ‹€.

μŠ€νƒμ˜ 맨 μœ„ μš”μ†Œκ°€ ν˜„μž¬ λ°°μ—΄ μš”μ†Œλ³΄λ‹€ ν¬κ±°λ‚˜ κ°™μœΌλ©΄ μŠ€νƒμ—μ„œ μ œκ±°ν•˜κ³ , ν˜„μž¬ μš”μ†Œλ₯Ό μŠ€νƒμ— μΆ”κ°€ν•˜λ©΄μ„œ 단쑰성을 μœ μ§€ν•  수 μžˆλŠ” κ²ƒμž…λ‹ˆλ‹€.

 

4. λͺ¨λ…Έν†€ μŠ€νƒ(Monotone stack) κ΄€λ ¨ 문제

 

[λ°±μ€€] 탑 보기 - μžλ°”(Java)

문제 링크https://www.acmicpc.net/problem/22866문제 μ„€λͺ…μΌμ§μ„ μœΌλ‘œ λ‹€μ–‘ν•œ λ†’μ΄μ˜ 건물이 총 Nκ°œκ°€ μ‘΄μž¬ν•œλ‹€. 각 건물 μ˜₯μƒμ—μ„œ μ–‘ μ˜†μ— μ‘΄μž¬ν•˜λŠ” 건물의 μ˜†μ„ λͺ‡ 개 λ³Ό 수 μžˆλŠ”μ§€ κΆκΈˆν•΄μ‘Œλ‹€. i번째 건

coding-vvon.tistory.com

 

 

참고 링크

 

λͺ¨λ…Έν†€ μŠ€νƒ, 큐,덱에 κ΄€ν•˜μ—¬.

μ„œλ‘ . μŠ€νƒ, 큐, 덱 λ¬Έμ œλ“€μ„ ν‘ΈλŠ” λ™μ•ˆ λ§Žμ€ μ•Œκ³ λ¦¬μ¦˜λ“€μ΄ ν•΄λ‹Ή μ•Œκ³ λ¦¬μ¦˜μ„ μ‚¬μš©ν•˜λŠ” 것을 λ³΄μ•˜λ‹€ ν•˜λ‚˜κ°™μ΄ ν•΄λ‹Ή ν˜•μ‹μ„ λ”°λ₯΄κ³  μžˆλŠ” 것을 보고 λ­”κ°€ μ •ν˜•ν™”λœ μ•Œκ³ λ¦¬μ¦˜μ΄ μžˆλ‚˜? 생각이 λ“€μ—ˆλŠ”

velog.io

 

μ§€λ‚œλ²ˆμ— 트라이λ₯Ό κ³΅λΆ€ν•˜κ³  ν¬μŠ€νŒ…μœΌλ‘œ μ •λ¦¬ν–ˆλŠ”λ°, μ•„λž˜ ν¬μŠ€νŒ…μ—μ„œλŠ” Map Interface둜 κ΅¬ν˜„μ„ ν–ˆμ—ˆμŠ΅λ‹ˆλ‹€.

그런데 λ°±μ€€ - μ „μ„€(19585)을 트라이 자료ꡬ쑰둜 풀어도 μ‹œκ°„ μ΄ˆκ³Όκ°€ λ‚˜κΈΈλž˜ 더 μ°Ύμ•„λ³΄λ‹ˆ Array둜 κ΅¬ν˜„ν•˜λŠ” 방식도 μžˆμ–΄μ„œ μ΄λ²ˆμ—λŠ” Array λ°©μ‹μœΌλ‘œ κ΅¬ν˜„ν•œ νŠΈλΌμ΄λ„ μΆ”κ°€ν–ˆμŠ΅λ‹ˆλ‹€.


트라이(Trie)λž€?

트라이(Trie)λŠ” λ¬Έμžμ—΄μ„ μ €μž₯ν•˜κ³  효율적으둜 νƒμƒ‰ν•˜κΈ° μœ„ν•œ 트리 ν˜•νƒœμ˜ 자료ꡬ쑰둜, μžμ„Έν•œκ±΄ μ§€λ‚œλ²ˆ ν¬μŠ€νŒ…μ„ μ°Έκ³ ν•΄μ£Όμ„Έμš”.

 

[자료ꡬ쑰] 트라이(Trie)

1. 트라이(Trie)λž€?μ›ν•˜λŠ” μš”μ†Œλ₯Ό μ°ΎκΈ°μœ„ν•΄ 자주 μ‚¬μš©λ˜λŠ” 이진 검색 트리 λ“±μ—μ„œλŠ” νƒ€κ²Ÿ μš”μ†Œλ₯Ό μ°ΎλŠ”λ°μ— O(logN)의 μ‹œκ°„μ΄ κ±Έλ¦½λ‹ˆλ‹€.ν•˜μ§€λ§Œ λ¬Έμžμ—΄μ˜ 경우 두 λ¬Έμžμ—΄μ„ λΉ„κ΅ν•˜κΈ° μœ„ν•΄μ„œλŠ” λ¬Έμžμ—΄

coding-vvon.tistory.com

 

트라이(Trie) κ΅¬ν˜„ 방식

트라이(Trie)λŠ” 두 κ°€μ§€ λ°©μ‹μœΌλ‘œ κ΅¬ν˜„ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

  1. Arrayλ₯Ό μ΄μš©ν•˜μ—¬ κ΅¬ν˜„
  2. Map Interfaceλ₯Ό μ΄μš©ν•˜μ—¬ κ΅¬ν˜„

Array둜 κ΅¬ν˜„ν•  경우, λΉ λ₯Έ μ €μž₯κ³Ό 탐색을 ν•  수 μžˆλ‹€λŠ” μž₯점이 μžˆμ§€λ§Œ ν•„μš”ν•œ λ©”λͺ¨λ¦¬μ˜ 크기가 λ„ˆλ¬΄ ν¬λ‹€λŠ” 것이 λ‹¨μ μž…λ‹ˆλ‹€.

Map Interface둜 κ΅¬ν˜„ν•  경우, λ©”λͺ¨λ¦¬λ₯Ό λ™μ μœΌλ‘œ ν• λ‹Ήν•˜μ—¬ ν•„μš” λ…Έλ“œλ§Œ λ©”λͺ¨λ¦¬λ₯Ό ν• λ‹Ήν•  수 μžˆλ‹€λŠ” μž₯점이 μžˆμ§€λ§Œ λ©”λͺ¨λ¦¬ μ œν•œμ΄ μžˆλŠ” 경우 μ΅œμ ν™” μž‘μ—…μ΄ κ½€ κΉŒλ‹€λ‘­λ‹€λŠ” 단점이 μžˆμŠ΅λ‹ˆλ‹€.

 

1. Array둜 κ΅¬ν˜„

Array둜 κ΅¬ν˜„ν•  경우, Trie의 Node별 μžμ‹ λ…Έλ“œλ₯Ό Nodeν˜• λ°°μ—΄λ‘œ κ΅¬ν˜„ν•©λ‹ˆλ‹€. 

μ΄λ•Œ, Trie Node에 λ“€μ–΄κ°€λŠ” λ‹¨μ–΄λŠ” μ˜μ–΄ μ†Œλ¬Έμžλ§Œ μžˆλ‹€κ³  κ°€μ •ν•˜λ©°, 각 λ…Έλ“œλŠ” μ˜μ–΄ μ†Œλ¬Έμžλ₯Ό μ •μˆ˜ν˜•(int)으둜 λ³€ν™˜ν•˜μ—¬ 인덱슀둜 μ‚¬μš©ν•©λ‹ˆλ‹€.

 

* μžμ„Έν•œ μ„€λͺ…은 이전 ν¬μŠ€νŒ…μ„ μ°Έκ³ ν•΄μ£Όμ„Έμš”.

 

[자료ꡬ쑰] 트라이(Trie)

1. 트라이(Trie)λž€?μ›ν•˜λŠ” μš”μ†Œλ₯Ό μ°ΎκΈ°μœ„ν•΄ 자주 μ‚¬μš©λ˜λŠ” 이진 검색 트리 λ“±μ—μ„œλŠ” νƒ€κ²Ÿ μš”μ†Œλ₯Ό μ°ΎλŠ”λ°μ— O(logN)의 μ‹œκ°„μ΄ κ±Έλ¦½λ‹ˆλ‹€.ν•˜μ§€λ§Œ λ¬Έμžμ—΄μ˜ 경우 두 λ¬Έμžμ—΄μ„ λΉ„κ΅ν•˜κΈ° μœ„ν•΄μ„œλŠ” λ¬Έμžμ—΄

coding-vvon.tistory.com

 

Node 클래슀 생성

Map으둜 κ΅¬ν˜„ν•œ Trie 의 Node와 λ‹€λ₯΄κ²Œ μžμ‹ λ…Έλ“œλ₯Ό Node[] 둜 μ΄ˆκΈ°ν™”ν•©λ‹ˆλ‹€.

childNode의 μΈλ±μŠ€μ— μ˜μ–΄ μ†Œλ¬Έμžλ₯Ό μ •μˆ˜ν˜•(int)둜 λ³€ν™˜ν•œ 값이 λ“€μ–΄κ°€κ²Œ λ©λ‹ˆλ‹€.

그리고 μžμ‹ λ…Έλ“œμ˜ 수(childCnt)와 ν•΄λ‹Ή λ…Έλ“œμ˜ 문자(val)도 μ €μž₯ν•΄μ€λ‹ˆλ‹€.

static final int SIZE = 26; // μ˜μ–΄ μ†Œλ¬ΈμžλŠ” 26자
private class Node {
    Node[] childNode = new Node[SIZE]; // μžμ‹ λ…Έλ“œ: μ˜μ–΄ μ†Œλ¬Έμžλ₯Ό μΈλ±μŠ€ν™” ν•˜μ—¬ μ €μž₯
    boolean endOfWord; // λ‹¨μ–΄μ˜ 끝인지에 λŒ€ν•œ ν”Œλž˜κ·Έ
    int childCnt = 0; // μžμ‹ λ…Έλ“œ 수
    char val; // λ…Έλ“œμ˜ 문자
}

 

트라이(Trie) μžλ£Œκ΅¬μ‘° ν΄λž˜μŠ€ μƒμ„±

public class TrieArray {
    private Node rootNode;
    public TrieArray() {
    	rootNode = new Node();
    }
}

κΈ°λ³Έ λ©”μ„œλ“œ

1) λ…Έλ“œ μΆ”κ°€(insert)

λ¬Έμžμ—΄μ˜ 문자λ₯Ό μˆœνšŒν•˜λ©΄μ„œ ν•΄λ‹Ή λ¬Έμžκ°€ μžμ‹λ…Έλ“œλ‘œ μžˆλŠ”μ§€ ν™•μΈν•˜μ—¬ λ…Έλ“œλ₯Ό μΆ”κ°€ν•˜λŠ” λ©”μ„œλ“œμž…λ‹ˆλ‹€. 

Map으둜 κ΅¬ν˜„ν–ˆμ„λ•Œμ™€λŠ” λ‹€λ₯΄κ²Œ ν˜„ λ…Έλ“œμ˜ μžμ‹ λ…Έλ“œκ°€ null 이 μ•„λ‹Œμ§€, 즉, μƒμ„±λ˜μ–΄μžˆλŠ”μ§€λ₯Ό ν™•μΈν•˜μ—¬, μ—†λ‹€λ©΄ μƒμ„±ν•©λ‹ˆλ‹€. 

μƒμ„±μ‹œμ—λŠ” μžμ‹ λ…Έλ“œλ₯Ό μΆ”κ°€ν•˜κ³ , μžμ‹ λ…Έλ“œμ˜ 문자λ₯Ό ν• λ‹Ήν•΄μ£Όκ³ , ν˜„ λ…Έλ“œμ˜ μžμ‹ λ…Έλ“œ 수λ₯Ό μ¦κ°€μ‹œν‚΅λ‹ˆλ‹€.

public void insert(String word) {
    Node node = this.rootNode;
    // λ‹¨μ–΄μ˜ 문자λ₯Ό μˆœνšŒν•˜λ©΄μ„œ μžμ‹ λ…Έλ“œ 확인
    for (int i=0; i < word.length(); i++) {
        char c = word.charAt(i);
        // λ‹¨μ–΄μ˜ νŠΉμ • 문자λ₯Ό 인덱슀(int)둜 λ³€ν™˜
        int intVal = charToInt(c);
        // ν˜„ λ…Έλ“œμ— 인덱슀둜 λ³€ν™˜ν•œ λ¬Έμžκ°€ μžμ‹λ…Έλ“œλ‘œ μžˆλŠ”μ§€ 확인
        if (node.childNode[intVal] == null) {
            // μžμ‹ λ…Έλ“œμ— μ—†λŠ” 문자라면 μƒˆλ‘œ 생성
            node.childNode[intVal] = new Node();
            node.childNode[intVal].val = c;
            node.childCnt++;
        }
        // λ‹€μŒ 탐색 λ…Έλ“œλ₯Ό μžμ‹λ…Έλ“œλ‘œ λ³€κ²½
        node = node.childNode[intVal];
    }
    node.endOfWord = true; // 단어(word)의 문자λ₯Ό λͺ¨λ‘ μˆœνšŒν•˜λ©΄ λ§ˆμ§€λ§‰ λ…Έλ“œλŠ” 리프 λ…Έλ“œ(=λ§ˆμ§€λ§‰ 문자)μ΄κΈ°λ•Œλ¬Έμ— endOfWordλ₯Ό true둜 μ„€μ •
}

2) 포함 μ—¬λΆ€ 확인(contains)

λ¬Έμžμ—΄μ΄ νŠΈλΌμ΄μ— μžˆλŠ”μ§€ ν™•μΈν•˜λŠ” λ©”μ„œλ“œλ‘œ, λ°°μ—΄ 방식에 맞게 κ΅¬ν˜„ν•΄μ£Όλ©΄ λ©λ‹ˆλ‹€.

public boolean contains(String word) {
    Node node = this.rootNode;
    char c;
    for (int i=0; i<word.length(); i++) {
        c = word.charAt(i);
        int intVal = charToInt(c); // 문자λ₯Ό 인덱슀둜 λ³€ν™˜
        if (node.childNode[intVal] == null) return false; // ν˜„μž¬ νƒμƒ‰ν•˜λŠ” λ¬Έμžκ°€ μžμ‹ λ…Έλ“œμ— μ—†λ‹€λ©΄ μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” 단어
        node = node.childNode[intVal];
    }
    return node != null && node.endOfWord; // λ§ˆμ§€λ§‰ 문자 μ—¬λΆ€λ₯Ό λ°˜ν™˜
}

3) λ…Έλ“œ μ‚­μ œ(delete)

νŠΈλΌμ΄μ— μ‘΄μž¬ν•˜λŠ” λ¬Έμžμ—΄μ„ μ‚­μ œν•˜λŠ” λ©”μ„œλ“œλ‘œ, μ‚­μ œ 쑰건에 맞좰 λ°°μ—΄ 방식에 맞게 κ΅¬ν˜„ν•΄μ£Όλ©΄ λ©λ‹ˆλ‹€.

public void delete(String word) {
    delete(this.rootNode, word, 0);
}
// μž¬κ·€ ν˜ΈμΆœμ„ μœ„ν•œ ν•¨μˆ˜ μ˜€λ²„λ‘œλ”©
public void delete(Node node, String word, int idx) {
    // λ‹¨μ–΄μ˜ λ§ˆμ§€λ§‰μΈ 경우
    if (idx == word.length()) {
        if (!node.endOfWord) throw new Error(word + " not exist"); // 단어가 μ‘΄μž¬ν•˜μ§€ μ•ŠμŒ
        node.endOfWord = false; // λ‹¨μ–΄μ˜ 끝 문자 ν‘œμ‹œλ₯Ό false둜 λ³€κ²½: ν•΄λ‹Ή 문자 λ…Έλ“œλ₯Ό μ‚­μ œν•˜κΈ° μœ„ν•¨
        return; // 리프 λ…Έλ“œμ˜ λΆ€λͺ¨ λ…Έλ“œλ‘œ return
    } else {
        if (node.childCnt == 0) throw new Error(word + " not exist"); // μžμ‹ λ…Έλ“œκ°€ μ—†λŠ” 경우(=단어가 μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” 경우)
    }

    char c = word.charAt(idx); // ν˜„μž¬ ν™•μΈν•΄μ•Όν•˜λŠ” 문자
    int intVal = charToInt(c); // 문자λ₯Ό 인덱슀둜 λ³€ν™˜

    Node child = node.childNode[intVal]; // μžμ‹ λ…Έλ“œ κ°€μ Έμ˜€κΈ°
    // μžμ‹ λ…Έλ“œκ°€ μžˆλ‹€λ©΄ 리프 λ…Έλ“œκΉŒμ§€ λ‚΄λ €κ°€μ•Ό ν•˜λ―€λ‘œ μž¬κ·€ 호좜
    delete(child, word, idx+1);
    // ν˜„ λ…Έλ“œμ˜ μžμ‹ λ…Έλ“œκ°€ μ‚­μ œλ˜μ—ˆλ‹€λ©΄, ν˜„ λ…Έλ“œμ˜ μžμ‹ λ…Έλ“œ 수 1 차감
    if (node.childNode[intVal] == null) node.childCnt--;
    // ν˜„ λ…Έλ“œμ˜ μžμ‹ λ…Έλ“œκ°€ 리프 λ…Έλ“œμ΄κ³ (=μžμ‹ λ…Έλ“œκ°€ μ—†μŒ), λ‹¨μ–΄μ˜ 끝인 λ¬Έμžκ°€ μ•„λ‹ˆλΌλ©΄ μ‚­μ œ
    if (child.childCnt == 0 && !node.endOfWord) node = null;
}

 

πŸ§ͺ κ΅¬ν˜„ ν…ŒμŠ€νŠΈ

κ΅¬ν˜„ ν…ŒμŠ€νŠΈλŠ” Map 방식과 λ™μΌν•˜λ‹ˆ, ν•΄λ‹Ή ν¬μŠ€νŒ…μ„ μ°Έκ³ ν•΄μ£Όμ„Έμš” :)

 

πŸ’‘ μ „체 μ½”λ“œ(Array둜 κ΅¬ν˜„)λŠ” μ•„λž˜ λ§ν¬μ—μ„œ ν™•μΈν•΄μ£Όμ„Έμš”.

 

Algorithm/src/Trie/TrieArray.java at main · hywnj/Algorithm

Contribute to hywnj/Algorithm development by creating an account on GitHub.

github.com

 

2. Map Interface둜 κ΅¬ν˜„

μ•„λž˜ μ½”λ“œλŠ” Map Interface둜 κ΅¬ν˜„ν•œ μ½”λ“œμ΄λ©°, μžμ„Έν•œ μ„€λͺ…은 이전 ν¬μŠ€νŒ…μ„ μ°Έκ³ ν•΄μ£Όμ„Έμš”.

 

[자료ꡬ쑰] 트라이(Trie)

1. 트라이(Trie)λž€?μ›ν•˜λŠ” μš”μ†Œλ₯Ό μ°ΎκΈ°μœ„ν•΄ 자주 μ‚¬μš©λ˜λŠ” 이진 검색 트리 λ“±μ—μ„œλŠ” νƒ€κ²Ÿ μš”μ†Œλ₯Ό μ°ΎλŠ”λ°μ— O(logN)의 μ‹œκ°„μ΄ κ±Έλ¦½λ‹ˆλ‹€.ν•˜μ§€λ§Œ λ¬Έμžμ—΄μ˜ 경우 두 λ¬Έμžμ—΄μ„ λΉ„κ΅ν•˜κΈ° μœ„ν•΄μ„œλŠ” λ¬Έμžμ—΄

coding-vvon.tistory.com

 

 

πŸ’‘ Trieλ₯Ό Map, Array둜 κ΅¬ν˜„ν•œ μ½”λ“œλŠ” μ•„λž˜ λ§ν¬μ—μ„œ ν™•μΈν•΄μ£Όμ„Έμš”.

 

Algorithm/src/Trie at main · hywnj/Algorithm

Contribute to hywnj/Algorithm development by creating an account on GitHub.

github.com

 

Reference

 

[자료ꡬ쑰 | Java] Trie(트라이)

이번 μ‹œκ°„μ—λŠ” 자료ꡬ쑰 νŠΈλΌμ΄μ— λŒ€ν•΄ μ•Œμ•„λ³΄λ„λ‘ ν•˜κ² μŠ΅λ‹ˆλ‹€. 1. 트라이(Trie)λž€? 효율적인 λ¬Έμžμ—΄ μ €μž₯ 및 탐색이 κ°€λŠ₯ν•˜λ„λ‘ λ§Œλ“  자료ꡬ쑰 ν˜•νƒœ 쀑 ν•˜λ‚˜μž…λ‹ˆλ‹€. 코딩을 ν•˜λ‹€ 보면 μˆ˜λ§Žμ€ λ¬Έ

cdragon.tistory.com

1. 트라이(Trie)λž€?

μ›ν•˜λŠ” μš”μ†Œλ₯Ό μ°ΎκΈ°μœ„ν•΄ 자주 μ‚¬μš©λ˜λŠ” 이진 검색 트리 λ“±μ—μ„œλŠ” νƒ€κ²Ÿ μš”μ†Œλ₯Ό μ°ΎλŠ”λ°μ— O(logN)의 μ‹œκ°„μ΄ κ±Έλ¦½λ‹ˆλ‹€.
ν•˜μ§€λ§Œ λ¬Έμžμ—΄μ˜ 경우 두 λ¬Έμžμ—΄μ„ λΉ„κ΅ν•˜κΈ° μœ„ν•΄μ„œλŠ” λ¬Έμžμ—΄μ˜ 길이만큼의 μ‹œκ°„μ΄ 걸리기 λ•Œλ¬Έμ—,
μ›ν•˜λŠ” λ¬Έμžμ—΄μ„ μ°ΎκΈ° μœ„ν•΄μ„œλŠ” O(MlogN)의 μ‹œκ°„μ΄ κ±Έλ¦½λ‹ˆλ‹€.
λ”°λΌμ„œ 이 μž‘μ—…μ„ μ—¬λŸ¬λ²ˆ μˆ˜ν–‰ν•œλ‹€λ©΄ μ‹œκ°„μ΄ 였래 걸리게 λ©λ‹ˆλ‹€. 
μ΄λŸ¬ν•œ λ¬Έμžμ—΄ κ²€μƒ‰μ‹œμ˜ 단점을 ν•΄κ²°ν•˜κΈ° μœ„ν•΄ λ‚˜μ˜¨ μžλ£Œκ΅¬μ‘°κ°€ 트라이(Trie)μž…λ‹ˆλ‹€.

 

  • 트라이(Trie)λŠ” λ¬Έμžμ—΄μ„ μ €μž₯ν•˜κ³  효율적으둜 νƒμƒ‰ν•˜κΈ° μœ„ν•œ 트리 ν˜•νƒœμ˜ 자료ꡬ쑰
  • κ΅¬κΈ€μ΄λ‚˜ 넀이버 등에 검색어λ₯Ό 검색할 λ•Œ λ³Ό 수 μžˆλŠ” μžλ™μ™„μ„± κΈ°λŠ₯, 사전 검색 λ“± λ¬Έμžμ—΄μ„ νƒμƒ‰ν•˜λŠ”λ° νŠΉν™”λ˜μ–΄μžˆλŠ” 자료ꡬ쑰라고 ν•©λ‹ˆλ‹€.
  • λž˜λ”•μŠ€ 트리(Radix Tree)λ‚˜ 접두사 트리(Prefix Tree), 탐색 트리(Retrieval Tree)라고도 ν•©λ‹ˆλ‹€
    • 트라이(Trie)λŠ” Retrieval Treeμ—μ„œ λ‚˜μ˜¨ 단어

예λ₯Ό λ“€μ–΄ 'Tistory'λΌλŠ” 단어λ₯Ό κ²€μƒ‰ν•˜κΈ° μœ„ν•΄ 제일 λ¨Όμ € 'T'λ₯Ό μ°Ύκ³ , λ‹€μŒμ— 'i', 's', 't', ... 의 μˆœλŒ€λ‘œ 찾으면 λ©λ‹ˆλ‹€.

이런 κ°œλ…μ„ μ μš©ν•œ μžλ£Œκ΅¬μ‘°κ°€ 트라이(Trie)μž…λ‹ˆλ‹€.

 

2. 트라이(Trie)의 μž‘λ™ 원리

λ‹€μŒ 그림은 λ¬Έμžμ—΄ μ§‘ν•© {"apple", "app", "dad", "dream", "age"}λ₯Ό 트라이둜 κ΅¬ν˜„ν•œ κ²ƒμž…λ‹ˆλ‹€.

*트라이(Trie)λŠ” 집합에 ν¬ν•¨λœ λ¬Έμžμ—΄μ˜ 접두사듀에 λŒ€μ‘λ˜λŠ” λ…Έλ“œλ“€μ΄ μ„œλ‘œ μ—°κ²°λœ νŠΈλ¦¬μž…λ‹ˆλ‹€.

 

[곡톡 접두어]

"apple"κ³Ό "app"은 "app"λΌλŠ” 곡톡 접두어가 μžˆμœΌλ―€λ‘œ 같은 μ„œλΈŒ νŠΈλ¦¬μ— ν‘œμ‹œλ©λ‹ˆλ‹€.

즉, "a"λΌλŠ” 곡톡 접두어λ₯Ό κ°€μ§„ "apple", "app", "age"λŠ” Head의 μžμ‹ λ…Έλ“œ 'a'에,

"d"λΌλŠ” 곡톡 접두어λ₯Ό κ°€μ§„ "dad", "dream"은 Head의 μžμ‹ λ…Έλ“œ 'd'에 ν‘œμ‹œλ©λ‹ˆλ‹€.

 

[Leaf Node]

리프 λ…Έλ“œλŠ” 각 λ‹¨μ–΄μ˜ λ§ˆμ§€λ§‰ 문자λ₯Ό μ˜λ―Έν•˜λ―€λ‘œ, λ‹¨μ–΄μ˜ 끝 λ¬ΈμžλΌλŠ” ν‘œμ‹œ(endOfWord)λ₯Ό ν•΄μ•Όν•©λ‹ˆλ‹€.

λ˜ν•œ 리프 λ…Έλ“œμ˜ κ°œμˆ˜λŠ” λ‹¨μ–΄μ˜ 개수λ₯Ό μ˜λ―Έν•©λ‹ˆλ‹€.

 

[트라이(Trie) 생성 κ³Όμ •]

1. "apple"λ₯Ό νŠΈλΌμ΄μ— μ‚½μž…

  • 'a': 첫번째 문자인 'a'λŠ” μ΄ˆκΈ°μ— 트라이 자료ꡬ쑰 내에 아무것도 μ—†μœΌλ―€λ‘œ Head의 μžμ‹λ…Έλ“œμ— 'a'λ₯Ό μΆ”κ°€
  • 'p': 'a' λ…Έλ“œμ—λ„ ν˜„μž¬ μžμ‹μ΄ ν•˜λ‚˜λ„ μ—†μœΌλ―€λ‘œ 'a'의 μžμ‹λ…Έλ“œμ— 'p'λ₯Ό μΆ”κ°€
  • 'p', 'l', 'e'에 λŒ€ν•΄μ„œλ„ μžμ‹λ…Έλ“œλ‘œ μΆ”κ°€
  • 'l': "apple" 단어가 끝남을 μ•ŒκΈ° μœ„ν•΄ 'l' λ…Έλ“œμ— λ§ˆμ§€λ§‰ λ…Έλ“œ(endOfWord)μž„μ„ ν‘œμ‹œ

2. "app"λ₯Ό νŠΈλΌμ΄μ— μ‚½μž…

  • 'a': ν˜„μž¬ Head의 μžμ‹ λ…Έλ“œλ‘œ 'a'κ°€ 이미 μ‘΄μž¬ν•˜κΈ° λ•Œλ¬Έμ— 'a' λ…Έλ“œλ₯Ό μΆ”κ°€ν•˜μ§€ μ•Šκ³  기쑴에 μžˆλŠ” 'a'λ…Έλ“œλ‘œ 이동
  • 'p': 'p'도 'a'의 μžμ‹ λ…Έλ“œλ‘œ μžˆμœΌλ―€λ‘œ 'p'둜 이동
  • 'p': 'p'도 'a'의 μžμ‹ λ…Έλ“œλ‘œ μžˆμœΌλ―€λ‘œ 'p'둜 μ΄λ™ν•˜λ©΄μ„œ, 'p'λ…Έλ“œμ— λ§ˆμ§€λ§‰ λ…Έλ“œμž„μ„ ν‘œμ‹œ

3. "dad", "dream",  "age"λ₯Ό νŠΈλΌμ΄μ— μ‚½μž…

  • "dad"와 "dream"의 첫 문자인 'd'λŠ” Head의 μžμ‹ λ…Έλ“œλ‘œ μ—†κΈ° λ•Œλ¬Έμ— μΆ”κ°€ν•΄μ„œ, 각 λ‹¨μ–΄λ³„λ‘œ 문자λ₯Ό 순차적으둜 μΆ”κ°€
  • "age"λŠ” Head의 μžμ‹ λ…Έλ“œλ‘œ 'a'κ°€ 있기 λ•Œλ¬Έμ—,
    • 'a'둜 이동 => 'a'의 μžμ‹ λ…Έλ“œμ— 'g'κ°€ μ—†μœΌλ―€λ‘œ 'g'λ₯Ό μΆ”κ°€ => 'e'도 'g'의 μžμ‹ λ…Έλ“œλ‘œ μΆ”κ°€ν•œ ν›„ λ§ˆμ§€λ§‰ λ…Έλ“œ ν‘œμ‹œ

 

3. 트라이(Trie) κ΅¬ν˜„(Java)

그럼 트라이λ₯Ό Java둜 κ΅¬ν˜„ν•˜λ €λ©΄ μ–΄λ–»κ²Œ ν•΄μ•Όν• κΉŒ?

κΈ°λ³Έμ μœΌλ‘œλŠ” Map Interfaceλ₯Ό μ‚¬μš©ν•΄μ„œ κ΅¬ν˜„ν•©λ‹ˆλ‹€.

Map의 Keyκ°’μ—λŠ” 단어λ₯Ό μ΄λ£¨λŠ” 각각의 λ¬Έμžλ“€μ΄ λ“€μ–΄κ°€κ³ , Valueμ—λŠ” μžμ‹ λ…Έλ“œ 클래슀λ₯Ό μ €μž₯ν•©λ‹ˆλ‹€.

 

이제 순차적으둜 μžλ°”λ‘œ κ΅¬ν˜„μ„ ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

Node 클래슀 생성

Node ν΄λž˜μŠ€λŠ” ν•΄λ‹Ή λ…Έλ“œμ˜ μžμ‹ λ…Έλ“œμ •λ³΄μ™€ λ‹¨μ–΄μ˜ 끝인지λ₯Ό νŒλ‹¨ν•˜λŠ” λ³€μˆ˜λ‘œ κ΅¬μ„±ν•©λ‹ˆλ‹€.

private class Node {
    HashMap<Character, Node> childNode = new HashMap<>(); // μžμ‹ λ…Έλ“œ
    boolean endOfWord; // λ‹¨μ–΄μ˜ 끝인지에 λŒ€ν•œ ν”Œλž˜κ·Έ
}

 

트라이(Trie) 자료ꡬ쑰 클래슀 생성

기본적으둜 루트 λ…Έλ“œλ₯Ό μ΄ˆκΈ°ν™”ν•΄ μ€λ‹ˆλ‹€.

*루트 λ…Έλ“œλŠ” 1) 항상 λΉ„μ–΄μžˆκ³  2) λ£¨νŠΈλ…Έλ“œμ˜ μžμ‹ λ…Έλ“œλŠ” 각 λ‹¨μ–΄μ˜ 첫 κΈ€μžμž…λ‹ˆλ‹€.

public class Trie {
    private Node rootNode;
    Trie() {
        // Trie μƒμ„±μ‹œ 루트 λ…Έλ“œλŠ” 기본으둜 생성
        rootNode = new Node(); // 루트 λ…Έλ“œλŠ” 빈 λ…Έλ“œ
    }
}

 

κΈ°λ³Έ λ©”μ„œλ“œ

1) λ…Έλ“œ μΆ”κ°€(insert)

μž…λ ₯ 받은 단어λ₯Ό 문자 μˆœλŒ€λ‘œ 트리 계측 ꡬ쑰의 μžμ‹ λ…Έλ“œλ‘œ μƒμ„±ν•΄μ„œ λ„£μŠ΅λ‹ˆλ‹€.

  • λ¬Έμžκ°€ μžμ‹ λ…Έλ“œμ— μ‘΄μž¬ν•˜λ©΄ μƒˆλ‘œ μƒμ„±ν•˜μ§€ μ•Šκ³  μžμ‹ λ…Έλ“œλ‘œ 이동
  • λ‹¨μ–΄μ˜ λ§ˆμ§€λ§‰ 문자인 λ…Έλ“œλΌλ©΄ endOfWord = true둜 μ„€μ •
public void insert(String word) {
    Node node = this.rootNode;
    // λ‹¨μ–΄μ˜ 문자λ₯Ό μˆœνšŒν•˜λ©΄μ„œ μžμ‹ λ…Έλ“œλ₯Ό 확인
    //  1) μžμ‹ λ…Έλ“œμ— μžˆλŠ” 문자라면 μžμ‹ λ…Έλ“œλ₯Ό κ°€μ Έμ˜΄
    //  2) μžμ‹ λ…Έλ“œμ— μ—†λŠ” 문자라면 μƒˆλ‘­κ²Œ 생성
    for (int i=0; i<word.length(); i++) node = node.childNode.computeIfAbsent(word.charAt(i), key -> new Node());
    node.endOfWord = true; // 단어(word)의 문자λ₯Ό λͺ¨λ‘ μˆœνšŒν•˜λ©΄ λ§ˆμ§€λ§‰ λ…Έλ“œλŠ” 리프 λ…Έλ“œ(=λ§ˆμ§€λ§‰ 문자)μ΄κΈ°λ•Œλ¬Έμ— endOfWordλ₯Ό true둜 μ„€μ •
}

 

2) 포함 μ—¬λΆ€ 확인(contains)

νŠΉμ • 단어가 νŠΈλΌμ΄μ— μ‘΄μž¬ν•˜λŠ”μ§€ ν™•μΈν•©λ‹ˆλ‹€.

  • 루트 λ…Έλ“œλΆ€ν„° λ‹¨μ–΄μ˜ 문자 μˆœλŒ€λ‘œ μΌμΉ˜ν•˜λŠ” μžμ‹ λ…Έλ“œκ°€ 계측을 이루고 μžˆλŠ”μ§€ 확인
  • λ‹¨μ–΄μ˜ λ§ˆμ§€λ§‰ λ¬Έμžμ— endOfWordκ°€ true

두 쑰건을 λ§Œμ‘±ν•˜λŠ” κ²½μš°κ°€ νŠΈλΌμ΄μ— μ‘΄μž¬ν•˜λŠ” κ²½μš°μž…λ‹ˆλ‹€.

public boolean contains(String word) {
    Node node = this.rootNode;
    char c;
    for (int i=0; i<word.length(); i++) {
        c = word.charAt(i);
        if (!node.childNode.containsKey(c)) return false; // ν˜„μž¬ νƒμƒ‰ν•˜λŠ” λ¬Έμžκ°€ μžμ‹ λ…Έλ“œμ— μ—†λ‹€λ©΄ μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” 단어
        node = node.childNode.get(c); // λ§ˆμ§€λ§‰ 문자λ₯Ό node에 μ €μž₯
    }
    return node.endOfWord; // λ§ˆμ§€λ§‰ 문자 μ—¬λΆ€λ₯Ό λ°˜ν™˜
}

 

3) λ…Έλ“œ μ‚­μ œ(delete)

νŠΈλΌμ΄μ— μΆ”κ°€ν–ˆλ˜ 단어λ₯Ό μ‚­μ œν•©λ‹ˆλ‹€.

  • λΆ€λͺ¨ λ…Έλ“œμ— λŒ€ν•œ μ •λ³΄λŠ” μ—†κΈ° λ•Œλ¬Έμ— μ‚­μ œν•  λ‹¨μ–΄μ˜ λ§ˆμ§€λ§‰ λ¬ΈμžκΉŒμ§€ μ΄λ™ν•΄μ„œ 문자λ₯Ό μ‚­μ œν•΄μ™€μ•Ό ν•©λ‹ˆλ‹€. => μž¬κ·€
  • λ‹€μŒ λ¬Έμžμ— μ‘΄μž¬ν•˜λŠ” μžμ‹λ…Έλ“œκ°€ 있고, μ‚­μ œλ₯Ό μ‹œμž‘ν•˜λŠ” λ§ˆμ§€λ§‰ λ…Έλ“œ(리프 λ…Έλ“œ)μ—λŠ” endOfWordκ°€ true둜 λ˜μ–΄μžˆμ–΄μ•Ό ν•©λ‹ˆλ‹€. 
    • 즉, μ‚­μ œν•  단어가 νŠΈλΌμ΄μ— ν¬ν•¨λ˜μ–΄μžˆμ–΄μ•Ό ν•©λ‹ˆλ‹€.
  • 단어 μ‚­μ œλ₯Ό μœ„ν•΄ λ§ˆμ§€λ§‰ 문자 λ…Έλ“œμΈ 리프 λ…Έλ“œμ˜ endOfWordλ₯Ό false둜 λ³€κ²½ν•©λ‹ˆλ‹€. 이후 순차적으둜 거꾸둜 μ˜¬λΌκ°€λ©΄μ„œ λ…Έλ“œλ₯Ό μ‚­μ œν•©λ‹ˆλ‹€. 단, μ•„λž˜ 두 쑰건이 μ•„λ‹Œ κ²½μš°μ—λ§Œ μ‚­μ œν•©λ‹ˆλ‹€.
    • ν•΄λ‹Ή λ…Έλ“œμ˜ μžμ‹λ…Έλ“œκ°€ μžˆλŠ” 경우
      • μžμ‹λ…Έλ“œκ°€ μžˆλ‹€λŠ” λœ»μ€ λ‹€λ₯Έ 단어에 μ‚¬μš©λ˜λŠ” λ¬ΈμžλΌλŠ” λœ»μ΄λ―€λ‘œ μ‚­μ œν•˜λ©΄ μ•ˆλ©λ‹ˆλ‹€.
    • endOfWordκ°€ true인 경우
      • λ‹¨μ–΄μ˜ 끝인 문자인 경우 λ‹€λ₯Έ 단어에 μ‚¬μš©λ˜λŠ” λ¬ΈμžλΌλŠ” λœ»μ΄λ―€λ‘œ μ‚­μ œν•˜λ©΄ μ•ˆλ©λ‹ˆλ‹€.
public void delete(String word) {
    delete(this.rootNode, word, 0);
}
// μž¬κ·€ ν˜ΈμΆœμ„ μœ„ν•œ ν•¨μˆ˜ μ˜€λ²„λ‘œλ”©
private void delete(Node node, String word, int idx) {
    // λ‹¨μ–΄μ˜ λ§ˆμ§€λ§‰ 인덱슀인 경우
    if (idx == word.length()) {
        if (!node.endOfWord) throw new Error(word + " not exist"); // 단어가 μ‘΄μž¬ν•˜μ§€ μ•ŠμŒ
        node.endOfWord = false; // λ‹¨μ–΄μ˜ 끝 문자 ν‘œμ‹œλ₯Ό false둜 λ³€κ²½: ν•΄λ‹Ή 문자 λ…Έλ“œλ₯Ό μ‚­μ œν•˜κΈ° μœ„ν•¨
        return; // 리프 λ…Έλ“œμ˜ λΆ€λͺ¨ λ…Έλ“œλ‘œ return
    }
    char c = word.charAt(idx); // ν˜„μž¬ ν™•μΈν•΄μ•Όν•˜λŠ” 문자
    if (!node.childNode.containsKey(c)) throw new Error(word + " not exist"); // μžμ‹ λ…Έλ“œκ°€ μ—†λŠ” 경우(=단어가 μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” 경우)
    Node child = node.childNode.get(c); // μžμ‹ λ…Έλ“œ κ°€μ Έμ˜€κΈ°
    // μžμ‹ λ…Έλ“œκ°€ μžˆλ‹€λ©΄ 리프 λ…Έλ“œκΉŒμ§€ λ‚΄λ €κ°€μ•Ό ν•˜λ―€λ‘œ μž¬κ·€ 호좜
    delete(child, word, idx+1);
    // ν˜„ λ…Έλ“œμ˜ μžμ‹ λ…Έλ“œκ°€ 리프 λ…Έλ“œμ΄κ³ (=μžμ‹ λ…Έλ“œκ°€ μ—†μŒ), λ‹¨μ–΄μ˜ 끝인 λ¬Έμžκ°€ μ•„λ‹ˆλΌλ©΄ μ‚­μ œ
    if (child.childNode.isEmpty() && !child.endOfWord) node.childNode.remove(c, child);
}

 

πŸ§ͺ κ΅¬ν˜„ ν…ŒμŠ€νŠΈ

public static void main(String[] args) {
        Trie trie = new Trie();
        // insert
        String[] arr = {"apple", "app", "dream", "dad", "age"};
        for (String word: arr) {
            trie.insert(word);
        }
        // delete
        trie.delete("app");
        trie.delete("dream");
        // contains
        System.out.println(trie.contains("apple")); // true
        System.out.println(trie.contains("app")); // μ‚­μ œν–ˆμœΌλ―€λ‘œ false
        System.out.println(trie.contains("age")); // true
        System.out.println(trie.contains("dream")); // μ‚­μ œν–ˆμœΌλ―€λ‘œ false
        System.out.println(trie.contains("dad")); // true
}

 

μœ„μ—μ„œ μ˜ˆμ‹œλ‘œ λ“€μ—ˆλ˜ λ¬Έμžμ—΄ 배열을 κΈ°μ€€μœΌλ‘œ 트라이λ₯Ό μƒμ„±ν•˜κ³  μ‚­μ œ, 포함여뢀λ₯Ό ν™•μΈν•˜λ©΄ μ•„λž˜μ™€ 같은 κ²°κ³Όλ₯Ό 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

 

πŸ’‘ 전체 μ½”λ“œλŠ” μ•„λž˜ λ§ν¬μ—μ„œ ν™•μΈν•΄μ£Όμ„Έμš”.

 

Algorithm/src/Trie.java at main · hywnj/Algorithm

Contribute to hywnj/Algorithm development by creating an account on GitHub.

github.com

 

4. 트라이(Trie) μ‹œκ°„ λ³΅μž‘λ„

총 문자의 개수λ₯Ό N, κ°€μž₯ κΈ΄ λ¬Έμžμ—΄μ˜ 길이가 L이라고 ν• λ•Œ

  • 생성 μ‹œκ°„ λ³΅μž‘λ„: O(N*L)
    • λͺ¨λ“  λ¬Έμžμ—΄ N개λ₯Ό λ„£μ–΄μ•Όν•˜κ³ , Nκ°œμ— λŒ€ν•΄ νŠΈλΌμ΄μ— λ„£λŠ” 건 κ°€μž₯ κΈ΄ λ¬Έμžμ—΄ 길이인 L만큼 κ±Έλ¦°λ‹€.
    • μ‚½μž…μ€ O(L)만 μ†Œμš”
  • 탐색 μ‹œκ°„ λ³΅μž‘λ„: O(L)
    • 트리λ₯Ό κ°€μž₯ 깊게 νƒμƒ‰ν•˜λŠ” κ²½μš°λŠ” κ°€μž₯ κΈ΄ λ¬Έμžμ—΄ 길이인 LκΉŒμ§€ λ“€μ–΄κ°€λŠ” κ²ƒμ΄λ―€λ‘œ O(L)

 

5. 트라이(Trie) μž₯단점

  • λ¬Έμžμ—΄μ— λŒ€ν•œ 검색을 λΉ λ₯΄κ²Œ ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
    • λ¬Έμžμ—΄μ„ 집합에 μΆ”κ°€ν•˜λŠ” κ²½μš°μ—λ„ λ¬Έμžμ—΄μ˜ 길이만큼 λ…Έλ“œλ₯Ό λ”°λΌκ°€κ±°λ‚˜ μΆ”κ°€ν•˜λ©΄ 되기 λ•Œλ¬Έμ— λ¬Έμžμ—΄μ˜ 좔가와 탐색 λͺ¨λ‘ O(M)λ§Œμ— κ°€λŠ₯ν•©λ‹ˆλ‹€.
  • ν•„μš”λ‘œ ν•˜λŠ” λ©”λͺ¨λ¦¬μ˜ 크기가 λ„ˆλ¬΄ ν¬λ‹€λŠ” 단점이 μžˆμŠ΅λ‹ˆλ‹€.
    • 각 λ…Έλ“œμ—μ„œ μžμ‹λ“€μ— λŒ€ν•œ 포인터듀을 λ°°μ—΄λ‘œ λͺ¨λ‘ μ €μž₯ν•˜κ³  μžˆκΈ°μ— μ €μž₯κ³΅κ°„μ˜ 크기가 ν¬λ‹€λŠ” 단점이 μžˆμŠ΅λ‹ˆλ‹€.
    • λ¬Έμžμ—΄μ΄ λͺ¨λ‘ μ˜μ†Œλ¬Έμžλ‘œ 이루어져 μžˆμ–΄λ„, μžμ‹ λ…Έλ“œλ₯Ό κ°€λ¦¬ν‚€λŠ” 26개의 포인터λ₯Ό μ €μ •ν•΄μ•Όν•˜λŠ”λ°
    • μ΅œμ•…μ˜ κ²½μš°μ—λŠ” 집합에 ν¬ν•¨λ˜λŠ” λ¬Έμžμ—΄μ˜ 길이의 총 ν•©λ§ŒνΌμ˜ λ…Έλ“œκ°€ ν•„μš”ν•˜κΈ° λ•Œλ¬Έμ— 총 λ©”λͺ¨λ¦¬λŠ” O(포인터 크기*포인터 λ°°μ—΄ 개수*총 λ…Έλ“œ 개수)κ°€ λ©λ‹ˆλ‹€.
      • λ§Œμ•½ 1,000자리의 λ¬Έμžμ—΄μ΄ 1,000λ§Œκ°œκ°€ μžˆλ‹€λ©΄ μ΄λ•Œ, 100만개의 λ…Έλ“œκ°€ ν•„μš”ν•˜κ³  ν¬μΈν„°μ˜ 크기가 8Byte라 ν•˜λ©΄ μ•½ 200MB의 λ©”λͺ¨λ¦¬κ°€ ν•„μš”ν•©λ‹ˆλ‹€.

트라이(Trie) 단점 해결을 μœ„ν•œ 방법

'λ©”λͺ¨λ¦¬μ˜ 크기가 λ„ˆλ¬΄ 큰' 단점을 ν•΄κ²°ν•˜κΈ° μœ„ν•΄, 보톡 mapμ΄λ‚˜ vectorλ₯Ό μ΄μš©ν•΄μ„œ ν•„μš”ν•œ λ…Έλ“œλ§Œ λ©”λͺ¨λ¦¬λ₯Ό ν• λ‹Ήν•˜λŠ” 방식을 μ‚¬μš©ν•©λ‹ˆλ‹€. κ·ΈλŸ¬λ‚˜ μ΄λŠ” λ©”λͺ¨λ¦¬ μ œν•œμ΄ νƒ€μ΄νŠΈν•œ κ²½μš°μ—λŠ” μ΅œμ ν™”κ°€ κΉŒλ‹€λ‘­μŠ΅λ‹ˆλ‹€.

λ˜ν•œ λ¬Έμ œμ—μ„œ μ£Όμ–΄μ§„ 쑰건을 보고 트라이λ₯Ό μ΄μš©ν•  수 μžˆλŠ” λ¬Έμ œμΈμ§€ νŒŒμ•…ν•˜λŠ” 것도 μ€‘μš”ν•˜λ‹€κ³  ν•©λ‹ˆλ‹€.

 

6. 트라이(Trie) μ½”λ”©ν…ŒμŠ€νŠΈ 문제

 

μ°Έκ³ ν•œ 링크

+ Recent posts