C语言每日练习之二叉堆

C语言每日练习之二叉堆

什么是二叉堆?

二叉堆是一种特殊的二叉树,它满足两个特性:

  1. 堆的父节点的键值总是大于或等于(小于或等于)任何一个子节点的键值;
  2. 堆总是一棵完全二叉树。

实现二叉堆

数据结构

为了实现二叉堆,我们需要先定义数据结构。二叉堆常常使用数组来表示,数组中第一个元素一般为根节点,其余元素依次为树中其它节点的值。通过数组下标计算节点间的关系,可以快速地访问堆中的元素。

typedef struct 
{
  int* array;     // 指向堆的数组
  int count;      // 堆中元素的数量
  int capacity;   // 堆的容量,即最多可以存储多少个元素
} Heap;

创建二叉堆

我们可以使用自上而下或自下而上两种方式创建二叉堆。这里我们讲下自下而上的方法,从最后一个非叶子节点开始。假设当前节点的下标是i,其父节点的下标为(i-1)/2,左右子节点的下标为2*i+12*i+2,那么可以用下面的代码创建一个二叉堆。

void buildHeap(Heap* heap, int* array, int count)
{
  // 将数组复制到堆中,注意下标从0开始
  memcpy(heap->array, array, count * sizeof(int));
  heap->count = count;
  heap->capacity = count;

  for (int i = (count - 1) / 2; i >= 0; i--)
  {
    shiftDown(heap, i);
  }
}

插入元素

在向堆中插入元素时,我们可以将其插到数组的尾部,并沿着其父节点和祖先节点逐步上移,以保持堆的特性。

void insert(Heap* heap, int data)
{
  assert(heap->count < heap->capacity);

  heap->array[heap->count] = data;
  heap->count++;

  int i = heap->count - 1;
  while (i > 0 && heap->array[(i - 1) / 2] < heap->array[i])
  {
    swap(&heap->array[(i - 1) / 2], &heap->array[i]);
    i = (i - 1) / 2;
  }
}

弹出堆顶元素

在弹出堆顶元素时,我们可以取出根节点,并将堆尾的元素放到根节点的位置,然后沿着其子节点逐步下移,以保持堆的特性。

int pop(Heap* heap)
{
  assert(heap->count > 0);

  int max_value = heap->array[0];
  heap->count--;

  heap->array[0] = heap->array[heap->count];
  shiftDown(heap, 0);

  return max_value;
}

下移元素

在下移元素时,我们可以将其与其两个子节点中较大的一个比较,如果小于子节点的值,则交换位置,继续向下比较。这样可以确保每次下移时,选择的都是当前节点和子节点中最大的那个,从而保证堆的特性不变。

void shiftDown(Heap* heap, int i)
{
  int max = i;
  int left = 2 * i + 1;
  int right = 2 * i + 2;

  if (left < heap->count && heap->array[left] > heap->array[max])
  {
    max = left;
  }

  if (right < heap->count && heap->array[right] > heap->array[max])
  {
    max = right;
  }

  if (max != i)
  {
    swap(&heap->array[i], &heap->array[max]);
    shiftDown(heap, max);
  }
}

示例说明

示例一:使用二叉堆来进行堆排序

假设我们有一个数组int array[] = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};,我们可以使用一个二叉堆来排序这个数组。首先将数组中的元素插入到二叉堆中,然后反复弹出堆顶元素。按照从大到小(或从小到大)的顺序,输出弹出的元素即为排好序的数组。

void heapSort(int* array, int count)
{
  Heap heap;
  heap.array = (int*)malloc(count * sizeof(int));
  heap.count = 0;
  heap.capacity = count;

  for (int i = 0; i < count; i++)
  {
    insert(&heap, array[i]);
  }

  for (int i = count - 1; i >= 0; i--)
  {
    array[i] = pop(&heap);
  }

  free(heap.array);
}

示例二:求一个数组中第k大的元素

假设我们有一个数组int array[] = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};,我们想要求出其中第5大的元素。我们可以使用一个小根堆来维护前k大的元素(假设我们维护的是前k大的元素,那么堆的容量为k),将数组中的元素逐个插入到堆中,如果堆中元素的数量大于k,就弹出堆顶元素。当插入完所有的元素后,堆顶元素即为第k大的元素。

int findKthLargest(int* array, int count, int k)
{
  Heap heap;
  heap.array = (int*)malloc(k * sizeof(int));
  heap.count = 0;
  heap.capacity = k;

  for (int i = 0; i < count; i++)
  {
    if (heap.count < k)
    {
      insert(&heap, array[i]);
    }
    else if (array[i] > heap.array[0])
    {
      heap.array[0] = array[i];
      shiftDown(&heap, 0);
    }
  }

  int result = heap.array[0];
  free(heap.array);

  return result;
}

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:C语言每日练习之二叉堆 - Python技术站

(0)
上一篇 2023年6月27日
下一篇 2023年6月27日

相关文章

  • Go语言依赖管理三要素示例解析

    Go语言依赖管理三要素示例解析攻略 介绍 Go语言是一种现代化的编程语言,具有强大的依赖管理系统。在Go语言中,依赖管理的三个要素是:go.mod文件、go.sum文件和go get命令。本攻略将详细讲解这三个要素,并提供两个示例说明。 1. go.mod文件 go.mod文件是Go语言项目的模块定义文件,用于管理项目的依赖关系。它包含了项目的模块路径、版本…

    other 2023年9月7日
    00
  • Javascript 实现广告后加载 可加载百度谷歌联盟广告 原创

    Javascript 实现广告后加载 可加载百度谷歌联盟广告 简介 在网页应用开发中,广告投放是一项重要的商业模式,但是直接加载广告会影响页面的加载速度和用户体验。为了解决这个问题,通常会采用广告异步加载的方式,即在页面初始化后再加载广告。本文将详细讲解如何使用Javascript实现广告后加载,以及如何加载百度谷歌联盟广告。 实现方式 1. 使用div容器…

    other 2023年6月25日
    00
  • weblogic服务器的简单使用(一)

    Weblogic服务器的简单使用(一) Weblogic服务器是一个被广泛使用于企业级应用的Java服务器,它提供了高可靠性、高可扩展性和高安全性等优点。在本文中,我们将会介绍如何在Windows操作系统下搭建Weblogic服务器,以及简单部署Web应用程序的步骤。 安装Weblogic服务器 首先,我们需要从Oracle官网下载Weblogic服务器的安…

    其他 2023年3月28日
    00
  • 【mq读书笔记】消息拉取长轮训机制(Broker端)

    【mq读书笔记】消息拉取长轮训机制(Broker端)的完整攻略 本文将为您详细讲解消息队列中的消息拉取长轮训机制,包括概念、实现原理、示例说明等内容。 概念 消息拉取长轮训机制是一种消息队列中的消费者拉取消息的方式。在该机制中,消费者向消息队列发送拉取请求,消息队列会在一定时间内等待消息的到来,如果有消息到来,则立即返回给消费者;如果没有消息到来,则等待一定…

    other 2023年5月6日
    00
  • java自定义封装StringUtils常用工具类

    下面是详细讲解“java自定义封装StringUtils常用工具类”的完整攻略。 简介 StringUtils是Apache Commons Lang库中的一个常用工具类,提供了大量对字符串的操作方法。然而,有时我们需要扩展该类的功能或自定义一些字符串操作方法。因此,可以自定义封装StringUtils常用工具类。 实现步骤 新建StringUtilsExt…

    other 2023年6月25日
    00
  • asp.net 编译器错误信息: CS0006: 未能找到元数据文件 该死的.NET

    CS0006是ASP.NET编译器错误之一,它通常与未能找到元数据文件有关。这意味着编译器无法访问它需要的程序集或引用。以下是解决此错误的步骤: 步骤1:检查应用程序文件的配置您可以检查应用程序的配置文件并确保它们引用了正确的程序集。例如,如果您在Web.config中引用了一个程序集,并且此程序集不在GAC中,则可能会引发此错误。您可以按照以下步骤解决此问…

    other 2023年6月26日
    00
  • ​​​​​​​C语言实现单链表基本操作方法

    下面是C语言实现单链表基本操作方法的完整攻略: 1. 定义单链表结构体 首先,需要定义一个单链表结构体,来描述节点的信息。结构体应该包含两个部分:数据域和指针域。数据域存储节点的值,指针域存储指向下一个节点的指针。 struct ListNode { int val; // 数据域,此处数据类型为 int struct ListNode *next; // …

    other 2023年6月27日
    00
  • linux下使用fdisk结合partprobe命令不重启系统添加一块新的磁盘分区

    添加一块新的磁盘分区通常需要使用fdisk命令和partprobe命令,但有时我们不想重启系统,可以使用以下步骤添加新的分区: 1. 查看所有磁盘分区信息 使用fdisk命令查看所有磁盘分区信息,输入以下命令: fdisk -l 该命令将列出所有的磁盘和分区信息。 2. 新建分区 我们假定我们要在/dev/sdb上新建一个分区,输入以下命令: fdisk /…

    other 2023年6月27日
    00
合作推广
合作推广
分享本页
返回顶部