문제 풀이

 

간단한 수학 문제다.

첫 번째 링을 한 바퀴 돌리면 그 링은 몇 바퀴 도는지 기약 분수 형태로 출력한다.

기약 분수 형태로 출력하고자 한다면 유클리드 호제법을 떠올리면 된다.

어떤 분수를 기약 분수 형태로 만들고 싶을 때 최대 공약수로 나누면 기약 분수 형태를 만들 수 있기 때문이다.

 

소스 코드
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int gcd(int a, int b)
{
	if (b == 0) return a;
	else return gcd(b, a % b);
}

int main()
{
	ios_base::sync_with_stdio(0);
	cin.tie(0);

	int n;
	cin >> n;
	vector<int> v(n);
	for (int i = 0; i < n; i++) cin >> v[i];
	for (int i = 1; i < n; i++)
	{
		int g = gcd(v[0], v[i]);
		int A = v[0] / g;
		int B = v[i] / g;
		cout << A << '/' << B << '\n';
	}
	return 0;
}

'Algorithm' 카테고리의 다른 글

[백준 6593] 상범 빌딩  (0) 2021.11.11
[백준 2573] 빙산  (0) 2021.11.10
[백준 1037] 약수  (0) 2021.11.09
[백준 2665] 미로만들기  (0) 2021.11.08
[백준 14221] 편의점  (0) 2021.11.04
문제 풀이

 

재밌는 문제다.

1과 N을 제외한 진짜 약수가 모두 주어졌을 때, N을 구하는 문제.

 

12의 약수를 생각해보자.

{1, 2, 3, 4, 6, 12}

문제에서 1과 N을 제외한 진짜 약수가 주어진다고 하면 아마 다음과 같을 것이다.

{2, 3, 4, 6}

위의 수열은 섞여있을 수도 있다. 그렇지만 진짜 약수들만 주어지므로 요소는 똑같다.

어떻게 N을 찾을 수 있을까?

 

간단하다.

가장 작은 수랑 가장 큰 수를 곱하면 그것이 즉 N이다.

2 x 6 = 12 (우리가 구하려고 하는 N이다)

 

따라서 수열이 주어지면 가장 작은 수랑 큰 수를 곱하면 답이다.

 

소스 코드

 

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main()
{
	ios_base::sync_with_stdio(0);
	cin.tie(0);

	int n;
	cin >> n;
	vector<int> v(n);
	for (int i = 0; i < n; i++) cin >> v[i];
	int a = *min_element(v.begin(), v.end());
	int b = *max_element(v.begin(), v.end());
	cout << a * b << endl;
	return 0;
}

'Algorithm' 카테고리의 다른 글

[백준 2573] 빙산  (0) 2021.11.10
[백준 3036] 링  (0) 2021.11.09
[백준 2665] 미로만들기  (0) 2021.11.08
[백준 14221] 편의점  (0) 2021.11.04
[백준 18352] 특정 거리의 도시 찾기  (0) 2021.11.04
문제 풀이

 

(1, 1) 시작점에서 (n, n) 도착점에 도착하기 위해서 몇 번의 검은 방을 부셔야 하는지 최소의 횟수를 구하는 문제다.

 

단순히 BFS 알고리즘을 돌리기에는 에러가 있다.

(3, 3) 의 좌표를 보자.

단순히 BFS를 돌리게 되면 (3,3)은 이전의 (2,3)이나 (3,2)로부터 부신 횟수에 + 1을 하여서 2가 될 수 있지만,

그림을 보면 (3,3)도 1번만 부셔서 갈 수 있는 곳이 된다.

 

기존에 방문을 했더라도 더 빠른 경로가 있다면 그 경로로 값을 바꿔주거나 갱신을 시켜줘야 한다는 것이다.

만약 내 앞의 방이 흰방이면, 방문을 했더라도 더 빠른 경로라면 더 빠른 경로로 갱신하고 다시 살펴본다.

만약 내 앞의 방이 검은방이면, 방문을 했더라도 더 빠른 경로라면 더 빠른 경로로 갱신하고 다시 살펴본다.

이미 방문을 했더라도 더 빠른 경로로 계속 갱신하면서 출구를 찾아나간다.

 

단순한 BFS의 프레임에서 벗어난 새로운 방법의 문제였다.

소스 코드

 

#include <iostream>
#include <queue>
#include <string>
#include <algorithm>
using namespace std;

const int MAX = 51;
const int INF = 987654321;
const int dx[] = { -1,1,0,0 };
const int dy[] = { 0,0,-1,1 };

int n;
int board[MAX][MAX];
int visit[MAX][MAX]; // visit[a][b] = c, (a,b) 까지 오는데 검은방을 흰방으로 바꾼 갯수가 c개

// 핵심은 이미 방문한 좌표라도 더 적은 경로가 있다면 재방문하는 것
void bfs()
{
	fill(visit[0], visit[0] + MAX * MAX, INF);
	queue<pair<int, int> > q;
	q.push({ 0,0 });
	visit[0][0] = 0;
	while (!q.empty())
	{
		int x = q.front().first;
		int y = q.front().second;
		q.pop();

		for (int i = 0; i < 4; i++)
		{
			int nx = x + dx[i];
			int ny = y + dy[i];
			if (nx < 0 || ny < 0 || nx >= n || ny >= n) continue;
			// 흰방
			if (board[nx][ny] == 1)
			{
				if (visit[nx][ny] > visit[x][y])
				{
					visit[nx][ny] = visit[x][y];
					q.push({ nx,ny });
				}
			}
			// 검은방이면 부수고 지나갈때
			else
			{
				if (visit[nx][ny] > visit[x][y] + 1)
				{
					visit[nx][ny] = visit[x][y] + 1;
					q.push({ nx,ny });
				}
			}
		}
	}
}

int main()
{
	ios_base::sync_with_stdio(0);
	cin.tie(0);

	cin >> n;
	for (int i = 0; i < n; i++)
	{
		string str;
		cin >> str;
		for (int j = 0; j < str.length(); j++)
		{
			board[i][j] = str[j] - '0';
		}
	}

	bfs();
	cout << visit[n - 1][n - 1] << endl;
	return 0;
}

'Algorithm' 카테고리의 다른 글

[백준 3036] 링  (0) 2021.11.09
[백준 1037] 약수  (0) 2021.11.09
[백준 14221] 편의점  (0) 2021.11.04
[백준 18352] 특정 거리의 도시 찾기  (0) 2021.11.04
[백준 10282] 해킹  (0) 2021.10.31
문제 풀이

 

편의점의 후보와 집의 후보가 나열되는데, 이때 우리는 편의점이랑 가까운 집을 찾으려고 한다.

어떻게 찾을 수 있을까?

 

편의점으로부터 가까운 집을 고르는 것이기 때문에 이는 최단 경로를 구해야 된다.

그런데 그러한 경로는 다양하기 때문에 이러한 것들을 빠르게 찾아낼 수 있는 것이 바로 다익스트라다.

한 정점에서 다른 모든 정점까지의 최단 경로를 구할 수 있기 때문이다.

 

여기서 핵심은 역발상이다.

집이라는 후보가 정해져 있기 때문에 해당 편의점을 시작 지점으로 잡은 다음 출발하여 집의 후보를 발견했다면 그때의 경로와 집의 번호를 pair로 정답 배열에 저장한다. 이렇게 저장된 정답 배열을 오름차순으로 정렬하면 우리가 구하고자 하는 답이 나오게 된다.

 

시간 복잡도를 생각해보자.

다익스트라를 인접 리스트로 구현했다면 O(ElogV)다.

모든 편의점에서부터 다익스트라를 구하기 때문에 O(q x ElogV) = O(5,000 x 100,000 x log5,000)

얼핏 보면 시간이 빡빡한데, 통과했다. 아마 테케가 더 빡빡하게 주어지면 재채점시에 틀릴 수도 있을 것 같다.

더 빠른 풀이를 보니까 하나의 시작점에서 생각하는 게 아니라 여러 시작점(편의점을 시작점으로 해서 편의점의 수만큼 우선순위 큐에 넣고 돌리는 것이다.)

 

소스 코드

 

#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;

const int MAX = 5000 + 1;
const int INF = 2e9;
typedef pair<int, int> pii;

int n, m, p, q;
vector<pii> adj[MAX];
vector<int> houses, shops;
vector<pii> ans;

void dijkstrea(int start)
{
    vector<int> dist(n + 1, INF);
    priority_queue<pii> pq;
    pq.push({0, start});
    dist[start] = 0;
    while(!pq.empty())
    {
        int cur = pq.top().second;
        int distance = -pq.top().first;
        pq.pop();
        
        if(dist[cur] < distance) continue;
        for(int i = 0; i < adj[cur].size(); i++)
        {
            int next = adj[cur][i].first;
            int nextDistance = adj[cur][i].second + distance;
            if(dist[next] > nextDistance)
            {
                dist[next] = nextDistance;
                pq.push({-nextDistance, next});
            }
        }
    }
    
    for(int house : houses)
    {
        if(dist[house] != INF)
            ans.emplace_back(dist[house], house);
    }
}

int main()
{
    ios_base::sync_with_stdio(0);
    cin.tie(0);
    
    cin >> n >> m;
    for(int i = 0; i < m; i++)
    {
        int a,b,c;
        cin >> a >> b >> c;
        adj[a].emplace_back(b,c);
        adj[b].emplace_back(a,c);
    }
    
    cin >> p >> q;
    houses.resize(p);
    shops.resize(q);
    for(int i = 0; i < p; i++) cin >> houses[i];
    for(int i = 0; i < q; i++) cin >> shops[i];
    // 편의점에서 출발해서 각 집의 후보지를 탐색한 후 정답 배열에 넣어준다.
    for(auto start : shops)
    {
        dijkstrea(start);
    }
    
    sort(ans.begin(), ans.end());
    cout << ans[0].second << endl;
    return 0;
}

'Algorithm' 카테고리의 다른 글

[백준 1037] 약수  (0) 2021.11.09
[백준 2665] 미로만들기  (0) 2021.11.08
[백준 18352] 특정 거리의 도시 찾기  (0) 2021.11.04
[백준 10282] 해킹  (0) 2021.10.31
[백준 1251] 단어 나누기  (0) 2021.10.24
문제 풀이

 

다익스트라의 기본 개념을 알고 있는지 묻는 문제다.

출발점 X의 도시에서 다른 모든 도시까지의 최단 경로를 구한 다음에

어떤 도시에서 출발해서 어떤 도시의 도착하는 비용이 K와 같다면 정답 배열에 넣고

정답 배열을 정렬해서 출력하면 된다.

만약에 배열의 사이즈가 0이라면 도시가 없기 때문에 -1을 출력하면 된다.

 

소스 코드

 

#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;

const int MAX = 300000 + 1;
const int INF = 987654321;
typedef pair<int, int> pii;

int N, M, K, X;
vector<pii> adj[MAX];

void dijkstra()
{
    vector<int> dist(N + 1, INF);
    priority_queue<pii> pq;
    pq.push({0, X});
    dist[X] = 0;
    while(!pq.empty())
    {
        int cur = pq.top().second;
        int distance = -pq.top().first;
        pq.pop();
        
        if(dist[cur] < distance) continue;
        for(int i = 0; i < adj[cur].size(); i++)
        {
            int next = adj[cur][i].first;
            int nextDistance = adj[cur][i].second + distance;
            if(dist[next] > nextDistance)
            {
                dist[next] = nextDistance;
                pq.push({-nextDistance, next});
            }
        }
    }
    
    vector<int> ans;
    for(int i = 1; i <= N; i++)
    {
        if(dist[i] == K)
            ans.push_back(i);
    }
    
    if(ans.size() == 0)
        cout << "-1\n";
    else
    {
        sort(ans.begin(), ans.end());
        for(auto n : ans)
            cout << n << '\n';
    }
}

int main()
{
    ios_base::sync_with_stdio(0);
    cin.tie(0);
    
    cin >> N >> M >> K >> X;
    for(int i = 0; i < M; i++)
    {
        int a, b;
        cin >> a >> b;
        adj[a].emplace_back(b,1);
    }
    
    dijkstra();
    
    return 0;
}

'Algorithm' 카테고리의 다른 글

[백준 2665] 미로만들기  (0) 2021.11.08
[백준 14221] 편의점  (0) 2021.11.04
[백준 10282] 해킹  (0) 2021.10.31
[백준 1251] 단어 나누기  (0) 2021.10.24
[백준 5670] 휴대폰 자판  (0) 2021.10.19
문제 풀이

 

a b s 가 주어지면, b가 감염되면 s초 후에 컴퓨터 a가 감염된다.

이 말은 즉슨 b부터 감염을 시작하면 a까지의 도달하는 비용은 s초다.

 

출발 노드로부터 다익스트라 알고리즘을 순회하면 문제를 해결할 수 있다.

감염된 컴퓨터의 수는 dist 배열에서 INF가 아닌 컴퓨터를 카운팅 하면 된다.

모든 컴퓨터가 감염되기까지 걸린 시간 역시 INF가 아닌 dist 배열에서 가장 큰 수를 저장해서 출력하면 된다.

 

소스 코드
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;

int t, n, d, c;

const int INF = 987654321;
typedef pair<int, int> pii;

vector<pii> adj[10001];
int dist[10001];


void dijkstra(int start)
{
    fill(dist, dist + 10001, INF);
    priority_queue<pii, vector<pii>, greater<pii>> pq;
    pq.push({0, start});
    dist[start] = 0;
    while(!pq.empty())
    {
        int distance = pq.top().first;
        int cur = pq.top().second;
        pq.pop();
        
        if(dist[cur] < distance) continue;
        
        for(int i = 0; i < adj[cur].size(); i++)
        {
            int next = adj[cur][i].first;
            int nextDistance = adj[cur][i].second + distance;
            if(dist[next] > nextDistance)
            {
                dist[next] = nextDistance;
                pq.push({nextDistance, next});
            }
        }
    }
    
    int time = 0;
    int cnt = 0;
    for(int i = 1; i <= n; i++)
    {
        if(dist[i] != INF)
        {
            cnt++;
            time = max(time, dist[i]);
        }
    }
    
    cout << cnt << ' ' << time << '\n';
}

int main()
{
    ios_base::sync_with_stdio(0);
    cin.tie(0);
    
    cin >> t;
    while(t--)
    {
        cin >> n >> d >> c;
        
        for(int i = 0; i < 10001; i++)
            adj[i].clear();
        
        for(int i = 0; i < d; i++)
        {
            int a, b, s;
            cin >> a >> b >> s;
            adj[b].emplace_back(a,s);
        }
        
        dijkstra(c);
    }
    
    return 0;
}

'Algorithm' 카테고리의 다른 글

[백준 14221] 편의점  (0) 2021.11.04
[백준 18352] 특정 거리의 도시 찾기  (0) 2021.11.04
[백준 1251] 단어 나누기  (0) 2021.10.24
[백준 5670] 휴대폰 자판  (0) 2021.10.19
[백준 4358] 생태학  (0) 2021.10.19
문제 풀이

 

주어진 문자열을 3개의 문자열로 나눈 다음에 각각의 문자열을 뒤집고, 뒤집은 3개의 문자열을 다시 합치면 된다.

문제는 어떻게 3등분을 하는가이다.

문제에서 문자열을 나눌 때 최소한 문자열의 길이가 1을 보장해야 한다고 명시되어있다.

 

문제에서 주어진 예시(arrested)를 3등분한다고 한다면? 아래처럼 다양하게 나올 것이다.

a / r / rested

a / rr / ested

a / rre / sted

...

arrest / e / d

 

하나의 색종이를 2등분으로 만들고 싶다면? 가위로 한 번 자르면 된다.

하나의 색종이를 3등분으로 만들고 싶다면? 가위로 두 번 자르면 된다.

우리는 3등분을 원하므로 총 2번의 경계선만 구하면 된다.

[문자열의 시작 지점 - i  - j - 문자열의 끝 지점]

i와 j는 3등분을 하는 경계선이라고 생각해보면 다음과 같이 정리할 수 있다.

(0 ~ i) 까지 부분 문자열 추출

(i ~ j) 까지 부분 문자열 추출

(i+j ~ 끝)까지 부분 문자열 추출

 

경계선을 구했다.

그런데 i를 어디까지 보면 될까?

문제에서 봤듯이 문자열은 최소 1개의 문자를 포함해야하므로 입력된 문자열 길이의 - 2까지만 돌면 된다.

그러면 j는 어디까지 보면 될까?

만약에 i가 최대 문자열의 길이 - 2 까지 오게 됬다면 j는 문자열의 길이 - i - 1까지만 돌면 된다.

 

다양한 문자열이 나타났을 때,

사전순으로 가장 앞선 단어를 구하기 위해서는 문자열끼리 비교해서 더 작은 문자열로 갱신하면 된다.

소스 코드

 

#include <iostream>
#include <string>
#include <algorithm>
using namespace std;

int main()
{
    ios_base::sync_with_stdio(0);
    cin.tie(0);
    
    string str;
    cin >> str;
    
    string ans;
    int len = static_cast<int>(str.size());
    for(int i = 1; i <= len - 2; i++)
    {
        for(int j = 1; j <= len - i - 1; j++)
        {
            string a = str.substr(0,i);
            string b = str.substr(i,j);
            string c = str.substr(i+j);
            
            reverse(a.begin(), a.end());
            reverse(b.begin(), b.end());
            reverse(c.begin(), c.end());
            
            string ret = a + b + c;
            if(ans == "") ans = ret;
            else if(ans > ret) ans = ret;
        }
    }
    
    cout << ans << endl;
    return 0;
}

 

 

'Algorithm' 카테고리의 다른 글

[백준 18352] 특정 거리의 도시 찾기  (0) 2021.11.04
[백준 10282] 해킹  (0) 2021.10.31
[백준 5670] 휴대폰 자판  (0) 2021.10.19
[백준 4358] 생태학  (0) 2021.10.19
[백준 15903] 카드 합체 놀이  (0) 2021.10.13
문제 풀이

 

정리해보면, 사전이 주어졌을 때 각 단어를 입력하기 위해 버튼을 눌러야 하는 최소의 횟수를 구해서 평균을 매기면 된다.

어떻게 하면 최소의 횟수를 구할 수 있을까?

 

사전에 아래와 같이 등록되었다고 생각해보자.

hello

hell

heaven

 

문제에서 보았듯이 첫 글자는 무조건 버튼을 눌러야 한다.

h를 눌러보자.

h를 눌렀더니 자동적으로 e가 타이핑이 된다.

왜냐하면 사전에는 h 다음으로 등록되어 있는 것이 e가 공통이기 때문이다.

그런데 he에서 보자.

he는 hello, hell, heaven 다 될 수 있기 때문에 자동적으로 등록되지 못한다. (선택 장애)

따라서 사용자는 어쩔 수 없이 버튼을 눌러야 한다.

만약에 l을 누른다고 해보자.

l을 누르자마자 자동적으로 l이 추가되어 hell가 된다.

왜냐하면 사전에는 l 다음으로 등록되어 있는 것이 l이 공통이기 때문이다.

 

hell를 만든다고 했다면 총 2번의 버튼 입력이 필요하고,

더 나아가서 hello를 만든다고 한다면 사용자는 버튼을 한 번 더 눌러서 hello를 완성시킨다.

 

버튼의 증가 타이밍을 정리하면 다음과 같다.

  1. 첫 글자면 증가
  2. 한 노드의 자식 노드 개수가 2개 이상일 때
  3. end를 지나칠 때 (hell에서 끝났다는 표시가 있는데 더 나아가서 hello를 만들려고 할 때)

트라이 자료구조에 한 노드의 자식 노드 개수를 파악할 수 있도록 변수를 추가하여 구현한다.

시간 복잡도는 O(100,000(testCase) x 80(문자열 길이)) = O(8,000,000) 이는 1초 이내로 해결할 수 있게 된다.

소스 코드

 

#include <iostream>
#include <string>
#include <vector>

using namespace std;

int n, ans;
bool init;

struct Trie
{
    Trie* ch[26];
    bool end;
    int cnt;
    
    Trie() : end(false), cnt(0)
    {
        for(int i = 0; i < 26; i++)
            ch[i] = nullptr;
    }
    
    ~Trie()
    {
        for(int i = 0; i < 26; i++)
            if(ch[i]) delete ch[i];
    }
    
    void insert(const char* s)
    {
        if(!*s)
        {
            end = true;
            return;
        }
        
        int cur = *s - 'a';
        if(ch[cur] == nullptr)
        {
            ch[cur] = new Trie();
            cnt++;
        }
        ch[cur]->insert(s + 1);
    }
    
    void find(const char* s)
    {
        if(!*s) return;
        if(init)
        {
            init = false;
            ans++;
        }
        //문자열의 끝을 지날 때도 카운팅
        else if(end) ans++;
        //한 노드의 자식의 노드가 2개 이상일 때 카운팅(분별할 수 없으니까 사용자가 입력해야 된다.)
        else if(cnt >= 2) ans++;
        
        int cur = *s - 'a';
        ch[cur]->find(s + 1);
    }
};

int main()
{
    ios_base::sync_with_stdio(0);
    cin.tie(0);
    
    cout << fixed;
    cout.precision(2);
    while(true)
    {
        cin >> n;
        if(cin.eof() == true) break;
        
        vector<string> list(n);
        Trie* root = new Trie();
        for(int i = 0; i < n; i++)
        {
            cin >> list[i];
            root->insert(list[i].c_str());
        }
        
        ans = 0;
        for(int i = 0; i < n; i++)
        {
            init = true; //첫글자는 반드시 카운팅!
            root->find(list[i].c_str());
        }
        cout << ans / static_cast<double>(n) << '\n';
        delete root;
    }
    return 0;
}

 

'Algorithm' 카테고리의 다른 글

[백준 10282] 해킹  (0) 2021.10.31
[백준 1251] 단어 나누기  (0) 2021.10.24
[백준 4358] 생태학  (0) 2021.10.19
[백준 15903] 카드 합체 놀이  (0) 2021.10.13
[백준 14235] 크리스마스 선물  (0) 2021.10.13
문제 풀이

 

단순하게 완전탐색으로는 문제를 해결할 수 없다.

해당 나무의 종이 몇번 등장하는지 알아야 하기 때문에 해당 나무가 있는지 없는지를 빠르게 파악할 수 있어야 한다.

이걸 풀어서 생각해보면 탐색을 O(logN)에 해결하면 쉽게 풀 수 있다.

O(입력받는 나무의 종 10000 x log1000000) => O(60000)

 

탐색을 빠르게 할 수 있으면서 해당 종이 몇 개 있는지 알 수 있는 자료구조는 map이 있다.

해당 나무의 종이 없다면 새롭게 1로 할당하면 되고, 이미 있는 종이라면 그 횟수를 늘리는 식으로 카운팅을 해놓고

해당 나무의 종의 개수 / (전체 입력 수) * 100을 하게 되면 해당 나무의 % 비율을 계산할 수 있게 된다.

 

나무 종의 이름이 공백을 포함한 형태이기 때문에 공백을 포함해서 문자열을 입력받아야 한다.

또한, 입력이 끝났는지를 판별하는 eof 개념도 알고 있어야 한다.

 

소스 코드

 

#include <iostream>
#include <map>
#include <string>

using namespace std;

int main()
{
    ios_base::sync_with_stdio(0);
    cin.tie(0);
    
    string tree;
    map<string, int> mp;
    double cnt = 0;
    while(getline(cin, tree))
    {
        mp[tree]++;
        cnt++;
    }
    
    cout << fixed;
    cout.precision(4);
    for(const auto& m : mp)
    {
        //몇 %를 차지하는지 나타내기 위해서는 x100을 해야한다.
        cout << m.first << ' ' << m.second / cnt * 100 << '\n';
    }
    return 0;
}

'Algorithm' 카테고리의 다른 글

[백준 1251] 단어 나누기  (0) 2021.10.24
[백준 5670] 휴대폰 자판  (0) 2021.10.19
[백준 15903] 카드 합체 놀이  (0) 2021.10.13
[백준 14235] 크리스마스 선물  (0) 2021.10.13
[백준 1417] 국회위원 선거  (0) 2021.10.13

미루고 미뤘던 자료구조, 이번 기회에 트라이에 대해서 공부하게 됐다.

내가 여러 사이트를 검색하면서 공부한 것을 정리했다.

트라이(Trie) 자료구조란?

 

쉽게 생각해서 문자열을 빠르게 탐색할 수 있는 자료구조다.

얼마나 빠르길래 이렇게 따로 명칭까지 있는 것일까?

 

결론부터 말하자면 M이 최대 문자열의 길이라고 했을 때, O(M)만에 검색할 수 있다.

 

트라이를 표현하는 특징은 여러 가지가 있다.

1) 집합에 포함된 문자열의 접두사들에 대응되는 노드들이 서로 연결된 트리구조

2) 루트에서부터 내려가면서 만나는 글자들을 모으면 찾고자 하는 문자열의 접두사를 얻을 수 있다.

3) 문자열 트리 구조

 

트라이 작동 원리

 

트라이는 주어진 문자열을 이루고 있는 문자를 앞에서부터 하나씩 노드를 생성해가면서 만들어진다.

이 과정에서는 반복적으로 재귀 호출이 일어나게 된다.

  1. 주어진 문자열에서 현재 문자를 가져온다.
  2. 현재 문자로 이루어진 노드를 확인한다.
    1. 만약 노드가 존재한다면, 그 노드로부터 다음 문자열을 탐색하러 간다.
    2. 만약 노드가 없다면, 노드를 새로 만들고, 해당 노드를 통해 다음 문자열을 탐색한다.
  3. 문자열의 마지막이 될 때까지 위의 과정을 반복한다.

삽입하는 과정이면,

해당 문자열이 "ABC"라고 해보자.

루트 노드로부터 ABC 문자열에서 문자 A를 검색한다.

문자 노드 A가 있으면 그 아래 자식 노드를 검색하러 가고, 없으면 새롭게 A라는 노드를 만든다.

문자 노드 B가 있으면 그 아래 자식 노드를 검색하러 가고, 없으면 새롭게 B라는 노드를 만든다.

이렇게 쭉 가다가 C가 끝나고 그다음을 확인해보니까 문자열의 끝인 널문자에 도달하게 됐다.

만약에 ABC라는 문자열을 트라이에 저장했다고 한다면 그 끝을 표현하는 Bool 변수를 true로 만들어주면 된다.

 

검색하는 과정이라면,

해당 문자열에 앞에서부터 문자를 하나씩 살펴보면서 해당 문자가 노드가 없으면 false고,

있다면 그 문자에 연결된 다음 자식 노드를 확인해보러 가면 된다.

 

트라이 자료구조 장/단점

 

장점은 문자열을 빠르게 찾을 수 있다.

여기서 빠르게 찾는다는 것은 탐색과 삽입이 모두 O(M)에 이루어진다.

왜? 트라이에서 검색을 할 경우에 최대 트리의 높이까지 탐색하게 되는데,

이때 시간 복잡도는 O(H)이지만, 트리의 높이가 결국 최대 문자열의 길이가 되기 때문에 결국 O(M)에 가능하다.

 

어떻게 삽입하든 간에 트라이가 구성된 트리는 항상 똑같다. 이는 자동으로 정렬된 효과를 볼 수 있다.

왜냐하면 A로 시작하는 문자열은 A라는 노드 아래로 쭉 이어진 서브 트리를 이루는 형태이기 때문이다.

 

단점은 공간 복잡도가 매우 크다. 빠르면 대가가 있다.(trade-off 관계)

숫자에 대한 트라이를 만든다면 0~9인 총 10개의 포인터 배열 선언 + 문자열의 끝을 의미하는 bool

알파벳에 대한 트라이를 만든다면 a~z인 총 26개의 포인터 배열 선언 + 문자열의 끝을 의미하는 bool

 

최종적으로 메모리는 [포인터 크기 x 포인터 배열 개수 x 트라이에 존재하는 총노드의 개수]

 

아니 map을 이용하면 트라이랑 비슷하지 않을까?

 

우리가 배운 map은 red-black tree 구조인 균형 이진 탐색 트리다.

어떤 자료를 검색할 때 O(logN)이 걸린다는 것을 안다.

 

그런데 생각해보자.

여러 개의 문자열 중에서 우리가 찾는 문자열을 검색하는 시간은 O(logN x M)이다.

왜? 여러 개의 문자열 N에서 우리가 찾고자 하는 문자열의 길이가 M이기 때문이다.

 

그런데 트라이는?

O(M)만에 끝내버린다.

 

어떻게 구현할까?

 

다른 알고리즘처럼 틀이 존재한다.

트라이는 자료구조이기 때문에 입맛에 맞게 변형하여 사용하면 된다.

다만 이것을 어떻게 활용할지는 우리에게 달렸다.

#include <iostream>
#include <string>

using namespace std;

//트라이를 구성하는 노드는 자손 노드를 가리키는 포인터 목록과 문자열의 끝인지를 나타내는 변수로 구성된다.
struct Trie
{
    Trie* ch[26]; //26가지 알파벳에 대한 트라이
    bool end;     //끝나는 지점을 표시해줌
    
    //문자열을 트라이에 넣어주는 함수
    void insert(const char* s)
    {
        //문자열 끝에 도달하는 경우
        if(*s == '\0')
        {
            this->end = true;
            return;
        }
        
        //현재 문자가 노드에 있으면 넘어가고, 아니라면 새롭게 하나 만들어주고 넘어간다.
        int now = *s - 'A';
        if(!ch[now]) ch[now] = new Trie();
        ch[now]->insert(s + 1);
    }
    
    bool find(const char* s)
    {
        //문자열 끝에 도달했을 때
        if(*s == '\0')
        {
            //문자열의 끝이라면 찾은 것이고, 아니라면 탐색에 실패
            if(end) return true;
            return false;
        }
        //다음으로 가는 노드가 있으면 다음으로 가고, 아니라면 탐색에 실패
        int now = *s - 'A';
        if(!ch[now]) return false;
        return ch[now]->find(s + 1);
    }
    
    Trie() : end(false)
    {
        for(int i = 0; i < 26; i++)
            ch[i] = nullptr;
    }
    ~Trie()
    {
        for(int i = 0; i < 26; i++)
        {
         if(ch[i])
             delete ch[i];
        }
    }
};

int main()
{
    ios_base::sync_with_stdio(0);
    cin.tie(0);
    
    Trie* root = new Trie();
    string s;
    cin >> s;
    root->insert(s.c_str());
    string temp = "AAA";
    if(root->find(temp.c_str()))
        cout << "find!\n";
    else
        cout << "No\n";
    
    delete root;
    return 0;
}

 

'Algorithm > Algorithm Concept' 카테고리의 다른 글

위상 정렬 (TopologySort)  (0) 2021.10.12
행렬의 다양한 연산들  (0) 2021.10.11
플로이드 워셜 (Floyd Warshall)  (0) 2021.09.03
투 포인터  (0) 2021.08.25
다익스트라 알고리즘  (0) 2021.08.24

+ Recent posts