트리에서 전위 중위 후위 순회 하는 방법은 각 순회의 특징을 그대로 적용하는 것이다.

전위는 현재 노드를 먼저 읽는 방식으로 현재노드 -> 좌 -> 우

중위는 좌측 노드부터 먼저 읽는 방식으로 좌 -> 현재노드 -> 우

후위는 자식들을 전부 읽고 자신을 읽는 방식으로 좌 -> 우 -> 현재노드

 

이를 코드로 표현하면 다음과 같다

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
struct tree {
    int    w;
    tree* parent = NULL;
    tree* left = NULL;
    tree* right = NULL;
    bool visit = false;
};
 
void pre(tree* now) { // 전위
    if (now == NULL || now->visit) return;
 
    now->visit = true;
    cout << now-><< " ";
    pre(now->left);
    pre(now->right);
}
 
void in(tree* now) { // 중위
    if (now == NULL || now->visit) return;
    now->visit = true;
    in(now->left);
    cout << now-><< " ";
    in(now->right);
}
 
void post(tree* now) { // 후위
    if (now == NULL || now->visit) return;
    now->visit = true;
    post(now->left);
    post(now->right);
    cout << now-><< " ";
}
 
void solve() {
    
    cout << "pre : ";
    pre(root);
    for (int i = 0; i < tree_size; i++) node[i].visit = false;
    cout << "\n";
    cout << "in : ";
    in(root);
    for (int i = 0; i < tree_size; i++) node[i].visit = false;
    cout << "\n";
    cout << "post : ";
    post(root);
    for (int i = 0; i < tree_size; i++) node[i].visit = false;
}
 

 

다음과 같은 결과를 얻을 수 있다.

 

pre : 5 3 1 0 2 4 7 6 8 10 9 12 11 
in : 0 1 2 3 4 5 6 7 8 9 10 11 12 
post : 0 2 1 4 3 6 9 11 12 10 8 7 5 

트리 변형 과제가 나온김에 포스팅해야겠다

 

임의의 트리에 대해서

전위와 중위 트리에 대한 정보가 주어졌을 떄 이 정보를 통해서 실제 트리를 구현해낼 수 있다.

 

단순히 재귀만 이용하면 되는 간단한 문제이다

 

전위, 중위 뿐만 아니라 중위, 후위 등등 최소 2개의 순서 정보만 있다면 실제 트리를 구현할 수 있다.

 

예를 들어 값 자체가 중위 순회 순서 값으로 이루어진 다음과 같은 트리를 보자

 

전위와 중위의 순서를 위 순서와 같다고 할 때 

다음과 같은 특징을 알 수 있다.

 

전위의 첫번째 값인 5는 곧 전체트리의 루트노드가 된다.

그럼 전체 트리중 5의 left 와 right는 어떻게 구별해야하나?

그것은 중위 순회의 순서에서 5를 찾아주면 된다.

결국 중위순회의 [0]~[4]번째까지는 5의 left 자식이 되고 [6]~[12]번째까지는 5의 right 자식이 된다

==> 전위 기준 [1]~[5]번째까지는 left의 자식, [6]~[12]번째까지는 5의 right 자식이 된다

 

결국 left, right를 나누는 기준은 전위, 중위의 배열 인덱스를 이용해서 현재 트리의 루트를 찾고 좌 우 를 나누는 것이다

 

이 특정 구간들을 다음과 같은 재귀문을 통해서 트리를 만들 수 있다.

 

order[] : 전위 순회의 순서가 들어간 배열

node[] : 중위 순회 순서의 값을 weight로 가지는 각 노드들

(node[]의 경우는 쉬운 예를 위해 이런식으로 표현한 것이고, 애초에 중위 순서가 주어진다면

이 순서를 배열에 넣고 사용하면 된다. 이 경우는 아예 값 자체를 중위 순회 순서로 넣은 것이니 착각하지말자)

1
2
3
4
5
6
7
8
9
void make_tree() {
    root = &node[order[0]];
 
    root->left = left_tree(0, order[0- 11);
    root->right = right_tree(order[0+ 1, tree_size - 1, order[0+ 1);
 
    if (root->left != NULL) root->left->parent = root;
    if (root->right != NULL) root->right->parent = root;
}
 

전역 변수 root는 order[0] (전위 순회의 첫번째 값) 을 루트노드로 가진다.

이 root의 left와 right에 다음과 같은 함수를 적용할 수 있다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
tree* right_tree(int l, int r, int idx) {
    return left_tree(l, r, idx);
}
 
tree* left_tree(int l, int r, int idx) {
    if (!(l <= r && idx < tree_size)) return NULL;
    tree* now = &node[order[idx]];
 
    if (l == r) return now;
 
    int next = order[idx];
    now->left = left_tree(l, next - 1, idx + 1);
    now->right = right_tree(next + 1, r, idx + (next - l) + 1);
 
    if (now->left != NULL) now->left->parent = now;
    if (now->right != NULL) now->right->parent = now;
 
    return now;
}
 

 

함수에서는 현재 노드를 루트로 보고 좌우의 트리를 반환받은 후에 루트노드를 리턴하는 형식이기 때문에 

결국 트리를 만드는 부분은 결국 left_tree 나 right_tree 가 같다고 볼 수 있다.

 

코드를 간단하게 설명하자면

중위 순회 : 0 1 2 3 4 5 6 7 ,,,

전위 순회 : 5 3 1 0 2 4 7 6 ... 

tree 함수는 다음과 같은 동작을 하게 된다

L,R 을 중위순회의 인덱스, idx 를 전위순회의 인덱스에 두고 좌 우를 찾게 된다

 

첫번째 left_tree를 기준으로

L : 0, R : 4, idx : 1 이다

order[idx] 는 3이 되고, 이 의미는 5의 좌측에 들어갈 루트노드가 3이 됨을 의미한다.

이 3을 중위 순회에서 몆번째 인덱스에 있는지 찾는다. (지금은 값 자체기 때문에 3이 된다)

중위 순회의 3번째 인덱스에 3이 있으므로, 3의 좌측에 들어갈 노드는

3 - 0(=> L이 0이므로) = 3개가 된다.

3의 좌측트리를 left_right(L, 3-1, idx+1) 의 반환값으로 호출 => 노드 0, 1, 2 를 3의 좌측트리에서 만든다

3의 우측트리를 right(3+1, R, idx+(3-L)+1) 의 반환값으로 호출 => 노드 4 를 3의 우측트리에서 만든다

 

다시 한번 말하지만

중위 순회를 0, 1, 2, 3, ... 순서대로 예를 든 것이기 때문에 L,R,next 같은 매개인자를 사용할 수 있는 것이고

애초에 이런식이 아닌 중위 순회의 값을 배열에 넣고 호출하는게 일반적일 것이다.

같은 의미로는 두 직선이 주어졌을 때 그 사이의 각도를 구하는 것

 

세점 A,B,C 가 있고

각 점은 구조체로써 x, y좌표를 가지고 있다면 

angle(A,B,C) -> 점 B를 중심으로 하는 점 A, B, C 사이의 각도를 구할 수 있다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct poly {
   lld x, y;
};
 
#define M_PI 3.14159
 
double angle(poly& a, poly& b, poly& c) {
    double aa, bb, cc;
    double ang, temp;
 
    aa = sqrt(pow(a.x - c.x, 2+ pow(a.y - c.y, 2));
    bb = sqrt(pow(a.x - b.x, 2+ pow(a.y - b.y, 2));
    cc = sqrt(pow(b.x - c.x, 2+ pow(b.y - c.y, 2));
 
    temp = (pow(bb, 2+ pow(cc, 2- pow(aa, 2)) / (2 * bb * cc);
    ang = acos(temp);
    ang = ang * (180 / M_PI);
 
    return ang;
}
 

 

 

가끔 코딩하다가 하는 실수인데,

typedef 로 여러 자료형을 묶어서 사용하면서

 

C++ 인수 목록이 일치하는 오버로드된 함수의 인스턴스가 없습니다

오류 C2676 이항 '<': 'const _Ty1'이(가) 이 연산자를 정의하지 않거나 미리 정의된 연산자에 허용되는 형식으로의 변환을 정의하지 않습니다.

 

등등의 오류를 볼 수 있다.

 

이 때, 오류의 원인은 아무리 set이나 map의 형식에 맞게 사용했어도

연산자체가 불가능한 값(또는 형태)을 넣었기 때문이다.

 

set, map 등등의 STL은 내부에서 비교연산이 이루어지는데,

다음 코드를 통해 연산이 가능한 경우와 불가능한 경우를 살펴보고 실수하지 말자

1
2
3
4
5
6
struct poly {
    int x, y;
};
set<pair<pair<poly, poly>int> > visit; // 안되는 경우
typedef pair<int,int> pii2;
set<pair<pair<pii2, pii2>int> > visit; // 되는 경우

 

+ Recent posts