欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

数据结构之二叉树与二叉堆

程序员文章站 2022-06-05 12:44:46
...

二叉树的数据结构:
A、每个结点都有一个指向其第一个孩子的指针
B、每个结点都有一个指向其第一个右兄弟的指针

数据结构之二叉树与二叉堆

普通树如何转换成二叉树:
凡是兄弟就用线连起来,然后去掉父亲到儿子的连线,只留下父母到其第一个子女的连线。

二叉树的遍历方式:
数据结构之二叉树与二叉堆

先序遍历(中 左 右): ABDFCEGH 优先级: 父>左孩子>右孩子

中序遍历(前 中 后): DFBACGEH 优先级:左孩子>父>右孩子

后序遍历(前 后 中): FDBGHECA 优先级 :左孩子>右孩子>父

层次遍历:按照层次 从左到右遍历:ABCDEFGH

二叉堆
二叉堆本质上是一种完全二叉树,它有2种:
1.最小堆(父结点的值,小于左右结点)
数据结构之二叉树与二叉堆
2.最大堆(父结点的值,大于左右结点)
数据结构之二叉树与二叉堆

二叉堆的基本操作:
a.插入操作(首先在末尾添加结点 再通过上浮操作 将结点放在合适的地方)
数据结构之二叉树与二叉堆
注:比较新增结点与其父结点的关系,从而决定是否上浮。

b.删除操作(比如要删除 堆顶元素 为了保持完全二叉树的结构 用最后一个结点补到堆顶位置(删除结点处)再进行下沉操作 )
数据结构之二叉树与二叉堆

通过比较新堆顶元素与左右结点的关系,从而决定是否进行下沉操作。

c.构建二叉堆
将一个无序的完全二叉树转换成 二叉堆(最小堆或者最大堆)

最小堆: 由底层到高层 对所有非叶子结点(不存在子节点),进行上浮操作。
由高层层到低层 对所有非叶子结点(不存在子节点),进行下沉操作。
最大堆 反过来

二叉堆的代码实现
二叉堆虽然是一颗完全二叉树,但它并不是链式存储,而是顺序存储。换句话说二叉堆的所有结点都是
存储在数组当中的。并且 父结点与左右结点的下标存在这样的关系:

左孩子=parent*2+1;
右孩子=parent*2+2;
parent=(孩子-1)/2 //左右孩子都是
通过这样的关系可以继续延伸
最后一个非叶子节点的下标为:(array.length-2)/2
判断是否有子节点:左孩子下标

public class HeapOperator {
       public static void upAdjust(int[] array) {     
             int childIndex=array.length-1;
             int parentIndex=(childIndex-1);

             //temp保持插入的叶子节点值,用于最后赋值
             int temp=array[childIndex];

             //childIndex不是堆顶 并且 子节点的值 小于父结点  
             //进行上浮操作
             while(childIndex>0&&temp<array[parentIndex])
             {
                    //无需真正交换   将被替换的节点 赋给上浮的子节点就行
                    array[childIndex]=array[parentIndex];
                    childIndex=parentIndex;
                    parentIndex=(parentIndex-1)/2; //变小


             }

             //若是上浮成功 对上浮来的子节点赋值
             array[childIndex]=temp;
       }
       /**
        * 下沉调整
        * @param array     待调整的堆
        * @param parentIndex    要下沉的父节点
        * @param parentIndex    堆的有效大小
        */
       public static void downAdjust(int[] array, int parentIndex, int length) {
             //保持父节点的值用于最后的赋值
             int temp=array[parentIndex];
             int childIndex=2*parentIndex+1;

             while(childIndex<length) //存在子节点
             {
                    //存在右孩子 并且右孩子的值小于左孩子  定位到右孩子
                    if(childIndex+1 <length&&array[childIndex-1]<array[childIndex])
                    {
                           childIndex++;
                    }
           //如果temp 小于任何一个结点的值直接跳出

                    if(temp <array[childIndex])

                    {
                           break;
                    }


                    //无需进行真正交换
                    array[parentIndex]=array[childIndex];
                    parentIndex=childIndex;
                    childIndex=2*childIndex+1;   //变大了

             }
             array[parentIndex]=temp;
       }
       /**
        * 构建堆
        * @param array     待调整的堆
        */
       public static void buildHeap(int[] array) {
           //从底层的最后一个非叶子节点进行下沉操作
             //这样效率偏低
//           for (int i=(array.length-2)/2;i>=0;i--)
//           {
//                  downAdjust(array, i, array.length);
//           }

             //这样理论更快点
             for(int i=0;i<=(array.length-2)/2;i++)
             {
                    downAdjust(array, i, array.length);
             }
       }
       public static void main(String[] args) {

             //二叉堆创建
             //非叶子结点  大值下沉
        long startTime=System.currentTimeMillis();//记录开始时间
             int[] array =new int[]{7,1,3,10,5,2,8,9,60};
             buildHeap(array);
             System.out.println(Arrays.toString(array));

             long endTime=System.currentTimeMillis();//记录结束时间
             float excTime=(float)(endTime-startTime)/1000;
             System.out.println("执行时间:"+excTime+"s");

             //节点添加
              array=new int[]{1,3,2,6,5,7,8,9,10,0};
             upAdjust(array);
             System.out.println(Arrays.toString(array));
       }
}

利用二叉堆实现 堆排列和优先级队列
特性
1.在末尾添加一个结点 通过优先级 进行上浮操作
2.完成堆顶节点(队列) 删除节点(栈顶与最后一个节点交换)时,通过下沉操作 会根据优先级选举出新的堆顶
3.遍历删除 会将最小堆变成最大堆

public class HeapSort {
       public static void downAdjust(int[] array, int parentIndex, int length) {
             //保持父节点的值用于最后的赋值
             int temp=array[parentIndex];
             int childIndex=2*parentIndex+1;

             while(childIndex<length) //存在子节点
             {
                    //存在右孩子 并且右孩子的值小于左孩子  定位到右孩子
                    if(childIndex+1 <length&&array[childIndex-1]<array[childIndex])
                    {
                           childIndex++;
                    }
           //如果temp 小于任何一个结点的值直接跳出

                    if(temp <array[childIndex])

                    {
                           break;
                    }


                    //无需进行真正交换
                    array[parentIndex]=array[childIndex];
                    parentIndex=childIndex;
                    childIndex=2*childIndex+1;   //变大了

             }
             array[parentIndex]=temp;
       }

       public static void heapSort(int [] array)
       {
             //进行下沉操作
             for(int i=0;i<=(array.length-2)/2;i++)
             {
                    downAdjust(array, i, array.length);
             }
             System.out.println(Arrays.toString(array));

             //循环删除堆顶元素

             for(int i=array.length-1;i>0;i--)
             {
                    //最后一个元素与第一个元素交换
                    int temp=array[i];
                    array[i]=array[0];
                    array[0]=temp;
                    //下沉调整最大堆
                    downAdjust(array, 0, i);

             }

       }

       public static void main(String[] args) {


             int[] arr=new int[]{1,3,2,6,5,7,8,9,10,0};
             //先变成最小堆
             //再通过删除栈顶变成了 最大堆
             heapSort(arr);
             System.out.println(Arrays.toString(arr));

       }
}

堆排序 (优先级队列排序) VS 快速排序法

快速排序法
627389
初始量 i=0 (指向第一个数据) j=n-1(指向最后一个数据) k参照值

第一次循环(分2个步骤 先小后大)
a. 从右往左 寻找比k小的值m
交换k与m 的位置
j=m的下标
327689
b.从左往右 寻找比k大的值l
交换k与l 的位置
i=l的下标
326789

下个循环…..
再一次
a.从右往左 寻找比k小的值m
交换k与m 的位置
j=m的下标
当i=j 时 排序完成

建堆:O(n)
堆排序:O(nlogin)
总和:O(nlogin)

堆排序 vs 快速排序
时间 O(nlogin) O (nlogin)
空间 O(1) O(n)

几种复杂的表示方式:
O(1): 耗时/耗空间与输入数据大小无关
O(n): 就代表数据量增大几倍,耗时也增大几倍。比如常见的遍历算法。
O(n^2),就代表数据量增大n倍时,耗时增大n的平方倍,这是比线性更高的时间复杂度。比如冒泡排序,就是典型的O(n^2)的算法,对n个数排序,需要扫描n×n次。
O( login)当数据增大n倍时,耗时增大logn倍(这里的log是以2为底的,比如,当数据增大256倍时,耗时只增大8倍,是比线性还要低的时间复杂度)。如:二分查找
O(nlogn)同理,就是n乘以logn,当数据增大256倍时,耗时增大256*8=2048倍。这个复杂度高于线性低于平方。归并排序就是O(nlogn)的时间复杂度。多次使用二分法。

相关标签: 二叉树 二叉堆