006 排行榜统计
题目描述
一个网站有很多页面 ( url ), 做一个 url 排行榜功能。排行根据 url 的访问次数 (pv) 排行。 排行榜需要实时准确即:某个页面每一次访问都会实时地影响到排行数据。
提示:排行榜本身也会有很高的实时访问需求, 注意读和写的时间复杂度。
考察点: 数据结构的组合使用。
具体解析如下:
本题在马士兵教育题解里面有出现,高效的排名算法
想要知道某个用户的名次,只需要知道比这个用户高的分的人数,这里作者根据积分范围,创建平衡二叉树
代码实现
本代码利用数组构建平衡二叉树,其它可以参考红黑树,跳跃表来实现
说明:
- 数组中 count[1] 为二叉树的根(count[0]保留不用)
- 节点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));
}
}
}