1)完全二叉树;父节点i,子节点对应2*i+1,2*i+2
在一个数组中,可以脑补一个完全二叉树
2)堆:
1.大根堆:在一个完全二叉树中,任何一个字树的最大值都是这颗子树的头部(根节点永远是所在子树中最大的)
2.建立大根堆:给你一个数组,建立大根堆
遍历数组,每遍历一个元素a[i]保证0-i范围上是大根堆,在遍历到i+1时,保证0-i+1范围上是大根堆。(完全二叉树上每增加一个元素进行调整,保证形成大根堆)直到for循环完毕,数组遍历完毕
2.调整过程(上浮):构建堆
进一个数a[i],跟父位置比较,如果大于父位置,交换,然后在新的位置上继续比较,如果大于新位置上的父位置,交换,直到小于当前父位置停止。
建立一个大根堆的复杂度:每加进一个数,最多swap(logn)次—(当前形成的二叉树高度为logn)所以数组中所有数字加进来复杂度是O(N*logN)
第一种方式构建堆代码_上浮代码:
for (int i = 0; i < arr.length; i++) {
heapInsert(arr, i);
}
public static void heapInsert(int[] arr, int index) {
while (arr[index] > arr[(index - 1) / 2]) {
swap(arr, index, (index - 1) / 2);
index = (index - 1) / 2;
}
}
优化:下沉构建堆
存在构建堆的过程为O(N)的方法(默认为此数组已经是一个大根堆,从倒数第二排最右边的数开始检验,是否符合大根堆,不符合的话,进行下沉操作)依次按排检验,直到堆顶检验完毕,此构建堆的过程是O(n)
证明:每一层开始检验(1*n/4+2*n/8+3*n/16+...+(log2N - 1)*n/(2^ log2N)
时间复杂度为O(N)
注意:
第一种入栈,每进入一个数都可能需要上浮log2i次,累计上浮最多次数(2*1+4*2+8*3+16*4+...+n/2*(log2N-1))当二叉树层数越多的时候,越多的叶子节点个数乘以层数。O(当前层节点个数*距顶距离)求和
第二种入栈:(下沉)从底部开始,向上遍历,O(当前层节点个数*距底部距离)求和 ,遍历过程中,离底部变远,但是子节点数量减少
调整过程(下沉):
3.下沉
当此大根堆中有一个数字变小了,怎么恢复大根堆呢:
这个变小的数字的位置中,找他的两个孩子,左右孩子进行比计较,找到最大值然后与他比较,子节点>根节点时,与相应子节点交换(下沉一个位置),在新的位置继续比较,根节点>两个字节点。
public static void heapify(int[] arr, int index, int size) {
int left = index * 2 + 1;
while (left < size) {//左孩子没有越界(如果左孩子越界,那么index位置已经是叶节点不许要下沉)
int largest = left + 1 < size && arr[left + 1] > arr[left] ? left + 1 : left;
largest = arr[largest] > arr[index] ? largest : index;
if (largest == index) {
break;
}
swap(arr, largest, index);
index = largest;
left = index * 2 + 1;
}
}
注意:堆的大小在数组中是可以伸缩的,和heapsize表示堆的大小(在下沉时,注意不要访问到没入堆的数字,(没入堆的数字也一样在数组中,此时堆之形成到了i,i后面的元素仍在数组中,只是没有还调整成大根堆)
4.减堆操作:弹出堆顶,再变成大根堆
堆得最后一个数与堆顶进行交换,heapSize--,然后新的堆顶进行下沉操作
堆排序:
先遍历数组,使整个数组变成大根堆,然后依次进行减堆操作,(弹出堆顶,重调成大根堆)时间复杂度:O(NlogN)重建堆的过程。额外空间复杂度O(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 size = arr.length;
swap(arr, 0, --size);
while (size > 0) {
heapify(arr, 0, size);
swap(arr, 0, --size);
}
}
public static void heapInsert(int[] arr, int index) {
while (arr[index] > arr[(index - 1) / 2]) {
swap(arr, index, (index - 1) / 2);
index = (index - 1) / 2;
}
}
public static void heapify(int[] arr, int index, int size) {
int left = index * 2 + 1;
while (left < size) {//左孩子没有越界(如果左孩子越界,那么index位置已经是叶节点不许要下沉)
int largest = left + 1 < size && arr[left + 1] > arr[left] ? left + 1 : left;
largest = arr[largest] > arr[index] ? largest : index;
if (largest == index) {
break;
}
swap(arr, largest, index);
index = largest;
left = index * 2 + 1;
}
}
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
(堆排序不可做到稳定性)