堆结构及堆排序、面试题

82 阅读3分钟

堆结构

  1. 堆结构就是用数组实现的完全二叉树结构
  2. 完全二叉树中如果每棵子数的最大值都在顶部就是大根堆
  3. 完全二叉树中如果每棵子数的最小值都在顶部就是小根堆
  4. 堆结构主要有 heapInsert 与 heapify 操作
  5. Java中优先级队列(PriorityQueue)结构就是堆结构

把数组想象成一个完全二叉树结构,这对于数组下标i
左孩子:2 x i + 1
右孩子:2 x i + 2
父节点:(i - 1)/ 2

heapInsert操作(向堆中新加一个元素):
heapSize记录堆元素个数,同时也表示新加元素要放置的位置
对于堆,每给一个数,heapSize++,同时循环和父元素比较,比父元素大则交换

heapify操作
从index位置往下看,不断地下沉
直到 我的孩子都不再比我大,或者已经没孩子

public class MyMaxHeap {

    /**
     * 左孩子:2 x i + 1
     * 右孩子:2 x i + 2
     * 父节点:(i - 1)/ 2
     */

    private int[] heap;
    //堆大小限制
    private final int limit;
    //堆收集的元素个数
    private int heapSize;

    public MyMaxHeap(int limit) {
        heap = new int[limit];
        this.limit = limit;
        heapSize = 0;
    }

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

    public boolean isFull() {
        return heapSize == limit;
    }

    public void push(int value) throws Exception {
        if (heapSize == limit) {
            throw new Exception("heap is full");
        }
        //放到想象的二叉树的最后一个
        heap[heapSize] = value;

        heapInsert(heap, heapSize++);
    }
    //和父元素比较,比父元素大则交换
    private static void heapInsert(int[] arr, int index) {
        //while停止条件
        //1.arr[index] 不比 arr[index父] 大了
        //2.index 到达0位置
        while (arr[index] > arr[((index - 1)/2)]) {
            swap(arr, index, ((index - 1)/2));
            index = ((index - 1)/2);
        }
    }

    public static void swap(int[] arr, int i, int j) {
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }

    //返回最大值,并且在大根堆中,把最大值删除,剩下的数,依然保持大根堆组织
    public int pop() {
        int ans = heap[0];
        //0位置和heapSize-1位置交换,heapSize减一
        swap(heap, 0, --heapSize);
        heapify(heap, 0, heapSize);
        return ans;
    }

    //从index位置往下看,不断地下沉
    //直到 我的孩子都不再比我大,或者已经没孩子
    private void heapify(int[] arr, int index, int heapSize) {
        int left = index * 2 + 1;
        //左孩子的下标不越界,说明我有孩子
        while (left < heapSize) {
            //左右两个孩子中,谁大把下标给 largest,largest就是左右孩子中下标大的
            //右孩子大的条件:有右孩子,右孩子比左孩子大
            int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;
            //index位置的值和左右孩子中较大的PK,谁大谁是largest
            largest = arr[largest] > arr[index] ? largest : index;
            if (largest == index) {
                break;
            }
            swap(arr, largest, index);
            index = largest;
            left = index * 2 + 1;
        }
    }
    
    public static void heapSort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        for (int i = 0; i < arr.length; i++) {
            heapInsert(arr, i);
        }
        
        int heapSize = arr.length;
        swap(arr, 0, --heapSize);
        while (heapSize > 0) {
            heapify(arr, 0, heapSize);
            swap(arr, 0, --heapSize);
        }
    }

    public static void main(String[] args) throws Exception {
        MyMaxHeap heap = new MyMaxHeap(7);
        heap.push(4);
        heap.push(3);
        heap.push(5);
        heap.push(7);
        heap.push(2);
        heap.push(1);
        while (!heap.isEmpty()) {
            System.out.println(heap.pop());
        }
    }
}

面试题 已知一个几乎有序的数组。几乎有序是指,如果把数组排好序的话,每个元素移动的距离不超过k,并且k相对于数组长度来说是比较小的。
请选择一个合适的排序策略,对这个数组进行排序。

public void sortedArrayDistanceLessK(int[] arr, int k) {
    PriorityQueue<Integer> heap = new PriorityQueue<>();
    int index = 0;
    //把k+1个数先放入小根堆
    for (;index <= Math.min(arr.length - 1, k); index++) {
        heap.add(arr[index]);
    }
    int i = 0;
    for (; index < arr.length; i++, index++) {
        heap.add(arr[index]);
        //把小根堆弹出最小值放到i位置
        arr[i] = heap.poll();
    }
    while (!heap.isEmpty()) {
        arr[i++] = heap.poll();
    }
}