阿里云技术面试红宝书-006 排行榜统计

363 阅读2分钟

006 排行榜统计

题目描述

一个网站有很多页面 ( url ), 做一个 url 排行榜功能。排行根据 url 的访问次数 (pv) 排行。 排行榜需要实时准确即:某个页面每一次访问都会实时地影响到排行数据。

提示:排行榜本身也会有很高的实时访问需求, 注意读和写的时间复杂度。

考察点: 数据结构的组合使用。

具体解析如下:

本题在马士兵教育题解里面有出现,高效的排名算法

想要知道某个用户的名次,只需要知道比这个用户高的分的人数,这里作者根据积分范围,创建平衡二叉树

积分范围平衡二叉树

积分范围平衡二叉树操作

代码实现

本代码利用数组构建平衡二叉树,其它可以参考红黑树,跳跃表来实现

说明:

  1. 数组中 count[1] 为二叉树的根(count[0]保留不用)
  2. 节点k(k>1)的左子树为 2 * k(肯定为偶数),右子树为 2 * k + 1(肯定为奇数)

笔记

package rank;

import java.util.concurrent.ConcurrentHashMap;

/**
 * 高效排名算法
 */
public class Rank {
    public static class RankBox {
        private final int N;
        private final int offset;
        int[] count;//表示满二叉树,为方便操作count[0]不使用,count[1]做为root节点,这样节点k的左子树为2*k(偶数),右子树为2*k+1(奇数)

        public RankBox(int n) {
            N = n;
            this.count = new int[N];
            this.offset = n / 2 - 1;//叶子节点的偏移量
        }

        /**
         * 将节点修改为1
         *
         * @param occurrence
         * @param value      -1 or 1
         * @return
         */
        private boolean modifyLeaf(int occurrence, int value) {
            int leaf = occurrence + offset;
            while (leaf > 0 && count[leaf] >= 0) {
                if (count[leaf] + value >= 0) {
                    count[leaf] += value;
                } else {
                    break;
                }
                leaf = leaf / 2;
            }
            return true;
        }

        /**
         * 快速统计等于大于 occ 的数量有多少个
         * 如果节点在左节点区间(左子树),则累加右节点区间的计数
         *
         * @param occ
         * @return
         */
        public int rankFast(int occ) {
            int sum = 0;
            int leaf = occ + offset;
            while (leaf > 0) {
                if (leaf % 2 == 1) {//右子树
                    sum += count[leaf];
                    leaf = leaf / 2;
                } else {
                    if (leaf > 1) {
                        leaf = leaf / 2 + 1;
                    } else {
                        leaf = 0;
                    }
                }
            }
            return sum;
        }

        /**
         * 统计大于等于 occ 的数量有多少个
         *
         * @param occ
         * @return
         */
        public int rank(int occ) {
            int sum = 0;
            int leaf = occ + offset;
            for (int i = leaf; i < N; i++) {
                sum += count[i];
            }
            return sum;
        }

        public boolean add(int occurrence) {
            modifyLeaf(occurrence, 1);
            modifyLeaf(occurrence - 1, -1);
            return true;
        }
    }

    /**
     * 测试代码
     *
     * @param args
     */
    public static void main(String[] args) {
        ConcurrentHashMap<String, Integer> urlCountMap = new ConcurrentHashMap<>();
        int N = 1024;//N为2的幂次方
        RankBox box = new RankBox(N);
        for (int i = 0; i < N / 2; i++) {
            for (int j = i; j < N / 2; j++) {
                String url = "url-" + j;
                Integer sum = urlCountMap.getOrDefault(url, 0) + 1;
                urlCountMap.put(url, sum);
                box.add(sum);
            }
        }
        for (int i = 0; i < N; i++) {
            String url = "url-" + i;
            Integer sum = urlCountMap.getOrDefault(url, 0);
            System.out.println("input [" + url + "] rank:" + box.rank(sum));
        }
    }
}