문자열의 집합이 주어졌을때, 여기서 원하는 문자열을 탐색하려면
브루트포스방법으로 생각하면 문자열 갯수N, 문자열길이가 M이면 최대 O(N*M) 시간이 걸린다.(문자열길이는 제각각 다르겠지만..)
이것을 우리가 배운 이진트리를 이용하여 이진탐색을 한다고하면
한문자를 검색하는데 이진검색은 O(log N)가 걸리지만, 문자열의 길이가 M으로 늘어난이상 최대 O(M log N)의 시간이 걸릴것이 분명하다.
이를 문자의 트리형태인 트라이 구조로 나타내면 O(M) 시간만에 해결이 가능하다.
root에서 모든 문자열의 0번째인덱스부터 하나의 문자를 담는 노드를 만들어 연결한다. 이때 노드는 실제값이아닌 인덱스만으로도 구현이 가능하다.(뒤에서 설명하겠다.)
그다음에는 트리구조처럼 그다음 인덱스의 문자를 이전 인덱스문자 아래의 자식 노드로 삽입시키면된다.
끝으로 문자열의 끝부분에는 별도의 체크를해두어
탐색하고자하는 문자가 존재하는지 파악하는데 사용하면된다.
트라이 구조체를 선언한다. 우리는 구조체로 구현할것이다.
여기서 Trie포인터형 node가 26개 존재하는데, 이것이 위에서 설명한
노드의 값이아닌 인덱스만으로 탐색에 사용한다는 의미다.
이미 고정형으로 26개의 공간이 마련되있는데,
해당 공간에 nullptr 형이면 그에 해당하는 문자(0~25 == A~Z)
가 존재하지않음 나타내는것이다.
여기에 finish변수를 두어, 문자열의 끝부분에 체크를 해줄것이다. (정확히 말하면 문자열의 끝 + 1위치의 노드에 체크하게된다. 자세한건 1번과정)
insert 함수 : 문자열을 트라이자료구조로 저장하는 함수
전체 코드
struct Trie {
Trie* node[26]; //노드를 나타내는 포인터
bool finish; //문자열의 끝체크 : K번째 노드에 true체크되있으면 문자열의 길이는 K-1임.
//0. 생성자 : 초기화
Trie() : node(), finish(false) {}
//1. 문자열을 트라이 자료구조로 만드는 함수
void insert(char* str) {
//1-1. 문자열의길이가 K면 K+1번쨰 노드에 finish = true체크하는겁니다
if (*str == '\0') {
finish = true;
return;
}
//1-2. 트라이구조에 다음노드가 업으면 생성
int cur = *str - 'A';
if (node[cur] == nullptr)
node[cur] = new Trie();
//1-3. 현재 노드와 다음 노드를 이어감. (이미 만들어져있었으면 이미 이어져있었을것)
node[cur]->insert(str + 1);
}
//2. 일치하는 문자열이 존재하는지 확인하는 함수
bool find(char* str) {
//2-1. 문자열의끝 + 1에 도달했지만 종료체크가 안되있으면 문자열 탐색 실패
if (*str == '\0' && !finish) return false;
//2-2. 문자열의끝 + 1에 도달했지만 종료체크가 되있으면 문자열 탐색에 성공
if (*str == '\0' && finish) return true;
//2-3. 트라이자료구조에 다음 문자가 존재하지 않으면 문자열 탐색 실패
int cur = *str - 'A';
if (node[cur] == nullptr) return false;
return node[cur]->find(str + 1); //트라이자료구조에 다음 문자가 존재하면 계속 탐색진행
}
//3. 접두사로만 문자열이 존재하는지 확인하는 함수
//문자열을 포함한 다른 문자열이 존재하는 여부를 리턴함
bool findPrefix(char* str) {
//3-1. 문자열 끝 + 1까지 탐색하는중 종료체크된 노드를 찾으면 접두사가존재, 탐색 성공
if (finish) return true; //str[0]~이전문자 까지로 구성된 접두사가 존재할거에옹
//3-2. 문자열끝 + 1에 도달했으면 탐색 실패
if (*str == '\0') return false;
////////////////////////////////////////////////////////////////////////////////////////////////
//만약 ' if(finish) return true '를 이 위치로 바꾸면 완벽히일치하는 문자열은 결과에서 제외가능//
////////////////////////////////////////////////////////////////////////////////////////////////
//3-3. 트라이구조를에 다음 문자가 없으면 탐색 실패
int cur = *str - 'A';
if (node[cur] == nullptr) return false;
//3-4. 트라이구조의 다음문자를 따라서 계속 탐색 진행
return node[cur]->findPrefix(str + 1);
}
//4. DFS in Trie
void DFS(int depth) {
for (int i = 0; i < 26; i++) {
if (node[i] == nullptr) continue;
////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////DFS탐색하며 쓸 코드 작성//////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
node[i]->DFS(depth + 1);
}
}
//5. BFS in Trie
//???
};
이번에는 조금더 범용성있는 map객체를 활용한 구현을 살펴보겠다.
0. 구조체 선언
map객체의 키값이 우리가 찾고자하는 문자에 해당한다. node[key] == nullptr이면 key가 존재하지않는것이고 그렇지않다면 value인 pointer로 다음 Trie객체를 따라 내려 갈 수 있다!. 이것으로 트리구조를 간단히 구현가능하다.
findPrefix함수 : 재귀적인 방법으로, 찾는 문자열의 일부분이라도 Trie구조에 저장되있는지 확인하는 함수다. 정확히 일치하는경우는 false이다.
이에대한 설명은 충분히 앞서 진행했다.
DFS전용 함수 : 재귀적으로 Trie구조의 모습을 탐색할 수 있도록 코드를 짜보았다.
키값을 검색할때 모든 아스키코드값을 검색하도록 0~127값을 검색하도록 했다.
이때 호출할때마다 고유의 특정값을 전달하고 싶으면 인자인 depth를 수정하거나 또 다른 인자를 추가하여
이후 line70의 재귀호출부분에서 다르게 호출하면 된다.
여튼 line70의 코드 또한 앞서 본 3가지 함수처럼 재귀적으로 포인터를 이용해 DFS함수를 호출 하는 모습이다.
아래는 전체 코드이다.
struct Trie {
std::map<char, Trie*> node;
bool finish;
Trie() : node(), finish(false) {}
//string을 Trie자료구조로 편입
void insert(char* str) {
if (*str == '\0') {
finish = true;
return;
}
if (node[*str] == nullptr)
node[*str] = new Trie();
node[*str]->insert(str + 1);
}
//완벽히 일치하는 문자열이 있는가
bool find(char* str) {
if (*str == '\0' && finish)
return true;
if (*str == '\0' && !finish)
return false;
if (node[*str] == nullptr)
return false;
return node[*str]->find(str + 1);
}
//접두사로만 일치하는 문자열이 있는가
bool findPrefix(char* str) {
if (*str == '\0')
return false;
if (finish)
return true;
if (node[*str] == nullptr)
return false;
return node[*str]->findPrefix(str + 1);
}
//4. DFS in Trie
void DFS(int depth) {
for (int c = 0; c < 128; c++) {
if (node[c] == nullptr) continue;
////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////DFS탐색하며 쓸 코드 작성//////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
node[c]->DFS(depth + 1);
}
}
//5. BFS in Trie
//???
};
https://www.acmicpc.net/problem/5052 >> https://velog.io/@cldhfleks2/5052
https://www.acmicpc.net/problem/14725 >> https://velog.io/@cldhfleks2/14725
https://www.acmicpc.net/problem/16934 >> https://velog.io/@cldhfleks2/16934