// optional
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;

public static class Program {
    public static void Main() {
        #region SetAutoFlushIsFalse
#if !DEBUG
        var sw = new StreamWriter(Console.OpenStandardOutput()) { AutoFlush = false };
        Console.SetOut(sw);
#endif
        #endregion

        var p = ReadLib.ReadAsIntArray();

        Console.WriteLine(p.GroupBy(i => i).Count());

        #region Flush
#if !DEBUG
        Console.Out.Flush();
        sw.Close();
#endif
        #endregion
    }
}

static class ReadLib {
    public static long ReadAsLong() {
        return long.Parse(Console.ReadLine());
    }

    public static int ReadAsInt() {
        return int.Parse(Console.ReadLine());
    }

    public static int[] ReadAsIntArray(int step = 0) {
        return Console.ReadLine().Split(' ').Select(s => int.Parse(s) + step).ToArray();
    }

    public static long[] ReadAsLongArray(int step = 0) {
        return Console.ReadLine().Split(' ').Select(s => long.Parse(s) + step).ToArray();
    }

    public static ulong[] ReadAsULongArray() {
        return Console.ReadLine().Split(' ').Select(s => ulong.Parse(s)).ToArray();
    }

    public static string[] ReadAsStringArray() {
        return Console.ReadLine().Split(' ');
    }

    public static int[] ReadNTimesAsInt(int n, int step = 0) {
        return Enumerable.Range(1, n).Select(s => int.Parse(Console.ReadLine()) + step).ToArray();
    }

    public static char[][] ReadNTimesAsCharArray(int n) {
        return Enumerable.Range(1, n).Select(i => Console.ReadLine().ToArray()).ToArray();
    }

    public static long[] ReadNTimesAsLong(int n, int step = 0) {
        return Enumerable.Range(1, n).Select(s => long.Parse(Console.ReadLine()) + step).ToArray();
    }

    public static int[][] ReadNTimesAsIntArray(int n, int step = 0) {
        return Enumerable.Range(1, n).Select(s => ReadAsIntArray(step)).ToArray();
    }

    public static string[] ReadNTimesAsString(int n) {
        return Enumerable.Range(1, n).Select(s => Console.ReadLine()).ToArray();
    }

}

namespace Heaps {
    /// <summary>優先度付きキュー</summary>
    public class PriorityQueue<T> : IEnumerable<T>, IReadOnlyCollection<T> where T : IComparable<T> {
        public List<T> HeapArray { get; }
        public T NIL { get; }
        public bool IsMax { get; }

        public int Count => HeapArray.Count;

        /// <param name="heapArray">ヒープ</param>
        /// <param name="NIL">NILの値</param>
        /// <param name="IsMax">True時：大きい方から取り出す、False時：小さい方から取り出す</param>
        public PriorityQueue(T NIL, IEnumerable<T> heapArray = null, bool IsMax = true) {
            this.HeapArray = heapArray == null ? new List<T>() : heapArray.ToList();
            this.NIL = NIL;
            this.IsMax = IsMax;
            Update();
        }

        /// <summary>KeyのValueを取得</summary>
        public T this[int id] {
            get {
                if (id <= 0 || id > HeapArray.Count) {
                    return NIL;
                }

                return HeapArray[id - 1];
            }
        }

        class Node {
            public int Key { get; }
            public T Value => outer[Key];
            readonly PriorityQueue<T> outer;

            public Node(int key, PriorityQueue<T> outer) {
                Key = key;
                this.outer = outer;
            }

            public Node Left {
                get {
                    var leftKey = 2 * Key;
                    return new Node(leftKey, outer);
                }
            }

            public Node Right {
                get {
                    var rightkey = 2 * Key + 1;
                    return new Node(rightkey, outer);
                }
            }

            public Node Parent {
                get {
                    var parentKey = Key / 2;
                    return new Node(parentKey, outer);
                }
            }
        }

        private Node GetNode(int key) {
            return new Node(key, this);
        }

        /// <summary>max Heap木とする O(N)</summary>
        public void Update() {
            var n = HeapArray.Count() / 2;
            /// <summary>葉から順に処理を行う</summary>
            for (int i = n; i >= 1; i--) {
                Heapify(i);
            }
        }

        /// <summary>idを根とする部分木について、Heap木を維持したまま、idを下降させる</summary>
        private void Heapify(int key) {
            var current = GetNode(key);

            var left = current.Left;
            var right = current.Right;

            var largest = current;
            var sign = IsMax ? 1 : -1;
            if (left.Value.CompareTo(NIL) != 0 && left.Value.CompareTo(current.Value) * sign > 0) {
                largest = left;
            }

            if (right.Value.CompareTo(NIL) != 0 && right.Value.CompareTo(largest.Value) * sign > 0) {
                largest = right;
            }

            if (largest.Key != key) {
                HeapArray.Swap(key - 1, largest.Key - 1);   // 元と異なるときはSwapする
                Heapify(largest.Key); // largest.Key は元のkeyの場所。葉で更にMaxHeaptyを行う。
            }
        }

        /// <summary>keyの値を挿入する</summary>
        public void Enqueue(T value) {
            HeapArray.Add(value);

            var inserted = GetNode(HeapArray.Count);    // 末尾に挿入
            var parent = inserted.Parent;
            var sign = IsMax ? 1 : -1;

            // 挿入Nodeが根になる or 親 > 子になるまで親とNodeを交換
            while (inserted.Key > 1 && value.CompareTo(parent.Value) * sign > 0) {
                HeapArray.Swap(inserted.Key - 1, parent.Key - 1);
                inserted = parent;
                parent = inserted.Parent;
            }
        }

        public T Dequeue() {
            if (HeapArray.Count == 0) {
                return NIL;
            }

            var popVal = HeapArray.First();
            var newRootVal = HeapArray.Last();  // 新しい根となる。

            HeapArray.RemoveAt(HeapArray.Count - 1);    // del last

            if (HeapArray.Count > 0) {
                HeapArray[0] = newRootVal;
                Heapify(1);
            }

            return popVal;
        }

        public T Peek() {
            return HeapArray.First();
        }

        public IEnumerator<T> GetEnumerator() {
            while (HeapArray.Count > 0) {
                yield return Dequeue();
            }
        }

        IEnumerator IEnumerable.GetEnumerator() {
            return GetEnumerator();
        }

        public void Clear() {
            HeapArray.Clear();
        }

        public bool Contains(T val) {
            return HeapArray.Contains(val);
        }

        public PriorityQueue<T> Clone() {
            return new PriorityQueue<T>(NIL, HeapArray.DeepCopy());
        }
    }
}

/// <summary>拡張メソッドs</summary>
static class MyExtention {
    /// <summary>iとjを入れ替える</summary>
    public static void Swap<T>(this T[] arr, int i, int j) {
        var buf = arr[i];
        arr[i] = arr[j];
        arr[j] = buf;
    }

    public static void Swap<T>(this List<T> list, int i, int j) {
        var buf = list[i];
        list[i] = list[j];
        list[j] = buf;
    }

    /// <summary>
    /// partition。末尾sArr[r]を基準として、左に基準より小さい数、右に基準より大きい数が存在するように配列を並び替える。
    /// </summary>
    /// <param name="sArr"></param>
    /// <param name="p">partitionの先頭の添字</param>
    /// <param name="r">partitionの末尾の添字</param>
    /// <returns>partitionの基準が存在する添字</returns>
    public static int Partition(this int[] sArr, int p, int r) {
        var x = sArr[r];
        var i = p - 1;
        for (int j = p; j < r; j++) {
            if (sArr[j] <= x) {
                i++;
                sArr.Swap(i, j);
            }
        }
        sArr.Swap(i + 1, r);
        return i + 1;
    }

    /// <summary>深いコピーをする</summary>
    public static T DeepCopy<T>(this T obj) {
        T result;
        var b = new BinaryFormatter();
        using (var mem = new MemoryStream()) {
            b.Serialize(mem, obj); //  binaryFormatterによりobjをmemにserial化して書き出す
            mem.Position = 0; // memのpotisionを先頭に戻す
            result = (T)b.Deserialize(mem); // memの内容を読み取る。
        }
        return result;
    }

    public static string Left(this string str, int length) {
        return str.Substring(0, length);
    }

    public static string Right(this string str, int length) {
        return str.Substring(str.Length - length, length);
    }

    public static int Find<T>(this T[] array, T obj) {
        return Array.IndexOf(array, obj);
    }

    public static int LastFind<T>(this T[] array, T obj) {
        return Array.LastIndexOf(array, obj);
    }

    /// <summary>同一の値で埋める</summary>
    public static void Initialize<T>(this T[] array, T value) {
        for (int i = 0; i < array.Length; i++) {
            array[i] = value;
        }
    }

    /// <summary>同一の値で埋める</summary>
    public static void Initialize<T>(this T[,] array, T value) {
        for (int i = 0; i < array.GetLength(0); i++) {
            for (int j = 0; j < array.GetLength(1); j++) {
                array[i, j] = value;
            }
        }
    }
}

static class MyMath {
    /// <summary>階乗</summary>
    public static long Factorial(int n) {
        if (n == 0) {
            return 1;
        }

        return Permutation(n, 0);
    }

    public static long Permutation(int n, int k) {
        return Enumerable.Range(0, n - k).Select(i => n - i).Aggregate(1, (b, i) => b * i);
    }

    #region FullPower
    /// <summary>総乗、π</summary>
    public static long FullPower(IEnumerable<long> vs) {
        return vs.Aggregate(1L, (a, b) => a * b);
    }

    public static long FullPower(IEnumerable<int> vs) {
        return vs.Aggregate(1L, (a, b) => a * b);
    }
    #endregion

    /// <summary>最大公約数</summary>
    public static long Gcd(long item1, long item2) {
        var a = Math.Max(item1, item2);
        var b = Math.Min(item1, item2);

        while (true) {
            long mod;
            Math.DivRem(a, b, out mod);
            if (mod == 0) {
                break;
            }

            a = b;
            b = mod;
        }
        return b;
    }

    /// <summary>約分。最大公約数で割ったものを返す。</summary>
    public static Tuple<long, long> DeviedByGcd(long item1, long item2) {
        var gcd = Gcd(item1, item2);
        return new Tuple<long, long>(item1 / gcd, item2 / gcd);
    }

    public static long Lcm(int item1, int item2) {
        return Math.BigMul(item1, item2) / Gcd(item1, item2);
    }

    public static ModCombination ModCombination(int maxN, int mod = 1000000007) {
        return new ModCombination(maxN, mod);
    }

    /// <summary>a^n mod</summary>
    public static int ModPow(long a, long n, int mod = 1000000007) {
        long res = 1;
        long pow = a;

        while (n > 0) {
            if ((n & 1) == 1) {
                res = (pow * res) % mod;    // resにaをかける
            }

            pow = (pow * pow) % mod;    // 次のaはa^2
            n >>= 1;
        }

        return (int)res;
    }

    /// <summary>約数を列挙</summary>
    public static long[] Divisors(long n) {
        var ret = new List<long>();
        var sqrt = Math.Ceiling(Math.Sqrt(n));
        for (long i = 1; i <= sqrt; i++) {
            if (i * i > n) {
                continue;
            }

            if (n % i != 0) {
                continue;
            }

            ret.Add(i);

            if (n / i == i) {
                continue;
            }

            ret.Add(n / i);
        }

        return ret.ToArray();
    }
}

static class MyMethod {
    /// <summary>数え上げて、key, 出現回数に変換</summary>
    public static Dictionary<T, int> CountUp<T>(IEnumerable<T> str) {
        return
            str
            .GroupBy(c => c)
            .Select(g => new KeyValuePair<T, int>(g.Key, g.Count()))
            .ToDictionary(e => e.Key, e => e.Value);
    }

    /// <summary>ある要素を持つindexをすべて返す</summary>
    public static int[] InducesOfValue<T>(this T[] arr, T searchValue) {
        return arr
            .Select((v, i) => new { v, i })
            .Where(o => searchValue.Equals(o.v))
            .Select(o => o.i)
            .ToArray();
    }

    /// <summary>ある要素を持つindexをすべて返す</summary>
    public static int[] Induces<T>(this IEnumerable<T> arr, T searchValue) {
        return arr.ToArray().InducesOfValue(searchValue);
    }
}

/// <summary>組み合わせ</summary>
class ModCombination {
    private int mod;
    private int[] fac;
    private int[] finv;
    private int[] inv;

    public ModCombination(int max, int mod = 1000000007) {
        this.mod = mod;
        fac = new int[max]; // modが法のn!の列

        inv = new int[max]; // indexの逆元の列
        finv = new int[max]; // modを法とした、invの累積の累積

        fac[0] = 1; fac[1] = 1;
        finv[0] = 1; finv[1] = 1;
        inv[1] = 1;

        for (int i = 2; i < max; i++) {
            fac[i] = (int)(Math.BigMul(fac[i - 1], i) % mod);  // factorを求める
            var s = Math.BigMul(inv[mod % i], mod / i) % mod;
            inv[i] = (int)(mod - s);　 // iに対する逆元を求める
            finv[i] = (int)(Math.BigMul(finv[i - 1], inv[i]) % mod);
        }
    }
    /// <summary>nCk</summary>
    public int Calculation(int n, int k) {
        if (n < k) return 0;
        if (n < 0 || k < 0) return 0;

        return (int)(Math.BigMul(fac[n], (int)(Math.BigMul(finv[k], finv[n - k]) % mod)) % mod);
    }
}

/// <summary>あまりを計算するためのintのラッパー</summary>
struct ModInt {
    int Value { get; }
    int Mod { get; }

    public override string ToString() {
        return Value.ToString();
    }

    public static explicit operator ModInt(long value) {
        return new ModInt(value);
    }

    public ModInt(long value, int mod = 1000000007) {
        this.Value = (int)(value % mod);
        this.Mod = mod;
    }

    public static explicit operator int(ModInt modInt) {
        return modInt.Value;
    }

    private static void CheckNotEqualMod(ModInt item1, ModInt item2) {
        if (item1.Mod != item2.Mod) {
            throw new Exception("mod not equal");
        }
    }

    public static ModInt operator +(ModInt item1, ModInt item2) {
        CheckNotEqualMod(item1, item2);
        long value = item1.Value + item2.Value;
        return new ModInt(value, item1.Mod);
    }

    public static ModInt operator *(ModInt item1, ModInt item2) {
        CheckNotEqualMod(item1, item2);
        long value = Math.BigMul(item1.Value, item2.Value);
        return new ModInt(value, item1.Mod);
    }

    public static ModInt operator -(ModInt item1, ModInt item2) {
        CheckNotEqualMod(item1, item2);
        long value = item1.Value - item2.Value;
        if (value < 0) {
            value += item1.Mod;
        }
        return new ModInt(value, item1.Mod);
    }

    public static ModInt operator +(ModInt item1, int item2) {
        return item1 + new ModInt(item2, item1.Mod);
    }

    public static ModInt operator *(ModInt item1, int item2) {
        return item1 * new ModInt(item2, item1.Mod);
    }

    public static ModInt operator -(ModInt item1, int item2) {
        return item1 - new ModInt(item2, item1.Mod);
    }


    public override int GetHashCode() {
        return this.Value;
    }

    public override bool Equals(object obj) {
        if (obj == null || this.GetType() != obj.GetType()) {
            return false;
        }

        var modIntObj = (ModInt)obj;
        return modIntObj.Value == this.Value;
    }

    public static bool operator ==(ModInt item1, ModInt item2) {
        return item1.Value == item2.Value;
    }

    public static bool operator !=(ModInt item1, ModInt item2) {
        return !(item1 == item2);
    }

    public static bool operator ==(ModInt item1, int item2) {
        return item1.Value == item2;
    }

    public static bool operator !=(ModInt item1, int item2) {
        return !(item1.Value == item2);
    }

    public static bool operator >=(ModInt item1, ModInt item2) {
        return item1.Value >= item2.Value;
    }

    public static bool operator <=(ModInt item1, ModInt item2) {
        return item1.Value <= item2.Value;
    }

    public static bool operator >=(ModInt item1, int item2) {
        return item1.Value >= item2;
    }

    public static bool operator <=(ModInt item1, int item2) {
        return item1.Value <= item2;
    }

    public static bool operator >(ModInt item1, ModInt item2) {
        return item1.Value > item2.Value;
    }

    public static bool operator <(ModInt item1, ModInt item2) {
        return item1.Value < item2.Value;
    }

    public static bool operator >(ModInt item1, int item2) {
        return item1.Value >= item2;
    }

    public static bool operator <(ModInt item1, int item2) {
        return item1.Value <= item2;
    }
}

// optional
namespace Graph {
    /// <summary>無向グラフ</summary>
    class UndirectedGraph {
        public Dictionary<int, List<int>> Graph { get; }

        /// <param name="graphArr">point1 to point2</param>
        /// <param name="n">グラフの全要素数</param>
        public UndirectedGraph(int[][] graphArr, int n) {
            Graph = new Dictionary<int, List<int>>();

            for (int i = 1; i <= n; i++) {
                Graph.Add(i, new List<int>());
            }

            foreach (var arr in graphArr) {
                Graph[arr[0]].Add(arr[1]);
                Graph[arr[1]].Add(arr[0]);
            }
        }
    }

    /// <summary>有向グラフの隣接行列表現</summary>
    class AdjacencyMatrixCreator {
        public int[][] Matrix { get; }

        public AdjacencyMatrixCreator(int[][] matrix) {
            this.Matrix = matrix;
        }
        /// <summary>ノード数nの隣接行列表現を生成.1オリジン</summary>
        public AdjacencyMatrixCreator(int n, int defaultVal = 0) {
            Matrix = new int[n + 1][];
            for (int i = 0; i <= n; i++) {
                Matrix[i] = new int[n + 1];
                for (int j = 0; j <= n; j++) {
                    Matrix[i][j] = defaultVal;
                }
            }
        }

        /// <summary>
        /// 辺の追加 (行列に1を足す)
        /// </summary>
        /// <param name="nodeNo">元ノードのNo</param>
        /// <param name="outDegree">出次数</param>
        /// <param name="outNodeNos">向かう先のノードNo</param>
        public void AddEdgeVal(int nodeNo, int outDegree, int[] outNodeNos) {
            foreach (var outNodeNo in outNodeNos) {
                Matrix[nodeNo][outNodeNo] = 1;
            }
        }

        /// <summary>
        /// 辺の削除 (行列を0にする)
        /// </summary>
        /// <param name="nodeNo">元ノードのNo</param>
        /// <param name="outDegree">出次数</param>
        /// <param name="outNodeNos">向かう先のノードNo</param>
        public void DelEdgeVal(int nodeNo, int outDegree, int[] outNodeNos) {
            foreach (var outNodeNo in outNodeNos) {
                Matrix[nodeNo][outNodeNo] = 0;
            }
        }
    }
}

namespace Sort {
    static class MergeSort {
        /// <summary>ソートを行う（mutable）</summary>
        public static void Sort(long[] arr) {
            Sort(arr, 0, arr.Count());
        }


        private static void Sort(long[] sArr, int left, int right) {
            if (left + 1 < right) {
                var mid = (left + right) / 2;
                Sort(sArr, left, mid);  // left ~ mid-1をソート
                Sort(sArr, mid, right); // mid ~ rightをソート
                Merge(sArr, left, mid, right);  // 昇順に並んだ2つの列を結合する。
            }
        }

        // arrのleftとrightは昇順に並ぶのでO(n1+n2)
        private static void Merge(long[] arr, int left, int mid, int right) {
            var lArr = new long[mid - left + 1];
            var rArr = new long[right - mid + 1];
            for (int i = 0; i < mid - left; i++) {
                lArr[i] = arr[left + i];
            }
            lArr[mid - left] = long.MaxValue;

            for (int i = 0; i < right - mid; i++) {
                rArr[i] = arr[mid + i];
            }
            rArr[right - mid] = long.MaxValue;

            int l = 0, r = 0;
            // arrを並び替える

            for (int i = left; i < right; i++) {
                if (lArr[l] > rArr[r]) {
                    arr[i] = rArr[r];
                    r++;
                } else {
                    arr[i] = lArr[l];
                    l++;
                }
            }
        }
    }

    static class QuickSort {
        internal static void Sort(int[] ts) {
            Sort(ts, 0, ts.Count() - 1);
        }
        /// <summary>Quickソート</summary>
        private static void Sort(int[] ts, int p, int r) {
            if (p < r) {
                var q = Partition(ts, p, r);
                Sort(ts, p, q - 1);
                Sort(ts, q + 1, r);
            }
        }

        public static int Partition(int[] sArr, int p, int r) {
            var x = sArr[r];
            var i = p - 1;
            for (int j = p; j < r; j++) {
                if (sArr[j] <= x) {
                    i++;
                    sArr.Swap(i, j);
                }
            }
            sArr.Swap(i + 1, r);
            return i + 1;
        }
    }

    /// <summary>計数ソート</summary>
    static class CountingSort {
        public static int[] Sort(int[] aArr, int k) {
            int[] b, c;
            var n = aArr.Length;
            b = new int[n];
            c = Enumerable.Repeat(0, k + 1).ToArray();


            for (int i = 0; i < n; i++) {
                c[aArr[i]]++;
            }

            for (int i = 1; i <= k; i++) {
                c[i] += c[i - 1];
            }

            for (int i = n - 1; i >= 0; i--) {
                b[c[aArr[i]] - 1] = aArr[i];
                c[aArr[i]]--;
            }

            return b;
        }
    }
}

namespace Tree {
    /// <summary>UnionFind木</summary>
    public class UnionFind {
        Dictionary<int, int> nodeToRoot;
        Dictionary<int, int> rank; // その根が配下に何個nodeがぶら下がっているか

        public UnionFind(int n, int startAt = 0) {
            nodeToRoot = Enumerable.Range(startAt, n).ToDictionary(i => i, i => i);    // n個根を作る
            rank = Enumerable.Range(startAt, n).ToDictionary(i => i, i => 0);
        }

        /// <summary>根を得る</summary>
        public int GetRoot(int x) {
            if (nodeToRoot[x] == x) { // 渡されたノードが根
                return x;
            }
            nodeToRoot[x] = GetRoot(nodeToRoot[x]);  // parentの根を得てset。調べるたびに最適化。

            return nodeToRoot[x];
        }

        /// <summary>xとyの木を併合 => 根を同一にする</summary>
        public void Unite(int x, int y) {
            var rootX = GetRoot(x);
            var rootY = GetRoot(y);

            if (rootX == rootY) {
                return; // pass
            }

            if (rank[rootX] < rank[rootY]) {    // yをxへ併合
                nodeToRoot[rootX] = nodeToRoot[rootY];
            } else {
                nodeToRoot[rootX] = nodeToRoot[rootY];

                if (rank[rootX] == rank[rootY]) {
                    rank[rootX]++;  // yの下に配下が1つ追加される。
                }
            }
        }

        public bool AreSameRoot(int x, int y) {
            return GetRoot(x) == GetRoot(y);
        }
    }

    /// <summary>根付き木</summary>
    public class RootedTree {
        readonly Dictionary<int, Node> nodes;
        const int NIL = -1;
        int startAt;
        int nNode;

        class Node {
            /// <summary>親のID</summary>
            public int Parent { get; set; }
            /// <summary>子の最左端</summary>
            public int LeftChild { get; set; }
            /// <summary>右の兄弟</summary>
            public int RightBrother { get; set; }
            /// <summary>深さ</summary>
            public int Depth { get; set; }

            public Node() {
                Parent = NIL;
                LeftChild = NIL;
                RightBrother = NIL;
                Depth = 0;
            }
        }

        /// <summary>根付き木</summary>
        public RootedTree(int n, int startAt = 0) {
            this.nodes = Enumerable.Range(startAt, n).ToDictionary(i => i, i => new Node());
            this.startAt = startAt;
            this.nNode = n;
        }

        /// <summary>Nodeに子を追加</summary>
        public void SetChilds(int id, int[] childs) {
            if (childs.Length == 0) {
                return;
            }

            nodes[id].LeftChild = childs.First();

            for (int i = 0; i < childs.Length - 1; i++) {
                int childId = childs[i];
                nodes[childId].Parent = id;
                nodes[childId].RightBrother = childs[i + 1];
            }

            nodes[childs.Last()].Parent = id;
        }

        /// <summary>親Idを取得</summary>
        public int GetParentId(int id) {
            return nodes[id].Parent;
        }

        /// <summary>子のidを取得</summary>
        public int[] GetChilds(int id) {
            var childs = new List<int>();

            var child = nodes[id].LeftChild;
            while (child != NIL) {
                childs.Add(child);
                child = nodes[child].RightBrother;
            }

            return childs.ToArray();
        }

        int Root = NIL;
        public int GetRoot() {
            if (Root == NIL) {
                var current = nodes.First().Key;  // 何でもいい
                var parent = nodes[current].Parent;

                while (parent != NIL) {
                    current = parent;
                    parent = nodes[current].Parent;
                }

                Root = current;
            }
            return Root;
        }

        bool isSetedDepth = false;
        /// <summary>深さを取得</summary>
        public int GetDepth(int id) {
            if (!isSetedDepth) {
                var root = GetRoot();
                SetDepth(root, depth: 0);
                isSetedDepth = true;
            }

            var current = id;
            var parent = nodes[id].Parent;
            var depth = 0;
            while (parent != NIL) {
                current = parent;
                parent = nodes[current].Parent;
                depth++;
            }

            return depth;
        }

        private void SetDepth(int id, int depth) {
            var node = nodes[id];
            node.Depth = depth;

            if (node.RightBrother != NIL) {
                SetDepth(node.RightBrother, depth);
            }

            if (node.LeftChild != NIL) {
                SetDepth(node.LeftChild, depth + 1);
            }

        }

        public int NodesCount() {
            return nodes.Count();
        }
    }

    /// <summary>二分木</summary>
    public class BinaryTree {
        const int NIL = int.MinValue;
        readonly int startAt;
        readonly int nNode;
        class Node {
            public int Parent { get; set; }
            public int LeftChild { get; set; }
            public int RightChild { get; set; }

            public int Depth { get; set; }
            public int Height { get; set; }
            /// <summary>唯一の兄弟</summary>
            public int Sibling { get; set; }

            public Node() {
                Parent = NIL;
                LeftChild = NIL;
                RightChild = NIL;
                Sibling = NIL;

                Depth = 0;
                Height = 0;
            }
        }


        Dictionary<int, Node> nodes;
        public BinaryTree(int n, int startAt) {
            this.nodes = Enumerable.Range(startAt, n).ToDictionary(i => i, i => new Node());
            this.startAt = startAt;
            this.nNode = n;
        }

        /// <summary>
        /// 子をセットする
        /// </summary>
        /// <param name="id">ノードのID</param>
        /// <param name="left">左子ID。存在しない場合は-1</param>
        /// <param name="right">右子ID。存在しない場合は-1</param>
        public void SetChilds(int id, int left, int right) {
            nodes[id].LeftChild = left;
            nodes[id].RightChild = right;

            if (left != NIL) {
                nodes[left].Parent = id;
                nodes[left].Sibling = right;
            }

            if (right != NIL) {
                nodes[right].Parent = id;
                nodes[right].Sibling = left;
            }
        }

        /// <summary>Root</summary>
        int Root = NIL;
        public int GetRoot() {
            if (Root == NIL) {
                var current = nodes.First().Key;
                var parent = nodes[current].Parent;

                while (parent != NIL) {
                    current = parent;
                    parent = nodes[current].Parent;
                }

                Root = current;
            }

            return Root;
        }

        #region Depth
        bool isSetDepth = false;
        public int GetDepth(int id) {
            if (!isSetDepth) {
                var root = GetRoot();
                SetDepth(root, 0);
                isSetDepth = true;
            }

            return nodes[id].Depth;
        }

        private void SetDepth(int id, int depth) {
            nodes[id].Depth = depth;

            if (nodes[id].LeftChild != NIL) {
                SetDepth(nodes[id].LeftChild, depth + 1);
            }

            if (nodes[id].RightChild != NIL) {
                SetDepth(nodes[id].RightChild, depth + 1);
            }
        }
        #endregion

        #region Height
        bool isSetHeight = false;
        /// <summary>高さを得る</summary>
        public int GetHeight(int id) {
            if (!isSetHeight) {
                var root = GetRoot();
                SetHeight(root);
                isSetHeight = true;
            }

            return nodes[id].Height;
        }

        public int SetHeight(int id) {
            var node = nodes[id];
            int hLeft = 0;
            int hRight = 0;

            if (node.LeftChild != NIL) {
                hLeft = SetHeight(node.LeftChild) + 1;
            }

            if (node.RightChild != NIL) {
                hRight = SetHeight(node.RightChild) + 1;
            }

            node.Height = Math.Max(hLeft, hRight);
            return node.Height;
        }
        #endregion

        /// <summary>兄弟を得る</summary>
        public int GetSibling(int id) {
            return nodes[id].Sibling;
        }

        /// <summary>親IDを得る</summary>
        public int GetParent(int id) {
            return nodes[id].Parent;
        }

        public int[] GetChilds(int id) {
            var ids = new List<int>();
            if (nodes[id].LeftChild != NIL) {
                ids.Add(nodes[id].LeftChild);
            }

            if (nodes[id].RightChild != NIL) {
                ids.Add(nodes[id].RightChild);
            }

            return ids.ToArray();
        }

        /// <summary>左子を取得。存在しない時はint.MinValue</summary>
        public int GetLeft(int id) {
            return nodes[id].LeftChild;
        }

        /// <summary>右子を取得。存在しない時はint.MinValue</summary>
        public int GetRight(int id) {
            return nodes[id].RightChild;
        }

        public int NodesCount() {
            return nodes.Count();
        }

        #region 巡回
        private enum OrderType {
            Pre, In, Post
        }

        /// <summary>先行順巡回のNodeIdを取得</summary>
        public IEnumerable<int> GetPreOrder() {
            return Process(OrderType.Pre);
        }

        /// <summary>中間順巡回のNodeIdを取得</summary>
        public IEnumerable<int> GetInOrder() {
            return Process(OrderType.In);
        }

        /// <summary>後方順巡回のNodeIdを取得</summary>
        public IEnumerable<int> GetPostOrder() {
            return Process(OrderType.Post);
        }

        /// <summary>巡回を出力。O(N)</summary>
        private IEnumerable<int> Process(OrderType orderType) {
            var order = new List<int>();
            var stack = new Stack<int>();
            var passed = Enumerable.Range(startAt, nNode).ToDictionary(i => i, i => false); // 巡回済
            var orderAdded = Enumerable.Range(startAt, nNode).ToDictionary(i => i, i => false); // 処理済

            Action<int> addOrder = id => {
                if (!orderAdded[id]) {
                    order.Add(id);
                    orderAdded[id] = true;
                }
            };

            stack.Push(GetRoot());

            while (stack.Count > 0) {
                var current = stack.First();
                passed[current] = true;

                if (orderType == OrderType.Pre) {
                    addOrder(current); // 先行順巡回
                }

                int left = GetLeft(current);
                if (left != -1 && !passed[left]) {
                    stack.Push(left);
                    continue;
                }

                if (orderType == OrderType.In) {
                    addOrder(current); // 中間順巡回
                }

                int right = GetRight(current);
                if (right != -1 && !passed[right]) {
                    stack.Push(right);
                    continue;
                }

                if (orderType == OrderType.Post) {
                    addOrder(current);　 // 後方順巡回
                }

                stack.Pop();
            }

            return order;
        }
        #endregion
    }

    /// <summary>二分探索木</summary>
    public class BinarySearchTree {
        static readonly Node NIL = new Node(int.MinValue);
        class Node {
            public int Key { get; set; }
            public Node Parent { get; set; }
            public Node Left { get; set; }
            public Node Right { get; set; }

            public Node(int key) {
                this.Key = key;
                this.Parent = NIL;
                this.Left = NIL;
                this.Right = NIL;
            }
        }

        Node root = NIL;

        #region Operate
        public void Insert(int key) {
            var z = new Node(key);

            var y = NIL;    // xの親
            var x = root;   // Treeの根

            while (x != NIL) {
                y = x;
                if (z.Key < x.Key) {
                    x = x.Left;
                } else {
                    x = x.Right;
                }
            }

            z.Parent = y;

            if (y == NIL) {
                root = z;
            } else if (z.Key < y.Key) {
                y.Left = z;
            } else {
                y.Right = z;
            }
        }
        public bool Find(int key) {
            var nearLeaf = GetNearLeaf(key);
            return nearLeaf.Key == key;
        }

        private Node GetNearLeaf(int key) {
            var x = this.root;
            while (x != NIL && x.Key != key) {
                if (x.Key < key) {
                    x = x.Right;
                } else {
                    x = x.Left;
                }
            }

            return x;
        }

        public void Delete(int key) {
            var cand = GetNearLeaf(key);
            if (cand.Key != key) {
                throw new KeyNotFoundException();
            }

            DeleteNode(cand);
        }

        private void DeleteNode(Node target) {
            // 削除対象ノードを選択
            Node delTarget;
            if (target.Left == NIL || target.Right == NIL) {
                delTarget = target;
            } else {
                delTarget = NextInOrder(target);
            }

            // DelTargetの親の新規子を決める
            Node newChild;
            if (delTarget.Left != NIL) {
                newChild = delTarget.Left;
            } else {
                newChild = delTarget.Right;
            }

            // 新規子 => 親をつなぐ
            if (newChild != NIL) {
                newChild.Parent = delTarget.Parent;
            }

            // 親 => 新規子をつなぐ
            var parent = delTarget.Parent;
            if (parent == NIL) {
                root = newChild;
            } else if (parent.Left == delTarget) {
                parent.Left = newChild;
            } else if (parent.Right == delTarget) {
                parent.Right = newChild;
            }

            // 消したノードの値を渡されたNodeに入れる
            if (delTarget != target) {
                target.Key = delTarget.Key;
            }
        }

        /// <summary>次の中間順巡回のNodeを取得</summary>
        /// <returns>NILの時は木が空</returns>
        private Node NextInOrder(Node node) {
            if (node.Right == NIL) {
                var current = node;
                var parent = node.Parent;

                // 親をたどっていき、初めて左子となっているNodeを取得
                while (parent != NIL && current == parent.Right) {
                    current = parent;
                    parent = current.Parent;
                }

                return node.Parent;
            }

            // 右部分木より最左を取得
            var root = node.Right;
            while (root.Left != NIL) {
                root = root.Left;
            }

            return root;
        }

        /// <summary>
        /// targetの親の子を、新しい子につなぎ替える
        /// </summary>
        /// <param name="target">対象のTarget</param>
        /// <param name="newChild">つなぎ変えた先の子</param>
        private static void ChangeParentOfChild(Node target, Node newChild) {
            var parent = target.Parent;
            if (parent.Left == target) {
                parent.Left = newChild;
            } else if (parent.Right == target) {
                parent.Right = newChild;
            } else {
                throw new KeyNotFoundException();
            }
        }

        #endregion

        #region Order
        public IEnumerable<int> GetInOrder() {
            var orderGetter = new OrderGetter();
            return orderGetter.GetInOrder(root);
        }

        public IEnumerable<int> GetPreOrder() {
            var orderGetter = new OrderGetter();
            return orderGetter.GetPreOrder(root);
        }

        class OrderGetter {
            private List<int> process = new List<int>();

            public IEnumerable<int> GetInOrder(Node root) {
                SetInOrder(root);
                return process;
            }

            public IEnumerable<int> GetPreOrder(Node root) {
                SetPreOrder(root);
                return process;
            }

            private void SetInOrder(Node node) {
                if (node == NIL) {
                    return;
                }

                SetInOrder(node.Left);
                process.Add(node.Key);
                SetInOrder(node.Right);
            }

            private void SetPreOrder(Node node) {
                if (node == NIL) {
                    return;
                }
                process.Add(node.Key);
                SetPreOrder(node.Left);
                SetPreOrder(node.Right);
            }
        }
        #endregion
    }

}

Measured memory_bytes: 13408