java 堆与堆排序

1.堆:堆是一种树,由它实现的优先级队列的插入和删除的时间复杂度都是O(logn),用堆实现的优先级队列虽然和数组实现相比较删除慢了些,但插入的时间快的多了。当速度很重要且有很多插入操作时,可以选择堆来实现优先级队列。

一般用在动态数据的排列上

堆分大根堆和小根堆,在这里主要说大根堆。

堆的插入,由于使用数组来实现堆,所以插入的元素一般在数组末尾,也就是堆得最后一个叶子结点,插入后,要是堆维持大根堆的性质,就要将插入的节点进行shiftUp操作

比如这里要插入52,现将52当做16的节点,然后和父节点比较谁大,然后交换位置

下一步,52到了索引为5的位置,之后在和父节点比较,交换位置

一直下去,直到再变成一个大根堆

取值:最大堆取值是取根节点,可保证每次取的都是堆中最大值,去完根节点之后,将最后一个叶节点放到根节点的位置,然后进行shiftDown操作

然后将根节点和两个子节点中交大的一个比较,如果根节点小,则交换

然后,将该节点再与子节点中较大的比较,向下换,直到再变成大根堆

代码如下:


public interface Heap {
    int size();

    boolean isEmpty();

    void insert(int item);

    int extractMax();
}

public class HeapImpl implements Heap {
    private int count;

    private List<Integer> data;

    HeapImpl() {
        count = -1;
        data = new ArrayList<>();
    }

    HeapImpl(int[] numbers) {
        data = new ArrayList<>();
        for (int i = 0; i < numbers.length; i++)
            data.add(numbers[i]);

        count = numbers.length - 1;
        for (int i = (count - 1) / 2; i >= 0; i--)
            shiftDown(i);
    }

    @Override
    public int size() {
        return count;
    }

    @Override
    public boolean isEmpty() {
        return count == -1;
    }

    @Override
    public void insert(int item) {
        data.add(item);
        count++;
        shiftUp(count);
    }

    /**
     * 插入元素之后,维持最大堆的过程
     * 将新插入的元素和父节点比较,如果比父节点大,则交换,一直比到父节点大的时候
     *
     * @param k
     */
    private void shiftUp(int k) {
        while (k >= 0 && data.get((k - 1) / 2) < data.get(k)) {
            swap((k - 1) / 2, k, data);
            k = (k - 1) / 2;
        }
    }

    @Override
    public int extractMax() {
        if (isEmpty()) return -1;
        int result = data.get(0);
        swap(0, count, data);
        count--;
        shiftDown(0);
        return result;
    }

    /**
     * 提取元素后,将末尾元素放到根节点,维持最大堆
     * 只要根节点比任何一个孩子小,就与较大的孩子进行交换,直到父节点比孩子都大,或者没有孩子
     *
     * @param k
     */
    private void shiftDown(int k) {
        while (((k * 2) + 1) <= count) {
            int j = k * 2 + 1;

            if (j + 1 <= count && data.get(j + 1) > data.get(j)) j++;

            if (data.get(k) >= data.get(j)) break;
            swap(k, j, data);
            k = j;
        }
    }

    private void swap(int a, int b, List<Integer> data) {
        int temp;
        temp = data.get(a);
        data.set(a, data.get(b));
        data.set(b, temp);
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }

    public List<Integer> getData() {
        return data;
    }

    public void setData(List<Integer> data) {
        this.data = data;
    }
}

然后是堆排序,最简单的方法就是把元素加到堆里,然后一个一个取根节点

    public void heapSort1(int[] data) {
        HeapImpl heap = new HeapImpl();
        for (int num : data) {
            heap.insert(num);
        }
        for (int i = data.length - 1; i >= 0; i--) {
            data[i] = heap.extractMax();
        }
    }

插入的过程为O(nlogn)

还有一种是直接将一个数组变成大根堆

过程是:每个叶子结点可以看做是一个堆,然后加上他的父节点,进行shiftDown操作,是叶节点和父节点变成一个大根堆,然后重复,最后整个数组变成大根堆。

    /**
     * 从最后一个叶节点的父节点开始,进行shiftDown操作,使以该父节点为根节点的树变成大根堆
     * @param numbers
     */
 HeapImpl(int[] numbers) {
        data = new ArrayList<>();
        for (int i = 0; i < numbers.length; i++)
            data.add(numbers[i]);

        count = numbers.length - 1;
        for (int i = (count - 1) / 2; i >= 0; i--)
            shiftDown(i);
    }
    public void heapSort2(int[] data) {
        HeapImpl heap = new HeapImpl(data);
        for (int i = data.length - 1; i >= 0; i--) {
            data[i] = heap.extractMax();
        }
    }

参考bobo老师的视频,截图均来自bobo老师

https://coding.imooc.com/learn/list/71.html

猜你喜欢

转载自blog.csdn.net/zyh568879280/article/details/87925166