一、二叉树的顺序存储
在前面我们已经讲了二叉树的链式存储,就是一棵树的左孩子和右孩子
而现在讲的是:顺序存储一棵二叉树。
1.1、存储方式
使用数组保存二叉树结构,方式即将二叉树用层序遍历方式放入数组中。 一般只适合表示完全二叉树,因为非完全二叉树会有空间的浪费。
这种方式的主要用法就是堆的表示
下标关系
已知双亲(parent)的下标,则:
左孩子(left)下标 = 2 * parent + 1;
右孩子(right)下标 = 2 * parent + 2;
已知孩子(不区分左右)(child)下标,则:
双亲(parent)下标 = (child - 1) / 2;
也就是前面我们将的性质5:
对于具有n个结点的完全二叉树,如果按照从上至下从左至右的顺序对所有节点从0开始编号,则对于序号为i的结点有:
(1)若i>0,双亲序号:(i-1)/2;i=0,i为根结点编号,无双亲结点
(2)若2i+1<n,左孩子序号:2i+1,否则无左孩子
(3)若2i+2<n,右孩子序号:2i+2,否则无右孩子
二、堆
2.1、概念
- 堆逻辑上是一棵完全二叉树
- 堆物理上是保存在数组中
- 满足任意结点的值都大于其子树中结点的值,叫做大堆,或者大根堆,或者最大堆
- 反之,则是小堆,或者小根堆,或者最小堆
- 堆的基本作用是,快速找集合中的最值 :无论是 大根堆还是小根堆, 它们的 最值【最大值 和 最小值】都处于 二叉树的 根结点处。要想获得 最值,直接 peek 方法,就能获得 树 的 根结点值 / 最值。
2.2、操作-向下调整
前提:左右子树必须已经是一个 堆 / 逻辑上是一棵完全二叉树。
将一组 记录完全二叉树数据 的 数组 转换成 大根堆。
向下调整出现的问题:
得出结论:其实每棵树的调整结束位置都是一样的︰不能超过数组长度。
如何构造一个 向下调整的函数 - 重点
public class TestHeap {
public int[] elem;//底层是一个数组
public int usedSize;
//
public TestHeap(){
this.elem = new int[10];
}
/**
* 创建堆
* @param array 堆里面存放的元素
*/
public void creatHeap(int[] array){
//将array数组的元素存入elme 数组
for (int i = 0; i < array.length; i++) {
elem[i] = array[i] ;
usedSize++;
}
for (int praent = (usedSize-1-1)/2; praent >= 0; praent--) {
shiftDown(praent,usedSize);
}
}
/**
* 向下调整
* @param praent 每棵子树的父亲节点
* @param len 调整的结束位置,不能大于数组的长度
*/
public void shiftDown(int praent, int len){
int child = 2+praent +1;
while (child < len){
if(child + 1 > len && this.elem[child] < this.elem[child+1]){
child++;
}
if(elem[child] > elem[praent]){
int tmp = elem[child];
elem[child] = elem[praent];
elem[praent] = tmp;
}else {
break;
}
}
}
}
测试一下:
模拟实现 堆 的 时间复杂度
上图转载于:堆 / 优先队列
粗略估算,可以认为是在循环中执行向下调整,为 O(n * log(n))
(了解)实际上是 O(n)
堆排序中建堆过程时间复杂度O(n)怎么来的?
2.3、操作-建堆
下面我们给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过算法,把它构建成一个堆。根节点左右子树不是堆,我们怎么调整呢?这里我们从倒数的第一个非叶子节点的子树开始调整,一直调整到根节点的树,就可以调整成堆
图示(以大堆为例):
// 建堆前
int[] array =