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老师