数据结构与算法---堆的基本操作

堆的定义

堆可以看做是一种特殊的树,堆结构满足两个条件:

1、堆是一个完全二叉树。
2、堆的每一个节点的值都大于等于(或小于等于)其子节点的值。

大于等于子节点的值我们叫它:大顶堆
小于等于子节点的值我们叫它:小顶堆

大顶堆

在这里插入图片描述

小顶堆

在这里插入图片描述

非堆

不满足完全二叉树定义,不是一个堆
在这里插入图片描述

堆的构建

堆化

我们知道堆是一种完全二叉树,所以我们一般直接用数组来表示堆的结构,通过数组的下标就能够定位到树的任意位置,这是一种即高效又节省空间的选择。

举个例子:
在这里插入图片描述

数组下标中:对于第i位置的元素来说,第i2+1的位置是它的左子节点,第i2+2的位置是它的右子节点。对于每个子节点来说第(i-1)/2的位置就是它的父节点。

因此,当我们需要往堆中添加一个元素的时候,只需要直接先把元素添加到数组的最后即可,当然直接添加到数组最后就不满足堆的结构了,比如这时候要添加一个值为8,如果直接添加到数组最后显示不合适,因此我们还需要进行一些调整,而这个调整的过程一般称为堆化(heapify)。

堆化有两种,一种是自下而上,一种是自上而下。

1、向堆中添加一个元素

添加一个新的元素时,一般使用自下而上的方式,思想也比较简单,就是每次和其父节点比较,如果比父节点大,就交换,并继续与其父节点比较,直到比其父节点小,或者已经找到最顶层。

代码示例:

public class MyHeap {
    private int[] heapArr;
    private final int capacity;
    private int heapSize;

    public MyHeap(int capacity) {
        // 初始化数组大小
        heapArr = new int[capacity];
        // 初始化堆的大小
        this.capacity = capacity;
        // 初始化堆的当前大小
        heapSize = 0;
    }

    public void push(int val) {
        // 超过了堆的大小
        if (heapSize == capacity) {
            return;
        }
        // 先放到数组最后一位
        heapArr[heapSize] = val;
        heapInsert(heapArr, heapSize++);
    }

    private void heapInsert(int[] arr, int idx) {
        // 用当前节点和其父节点进行比较,如果比父节点大则进行交换,再继续比较。
        while (arr[idx] > arr[(idx - 1) / 2]) {
            swap(arr, idx, (idx - 1) / 2);
            idx = (idx - 1) / 2;
        }
    }

    private void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

2、从堆中取出一个元素

堆化的另一种就是自上而下的,比如要从大顶堆中取出一个最大的数,那就直接从堆顶取即可,但取完之后,肯定还需要从其左子节点或者右子节点中取出一个较大的变成新的堆顶值,依次向下类推。

比如当前堆是这样的,我们现在需要取出最大的值,也就是9
在这里插入图片描述

过程应该是把较大的子节点向上移,也就是8,然后再把8下面的子节点中较大的向上移,最后变成这样。
在这里插入图片描述

但是这边有个问题就是,你应该已经看出来了,最后的结果已经不满足堆的条件了(不是完全二叉树)。

所以实际上我们需要改变一下思路,每次应当把最后一个节点的值放到最上面,然后再进行判断就好了。

第一步应该是这样,把最后一个节点3移上来。
在这里插入图片描述

然后进行判断,所以最后应该是这样,保持了大顶堆的结构。

在这里插入图片描述

代码示例:

	// 取出堆顶的值
    public int pop() {
        int ans = heapArr[0];
        swap(heapArr, 0, --heapSize);
        heapify(heapArr, 0, heapSize);
        return ans;
    }

    private void heapify(int[] heapArr, int idx, int heapSize) {
        // 拿到当前节点的左子节点
        int left = idx * 2 + 1;
        // 如果左子节点的下标超过当前堆的大小,说明当前节点已经没有子节点了。
        while (left < heapSize) {
            // 用于存放,左右子节点中,较大的节点值
            int largest;
            // 看看是否存在右子节点,如果不存在直接取左子节点为较大的节点即可
            if (left + 1 == heapSize) {
                largest = left;
            } else {
                // 取左右子节点中较大的一个
                largest = heapArr[left] > heapArr[left + 1] ? left : left + 1;
            }
            // 取出子节点中较大的一个,再与父节点比较,看看谁大
            largest = heapArr[largest] > heapArr[idx] ? largest : idx;
            // 如果 largest == idx 说明左右子节点都不比自己大了
            if (largest == idx) {
                break;
            }
            // 把子节点中较大的交换上去
            swap(heapArr, largest, idx);
            // 重新赋值idx位置
            idx = largest;
            // 继续从子节点的左子节点开始判断
            left = idx * 2 + 1;
        }
    }

堆的应用场景

下篇文章中我们在看看堆有哪些应用场景:数据结构与算法—堆的应用场景

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
你好!C语言中的数据结构与算法是编写高效和可维护代码的关键。C语言提供了一些基本的数据结构,如数组和结构体,并且允许用户自定义更复杂的数据结构。在算法方面,C语言提供了各种处理和操作数据的功能。 以下是一些常见的数据结构算法的示例: 1. 数组:C语言中的数组是一种线性数据结构,可以存储相同类型的多个元素。数组可以通过索引来访问和修改其中的元素。 2. 链表:链表是一种动态数据结构,由一系列节点组成,每个节点包含数据和指向下一个节点的指针。链表可以用于实现栈、队列等数据结构。 3. 栈和队列:栈和队列都是线性数据结构,具有不同的插入和删除操作顺序。栈采用先进后出(LIFO)的原则,而队列采用先进先出(FIFO)的原则。 4. 树:C语言中可以使用指针和结构体来实现二叉树、二叉搜索树、等树型数据结构。树在搜索、排序等方面有广泛应用。 5. 图:图由节点和边组成,用于表示不同元素之间的关系。图可以使用邻接矩阵或邻接表来表示。 在算法方面,C语言提供了各种排序算法(如冒泡排序、插入排序、快速排序等),查找算法(如线性查找、二分查找等)和图算法(如最短路径算法、最小生成树算法等)的实现。 这只是数据结构与算法的一个简单介绍,如果你对特定的数据结构算法有更深入的问题,可以继续提问!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码拉松

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值