亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

在C#中使用二叉樹(shù)實(shí)時(shí)計(jì)算海量用戶(hù)積分排名的實(shí)現(xiàn)詳解

 更新時(shí)間:2020年01月07日 10:36:22   作者:balahoho  
這篇文章主要介紹了在C#中使用二叉樹(shù)實(shí)時(shí)計(jì)算海量用戶(hù)積分排名的實(shí)現(xiàn)詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

從何說(shuō)起

前些天和朋友討論一個(gè)問(wèn)題,他們的應(yīng)用有幾十萬(wàn)會(huì)員然后對(duì)應(yīng)有積分,現(xiàn)在想做積分排名的需求,問(wèn)有沒(méi)有什么好方案。這個(gè)問(wèn)題也算常見(jiàn),很多地方都能看到,常規(guī)做法一般是數(shù)據(jù)定時(shí)跑批把計(jì)算結(jié)果到中間表然后直接查表就行,或者只顯示個(gè)TOP N的排行榜,名次高的計(jì)算真實(shí)名次,名次比較低的直接顯示在xxx名開(kāi)外這種。但是出于探索問(wèn)題的角度,我還是想找一下有沒(méi)有實(shí)時(shí)計(jì)算的辦法,并且效率能夠接受。

在博客園搜到一篇不錯(cuò)的文章,基本羅列了常用的方案,每種算法詳細(xì)介紹了具體思路,其中基于二叉樹(shù)的算法是個(gè)非常不錯(cuò)的方案,文章中只給了思路沒(méi)有給出代碼,于是我決定自己用C#實(shí)現(xiàn)出來(lái)。

這里只討論具體算法實(shí)現(xiàn),不考慮業(yè)務(wù)需求是否合理。

思路解析

關(guān)于算法核心思想前面的文章中寫(xiě)的很詳細(xì),我不再重復(fù)描述,這里只用一個(gè)具體示例演示這個(gè)過(guò)程。
假設(shè)積分范圍是0-5,我們對(duì)它不斷進(jìn)行中位分區(qū)直到不能分為止,形成如下一棵二叉樹(shù):

其中每個(gè)樹(shù)節(jié)點(diǎn)包含2個(gè)信息:節(jié)點(diǎn)范圍range[min,max) 和命中數(shù)量計(jì)數(shù)器count ,可以看到葉子節(jié)點(diǎn)的range一定是相鄰的2個(gè)數(shù)。

假如現(xiàn)在有一個(gè)積分3要插入到樹(shù)中,該如何操作呢?當(dāng)前節(jié)點(diǎn)從根節(jié)點(diǎn)開(kāi)始,分別判斷是否包含于左右子節(jié)點(diǎn),如果包含的話(huà)當(dāng)前節(jié)點(diǎn)改為這個(gè)子節(jié)點(diǎn),同時(shí)計(jì)數(shù)器加1,然后再次進(jìn)行相同判斷,直到遍歷到葉子節(jié)點(diǎn)為止,遍歷順序如下:

再依次插入1和4,二叉樹(shù)的演變情況為:


數(shù)據(jù)放進(jìn)去后怎么判斷它是排名多少呢?還是從根節(jié)點(diǎn)開(kāi)始,判斷它是否包含于左子節(jié)點(diǎn),如果包含的話(huà)說(shuō)明它比右子節(jié)點(diǎn)中count個(gè)數(shù)?。ㄔ赾ount名之外),然后再往下一級(jí)做同樣的判斷;如果包含于右子節(jié)點(diǎn)那就繼續(xù)往下判斷,直到碰到葉子節(jié)點(diǎn)為止。依次累加count最后加上葉子節(jié)點(diǎn)占的一位就得到了它在這棵樹(shù)里的排名,以1為例演示判斷步驟(排名為2+1=3):

好了,一切就緒,只欠代碼。

擼碼實(shí)現(xiàn)

樹(shù)結(jié)構(gòu)由節(jié)點(diǎn)構(gòu)成,那首先設(shè)計(jì)一個(gè)節(jié)點(diǎn)類(lèi):

  /// <summary>
  /// 樹(shù)節(jié)點(diǎn)對(duì)象
  /// </summary>
  public class TreeNode
  {
    /// <summary>
    /// 節(jié)點(diǎn)的最小值
    /// </summary>
    public int ValueFrom { get; set; }

    /// <summary>
    /// 節(jié)點(diǎn)的最大值
    /// </summary>
    public int ValueTo { get; set; }

    /// <summary>
    /// 在節(jié)點(diǎn)范圍內(nèi)的數(shù)量
    /// </summary>
    public int Count { get; set; }

    /// <summary>
    /// 節(jié)點(diǎn)高度(樹(shù)的層級(jí))
    /// </summary>
    public int Height { get; set; }

    /// <summary>
    /// 父節(jié)點(diǎn)
    /// </summary>
    public TreeNode Parent { get; set; }

    /// <summary>
    /// 左子節(jié)點(diǎn)
    /// </summary>
    public TreeNode LeftChildNode { get; set; }

    /// <summary>
    /// 右子節(jié)點(diǎn)
    /// </summary>
    public TreeNode RightChildNode { get; set; }
  }

樹(shù)節(jié)點(diǎn)的屬性主要包含范圍值ValueFrom、ValueTo、計(jì)數(shù)器Count、左子節(jié)點(diǎn)LeftChildNode和右子節(jié)點(diǎn)RightChildNode,由此組成一個(gè)有層次的樹(shù)結(jié)構(gòu)。
然后就是定義我們的樹(shù)對(duì)象了,它的核心字段就是代表源頭的根節(jié)點(diǎn):

  public class RankBinaryTree
  {
    /// <summary>
    /// 根節(jié)點(diǎn)
    /// </summary>
    private TreeNode _root;

  }

根據(jù)前面的算法思想,創(chuàng)建樹(shù)的時(shí)候要用積分范圍初始化所有節(jié)點(diǎn),這里約定了最小積分為0,通過(guò)構(gòu)造函數(shù)傳入最大值并創(chuàng)建樹(shù)結(jié)構(gòu):

   /// <summary>
    /// 構(gòu)造函數(shù)初始化根節(jié)點(diǎn)
    /// </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>
    /// 遍歷創(chuàng)建子節(jié)點(diǎn)
    /// </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;
    }

有了樹(shù)以后下一步就是往里面插入數(shù)據(jù),根據(jù)前面介紹的邏輯:

  /// <summary>
    /// 往樹(shù)中插入一個(gè)值
    /// </summary>
    /// <param name="value"></param>
    public void Insert(int value)
    {
      InnerInsert(_root, value);
      _data.Add(value);
    }

    /// <summary>
    /// 子節(jié)點(diǎn)判斷范圍遍歷插入
    /// </summary>
    /// <param name="node"></param>
    /// <param name="value"></param>
    private void InnerInsert(TreeNode node, int value)
    {
      if (node == null) return;
      //判斷是否在這個(gè)節(jié)點(diǎn)范圍內(nèi)
      if (value >= node.ValueFrom && value < node.ValueTo)
      {
        //更新節(jié)點(diǎn)總數(shù)信息
        node.Count++;
        //更新左子節(jié)點(diǎn)
        InnerInsert(node.LeftChildNode, value);
        //更新右子節(jié)點(diǎn)
        InnerInsert(node.RightChildNode, value);
      }
    }

下一步提供方法獲取指定值在樹(shù)中的排名:

   /// <summary>
    /// 從樹(shù)中獲取總排名
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    public int GetRank(int value)
    {
      if (value < 0) return 0;
      return InnerGet(_root, value);
    }

    /// <summary>
    /// 遍歷子節(jié)點(diǎn)獲取累計(jì)排名
    /// </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)
      {
        //當(dāng)這個(gè)值存在于左子節(jié)點(diǎn)中時(shí),要累加右子節(jié)點(diǎn)的總數(shù)(表示這個(gè)數(shù)在多少名之后)
        return node.RightChildNode.Count + InnerGet(node.LeftChildNode, value);
      }
      else
      {
        //如果在右子節(jié)點(diǎn)中就繼續(xù)遍歷
        return InnerGet(node.RightChildNode, value);
      }
    }

到這里,核心功能已經(jīng)實(shí)現(xiàn)了??紤]到有積分更新的情況,我們可以加上節(jié)點(diǎn)更新和刪除的方法。刪除很容易,和插入逆向操作就行,更新就更容易了,把舊節(jié)點(diǎn)刪除再計(jì)算出新值插入即可,完整代碼已經(jīng)上傳到Github。
這棵樹(shù)究竟效率如何,下面我們跑個(gè)分看看。

測(cè)試走起來(lái)

在測(cè)試程序中,我模擬了積分范圍0-1000000的場(chǎng)景,這個(gè)范圍幾乎覆蓋了真實(shí)業(yè)務(wù)中90%的積分值,100萬(wàn)積分以上的會(huì)員系統(tǒng)應(yīng)該比較少見(jiàn)了。

而會(huì)員的積分值分布也是不均勻的,一般來(lái)說(shuō)擁有小額積分的用戶(hù)比例最大,積分值越高所占用戶(hù)比例越小。
在程序中我假設(shè)有100萬(wàn)個(gè)會(huì)員,其中50W用戶(hù)積分都在100以?xún)?nèi),30W用戶(hù)積分在100-10000,15W用戶(hù)積分在10000-50000,5W用戶(hù)積分在50000以上。

下面是各個(gè)操作的耗時(shí)時(shí)間:

可以看到,這個(gè)效率不是一般的快啊,其中獲取排名的查詢(xún)時(shí)間幾乎可以忽略不計(jì)。
這時(shí)候有人問(wèn)了,這么多數(shù)據(jù)會(huì)不會(huì)非常吃?xún)?nèi)存,下面用任務(wù)管理器分別查看不使用樹(shù)和使用樹(shù)的內(nèi)存情況:


運(yùn)行環(huán)境是.NetCore3.0 Console,測(cè)試主機(jī)配置情況:

100萬(wàn)數(shù)據(jù)只有130M內(nèi)存占用,對(duì)現(xiàn)代計(jì)算機(jī)來(lái)說(shuō)簡(jiǎn)直是灑灑水~

業(yè)務(wù)環(huán)境中使用務(wù)必注意線(xiàn)程安全問(wèn)題?。?!

寫(xiě)在最后

以上的二叉樹(shù)算法處理排名問(wèn)題確實(shí)比較巧妙,實(shí)現(xiàn)起來(lái)也不算特別復(fù)雜,如果上述代碼有缺陷或有其他更好的方案,歡迎探討,也算拋磚引玉了~

完整代碼及測(cè)試用例請(qǐng)戳這里https://github.com/hey-hoho/NetCoreDemo/tree/master/ConsoleApp/ScoreRank

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • C#?Windows?Forms中實(shí)現(xiàn)控件之間的連接線(xiàn)的方法詳解

    C#?Windows?Forms中實(shí)現(xiàn)控件之間的連接線(xiàn)的方法詳解

    這篇文章主要為大家詳細(xì)介紹了如何在C#?Windows?Forms應(yīng)用程序中實(shí)現(xiàn)繪圖工具中多個(gè)控件之間的連接線(xiàn)功能,文中的示例代碼講解詳細(xì),需要的可以參考下
    2024-02-02
  • C#圖像處理之邊緣檢測(cè)(Smoothed)的方法

    C#圖像處理之邊緣檢測(cè)(Smoothed)的方法

    這篇文章主要介紹了C#圖像處理之邊緣檢測(cè)(Smoothed)的方法,使用自定義smoothed算子實(shí)現(xiàn)對(duì)圖像邊緣檢測(cè)的功能,需要的朋友可以參考下
    2015-04-04
  • C#和lua相互調(diào)用的方法教程

    C#和lua相互調(diào)用的方法教程

    lua是一種腳本語(yǔ)言,可以方便的移植到各種宿主語(yǔ)言中,并且可以支持熱更新,在游戲開(kāi)發(fā)中也能當(dāng)做主要的語(yǔ)言來(lái)編寫(xiě)游戲的邏輯,所以這篇文章主要給大家介紹了關(guān)于C#和lua相互調(diào)用的方法教程,需要的朋友可以參考下。
    2017-11-11
  • 區(qū)分WCF與WebService的異同、優(yōu)勢(shì)

    區(qū)分WCF與WebService的異同、優(yōu)勢(shì)

    這篇文章主要幫助大家區(qū)分WCF與WebService的異同、優(yōu)勢(shì),分為三大方面進(jìn)行研究學(xué)習(xí),感興趣的小伙伴們可以參考一下
    2016-03-03
  • c# 垃圾回收(GC)優(yōu)化

    c# 垃圾回收(GC)優(yōu)化

    這篇文章主要介紹了c# 垃圾回收(GC)優(yōu)化的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)c#,感興趣的朋友可以了解下
    2021-02-02
  • Unity實(shí)現(xiàn)VR中在黑板上寫(xiě)字效果

    Unity實(shí)現(xiàn)VR中在黑板上寫(xiě)字效果

    這篇文章主要為大家詳細(xì)介紹了Unity實(shí)現(xiàn)VR中在黑板上寫(xiě)字效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-07-07
  • C#實(shí)現(xiàn)文本讀取的7種方式

    C#實(shí)現(xiàn)文本讀取的7種方式

    這篇文章主要介紹了C#實(shí)現(xiàn)文本讀取的7種方式,文本讀取在上位機(jī)開(kāi)發(fā)中經(jīng)常會(huì)使用到,實(shí)現(xiàn)的方式也有很多種,下面我們就來(lái)分享七種方式,需要的小伙伴可以參考一下
    2022-05-05
  • 你是不是這樣寫(xiě)異常處理代碼的呢?

    你是不是這樣寫(xiě)異常處理代碼的呢?

    本篇文章是對(duì),你是不是這樣寫(xiě)異常處理代碼的進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下
    2013-05-05
  • 深入理解C#之枚舉

    深入理解C#之枚舉

    這篇文章主要介紹了C#中可枚舉類(lèi)型,IEnumerable和IEnumerator接口及其泛型實(shí)現(xiàn)和迭代器,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-07-07
  • C#中常見(jiàn)的文件處理操作小結(jié)

    C#中常見(jiàn)的文件處理操作小結(jié)

    這篇文章主要為大家詳細(xì)介紹了C#中常見(jiàn)的一些文件處理操作,例如文件管理,獲取文件信息和控制處理文件,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2024-03-03

最新評(píng)論