二叉堆
最大堆
最大堆的任何一个父节点的值,都大于或等于它
左、右孩子节点的值。
最小堆
最小堆的任何一个父节点的值,都小于或等于它
左、右孩子节点的值。
二叉堆的自我调整
插入节点
插入位置是完全二叉树的最后一个位置。然后慢慢上浮合适的位置,直到满足根节点都比孩子节点都小即可。
删除节点
删除的是处于堆顶的节点。
把堆的最后一个节点10临时补到原本堆顶的位置。
节点10和它的左、右孩子进行比较,和最小的一个交换即可。
构建二叉堆
把一个无序的完全二叉树调整为二叉堆,本质就是让所有非叶子节点依次“下沉”。
找到比当前节点最小的一个孩子节点交换。
堆的插入操作是单一节点的“上浮”,堆的删除操作是单一节点的“下沉”,这两个操作的平均交换次数都是
堆高度的一半,所以时间复杂度是O(logn)。
构建堆的时间复杂度是O(n)。
二叉堆的存储格式(采用数组进行层序遍历存储)
假设父节点的下标是parent,那么它的左孩子下标就是
2×parent+1;右孩子下标就是2×parent+2。
/**
* 上浮调整
* @param array 待调整的堆
*/
public static void upAdjust(int[] array) {
int childIndex = array.length-1;
int parentIndex = (childIndex-1)/2;
// temp保存插入的叶子节点值,用于最后的赋值
int temp = array[childIndex];
while (childIndex > 0 && temp < array[parentIndex])
{
//无需真正交换,单向赋值即可
array[childIndex] = array[parentIndex];
childIndex = parentIndex;
//这儿有大事情:如果当前节点的处于右孩子,则减1,还剩下1,
//不能整除2,而除2之后会把多余的1给磨掉;
//如果当前节点的处于左孩子,减1之后正好,可以够整除。
parentIndex = (parentIndex-1) / 2;
}
array[childIndex] = temp;
}
/**
* 下沉调整
* @param array 待调整的堆
* @param parentIndex 要下沉的父节点
* @param length 堆的有效大小
*/
public static void downAdjust(int[] array, int parentIndex, int length) {
// temp保存父节点值,用于最后的赋值
int temp = array[parentIndex];
int childIndex = 2 * parentIndex + 1;
while (childIndex < length) {
// 如果有右孩子,且右孩子小于左孩子的值,则定位到右孩子
//找到最小的那一个孩子
if (childIndex + 1 < length && array[childIndex + 1] < array[childIndex]) {
childIndex++;
}
// 如果父节点小于任何一个孩子的值,直接跳出
if (temp <= array[childIndex])
break;
//无需真正交换,单向赋值即可
array[parentIndex] = array[childIndex];
parentIndex = childIndex;
childIndex = 2 * childIndex + 1;
}
array[parentIndex] = temp;
}
/**
* 构建堆
* @param array 待调整的堆
*/
public static void buildHeap(int[] array) {
// 从最后一个非叶子节点开始,依次下沉调整
for (int i = (array.length-2)/2; i >= 0; i--) {
downAdjust(array, i, array.length);
}
}
public static void main(String[] args) {
int[] array = new int[] {1,3,2,6,5,7,8,9,10,0};
upAdjust(array);
System.out.println(Arrays.toString(array));
array = new int[] {7,1,3,10,5,2,8,9,6};
buildHeap(array);
System.out.println(Arrays.toString(array));
}