Java解決TopK問題(使用集合和直接實現)

在處理大量數據的時候,有時候每每須要找出Top前幾的數據,這時候若是直接對數據進行排序,在處理海量數據的時候每每就是不可行的了,並且在排序最好的時間複雜度爲nlogn,當n遠大於須要獲取到的數據的時候,時間複雜度就顯得太高。
使用最小堆或者最大堆能夠很好地解決Top大問題或者Top小問題。java

  • Top大問題解決思路:使用一個固定大小的最小堆,當堆滿後,每次添加數據的時候與堆頂元素比較,若小於堆頂元素,則捨棄,若大於堆頂元素,則刪除堆頂元素,添加新增元素,對堆進行從新排序。
  • Top小問題解決思路:使用一個固定大小的最大堆,當堆滿後,每次添加數據到時候與堆頂元素進行比較,若大於堆頂元素,則捨棄,若小於堆頂元素,則刪除堆頂元素,添加新增元素,對堆進行從新排序。

對於n個數,取Top m個數,時間複雜度爲O(nlogm),這樣在n較大狀況下,是優於nlogn的時間複雜度的。數據結構

好比10000個數據,取前100大的數,那麼時間複雜度就是O(10000log100)。
由於在插入數據的時候須要遍歷元素時間複雜度達到了O(10000),而後每次插入過程當中進行調整的複雜度爲O(log100),因此整體時間複雜度爲O(10000log100)。ide

使用Java類庫集合實現

Java集合中的PriorityQueue就能夠實現最大堆或者最小堆,從名字能夠知道該集合是優先隊列,數據結構中的優先隊列就是使用堆來實現的。函數

// 底層經過一個Object類型數據保存元素
transient Object[] queue;

// 經過Comparator制定比較方法
private final Comparator<? super E> comparator;


// 其中一個構造函數
public PriorityQueue(int initialCapacity,
                     Comparator<? super E> comparator) {
    // Note: This restriction of at least one is not actually needed,
    // but continues for 1.5 compatibility
    if (initialCapacity < 1)
        throw new IllegalArgumentException();
    this.queue = new Object[initialCapacity];
    this.comparator = comparator;
}

下面就使用PriorityQueue來實現最小堆和最大堆。this

  • 在構造PriorityQueue的時候須要傳入一個size和一個比較函數,制定堆中元素比較規則。
  • 重寫compare(o1, o2)方法,最小堆使用o1 - o2,最大堆使用o2 - o1。
public class TopK<E extends Comparable> {
    private PriorityQueue<E> queue;
    private int maxSize; //堆的最大容量

    public TopK(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalStateException();
        }
        this.maxSize = maxSize;
        this.queue = new PriorityQueue<>(maxSize, new Comparator<E>() {
            @Override
            public int compare(E o1, E o2) {
                // 最大堆用o2 - o1,最小堆用o1 - o2
                return (o1.compareTo(o2));
            }
        });
    }

    public void add(E e) {
        if (queue.size() < maxSize) {
            queue.add(e);
        } else {
            E peek = queue.peek();
            if (e.compareTo(peek) > 0) {
                queue.poll();
                queue.add(e);
            }
        }
    }

    public List<E> sortedList() {
        List<E> list = new ArrayList<>(queue);
        Collections.sort(list);
        return list;
    }

    public static void main(String[] args) {
        int[] array = {4, 5, 1, 6, 2, 7, 3, 8};
        TopK pq = new TopK(4);
        for (int n : array) {
            pq.add(n);
        }
        System.out.println(pq.sortedList());
    }
}

運行結果:
rest

使用Java實現

經過上述講述,基本瞭解最大堆和最小堆狀況以及它們與TopK問題的關係,上面是使用集合實現,下面使用Java來實現最小堆,並解決TopK大問題。code

  • 限定數據大小。
  • 若堆滿,則插入過程當中與堆頂元素比較,並作相應操做。
  • 每次刪除堆頂元素後堆作一次調整,保證最小堆特性。
public class TopK {
    int[] items;
    int currentSize = 0;

    // 初始化爲size + 1,從下標1開始保存元素。
    public TopK(int size) {
        items = new int[size + 1];
    }

    // 插入元素
    public void insert(int x) {
        if (currentSize == items.length - 1) {
            if (compare(x, items[1]) < 0) {
                return;
            } else if (compare(x, items[1]) > 0) {
                deleteMin();
            }
        }

        int hole = ++currentSize;
        for (items[0] = x; compare(x, items[hole / 2]) < 0; hole /= 2) {
            items[hole] = items[hole / 2];
        }
        items[hole] = x;
    }

    // 刪除最小堆中最小元素
    public int deleteMin() {
        int min = items[1];
        items[1] = items[currentSize--];
        percolateDown(1);
        return min;
    }

    // 下濾
    public void percolateDown(int hole) {
        int child;
        int temp = items[1];

        for (; hole * 2 <= currentSize; hole = child) {
            child = 2 * hole;
            if (child != currentSize && compare(items[child + 1], items[child]) == -1) {
                child++;
            }
            if (compare(items[child], temp) < 0) {
                items[hole] = items[child];
            } else {
                break;
            }
        }
        items[hole] = temp;
    }

    // 制定比較規則
    public static int compare(int a, int b) {
        if (a < b) {
            return -1;
        } else if (a > b) {
            return 1;
        }
        return 0;
    }

    public static void main(String[] args) {
        TopK topK = new TopK(10);
        for (int i = 1; i <= 100; i++) {
            topK.insert(i);
        }
        for (int j = 1; j <= topK.currentSize; j++) {
            System.out.print(topK.items[j] + " ");
        }
        System.out.println();
    }
}

運行結果:
blog