datastructure

트라이

트라이란?

자동 완성 기능을 구현할 수 있는 최고의 자료구조

  • retrieval(검색) 에서 유래되었다.
  • 트라이는 저장하려는 단어를 한 글자씩 나눠서 해시테이블을 사용해 Key에는 글자를 저장하고 Value에는 자식 노드를 저장한다.
  • 해시테이블의 성능은 O(1) 이지만, 글자 수에 따라 성능이 결정되므로 O(k)이다.
  • 마지막 글자를 나타내기 위해 Key에는 애스터리스크(*), Value에는 0을 넣어준다.

구현 - 자동완성까지

class Node{
	constructor() {
		this.children = {};
	}
}

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

	insert(word) {
		let currentNode = this.root;
		for(let char of word) { // 김 -> 치 -> 찌 -> 개
			// 이미 단어가 존재하는 경우
			if(currentNode.children[char] != null) {
				currentNode = currentNode.children[char];
			// 단어가 존재하지 않는 경우
			} else {
				let newNode = new Node();
				currentNode.children[char] = newNode;
				currentNode = newNode;
			}
		}
		// 마지막 글자라는 것을 표시
		currentNode.children["*"] = 0;
	}

	search(word) {
		let currentNode = this.root;
		for(let char of word) { 
			// 검색하려는 단어가 존재하는 경우
			if(currentNode.children[char] != null) {
				currentNode = currentNode.children[char];
			// 검색하려는 단어가 없는 경우
			} else {
				return null;
			}
		}
		return currentNode;
	}

	// 자동 완성 기능을 위해 필요한 함수
	getAllWords(startNode = null, word = "", words  []) {
		let currentNode = this.root;
		if(startNode != null) {
			currentNode = startNode;
		}

		for(let key in currentNode.children) {
			// 단어의 끝인 경우
			if(key == "*") {
				words.push(word);
			} else {
				let childNode = currentNode.children[key];
				this.getAllWords(childNode, word + key, words);
			}
		}

		return words;
	}

	autoComplete(word) {
		let currentNode = this.search(word);
		if(currentNode == null) {
			return null;
		}

		return this.getAllWords(currentNode, word);
	}
}

구현 - 검색 빈도가 높은 순으로 자동완성

import { Heap } from ""
class Node{
	constructor() {
		this.children = {};
	}
}

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

	insert(word) {
		let currentNode = this.root;
		for(let char of word) { // 김 -> 치 -> 찌 -> 개
			// 이미 단어가 존재하는 경우
			if(currentNode.children[char] != null) {
				currentNode = currentNode.children[char];
			// 단어가 존재하지 않는 경우
			} else {
				let newNode = new Node();
				currentNode.children[char] = newNode;
				currentNode = newNode;
			}
		}
		// 마지막 글자라는 것을 표시
		currentNode.children["*"] = 0;
	}

	search(word, isCounting = false) {
		let currentNode = this.root;
		for(let char of word) { 
			// 검색하려는 단어가 존재하는 경우
			if(currentNode.children[char] != null) {
				currentNode = currentNode.children[char];
			// 검색하려는 단어가 없는 경우
			} else {
				return null;
			}
		}
		if(isCounting == true) {
			currentNode.children["*"]++; // 마지막 글자의 에스터리스크 값이 0에서 증가됨.
		}
		
		return currentNode;
	}

	// 자동 완성 기능을 위해 필요한 함수
	getAllWords(startNode = null, word = "", words  []) {
		let currentNode = this.root;
		if(startNode != null) {
			currentNode = startNode;
		}

		for(let key in currentNode.children) {
			let childNode = currentNode.children[key];
			// 단어의 끝인 경우
			if(key == "*") {
				words.push(new WordData(word, childNode));
			} else {
				this.getAllWords(childNode, word + key, words);
			}
		}

		return words;
	}

	autoComplete(word) {
		let currentNode = this.search(word);
		if(currentNode == null) {
			return null;
		}

		return this.getAllWords(currentNode, word);
	}

	autoCompleteByCount(word) {
		let words = this.autoComplete(word);

		let heap = new Heap();
		// 힙을 최대 힙으로 바꿔줌
		heap.isBigPriority = function(first, second) {
			return (first.priority > second.priority);
		}

		for(let word of words) {
			heap.insert(word);
		}

		// 우선순위 순서대로 정렬해서 데이터 반환 (dequeue 하면서 정렬되니까.)
		let sortedBySearchCount = [];
		do {
			let removed = heap.remove();
			if(removed = null){
				break;
			} else {
				srotedBySearchCount.push(removed);
			}
		}while(true)

		return sortedBySearchCount;
	}
}

class WordData{
	constructor(word, count) {
		this.word = word;
		this.count = count;
		this.priority = count; // 검색된 횟수가 우선순위
	}
}