前言

当前所有算法都使用测试用例运行过,但是不保证100%的测试用例,如果存在问题务必联系批评指正~

问题介绍

原问题
设计一个数据结构,该数据结构有以下几个功能:
1、能够无限接收字符串类型的元素
2、能够随时打印所有出现的字符串中,出现次数最多的字符串

解决方案

原问题
1、首先设计一个容器record能够存储当前字符串和当前字符串出现的次数
2、数据结构中含有两个重要的数据结构:一个是小根堆(元素类型为Record),一个是map,map中key为Record,value为当前Record在堆数组中的位置,如果是-1说明不在堆中
3、获取出现次数最多的topk字符串直接将堆中的值全部遍历输出即可
4、加入一个元素时,应该做如下判断:
- 首先判断该元素是否已经出现过,通过map进行判断,没有的话则加入map,增加次数,有的话直接增加次数
- 其次查看当前元素是否在堆中,如果在,则直接直接定位到堆位置进行下沉操作,如果不在则判断堆是否满了,如果没满直接放入,如果满了则判断出现次数是否大于堆顶,大于的话则将堆顶弹出,插入当前元素即可,如果小于堆顶,则什么都不做

代码编写

java语言版本

原问题:
原问题:

 public class TopKRecordCp1 {


    /**
     * 小根堆结构
     */
    private CommonHeapPlus<Record> heap;



    /**
     * 用于判断用户当前输入的是否已经有了
     */
    private Map<String, Record> mapStringRecord;

    /**
     * 用于判断当前记录对象是否在堆中,如果不存在为-1,如果存在则可以直接定位到堆位置
     */
    private Map<Record, Integer> mapRecordInteger;


    public TopKRecordCp1(int k) {
        // 小根
        this.heap = new CommonHeapPlus<>(k, new Comparator<Record>() {
            @Override
            public int compare(Record o1, Record o2) {
                return o2.getTimes() - o1.getTimes();
            }
        });
        mapStringRecord = new HashMap<>();
        mapRecordInteger = new HashMap<>();
    }


    /**
     * 加入一个str
     * @param str
     */
    public void add(String str) {
        if (str == null) {
            return;
        }
        Record cur = null;
        // 先处理第一个map
        if (mapStringRecord.containsKey(str)) {
            cur = mapStringRecord.get(str);
            cur.setTimes(cur.getTimes()+1);
            if (mapRecordInteger.containsKey(cur) && mapRecordInteger.get(cur) >= 0) {
                // 已经在堆中
                heap.heapify(mapRecordInteger.get(cur));
                return;
            }
            // 不再堆中的逻辑可以和下面一样
        }else {
            cur = new Record(str, 1);
            mapStringRecord.put(str, cur);
        }
        // 到这里说明不管cur是否是老str,都不在堆中,所以现在判断是否应该加入堆
        // 看下堆是否满了
        if (!heap.isFull()) {
            //没有满直接放进去,返回所在堆的index
            heap.heapInsetForTopK(cur, mapRecordInteger);
            // 更新indexmap
            //mapRecordInteger.put(cur, curIndex);
        }else{
            // 满了的话需要判断是否有资格进去
            if (heap.getHead().getTimes() >= cur.getTimes()) {
                // 连最小值都大于当前的times,没资格进堆
                mapRecordInteger.put(cur, -1);
            }else {
                // 有资格进堆
                heap.popHead();
                heap.heapInsetForTopK(cur, mapRecordInteger);
                //int curIndex = heap.heapInsert(cur);
                //mapRecordInteger.put(cur, curIndex);
            }
        }
    }


    /**
     * 打印到目前为止的次数topk
     */
    public void printTopK() {
        while (!heap.isEmpty()) {
            Record record = heap.popHead();
            System.out.println("Str: " + record.getVlaue() + " Times : " + record.getTimes());
        }
    }






    /**
     * 存储两个变量
     * value:用户输入的字符串
     * times:该字符串目前出现的次数
     */
    public class Record{



        private String vlaue;

        private int times;


        public Record(String vlaue, int times) {
            this.vlaue = vlaue;
            this.times = times;
        }

        public String getVlaue() {
            return vlaue;
        }

        public void setVlaue(String vlaue) {
            this.vlaue = vlaue;
        }

        public int getTimes() {
            return times;
        }

        public void setTimes(int times) {
            this.times = times;
        }
    }


    public static void main(String[] args) {
        TopKRecordCp1 topKRecordCp1 = new TopKRecordCp1(2);
        topKRecordCp1.add("1");
        topKRecordCp1.add("1");
        topKRecordCp1.add("2");
        topKRecordCp1.add("3");
        topKRecordCp1.printTopK();
    }



}

通用堆结构

public class CommonHeapPlus<T> {

    /**
     * 堆数组
     */
    private Object[] heap;

    /**
     * 堆大小
     */
    private int index;

    /**
     * 比较规则:默认大根堆
     */
    private Comparator<T> comparator;

    /**
     * 仿照Arraylist的初始化方式
     * @param size
     */
    public CommonHeapPlus(int size, Comparator<T> comparator) {
        this.heap = new Object[size];
        this.comparator = comparator;
    }


    /**
     * 插入并上浮
     * 返回最终插入的元素所在的位置
     */
    public int heapInsert(T elm) {
        if (index == heap.length) {
            // 堆满了
            throw new RuntimeException("堆满了,暂时不支持扩容");
        }
        heap[index++] = elm;
        int cur = index - 1;
        while (cur != 0) {
            int parent = (cur-1)/2;
            if (comparator.compare((T)heap[cur], (T)heap[parent]) > 0) {
                CommonUtils.swapPlus(heap, parent, cur);
                cur = parent;
            }else {
                break;
            }
        }
        return cur;
    }

    /**
     * 定制化接口
     * @return
     */
    public int heapInsetForTopK(T elm , Map<T, Integer> map) {
        if (index == heap.length) {
            // 堆满了
            throw new RuntimeException("堆满了,暂时不支持扩容");
        }
        heap[index++] = elm;
        int cur = index - 1;
        // 初始化位置
        map.put(elm, cur);
        while (cur != 0) {
            int parent = (cur-1)/2;
            if (comparator.compare((T)heap[cur], (T)heap[parent]) > 0) {
                CommonUtils.swapPlus(heap, parent, cur);
                // 交换的时候需要更新index
                map.put((T)heap[cur], cur);
                map.put((T)heap[parent], parent);
                cur = parent;
            }else {
                break;
            }
        }
        return cur;
    }


    /**
     * 下沉
     */
    public void heapify(int head) {
        int cur = head;
        int left = (cur * 2) + 1;
        while (left < index) {
            int max = comparator.compare((T) heap[left], (T) heap[cur]) > 0 ? left : cur;
            int right = left + 1;
            while (right < index && comparator.compare((T)heap[right], (T) heap[max]) > 0) {
                max = right;
            }
            if (max == cur) {
                break;
            }
            CommonUtils.swapPlus(heap, cur, max);
            cur = max;
            left = (cur * 2) + 1;
        }
    }

    public boolean isEmpty() {
        return index == 0;
    }

    public boolean isFull() {
        return index == heap.length;
    }


    public T getHead() {
        return (T) heap[0];
    }




    /**
     * 弹出头节点
     * @return
     */
    public T popHead() {
        T res = (T) heap[0];
        CommonUtils.swapPlus(heap, 0, index-1);
        index--;
        heapify(0);
        return res;
    }

    /**
     * 获取当前堆的大小
     * @return
     */
    public int getIndex() {
        return index;
    }

}


c语言版本

正在学习中

c++语言版本

正在学习中

思考感悟

有时候求最大的k个值问题需要用到的恰恰是小根堆,因为堆的特性是实时能够知道当前堆中的最小值和最大值,如果我们知道topK的最小值,才知道当前值是否能够进入堆,如果小于最小值,那么就会小于堆中的任何一个元素,自然没有资格进入堆中。
因此topK问题通常需要反着定义堆的比较规则,这个需要注意一下

写在最后

06-18 17:45