/**
 * code generated by JHelper
 * More info: https://github.com/AlexeyDmitriev/JHelper
 * @author jcg
 */

#include <bits/stdc++.h>

#include <bits/stdc++.h>


// Complexity: O(n + k)
template<typename ForwardIt, typename Func>
std::vector<typename std::iterator_traits<ForwardIt>::value_type> counting_sort(ForwardIt first, ForwardIt last, const Func &func)
{
    int maxk = 0, n = 0;
    for (auto it = first; it != last; ++it, ++n)
        maxk = std::max(maxk, func(*it));
    std::vector<int> cnt(maxk + 1);
    for (auto it = first; it != last; ++it)
        ++cnt[func(*it)];
    for (int i = 0, sum = 0; i <= maxk; ++i)
    {
        sum += cnt[i];
        cnt[i] = sum - cnt[i];
    }
    std::vector<typename std::iterator_traits<ForwardIt>::value_type> result(n);
    for (auto it = first; it != last; ++it)
        result[cnt[func(*it)]++] = *it;
    return result;
}


struct parent_representation
{
    std::vector<int> parent;
    std::vector<int> ord; // preorder
};

parent_representation get_parent_representation(int root, const std::vector<int> &u, const std::vector<int> &v)
{
    const int n = u.size() + 1;
    std::vector<int> degree(n), edges(2 * u.size());
    for (int i = 0; i < n - 1; ++i)
    {
        ++degree[u[i]]; ++degree[v[i]];
        edges[i] = i;
        edges[i + u.size()] = i + u.size();
    }
    auto same = [&](int e) { return e < u.size() ? u[e] : v[e - u.size()]; };
    auto other = [&](int e) { return e < u.size() ? v[e] : u[e - u.size()]; };
    edges = counting_sort(edges.begin(), edges.end(), same);
    //std::sort(edges.begin(), edges.end(), [&](int x, int y) { return same(x) < same(y); });
    std::vector<int> first(n, -1), last(n);
    for (int i = 0; i < (int) edges.size(); ++i)
    {
        int e = edges[i];
        if (first[same(e)] == -1)
            first[same(e)] = i;
        last[same(e)] = i;
    }
    std::vector<int> parent(n, -1), ord, stk;
    for (stk.emplace_back(root); !stk.empty();)
    {
        int x = stk.back();
        ord.emplace_back(x);
        stk.pop_back();
        for (int k = first[x]; k <= last[x]; ++k)
        {
            int y = other(edges[k]);
            if (parent[y] == -1 && y != root)
                parent[y] = x, stk.push_back(y);
        }
    }
    //assert(ord.size() == n);
//    for (int i = 0; i < n; ++i)
//        std::cout << i+1 << " " << parent[i]+1 << std::endl;
//    for (int x : ord) std::cout << x+1 << " ";
//    std::cout << std::endl;
    return parent_representation{parent, ord};
}

std::vector<int> strategy_function(const std::vector<int> &u, const std::vector<int> &v)
{
    parent_representation rep = get_parent_representation(0, u, v);
    const auto &parent = rep.parent;
    const auto &ord = rep.ord;
    const int n = ord.size();
    std::vector<int> label(n), used(n), more(n);
    for (int i = n - 1; i >= 0; --i)
    {
        int u = ord[i];
        for (int k : {1, 2, 4, 8, 16})
            more[u] |= more[u] >> k;
        ++more[u];
        int possible = ~used[u] & -more[u];
        label[u] = possible & -possible;
        used[u] |= label[u];
        used[u] &= -label[u];
        if (parent[u] != -1)
        {
            int p = parent[u];
            more[p] |= used[u] & used[p];
            used[p] |= used[u];
        }
    }
    return label;
}

class DUninity
{
public:
    void solve(std::istream& in, std::ostream& out)
    {
        using std::vector;

        int n;
        in >> n;

        vector<int> u(n - 1), v(n - 1);
        for (int i = 0; i < n - 1; ++i)
        {
            in >> u[i] >> v[i];
            --u[i], --v[i];
        }

        auto label = strategy_function(u, v);
        out << std::__lg(*std::max_element(label.begin(), label.end())) << '\n';
    }
};


int main()
{
    std::ios_base::sync_with_stdio(0);
    std::istream& in(std::cin);
    std::ostream& out(std::cout);
    in.tie(0);
    DUninity solver;
    solver.solve(in, out);
    return 0;
}


Measured memory_bytes: 5352