#include <algorithm>
#include <cassert>
#include <climits>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <deque>
#include <functional>
#include <iomanip>
#include <iostream>
#include <map>
#include <numeric>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <vector>


using namespace std;
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;

#define REP(i,n) for(int i = 0; i < (int)(n); ++i)
#define FOR(i,a,b) for(int i = (a); i < (int)(b); ++i)
#define ALL(c) (c).begin(), (c).end()
#define SIZE(v) ((int)v.size())

#define pb push_back
#define mp make_pair
#define mt make_tuple

bool debug = false;

// 頂点は0-originとする
template <typename T>
class Dijkstra {
    struct edge { int to; T cost; };
    typedef pair<T, int> P; // <最短距離, 頂点番号>

private:
    int m_V;
    vector<vector<edge> > m_G;
public:
    const T INF = std::numeric_limits<T>::max();
    vector<int> m_prev_path; // 最短パスを辿るため

    Dijkstra(int V) : m_V(V) {
        m_G.resize(m_V);
    }

    // a -> bに片方向のエッジを張る
    void add_dir_edge(int a, int b, T cost) {
        m_G[a].push_back( edge{ b, cost } );
    }

    // a <-> bに両方向のエッジを張る
    void add_undir_edge(int a, int b, T cost) {
        add_dir_edge(a, b, cost);
        add_dir_edge(b, a, cost);
    }

    // 頂点sから各頂点までの距離を計算して返す
    vector<T> shortest_path(int s) {
        m_prev_path.clear(); m_prev_path.resize(m_V, -1);

        vector<T> d(m_V, INF);
        priority_queue<P, vector<P>, greater<P> > que;
        d[s] = 0;
        que.push(P(0, s));

        while(!que.empty()) {
            P p = que.top();
            que.pop();

            auto dist = p.first;
            auto v = p.second;
            if (d[v] < dist) continue;
            REP(i, m_G[v].size()) {
                edge e = m_G[v][i];
                if (d[e.to] > d[v] + e.cost) {
                    d[e.to] = d[v] + e.cost;
                    que.push(P(d[e.to], e.to));
                    m_prev_path[e.to] = v;
                }
            }
        }

        return d;
    }

    // shortest_path()が呼ばれた後に呼ばれることを期待する
    // s -> v1 -> v2 -> v3 -> t の場合、{s, v1, v2, v3, t} からなるvectorを返す
    vector<int> get_path(int t)
    {
        vector<int> path;
        for(; t != -1; t = m_prev_path[t]) path.push_back(t);
        reverse(ALL(path));
        return path;
    }

};

vector<int> solve(const int N, const vector<pair<int, int>>& ABs) {
    vector<int> ans(N + 1);

    vector<vector<int>> edges(N + 1);
    Dijkstra<int> dij(N);
    for(auto ab: ABs) {
        auto a = ab.first;
        auto b = ab.second;
        edges[a].pb(b);
        edges[b].pb(a);
        dij.add_undir_edge(a - 1, b - 1, 1);
    }

    // 直径を求める
    auto ret = dij.shortest_path(0);
    int far_v = 0;
    int far_d = 0;
    REP(n, N) {
        if (ret[n] > far_d) {
            far_v = n;
            far_d = ret[n];
        }
    }
    auto ret2 = dij.shortest_path(far_v);
    int diameter = 0;
    REP(n, N) {
        if (ret2[n] > diameter) {
            diameter = ret2[n];
        }
    }

    // 直径2以下なら任意の数列でOK
    // この場合分けはなくてもよかった
    if (diameter <= 2) {
        REP(n, N + 1) {
            ans[n] = n;
        }
        return ans;
    }

    vector<int> nodes;
    nodes.pb(1);
    // rootからの距離が奇数である点をred, 偶数である点をblueとする
    vector<int> blues;
    vector<int> reds;

    vector<int> seen(N + 1);
    bool one_flag = true;
    seen[1] = 1;
    while(!nodes.empty()) {
        vector<int> next_nodes;
        for(auto node: nodes) {
            if (one_flag) {
                blues.pb(node);
            } else {
                reds.pb(node);
            }
            for(auto nei: edges[node]) {
                if (!seen[nei]) {
                    next_nodes.pb(nei);
                    seen[nei] = true;
                }
            }
        }
        one_flag = !one_flag;
        nodes = next_nodes;
    }

    // 赤色の頂点が N/3 個以下のとき
    // 赤色の頂点に3, 6, 9, ...を割り当てていく
    if (SIZE(reds) <= N / 3) {
        vector<int> used(N + 1);
        int i = 3;
        for (auto red : reds) {
            ans[red] = i;
            used[i] = 1;
            i += 3;
        }
        i = 1;
        for (auto blue : blues) {
            while (used[i]) i++;
            ans[blue] = i;
            used[i] = 1;
        }
        return ans;
    }
    // 青色の頂点が N/3 個以下のとき
    // 青色の頂点に3, 6, 9, ...を割り当てていく
    else if (SIZE(blues) <= N / 3) {
        vector<int> used(N + 1);
        int i = 3;
        for (auto blue : blues) {
            ans[blue] = i;
            used[i] = 1;
            i += 3;
        }
        i = 1;
        for (auto red : reds) {
            while (used[i]) i++;
            ans[red] = i;
            used[i] = 1;
        }
        return ans;
    }
    // 赤色の頂点も青色の頂点も N/3 個より多いとき
    else {
        // 赤色の頂点に 3 で割って 2 余る整数を、
        // 青色の頂点に 3 で割って 1 余る整数を順に割り当てていき、
        // 残った頂点に 3 の倍数を割り当てる

        int mod0 = 0;
        int mod1 = 0;
        int mod2 = 0;
        FOR(i, 1, N + 1) {
            if (i % 3 == 0) ++mod0;
            else if (i % 3 == 1) ++mod1;
            else ++mod2;
        }

        if (SIZE(reds) > SIZE(blues)) {
            swap(blues, reds);
        }

        vector<int> zeros;
        while (SIZE(blues) > mod1) {
            zeros.pb(blues.back());
            blues.pop_back();
        }
        while (SIZE(reds) > mod2) {
            zeros.pb(reds.back());
            reds.pop_back();

        }

        int i = 3;
        for (auto z : zeros) {
            ans[z] = i;
            i += 3;
        }
        i = 1;
        for (auto o : blues) {
            ans[o] = i;
            i += 3;
        }
        i = 2;
        for (auto t : reds) {
            ans[t] = i;
            i += 3;
        }
        return ans;
    }
}

int main(void)
{
     //// for debug
     //{
     //    while(1) {
     //        int N = rand() % 10 + 2;
     //        vector<pair<int, int>> ABs;
     //        Dijkstra<int> dij(N);
     //        FOR(n, 2, N + 1) {
     //            int v = (rand() % (n - 1)) + 1;
     //            ABs.pb(mp(n, v));
     //            cout << "edge: " << n << " " << v << endl;
     //            dij.add_undir_edge(n - 1, v - 1, 1);
     //        }
     //        auto ans = solve(N, ABs);

     //        //// 検算
     //        //cout << "ans: ";
     //        //FOR(i, 1, N + 1) {
     //        //    cout << " " << ans[i];
     //        //}
     //        //cout << endl;

     //        REP(a, N) {
     //            auto dist = dij.shortest_path(a);
     //            REP(b, N) {
     //                if (a == b) continue;
     //                if (dist[b] == 3) {
     //                    if (ans.empty()) {
     //                        cout << "error! ans = -1" << endl;
     //                        return 0;
     //                        break;
     //                    }

     //                    int val1 = ans[a + 1];
     //                    int val2 = ans[b + 1];
     //                    if ((val1 + val2) % 3 == 0 || (val1 * val2) % 3 == 0) continue;

     //                    cout << "error!" << endl;
     //                    cout << "v" << (a + 1) << " and v" << (b + 1) << " fail" << endl;
     //                    return 0;
     //                }
     //            }
     //        }

     //        cout << endl;
     //    }
     //}

    cin.sync_with_stdio(false);
    int N;
    cin >> N;
    vector<pair<int, int>> ABs(N-1);

    REP(n, N - 1) {
        int a, b;
        cin >> a >> b;
        ABs[n] = mp(a, b);
    }

    auto ans = solve(N, ABs);
    if (SIZE(ans) == 0) {
        cout << -1 << endl;
    }
    else {
        FOR(i, 1, N + 1) {
            if (i != 1) cout << " ";
            cout << ans[i];
        }
        cout << endl;
    }

    
    return 0;
}


Measured memory_bytes: 32848