最常使用的K个单词

136 阅读2分钟

「这是我参与2022首次更文挑战的第14天,活动详情查看:2022首次更文挑战」。

在实时数据流中找到最常使用的k个单词. 实现TopK类中的三个方法: TopK(k), 构造方法 add(word), 增加一个新单词 topk(), 得到当前最常使用的k个单词.

LintCode

image.png

一、分析

原生的堆结构不能搞定一些事,已经进到堆里的东西,如果要改它的数据项还必须重新排(大根堆/小根堆),原生的堆是做不到的没有反向索引表,没法做到自调整

出现次数最多的字符串

数据结构:

  • map:key=>字符串,value=>次数
  • 数组:小根堆,对象(属性有字符串、次数),次数比大小
  • map:反向索引表,key=>字符串,value=>数组索引下标

利用小根堆实现,为什么是小根堆,因为每次来的字符串跟小根堆的头位置进行比较大小,如果在topK范围内,大于头位置,则替换,进一步heapify,总之,词频低的在上边,词频高的在下边

二、实现

public class TopK {

    private Node[] heap;
    private int heapSize;
    // 词频表   key  abc   value  (abc,7)
    private HashMap<String, Node> strNodeMap;
    // 反向索引表
    private HashMap<Node, Integer> nodeIndexMap;
    private NodeHeapComp comp;
    private TreeSet<Node> treeSet;

    public TopK(int K) {
        heap = new Node[K];
        heapSize = 0;
        strNodeMap = new HashMap<>();
        nodeIndexMap = new HashMap<>();
        comp = new NodeHeapComp();
        treeSet = new TreeSet<>(new NodeTreeSetComp());
    }

    public static class Node {
        public String str;
        public int times;

        public Node(String s, int t) {
            str = s;
            times = t;
        }
    }

    public static class NodeHeapComp implements Comparator<Node> {

        @Override
        public int compare(Node o1, Node o2) {
            return o1.times != o2.times ? (o1.times - o2.times) : (o2.str.compareTo(o1.str));
        }

    }

    public static class NodeTreeSetComp implements Comparator<Node> {

        @Override
        public int compare(Node o1, Node o2) {
            return o1.times != o2.times ? (o2.times - o1.times) : (o1.str.compareTo(o2.str));
        }

    }

    public void add(String str) {
        if (heap.length == 0) {
            return;
        }
        // str   找到对应节点  curNode
        Node curNode = null;
        // 对应节点  curNode  在堆上的位置
        int preIndex = -1;
        if (!strNodeMap.containsKey(str)) {
            curNode = new Node(str, 1);
            strNodeMap.put(str, curNode);
            nodeIndexMap.put(curNode, -1);
        } else {
            curNode = strNodeMap.get(str);
            // 要在time++之前,先在treeSet中删掉
            // 原因是因为一但times++,curNode在treeSet中的排序就失效了
            // 这种失效会导致整棵treeSet出现问题
            if (treeSet.contains(curNode)) {
                treeSet.remove(curNode);
            }
            curNode.times++;
            preIndex = nodeIndexMap.get(curNode);
        }
        if (preIndex == -1) {
            if (heapSize == heap.length) {
                if (comp.compare(heap[0], curNode) < 0) {
                    treeSet.remove(heap[0]);
                    treeSet.add(curNode);
                    nodeIndexMap.put(heap[0], -1);
                    nodeIndexMap.put(curNode, 0);
                    heap[0] = curNode;
                    heapify(0, heapSize);
                }
            } else {
                treeSet.add(curNode);
                nodeIndexMap.put(curNode, heapSize);
                heap[heapSize] = curNode;
                heapInsert(heapSize++);
            }
        } else {
            treeSet.add(curNode);
            heapify(preIndex, heapSize);
        }
    }

    public List<String> topk() {
        ArrayList<String> ans = new ArrayList<>();
        for (Node node : treeSet) {
            ans.add(node.str);
        }
        return ans;
    }

    private void heapInsert(int index) {
        while (index != 0) {
            int parent = (index - 1) / 2;
            if (comp.compare(heap[index], heap[parent]) < 0) {
                swap(parent, index);
                index = parent;
            } else {
                break;
            }
        }
    }

    private void heapify(int index, int heapSize) {
        int l = index * 2 + 1;
        int r = index * 2 + 2;
        int smallest = index;
        while (l < heapSize) {
            if (comp.compare(heap[l], heap[index]) < 0) {
                smallest = l;
            }
            if (r < heapSize && comp.compare(heap[r], heap[smallest]) < 0) {
                smallest = r;
            }
            if (smallest != index) {
                swap(smallest, index);
            } else {
                break;
            }
            index = smallest;
            l = index * 2 + 1;
            r = index * 2 + 2;
        }
    }

    private void swap(int index1, int index2) {
        nodeIndexMap.put(heap[index1], index2);
        nodeIndexMap.put(heap[index2], index1);
        Node tmp = heap[index1];
        heap[index1] = heap[index2];
        heap[index2] = tmp;
    }
}

三、总结

加强堆的深刻理解,反向索引表