文章目录
- 算法简介
- Java 实现
- 时间复杂度
- 空间复杂度
- 算法稳定性
算法简介
二叉堆
一棵完全二叉树,对于大顶堆来说,任何一个结点都要大于等于它左右孩子结点,对于小顶堆,任何一个结点都要小于等于它的左右孩子。
二叉堆与数组的联系
二叉堆一般存储在数组中,有这样的性质,如果我们把二叉堆按照从上到下,从左到右的顺序依次存进数组,如果 index 是某个结点的下标值,那么它左右孩子结点的下标值分别是2*index+1和2*index+2,不信的筒子们可以尝试写到数组中看看规律。我在数组中怎么知道这个下标的值是右结点还是左结点呢?我们减去 2 除以 2 除得尽就是右结点,否则是左结点,并且其父节点的下标也可以确定了。这个数组保存着这个二叉堆的所有信息
二叉堆的增删
二叉堆的新增和删除,我们会把一个结点新增到最下一层的最后面,然后与父节点比较,进而上浮或者不动;对于删除来讲,假如我们删除某一个中间结点,我们就需要用最尾部结点对删掉的节点位置进行补位,补上之后,再对这个补位的结点和最小(大)的直接子结点比较,进而选择下沉或不动
堆排序
我们利用二叉堆,上浮下沉的性质每次找到最大的结点上浮到顶端,然后我们再保存到最后面,这样就可以从小到大排序了!详细的说堆排序就是先创建大顶堆或小顶堆,然后把这个堆根和最尾部交换位置,将除了尾部的继续构成堆,这样不断循环就可以实现堆排序了
上浮还是下沉?
尾部添加结点时候是上浮操作,上浮比较简单,因为孩子结点只有一个双亲;删除结点是下沉操作,下沉麻烦一点,因为一个双亲结点有两个孩子结点,下沉要比较两个孩子
构建二叉堆
无序序列,非叶子结点开始往前遍历,每次遍历中,将该结点选择性下沉,这样遍历完就构建起了一个二叉堆
二叉堆的最后一个非叶子节点怎么求?
(arr.length-1-1)/2
Java 实现思路
利用二叉堆的存入数组的性质很容易实现堆排序,我们一起来写一下吧
// 二叉堆尾部添加结点之后的自行调整
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;
parentIndex = (parentIndex-1) / 2;
}
array[childIndex] = temp;
}
// 二叉堆删掉结点之后的自行调整
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;
}
// 构造小顶堆
public static void getHeap(int[] array) {
// 从最后一个非叶子节点开始,依次下沉调整
for (int i = array.length / 2; i >= 0; i--) {
downAdjust(array, i, array.length - 1);
}
}
// 堆排序(从大到小)
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));
}
时间复杂度
时间复杂度为 O(n*logn)
空间复杂度因为只用到有限的变量,并且是数组就地排序,空间复杂度为 O(1)
算法稳定性堆排序一般来说是不稳定的排序算法