二叉树和堆的联系与区分

本文介绍了二叉树的概念,包括满二叉树和完全二叉树的定义,以及如何用一维数组存储完全二叉树。接着详细讲解了堆的特性,区别于二叉搜索树,并探讨了最小堆和最大堆。堆排序的过程被详细阐述,包括如何构造初始堆、调整堆以及插入和删除元素。文章最后总结了堆排序的时间复杂度和稳定性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

首先引出树的定义:

1.树就是不包含回路的连通无向图,本质是!!
2.一棵树中的任意两个结点有且仅有唯一的一条路径连通。
3.一棵树如果有n个结点,那么它一定恰好有n-1条边。

二叉树

二叉树是一种特殊的树。二叉树的特点是每个结点最多有两个儿子,左边的叫做左子树,右边的叫做右子树,也就是说,二叉树要么为空,要么由根结点、左子树和右子树组成,而左子树和右子树分别是一棵二叉树,也就是说根结点左右可能为null。

满二叉树与完全二叉树

如果二叉树中每个内部结点都有两个儿子,这样的二叉树叫做满二叉树。

如果一棵二叉树除了最右边位置上一个或者几个叶结点缺少外其它是丰满的,那么这样的二叉树就是完全二叉树。

完全二叉树如何储存

完全二叉树中父亲和儿子之间有着神奇的规律,我们只需用一个一维数组就可以存储完全二叉树。首先将完全二叉树进行从上到下,从左到右编号。我们发现如果完全二叉树的一个父结点编号为k,那么它左儿子的编号就是2k,右儿子的编号就是2k+1。如果已知儿子(左儿子或右儿子)的编号是x,那么它父结点的编号就是x/2。

堆是什么?堆是一种特殊的完全二叉树,只不过父亲与儿子节点间有关系。也就是首先要满足完全二叉树的定义,只有右下角可能有缺。

所有父结点都比子结点要小的完全二叉树我们称为最小堆(Java中的优先级队列PriorityQueue默认最小堆)。反之,如果所有父结点都比子结点要大,这样的完全二叉树称为最大堆。

这里与二叉搜索树的区别是搜索树中根为中间值,左小右大!

堆的创建

把n个元素建立一个堆,首先我们可以将这n个结点以自顶向下、从左到右的方式从1到n编码,即以层序遍历的顺序编码,这样就可以把这n个结点转换成为一棵完全二叉树。紧接着从最后一个非叶结点(结点编号为n/2)开始到根结点(结点编号为1),逐个扫描所有的结点,根据需要将当前结点向下调整,直到以当前结点为根结点的子树符合堆的特性。
一般以堆排序为例,开始给你个无序数组,先把它写成完全二叉树的形式,就是按层编号一样。

堆的插入与删除

若最小(大)堆要进行删除最小(大)数并返回最小(大)数的操作,我们只需要删掉堆顶元素即最小(大)数,将最后一个数放在堆顶,通过比较与左右儿子的大小向下调整即可恢复为最小(大)堆。

若最小(大)堆要进行删除最小(大)数并插入一个新的数的操作,我们只需要删掉堆顶元素即最小(大)数,将新的数放在堆顶,通过比较与左右儿子的大小向下调整即可恢复为最小(大)堆。

若最小(大)堆只需进行插入一个新的数,我们只需要把新的数放在末尾,通过与父亲的比较向上调整即可恢复最小(大)堆。

总结来说就是删除就是删除堆顶元素,如果同时插入则把插入的数放在堆顶。
如果只删除那么堆顶的空缺就由最后一个元素(也就是最下面一行最右边的元素,即当前序号最大的元素)来填补上,再进行调整。
如果直接插的话就把插的数放在最后一个元素的后面,进行调整。

【堆排序(Heap Sort)】

堆排序的基本思想是:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次大值。如此反复执行,便能得到一个有序序列了。

1.构造初始堆。将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)。
看堆顶的序号,来判断第一个非叶子节点的索引。这点很重要!
2.此时我们从最后一个非叶子结点开始,从左至右,从下至上进行调整。
调整就是找每个非叶子节点的叶子节点是不是比他大,把大的交换上来,这一层交换结束再交换上一层,直到使根节点即堆顶元素为最大值。
这里要注意,如果上一层交换之后,当前层又不满足堆的要求,继续交换。
3. 将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换。

也就是说每一层堆顶与末尾交换,都能得到一个当前的最大值,放在有序数组的最后面相当于!!然后从后往前填充为有序数组。

再简单总结下堆排序的基本思路

a.将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;

b.将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;

c.重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。

代码如下!

堆排序是不稳定的排序。最差时间复杂度为O(nlogn),平均时间复杂度为O(nlogn)。

import java.util.*;

public class HeapSort {
    public int[] heapSort(int[] A, int n) {
        int i;
        //构建大顶堆(升序),从最后一个非叶子节点(数组长度一半/2)开始,从后往前调整.
        for (i = n/2 - 1; i >= 0; i--){
            //开始进行调整,大值往上来
            adjustHeap(A, i, n);
        }
        //已经是大顶堆啦,然后将根节点与最后一个节点交换,将根节点调整后已经为最大,这时交换放在最后,作为有序部分.
        for (int j = n - 1; j > 0; j--){
            swap(A, 0, j);
            //交换一个值后,再重新调整,此时是去掉最大值的其他元素构成大顶堆,只不过从0开始,相当于把小值一层层往下传,换第二大值上来,之后再交换.
            adjustHeap(A, 0, j);
        }
        return A;
    }

    //调整的方法主要是把大值往上送.是指的单个小子二叉树之间找最大值(确定k),调用前的for循环可以往上面一层层的送
    public static void adjustHeap(int[] A, int i, int n){
        //先把值取出来,等着进行交换
        int temp = A[i];
        //从指定非叶子节点的左子节点开始找,再找孙子节点,这里是往下一层层找,先从1.2层换,再2.3层换,只有第一次是从最下面非叶子节点开始,
        for (int k = 2 * i + 1; k < n; k = 2 * k + 1){   //第一次换好了最上层,然后下面第二层再继续调整,直到变成完美的大顶堆
            //左右两个叶子节点进行比较,让k指向大的那个,这里是两个叶子节点之间选,
            if (k + 1 < n && A[k] < A[k+1]){
                k++;   //就是左子节点小,则指向右子节点,相当于确定了k
            }
            //在该终端二叉树中找到那个大值,放到这个终端二叉树的根节点上,同时它也是上层数的子节点.
            if (A[k] > temp){
                A[i] = A[k];
                i = k;
            }else {
                break;
            }
        }
        A[i] = temp;   //把原来的非叶子节点的值传下来了.
    }

    public static void swap(int[] A, int a, int b){
        int temp = A[a];
        A[a] = A[b];
        A[b] = temp;
    }
}

堆排序是一种选择排序,常规选择排序是把最小值放去前面,依次向后填充,时间复杂度为O(n2)。堆排序整体主要由 构建初始堆 + 交换堆顶元素和末尾元素 并重建堆两部分组成。其中构建初始堆经推导复杂度为O(n),在交换并重建堆的过程中,需交换n-1次,而重建堆的过程中,根据完全二叉树的性质,[log2(n-1),log2(n-2)…1]逐步递减,近似为nlogn。所以堆排序时间复杂度一般认为就是O(nlogn)级。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值