1.二叉树的顺序存储
1.1存储方式
使用数组保存二叉树结构,层序遍历二叉树放入数组。
一般适合于表示完全二叉树,可避免空间的浪费。
这种方式的主要用法就是堆的表示。
1.2下标关系
已知双亲 >>> 求孩子:
左孩子 left= 2* parent +1;
右孩子 right = 2* parent +2;
已知孩子 >>> 求双亲:
parent = (child-1) / 2;
已知数组长度 len ,可得父亲节点:(len-1-1) / 2
2.堆(heap)
2.1 概念
- 逻辑上:堆是完全二叉树
- 物理上:堆是保存在数组中的
- 大堆(大根堆 / 最大堆):任意节点的值都大于其子树节点的值
- 小堆(小根堆 / 最小堆):任意节点的值都小于其子树节点的值
- 堆的基本作用:快速找到集合中的最值
2.2 操作
1.向下调整:
以大根堆为例
public class TestHeap {
public int[] elem;
public int usedSize;
//可提供一个构造方法 提供一个数组 大小为10
public TestHeap(){
this.elem = new int[10];
}
// 大根堆为例 调整保证每棵树都为大根堆
// 每棵树 向下调整 , 整棵树是 向上调整
// ( 局部变量 )调整每棵树 每棵树的调整位置都不能超过当前数组的长度
public void adjustDown(int parent,int len){
int child = 2*parent+1; // 左孩子
// 要找左右孩子的最大值
while (child<len){
// 存在右孩子 并且 右孩子大于左孩子
if (child+1<len && this.elem[child]<this.elem[child+1]){
child++; // child下标是 当前左右孩子中最大值的下标
}
if (this.elem[child]>this.elem[parent]){
// 孩子值大于父亲的值 >>交换值
int tmp =this.elem[child];
this.elem[child]=this.elem[parent];
this.elem[parent]=tmp;
// 换完后 还不确定当前节点是否存在左右孩子节点
parent = child; // 当前节点作为父亲节点 看是否存在孩子节点
child = 2*parent+1;
}else {
// 孩子小于父亲 不用换
break;
}
}
}
public static void main(String[] args) {
TestHeap testHeap = new TestHeap();
int[] array={ 27,15,19,18,28,34,65,49,25,37};
testHeap.createHeap(array);
// 调整后:65,49,34,25,37,27,19,18,15,28
}
}
2.建堆
public class TestHeap {
public int[] elem;
public int usedSize;
//可提供一个构造方法 提供一个数组 大小为10
public TestHeap(){
this.elem = new int[10];
}
// 大根堆为例 调整保证每棵树都为大根堆
public void createHeap(int[] array){ // elem开始没有值 >> 传一个数组
for (int i = 0; i <array.length ; i++) {
this.elem[i] = array[i]; // 相当于把array的值 赋给 elem
this.usedSize++;
}
// 说明数组里面有了数据
for (int p = (this.usedSize-1-1)/2; p >=0 ; p--) { // 确定父亲节点
// 每棵树的调整方案 局部调整每棵树 不影响 此处 p 的变化
adjustDown(p,this.usedSize);
}
}
建堆的时间复杂度:
可认为在循环中执行向下调整,
局部调整每棵树(即树的深度)是【log(n) +1】;
开始调整时父亲节点的位置p = (this.usedSize-1-1)/2 ,p- -,
可近似看做 p 遍历了每个节点 是 【n】
从代码思想上可以近似看为 O(n*log(n)) ;
实际通过数学推导可知是 O(n)
https://www.zhihu.com/question/20729324
3.入队列(向上调整)
优先级队列(Priority Queue)
返回最高优先级对象;
添加新的对象;
// 向上排序 主要看 child
public void adjustUp(int child){
int parent = (child-1)/2; // 传的 this.usedSize-1
while (child > 0){
if (this.elem[child]>this.elem[parent]){
int tmp =this.elem[child];
this.elem[child]=this.elem[parent];
this.elem[parent]=tmp;
child =parent ;
parent = (child-1)/2;
}else {
break;
}
}
}
// 入
public void push(int key){
if(isFull()){
// 对数组进行 扩容
this.elem = Arrays.copyOf(this.elem,this.elem.length*2);
}
this.elem[this.usedSize] = key;
this.usedSize++;
adjustUp(this.usedSize-1); // 传入的值 和 adjustUp中 判断边界 有关
}
// 放元素 需要判断 存在堆满 的情况
public boolean isFull(){
return this.usedSize == this.elem.length;
}
4. 出队列(优先级最高)
为了防止破坏堆的结构,删除时并不是直接将堆顶元素删除,而是用数组的最后一个元素替换堆顶元素,然后通过向下调整方式重新调整成堆。
public void pop()throws UnsupportedOperationException{ // 根节点 和 最后一个交换
if(isEmpty()){
throw new UnsupportedOperationException("优先级队列为空");
}
int tmp = this.elem[0];
this.elem[0] = this.elem[this.usedSize-1];
this.elem[this.usedSize-1] = tmp;
this.usedSize--; // 先-- 原尺寸 不包含新加的
adjustDown(0,this.usedSize);
}
public int getTop() throws UnsupportedOperationException{ // 出队 不删除
if(isEmpty()){
throw new UnsupportedOperationException("优先级队列为空");
}
return this.elem[0];
}
public boolean isEmpty(){
return this.usedSize == 0;
}