二叉树的顺序存储
1.使用数组来保存二叉树结构,就是将二叉树层序遍历放到数组中。
这种方法一般指适用于完全二叉树,因为非完全二叉树会存在空间上的浪费。主要用法,就是堆的表示。
2.在数组中的下标的关系
- 已知根节点下标(parent),则:
- 左子树下标=2*parent+1
- 右子树下标=2+parent+2
- 已知左右子树(child)下标,则:
- 根节点下标=(child-1)/2
堆的概念
1.概念:
- 堆在逻辑上,是一颗完全二叉树
- 堆在物理形式上,是保存在数组中的
- 堆有两种形式:所有节点的根节点的值大于其左右子树节点的值,则为大堆。
所有节点的值都小于其左右子树节点的值,则为小堆。 - 基于第三点,堆的基本作用,快速的在集合中找最值。(根节点)
堆的操作
1:向下调整(将一个不是堆结构的二叉树调整为堆结构)前提:左右子树必须已经是一个堆,才能调整。
一旦堆里面元素发生改变(插入或删除),就要对堆结构进行调整,堆的规则不能破坏。
思路及代码:
1.堆是完全二叉树,没有左子树则一定没有右子树,又因为堆的存储结构是数组,所以可以通过判断左子树的下标是否越界来判断是否含有左子树(left<size)
2.再判断是否含有右子树,确定左子树和右子树的最小值,再将最小值与根节点值比较,因为是设定是小堆,所以若最小值小,则与根节点值交换,最小值大,则不动。
若没有右子树,则直接比较根节点值与最小值(此时最小值就是left)
3.因为min位置的堆性质可能被破坏,所以要将min重新视为新一轮的根节点(index),重复上述比较过程。
时间复杂度:最坏情况就是从根一路比较到叶子节点,复杂度为:O(log(n));
//堆的向下调整,以小堆为例
//size是array哪些元素是有效的堆元素
//index是从哪个位置的下标开始调整,也就是当前的根节点
public static void shifDown(int[] array,int size,int index){
int left=index*2+1;
while (left<size){//说明左子树是存在的
int right=index*2+2;
int min=left;
if (right<size) {//如果右子树也存在
if (array[right]<array[left]) {//找到左右子树中较小值的下标,记录近min中去,
min=right;
}
}
//比较当前根节点的值和最小值,如果根节点小,则结束本次循环,如果根节点大,则交换。
if (array[index]<=array[min]){
break;
}
//交换当前值和左右子树中的最小值,保证根节点的值小于左右子树
else{
int temp=array[index];
array[index]=array[min];
array[min]=temp;
}
//更新当前位置,继续向下调整
index=min;
left=index*2+1;
}
}
2.建堆:
思路及代码:
通过算法将一个数组调整成为一个堆。从倒数第一个叶子节点的父节点开始调整,可用向上调整或向下调整法,直到调整到根节点的树,就ojk。
时间复杂度:看似是O(n*log(n))
实际上是O(n);
因为堆结构调整到一定程度就不需要再继续循环调整了
//建堆时,从前往后遍历:向上调整
//从后往前遍历:向下调整,从最后一个叶子节点的父节点开始遍历
//size-1,是最后一个叶子节点的位置,再-1/2,得到该叶子节点的父节点
// 如果是size-1的话,逻辑无影响,循环会多空转几回
public static void createHeap(int[] array,int size){
for (int i=(size-1-1)/2;i>=0;i--){
shifDown(array,size,i);
}
}
主函数及结果:
public static void main(String[] args){
int [] array={9,5,2,7,3,6,8};
createHeap(array,array.length);
System.out.println(Arrays.toString(array));
}
[2, 3, 6, 7, 5, 9, 8]