Queue Deque LinkedList PriorityQueue
1.二叉树的顺序存储
之前咱们介绍过的二叉树都是链式存储的二叉树,通过左孩子右孩子,每一个节点都在存放地址
对于一组数据:27 15 19 18 28 34 65 49 25 37现在不是一个大堆,想一下办法如何把它变成一个大堆
PriorityQueue queue=new PriorityQueue();//调用构造方法,一开始的默认值是11
queue.offer(12);//元素是不可放空null
queue.offer(99);
queue.offer(101);
queue.offer(999);
System.out.println(queue.poll());//默认是一个小堆,每一次默认出的都是最小的元素
System.out.println(queue.poll());//我们还要保证每一次弹出元素的时候,剩下的树都是一个小根堆或者大根堆
存储结构:我们通常使用数组来进行保存二叉树的结构,方式是将二叉树进行层序遍历的方式存放到数组中,因为非完全二叉树会有空间上面的浪费,这种方法的主要用法就是用堆的表示
2)下标关系
已知父亲节点下标,求孩子节点下标
1 左孩子下标=2*parent+1;
2 右孩子下标=2*parent+2;
已知孩子节点下标,求父亲节点下标 (不分左右孩子下标)
3)堆
定义:堆在集合框架中的数据结构是PriorityQueue,叫做优先级队列,他的底层是一棵完全二叉树,最后再调用二叉树的层序遍历来进行打印这个完全二叉树;
概念:
1)逻辑上是一颗完全二叉树(不是完全二叉树就会有空间上面的浪费,顺序存储的二叉树)
2)对物理上是保存在数组中,相当于把二叉树层序遍历的结果放到数组中
3)每棵树的根节点都小于孩子节点,叫小根堆,每颗二叉树结点都大于孩子节点,叫大根堆优先级队列默认的是小堆;
但是左右孩子的大小关系是不确定的
1)如何将一个二叉树调整成大堆呢
从最后一棵子树根节点进行调整,调整完后进行减减,每调整一棵子树时,都要进行向下调整,保证要调整的结点的下面的子树也是一个大堆,我们在这个过程中循环往复,直到最后调整的子树的节点是0;
1)调整是从最后一颗子树的根节点出发的
2)调整每一个节点时,进行向下调整;
1)我们如何找到最后一个子树的下标呢?
(arr1.length-1-1)/2,arr1.length-1是进行求出最后一个叶子节点;
2)我们在此时进行parent--操作就可以遍历到每一棵子树了
3)我们在这里面写一个公共的函数,进行调整每一棵子树时要共用同一份代码;
4)每棵树调整的结束位置怎么找?
每棵树调整的结束位置子节点的下标大于数组长度,那么就调整完了,每一棵树调整的结束位置都是一样的
5)在这里面我们分成两种情况
那么我们在进行调整每一个节点的时候为什么每一次都要进行向下调整呢?
A
B C
D E F G
1)array[child]>array[parent]假设我们现在要调整A B C这棵树,调整成大根堆,我们找出B,C中的最大值与A进行比较,比A大,就进行交换(假设A与B交换),但是此时如果A的值小于D,A=0,那么此时A,D,E就不是一个大根堆了,那么此时就要对A,D,E进行调整,一旦交换,就要进行向下调整
2)array[child]<array[parent]这个时候就不用进行调整,如果不进行交换,那么可以说这棵树是一个大根堆,那么他下面的子树也是一个大根堆(因为你再调整这棵树的时候,依据下标顺序,下面的子树你是之前一定都去调整过的)
2 入堆操作(保证入堆后还是大根堆或者是小根堆)
1)首先必须判断数组长度是否满了,数组满了就进行扩容;
2)把入队的元素放到数组的最后一个位置,array[usedsize]=elemnt,element++;
3) 从根节点进行向上调整(因为之前已经是一个大堆,调整也没那么麻烦)(画图),加上这个元素之后,从这个节点到根节点的所有路径都要进行判断,我们在这里面进行的向上调整
4)在我们进行向上调整的过程中,如果发现array[child]小于parent,那么我们就不需要再次向上进行调整了,因为此时的child的另一个节点的值一定小于父亲节点,此时我们调整结束,进行出栈操作
public boolean isFull(){//判断数组中的元素是否满了 return usedSize==this.array.length; } public void offer(int data){ if(isFull()){//进行判断当前数组元素是否满了 this.array=Arrays.copyOf(this.array,2*this.array.length); } array[usedSize]=data; usedSize++; upAdjust(array,usedSize); } private void upAdjust(int[] array, int usedSize) { int parent=(this.usedSize-1-1)/2;//找到新放进去的元素的根节点 int child=this.usedSize-1;//找到新放进去的元素的数组元素的下标 while(parent>=0){ if(array[child]>array[parent]){ //如果发现孩子元素大于父亲元素就进行交换 int temp=array[child]; array[child]=array[parent]; array[parent]=temp; child=parent; parent=(child-1)/2; }else{ break; } }
当我们进行优先级队列的向下调整的时候:
如果array[child]>array[parent],进行交换并进行向下交换
如果array[child]<array[parent],当前节点调整完毕,况且当前节点不需要进行向下调整,因为如果说向下调整的原因一定是调整了上面的元素而引起来的改动
class Heap{ public int[] array; public int usedSize; public Heap(int[] array){ this.array=array; this.usedSize=array.length; createBigTree(array,usedSize); } private void createBigTree(int[] array, int usedSize) { for(int i=(usedSize-1-1)/2;i>=0;i--){ DownJust(i,usedSize,array); } } private void DownJust(int i, int usedSize, int[] array) { int parent=i; int child=2*parent+1; int len=usedSize; while(child<len){ //1.判断是否有左孩子,至少有一个孩子 if(child+1<len&&array[child]<array[child+1]){ //前一个条件判断是否有右孩子,后一个条件进行判断左孩子的值是否小于右孩子的值 、、总而言之就是为了找出左右孩子的最大值的下标 child++; } if(array[child]>array[parent]){ int temp=array[child]; array[child]=array[parent]; array[parent]=temp; parent=child; child=2*parent+1; }else{ //因为此时当前的array[child]小于array[parent],当前说明节点已经调整完毕,此时就不需要进行向下调整,因为下面的节点已经调整过了 break; } } } public void display(){ System.out.println(Arrays.toString(array)); }} public class Main { public static void main(String[] args) { int[] elementData={12,10,90,79,68,100,78,190}; Heap heap=new Heap(elementData); heap.display(); } }
3)出队列:
每一次我们进行出队列的时候,都要保证出当前最大的元素或者是最小的元素
1)先判断当前数组是否为空
2)让当前的元素和数组的最后一个元素(array[usedsize-1]交换位置,然后让Usedzize--,将数组个数减1,然后再从数组的0下标开始向下调整即可(因为可以保证当前堆顶元素一定是当前数组最大的元素)
上面咱们就将80和37换了一下位置,此时就发现现在只有最上面的树不是一个大堆,在门只需要进行向下调整就可以了
public int pop(){
if(usedSize==0){
throw new RuntimeException("当前队列中没有多余的元素,我们无法进行出元素");
}
int data=array[0];
int temp=array[0];
array[0]=array[usedSize-1];
array[usedSize-1]=temp;
usedSize--;
DownJust(0,usedSize,array);
return data;
}
调整为大堆的代码:
class TestHeap{
public int array[];
int usedsize;
TestHeap()
{
this.array=new int[20];
}
public void display()
{
for(int i=0;i<usedsize;i++)
{
System.out.println(array[i]);
}
}
public void createBigHeap(int[] arr1)
{
for(int i=0;i<arr1.length;i++)
{
array[i]=arr1[i];
usedsize++;
}
for(int j=(usedsize-1-1)/2;j>=0;j--)
{//这里的代码是调整每一个节点,要传数组的长度,因为要判断是否有右节点和向下调整的限度
adjustDown(j,usedsize);
}
System.out.println(Arrays.toString(array));
}
public void adjustDown(int parent,int len)//这个函数用于处理二叉树每一个节点的向下调整
{
int child=2*parent+1;
//对于每一个节点,不是只调整一次就完了,而是要在保证他这个节点的子树也是一个大跟堆
while(child<len)
1)就是判断第一次进行入到这个循环的时候,判断左孩子是否存在
2)当孩子节点大于数据的长度,说明已经调整完了(这里面我们进行计算的孩子节点,只要计算的孩子节点无论是左孩子还是右孩子只要发现他的下标大于了有效数据的个数,那么就算调整完成
{
if (child + 1 < len && array[child] < array[child+1]){
//在这里面我们要注意一个点,我们此处做的目的是让child这个下标指向的位置这棵树根节点左右孩子最大位置,但是可不可以直接比较array[child]与array[child+1】
// 的大小呢,这是绝对不可以的,没有右孩子怎么办呢?
如何判断是否有右孩子呢?左孩子+1<len就可以了
child++;
}
if(array[child]>array[parent])
{
int temp=array[child];
array[child]=array[parent];
array[parent]=temp;
parent=child;
child=2*parent+1;
}else{
break;
}
}
}
}
现在我们来进行思考一下,建立一个大根堆的时间复杂度是多少呢?
叶子结点都是没有进行调整过的,我们只是调整过根节点,假设是一颗完全二叉树,根据代码显式的时间复杂度应该是N*logN?
A
B C
D E F G
H I J K L M N O
1)我们在建立整个的大堆的时候,第一层的节点,A个数是1(2^0),只有一个节点,相当于每一个节点要调整3(高度-1)次,最坏的情况下,都要进行向下调整
2)第二层的节点,个数是2(2^1)(因为我们在实际调整的时候,每一个节点都要进行调整,况且还要进行向下调整),每一个节点调整次数是(2次)(高度-2)
3)第三层的个数是4(2^2),每一个节点要调整一次;
最后得出的结论是:(骑个马)每一层的节点个数*这一层的高度((每一个节点调整的高度))
每一层一共有2^(N-1)个节点,每一个节点进行向下要调整(树的高度-1)次
时间复杂度为:T=2^0*(h-1)+2^1*(h-2)+2^2*(h-3).......+2^(h-2)*1(注意最后一层的节点数是2^(h-1),最后一层时不需要进行调整的,那么倒数第一层就是2^(h-1),倒数第一层的节点每一个节点调整一次,运用错位相减法
2T=2^1*(h-1)+2^2*(h-2)+2^3*(h-3)+2^4*(h-4)+........2^(h-1)*1
标红色位置的进行相减,最终得到T=-(h-1)+2^1+2^2+2^3+2^(h-1)
T=2^0-h+2^1+2^2+2^3+.......(-h)
h=log(N+1)
T=n-log(n+1)当N越来越大,接近于O(N),这就是建堆的时间复杂度
class hello {
static class Heap
{
int arr1[];
int usedsize;
public Heap() {
this.arr1 = new int[10];
}
public void createbig()
{
for(int i=(arr1.length-1-1)/2;i>=0;i--)
{
downadjust(i,usedsize);//这里的代码是调整每一个节点,要传数组的长度,因为要判断是否有右节点和向下调整的限度
}
}
public void downadjust(int front,int len)
{
int parent=front;
int child=2*parent+1;
while(child<len) {
if (child + 1 < len && arr1[child] < arr1[child + 1])//判断是否有右孩子并保证当前child指向的是左右孩子中最大的元素
{
child++;
}
if (arr1[child] > arr1[parent]) {
int temp=arr1[child];
arr1[child]=arr1[parent];
arr1[parent]=temp;
parent = child;
child = 2 * parent + 1;
} else {
break;
}
}
}
public void upjust(int child)
{
int parent=(child-1)/2;
while(child>0)
{
if(arr1[child]>arr1[parent])
{
int temp=arr1[child];
arr1[child]=arr1[parent];
arr1[parent]=temp;
child=parent;
parent=(child-1)/2;
}
else
{
break;
}
}
}
public boolean isfull()
{
return usedsize== arr1.length;
}
public void push(int data)
{ if(isfull())
{
this.arr1=Arrays.copyOf(arr1,2*arr1.length);
}
arr1[usedsize]=data;
usedsize++;
upjust(usedsize-1);
}
public void pop()//出当前的堆顶元素
{
int temp=arr1[0];
arr1[0]=arr1[usedsize-1];
arr1[usedsize-1]=temp;
usedsize--;
downadjust(0,usedsize);
}
public Boolean isnull()
{
return usedsize==0;
}
public int top()
{ if(isnull())
{
throw new RuntimeException("堆里面没有元素");
}
return arr1[0];
}
public void show()
{
for(int i=0;i<usedsize;i++)
{
System.out.println(arr1[i]);
}
}
}