目录
堆的定义:
现在我们有一颗完全二叉树,它一共有n颗节点,这颗二叉树的左右节点都比它的父节点小(大)。现在我们将这颗二叉树用数组表示出来,下标为0的节点为根节点,下标为1的节点为根节点的左节点,下标为2的节点为根节点的右节点,下标为3的节点为下标为2的节点的左节点......
这样逻辑上为一颗完全二叉树,并且左右节点比父节点小(大),以数组表示出来的存储结构称为堆。
父节点比左右节点小的(左右节点比父节点大的)称为小根堆。
父节点比左右节点大的(左右节点比父节点小的)称为大根堆。
堆的实现(以小根堆为例):
public class Heap {
private ArrayList<Integer> heap;
public Heap(int[] arr) {
if (arr == null || arr.length == 0) {
return;
}
heap = new ArrayList<>();
for (int j = 0; j < arr.length; j++) {
heap.add(arr[j]);
}
for (int i = (arr.length - 1 - 1) / 2 ; i >= 0; i--) {
//(arr.length - 1 - 1) / 2 是最后一个非叶子节点
//arr.length - 1 是最后一个元素的下标,再减1是因为我们的堆是由0下标开始放元素的
//所以想要求最后一个非叶子节点就要再减一次 1 再除以2
adjustDown(i);
}
}
public void adjustDown(int parent) {//小根堆:父节点比左右节点小
int child = parent * 2 + 1;//child开始是parent的左孩子
while (child < heap.size()) {
if (child + 1 < heap.size() && heap.get(child + 1) < heap.get(child)) {
child = child + 1;//如果右孩子存在且右孩子小于左孩子,child变成右孩子
//因为这个child需要时两个孩子中最小的
}
if (heap.get(child) < heap.get(parent)) {
swap(parent, child);//如果孩子比父亲节点小,则进行交换
} else {
break;//父节点已经比子节点小了,符合小根堆的要求了,就不用执行了~~
}
parent = child;
child = parent * 2 + 1;
//继续向下交换
}
}
public void swap(int a, int b) {
int tmp = heap.get(a);
heap.set(a, heap.get(b));
heap.set(b,tmp);
}
public void showHeap() {
for (int i = 0; i < heap.size(); i++) {
System.out.print(heap.get(i) + " ");
}
System.out.println();
}
}
public class Main {
public static void main(String[] args) {
int[] arr = {5,6,3,2,1,4,7,8,9,10,12};
Heap heap = new Heap(arr);
heap.showHeap();
}
}
运行结果:
建堆的时间复杂度:
时间复杂度:O(n)
堆的插入:
要实现堆的插入,我们首先要实现向上调整:
public void adjustUp(int child) {//小根堆:父节点比左右节点小
int parent = (child - 1) / 2;//求父节点下标公式
while (parent >= 0) {
if (heap.get(child) < heap.get(parent)) {
swap(parent, child);//如果孩子比父亲节点小,则进行交换
} else {
break;//父节点已经比子节点小了,符合小根堆的要求了,就不用执行了~~
}
child = parent;
parent = (child - 1) / 2;
//继续向上交换
}
}
然后我们在每次插入的时候将这个元素向上调整一下就可以了~~
public void heapPush(int val) {
heap.add(val);
adjustUp(heap.size() - 1);
}
堆的删除:
删除堆的数据就是删除堆的堆顶元素,我们将堆顶元素与最后一个元素调换,然后进行向下调整。
public void heapPop() {//删除堆顶元素
swap(0, heap.size() - 1);
heap.remove(heap.size() - 1);
adjustDown(0);
}
完整代码:
import javafx.scene.Parent;
import java.util.ArrayList;
import java.util.Arrays;
public class Heap {
private ArrayList<Integer> heap;
public Heap(int[] arr) {
if (arr == null || arr.length == 0) {
return;
}
heap = new ArrayList<>();
for (int j = 0; j < arr.length; j++) {
heap.add(arr[j]);
}
for (int i = (arr.length - 1 - 1) / 2 ; i >= 0; i--) {
//(arr.length - 1 - 1) / 2 是最后一个非叶子节点
//arr.length - 1 是最后一个元素的下标,再减1是因为我们的堆是由0下标开始放元素的
//所以想要求最后一个非叶子节点就要再减一次 1 再除以2
adjustDown(i, arr.length);
}
}
public void adjustDown(int parent/*终止位置*/) {//小根堆:父节点比左右节点小
int child = parent * 2 + 1;//child开始是parent的左孩子
while (child < heap.size()) {
if (child + 1 < heap.size() && heap.get(child + 1) < heap.get(child)) {
child = child + 1;//如果右孩子存在且右孩子小于左孩子,child变成右孩子
//因为这个child需要时两个孩子中最小的
}
if (heap.get(child) < heap.get(parent)) {
swap(parent, child);//如果孩子比父亲节点小,则进行交换
} else {
break;//父节点已经比子节点小了,符合小根堆的要求了,就不用执行了~~
}
parent = child;
child = parent * 2 + 1;
//继续向下交换
}
}
public void adjustUp(int child) {//小根堆:父节点比左右节点小
int parent = (child - 1) / 2;//求父节点下标公式
while (parent >= 0) {
if (heap.get(child) < heap.get(parent)) {
swap(parent, child);//如果孩子比父亲节点小,则进行交换
} else {
break;//父节点已经比子节点小了,符合小根堆的要求了,就不用执行了~~
}
child = parent;
parent = (child - 1) / 2;
//继续向上交换
}
}
public void swap(int a, int b) {
int tmp = heap.get(a);
heap.set(a, heap.get(b));
heap.set(b,tmp);
}
public void heapPush(int val) {
heap.add(val);
adjustUp(heap.size() - 1);
}
public void heapPop() {//删除堆顶元素
swap(0, heap.size() - 1);
heap.remove(heap.size() - 1);
adjustDown(0);
}
public void showHeap() {
for (int i = 0; i < heap.size(); i++) {
System.out.print(heap.get(i) + " ");
}
System.out.println();
}
public void heapSort(int[] arr) {
}
}
堆排序:
如果我们升序就用大根堆,降序就用小根堆。
以升序为例:
1.先从堆顶向下调整建堆。
2.建完堆之后堆顶存放的是最大的元素。
3.我们将这个最大的元素与最后一个元素调换,然后可以调整的元素个数-1,也就是说这个最大的元素固定在最后了,后面要操作的话只能修改这个元素之前的元素了。
4.元素调换之后,再继续从堆顶向下调整。
5.调整完之后最大的元素在堆顶,然后将这个元素与可调整的元素的最后一个调换。
6.然后可调整的元素个数-1。
7.重复4、5、6步。
public static void adjustDown(int[] arr, int parent, int end) {
int child = parent * 2 + 1;
while (child < end) {
if (child + 1 < end && arr[child + 1] > arr[child]) {
child = child + 1;
}
if (arr[child] > arr[parent]) {
Swap(arr, child, parent);
} else {
break;
}
parent = child;
child = parent * 2 + 1;
}
}
public static void heapSort(int[] arr) {
for (int i = (arr.length - 1 - 1) / 2; i >= 0; i--) {
adjustDown(arr, i, arr.length - 1);
}//建堆
for (int end = arr.length - 1; end > 0; end--) {
Swap(arr, 0, end);
adjustDown(arr, 0, end);
}
}
public static void Swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
时间复杂度:
好啦,以上就是全部内容啦,如果有不懂的地方就多敲几次代码,书读百遍,其意自见~~