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日

相关文章

  • vue-cli4.x创建企业级项目的方法步骤

    下面是详细讲解“vue-cli4.x创建企业级项目的方法步骤”的完整攻略。 步骤一:安装Node.js和Vue.js 首先,我们需要在本地电脑上安装Node.js。我们可以在官网上下载符合自己系统的安装包并安装完成。完成Node.js的安装后,我们需要使用npm命令安装Vue.js。安装Vue.js的方法如下: npm install vue 步骤二:安装V…

    other 2023年6月27日
    00
  • win8/win10更新系统后重启电脑没反应的四种解决方法

    win8/win10更新系统后重启电脑没反应的四种解决方法 在使用Windows 8或Windows 10等操作系统时,更新系统是非常常见的操作。但有时候在系统更新完毕后重启电脑时,会发现电脑没反应,无法正常启动。那么这时候我们该如何解决这个问题呢?以下是几种可行的解决方法。 1. 停止和清除软件分发文件夹的内容 步骤如下: 按下键盘上的Win+R键,打开“…

    other 2023年6月27日
    00
  • C语言基础之malloc和free函数详解

    C语言基础之malloc和free函数详解 在C语言中,malloc和free是用于动态内存分配和释放的两个重要函数。本文将详细讲解它们的使用方法和注意事项。 1. malloc函数 malloc函数用于在运行时动态分配内存空间。它的函数原型如下: void* malloc(size_t size); size参数表示要分配的内存空间的字节数。 malloc…

    other 2023年8月1日
    00
  • RabbitMQ在特来电的深度应用

    RabbitMQ在特来电的深度应用的完整攻略 本文将为您提供RabbitMQ在特来电的深度应用的完整攻略,包括介绍、使用方法和两个示例说明。 介绍 RabbitMQ是一款开源的消息队列软件,可以用于实现分布式系统中的消息传递和异步处理。特来电是一家提供新能源汽车充电服务的公司,使用RabbitMQ实现了充电桩和后台系统之间的消息传递和异步处理。本文将介绍Ra…

    other 2023年5月6日
    00
  • C++关于构造函数可向父类或者本类传参的讲解

    关于C++的构造函数可以向父类或者本类传参的问题,我们可以用以下内容进行详细讲解。 1. 构造函数可向父类传参 1.1 基本概念 在类的继承关系中,子类继承了父类的属性和方法,因此在子类的构造函数中,我们需要先调用父类的构造函数,然后再进行子类自身的初始化工作。这里就涉及到了父类构造函数的参数问题。 在调用父类构造函数时,可以将参数传递给父类构造函数,并在父…

    other 2023年6月26日
    00
  • java时间 java.util.Calendar深入分析

    Java时间:java.util.Calendar深入分析 java.util.Calendar是Java日期和时间处理的核心类之一。它能够处理Java程序中与日期和时间相关的操作。本文将深入介绍Calendar类,让开发者更加全面地了解它的使用。 1. Calendar类的概述 Calendar类是一个抽象类,用于将日期和时间抽象成一个可以操作的对象,使得…

    other 2023年6月27日
    00
  • MIP经典问题:旅行商问题 (traveling salesman problem)

    MIP经典问题:旅行商问题 (Traveling Salesman Problem) 旅行商问题(Traveling Salesman Problem,缩写为TSP)是一个经典的组合优化问题,它的目标是在已知的一组城市之间寻找一条路径,使得旅行商可以最小化旅行的总路程并回到出发城市。 问题描述 问题的输入是一组城市,这些城市之间的距离是已知的。旅行商需要从出…

    其他 2023年3月28日
    00
  • 不使用jQuery对Web API接口POST,PUT,DELETE数据

    不使用jQuery对Web API接口POST, PUT, DELETE数据 jQuery是一个流行的JavaScript库,被用于开发Web应用程序。然而,jQuery并非必需品,JavaScript本身就提供了许多功能,可以访问Web API,从而可以在不使用jQuery的情况下进行POST, PUT和DELETE的请求。在这篇文章中,我们将介绍如何使用…

    其他 2023年3月28日
    00
合作推广
合作推广
分享本页
返回顶部