대규모 사용자 순위를 달성하기 위해 계산 실시간을 사용하여 C #에서 이진 트리

어디서부터 시작

며칠 문제가 친구 일 전 토론, 애플리케이션은 회원 수십만 후 해당 지점이 있고, 지금 순위를 요구하려면, 더 좋은 솔루션이없는 물었다. 이 문제는 여러 곳에서 볼 수있는 일반적인 고려 될 수 있으며, 종래의 일반적인 접근 방식은 다음 직접 행하거나, 단지 TOP N의 리더를 보여 높은 순위 사실을 계산하는 순위 및 테이블 룩업 테이블의 중앙에 배치 데이터 타이밍 계산을 실행하는 것입니다, 이 이름은 멀리 xxx는 상대적으로 낮은 순위에 직접 표시됩니다. 그러나 관점의 탐사를위한 수단을 계산 실시간이 있다면 나는 여전히 찾으려 및 효율성은 받아 들일 수 있습니다.
좋은의에 정원 블로그 검색에서 기사 , 기본 목록 일반적인 시나리오, 각 알고리즘은 매우 좋은 프로그램 인 이진 트리 알고리즘을 기반으로, 세부 특정 아이디어에 설명되어 있습니다, 기사는 더이 때문에, 코드를 부여 생각했다 포기하지 나는 그것들을 달성하기 위해 C # .NET을 사용하기로 결정했다.

비즈니스 요구 사항이 합리적 여기에만 관계없이, 특정 알고리즘에 대해 설명합니다.


분석적 사고

알고리즘의 전면에 자세히 기록 된 문서의 핵심 아이디어이다, 나는 특정 예는이 과정을 보여줍니다 만 여기에 대한 설명을 반복하지 않을 것이다.
한다고 가정 통합 범위는 0-5, 우리가 지금까지 분할 할 수없는 지속적으로 중간 파티션, 이진 트리로 형성된다 :

트리의 각 노드는 두 가지 정보를 포함하는 것을 특징 : 노드 범위 range[min,max)및 히트 수 카운터 count, 리프 노드는 인접해야 번호 (2)의 범위를 알 수있다.
당신은 지금 3 점 트리에 삽입해야하는 경우는 어떻게 작동합니까? 포함 된 경우, 리프 노드까지 이송 될 때까지 현재의 루트 노드로부터 시작하여, 각각 좌측 및 우측 자식 노드에 포함되는 결정되고, 상기 자식 노드는 현재 노드 카운터는 동일한 결정 다음 다시 증가되고있는 동안, 탐색 순서는 다음과된다 :

1, 4 삽입 차례로, 이진 트리의 진화는 다음과 같습니다

数据放进去后怎么判断它是排名多少呢?还是从根节点开始,判断它是否包含于左子节点,如果包含的话说明它比右子节点中count个数小(在count名之外),然后再往下一级做同样的判断;如果包含于右子节点那就继续往下判断,直到碰到叶子节点为止。依次累加count最后加上叶子节点占的一位就得到了它在这棵树里的排名,以1为例演示判断步骤(排名为2+1=3):

好了,一切就绪,只欠代码。


撸码实现

树结构由节点构成,那首先设计一个节点类:

    /// <summary>
    /// 树节点对象
    /// </summary>
    public class TreeNode
    {
        /// <summary>
        /// 节点的最小值
        /// </summary>
        public int ValueFrom { get; set; }

        /// <summary>
        /// 节点的最大值
        /// </summary>
        public int ValueTo { get; set; }

        /// <summary>
        /// 在节点范围内的数量
        /// </summary>
        public int Count { get; set; }

        /// <summary>
        /// 节点高度(树的层级)
        /// </summary>
        public int Height { get; set; }

        /// <summary>
        /// 父节点
        /// </summary>
        public TreeNode Parent { get; set; }

        /// <summary>
        /// 左子节点
        /// </summary>
        public TreeNode LeftChildNode { get; set; }

        /// <summary>
        /// 右子节点
        /// </summary>
        public TreeNode RightChildNode { get; set; }
    }

树节点的属性主要包含范围值ValueFrom、ValueTo、计数器Count、左子节点LeftChildNode和右子节点RightChildNode,由此组成一个有层次的树结构。
然后就是定义我们的树对象了,它的核心字段就是代表源头的根节点:

    public class RankBinaryTree
    {
        /// <summary>
        /// 根节点
        /// </summary>
        private TreeNode _root;

    }

根据前面的算法思想,创建树的时候要用积分范围初始化所有节点,这里约定了最小积分为0,通过构造函数传入最大值并创建树结构:

        /// <summary>
        /// 构造函数初始化根节点
        /// </summary>
        /// <param name="max"></param>
        public RankBinaryTree(int max)
        {
            _root = new TreeNode() { ValueFrom = 0, ValueTo = max+1, Height = 1 };
            _root.LeftChildNode = CreateChildNode(_root, 0, max / 2);
            _root.RightChildNode = CreateChildNode(_root, max / 2, max);
        }

        /// <summary>
        /// 遍历创建子节点
        /// </summary>
        /// <param name="current"></param>
        /// <param name="min"></param>
        /// <param name="max"></param>
        /// <returns></returns>
        private TreeNode CreateChildNode(TreeNode current, int min, int max)
        {
            if (min == max) return null;
            var node = new TreeNode() { ValueFrom = min, ValueTo = max, Height = current.Height + 1 };
            node.Parent = current;
            int center = (min + max) / 2;
            if (min < max - 1)
            {
                node.LeftChildNode = CreateChildNode(node, min, center);
                node.RightChildNode = CreateChildNode(node, center, max);
            }
            return node;
        }

有了树以后下一步就是往里面插入数据,根据前面介绍的逻辑:

        /// <summary>
        /// 往树中插入一个值
        /// </summary>
        /// <param name="value"></param>
        public void Insert(int value)
        {
            InnerInsert(_root, value);
            _data.Add(value);
        }

        /// <summary>
        /// 子节点判断范围遍历插入
        /// </summary>
        /// <param name="node"></param>
        /// <param name="value"></param>
        private void InnerInsert(TreeNode node, int value)
        {
            if (node == null) return;
            //判断是否在这个节点范围内
            if (value >= node.ValueFrom && value < node.ValueTo)
            {
                //更新节点总数信息
                node.Count++;
                //更新左子节点
                InnerInsert(node.LeftChildNode, value);
                //更新右子节点
                InnerInsert(node.RightChildNode, value);
            }
        }

下一步提供方法获取指定值在树中的排名:

        /// <summary>
        /// 从树中获取总排名
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public int GetRank(int value)
        {
            if (value < 0) return 0;
            return InnerGet(_root, value);
        }

        /// <summary>
        /// 遍历子节点获取累计排名
        /// </summary>
        /// <param name="node"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        private int InnerGet(TreeNode node, int value)
        {
            if (node.LeftChildNode == null || node.RightChildNode == null) return 1;
            if (value >= node.LeftChildNode.ValueFrom && value < node.LeftChildNode.ValueTo)
            {
                //当这个值存在于左子节点中时,要累加右子节点的总数(表示这个数在多少名之后)
                return node.RightChildNode.Count + InnerGet(node.LeftChildNode, value);
            }
            else
            {
                //如果在右子节点中就继续遍历
                return InnerGet(node.RightChildNode, value);
            }
        }

到这里,核心功能已经实现了。考虑到有积分更新的情况,我们可以加上节点更新和删除的方法。删除很容易,和插入逆向操作就行,更新就更容易了,把旧节点删除再计算出新值插入即可,完整代码已经上传到Github。
这棵树究竟效率如何,下面我们跑个分看看。


测试走起来

在测试程序中,我模拟了积分范围0-1000000的场景,这个范围几乎覆盖了真实业务中90%的积分值,100万积分以上的会员系统应该比较少见了。
而会员的积分值分布也是不均匀的,一般来说拥有小额积分的用户比例最大,积分值越高所占用户比例越小。
在程序中我假设有100万个会员,其中50W用户积分都在100以内,30W用户积分在100-10000,15W用户积分在10000-50000,5W用户积分在50000以上。
下面是各个操作的耗时时间:

可以看到,这个效率不是一般的快啊,其中获取排名的查询时间几乎可以忽略不计。
这时候有人问了,这么多数据会不会非常吃内存,下面用任务管理器分别查看不使用树和使用树的内存情况:

运行环境是.NetCore3.0 Console,测试主机配置情况:

100万数据只有130M内存占用,对现代计算机来说简直是洒洒水~

业务环境中使用务必注意线程安全问题!!!


写在最后

以上的二叉树算法处理排名问题确实比较巧妙,实现起来也不算特别复杂,如果上述代码有缺陷或有其他更好的方案,欢迎探讨,也算抛砖引玉了~

完整代码及测试用例请戳这里https://github.com/hey-hoho/NetCoreDemo/tree/master/ConsoleApp/ScoreRank


추천

출처www.cnblogs.com/hohoa/p/12145689.html