【数据结构初阶】之堆(C语言实现)

数据结构初阶之堆(C语言实现)

  • 🌏 堆的概念
  • 🌏 堆的模拟实现
    • 🐓 堆的结构和方法接口
    • 🐓 堆的方法的模拟实现
      • 🙊 堆的初始化
      • 🙊 堆的构建
      • 🙊 堆的插入
      • 🙊 向上调整
      • 🙊 堆的删除
      • 🙊 向下调整
      • 🙊 堆的数据的个数
      • 🙊 堆的判空
      • 🙊 堆顶元素
      • 🙊 堆的销毁
      • 🙊 堆排序
        • 🌺 版本1 创建堆,通过push、pop进行操作 (异地操作)
        • 🌺 版本2 向上调整建堆(原地操作)
        • 🌺 版本3----向下调整建堆(原地操作)
          • 🍀 向下调整建堆的时间复杂度分析
      • 🙊 topK问题
        • 🌺 topK问题的分析
        • 🌺 topK问题的代码实现
        • 🌺 top问题的时间复杂度和空间复杂度分析
  • 🌏 测试程序
    • 🐓 打印堆
    • 🐓 验证是否是一个堆
    • 🐓 测试程序

前言:在二叉树基础篇我们提到了二叉树的顺序实现,今天让我们来学习一下特殊的二叉树———堆的相关知识。

📃博客主页: 小镇敲码人
💞热门专栏:数据结构与算法
🚀 欢迎关注:👍点赞 👂🏽留言 😍收藏
🌏 任尔江湖满血骨,我自踏雪寻梅香。 万千浮云遮碧月,独傲天下百坚强。 男儿应有龙腾志,盖世一意转洪荒。 莫使此生无痕度,终归人间一捧黄。🍎🍎🍎
❤️ 什么?你问我答案,少年你看,下一个十年又来了 💞 💞 💞

🌏 堆的概念

堆是一种完全二叉树,但是其节点值满足一些特定的规则,又分为小堆和大堆,小堆是任意根节点的值都小于等于它子树的节点值,大堆是任意根节点的值都大于等于它子树的值。

小堆:

在这里插入图片描述

大堆:

在这里插入图片描述

注意:不管是大堆还是小堆,其同一层的节点大小可任意。

🌏 堆的模拟实现

🐓 堆的结构和方法接口

在二叉树部分我们曾经提到,堆是一种完全二叉树,所以其使用顺序存储是不会浪费空间的,而顺序存储就是我们常说的动态顺序表。

#define  _CRT_SECURE_NO_WARNINGS 1
#include<stdbool.h>
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>typedef int HPDataType;
typedef struct Heap
{HPDataType* _a;int _size;int _capacity;
}Heap;//堆的初始化
void HeapInit(Heap* hp);
// 堆的构建(直接给一个数组)
void HeapCreate(Heap* hp, HPDataType* a, int n);
// 堆的销毁
void HeapDestory(Heap* hp);
// 堆的插入
void HeapPush(Heap* hp, HPDataType x);
// 堆的删除
void HeapPop(Heap* hp);
// 取堆顶的数据
//HPDataType HeapTop(Heap* hp);
// 堆的数据个数
int HeapSize(Heap* hp);
// 堆的判空
int HeapEmpty(Heap* hp);
//向上调整建堆
void AdjustUp(HPDataType* a, int child);
//向下调整
void AdjustDown(HPDataType* a, int parent, int n);
//两数交换
void Swap(HPDataType* hp1, HPDataType* hp2);
//打印堆
void HeapPrint(Heap* hp);
//堆排序
void Heapsort(HPDataType* a, int n);

🐓 堆的方法的模拟实现

下面我们的方法我们都默认建一个小堆。

🙊 堆的初始化

堆的初始化很简单,我们将其对应的指针置空,元素个数和空间大小置0即可。

//堆的初始化
void HeapInit(Heap* hp)
{assert(hp);//hp不为空hp->_a = NULL;//指针置为空hp->_capacity = hp->_size = 0;//元素和空间大小置为0
}

🙊 堆的构建

给你一个数组,你应该如何建一个小堆呢?我们来画图分析:

// 堆的构建
void HeapCreate(Heap* hp, HPDataType* a, int n)
{assert(hp);//断言,防止hp为空hp->_a = (HPDataType*)malloc(sizeof(HPDataType) * n);//为存节点的数组申请空间hp->_size = hp->_capacity = n;//更新空间大小和元素大小memcpy(hp->_a, a, sizeof(HPDataType) * n);//将数组里的值copy到我们的数据域里面//向上调整建一个小堆for (int i = 1; i < n; i++){AdjustUp(hp->_a,i);}
}

图解代码:
在这里插入图片描述

向下调整建堆的时间复杂度分析:

在这里插入图片描述

🙊 堆的插入

堆的插入应该如何实现呢?我们直接插入到线性表尾部就行,由于插入前是堆,所以我们走一个向上调整就可以将其调整为堆。

// 堆的插入
void HeapPush(Heap* hp, HPDataType x)
{assert(hp);//断言,防止hp为空if (hp->_size == hp->_capacity)//扩容{hp->_capacity = hp->_capacity == 0 ? 4 : hp->_capacity * 2;HPDataType* tmp = (HPDataType*)realloc(hp->_a,sizeof(HPDataType) * hp->_capacity);if (tmp == NULL){perror("realloc failed\n");exit(-1);}hp->_a = tmp;}hp->_a[hp->_size] = x;//插入hp->_size++;//向上调整AdjustUp(hp->_a,hp->_size - 1);//在插入位置走一个向上调整
}

🙊 向上调整

上面已经分析过了。

//向上调整建小堆
void AdjustUp(HPDataType* a, int child)
{assert(a);//a不为空int parent = (child - 1) / 2;//找到child的父亲节点,和其比较看是否需要调整while (child > 0){if (a[child] < a[parent])//建一个小堆,如果孩子节点比父亲节点的值小,需要调整{Swap(&a[child], &a[parent]);//交换child = parent;//将父亲节点的值给孩子,继续在这条路径上调整parent = (parent - 1) / 2;//更新父亲节点的下标}else{break;//孩子节点的值已经大于等于父亲节点的值了,不需要再继续调整}}
}

🙊 堆的删除

堆的删除我们是删除堆顶的数据,因为这个数据最值得去删,什么意思呢?举个例子1.因为删除最后一个节点不会改变堆的结构没什么意义,2.堆顶数据要么是最大值,要么是最小值,很特殊。

那我们应该如何实现堆的删除呢?我们还是画图来分析:

在这里插入图片描述
代码实现:

// 堆的删除
void HeapPop(Heap* hp)
{assert(hp);//hp不为空assert(hp->_size > 0);//堆的元素个数要大于0Swap(&hp->_a[0], &hp->_a[hp->_size - 1]);//交换堆顶元素和最后一个元素的值hp->_size--;//_size--//向下调整AdjustDown(hp->_a, 0, hp->_size);//根节点左树和右树为小堆,可以向下调整
}

🙊 向下调整

这个我们上面也已经分析了,直接给代码:

//向下调整(小堆)
void AdjustDown(HPDataType* a, int parent, int n)
{assert(a);//a不为空int child = parent * 2 + 1;//先假设最小的孩子是左孩子while (child < n){//找出最小的孩子if (child+1 < n && a[child] > a[child + 1])//如果右孩子存在且右孩子的值比左孩子更小{child = child + 1;}if (a[child] < a[parent])//最小的孩子比parent的值小,就交换{Swap(&a[child], &a[parent]);parent = child;//更新parentchild = parent * 2 + 1;//更新child,也假设最小的孩子是左孩子}else//最小的孩子比父亲节点的值大,已经不需要调整,是小堆了。{break;}}
}

🙊 堆的数据的个数

直接返回_size的值。

// 堆的数据个数
int HeapSize(Heap* hp)
{assert(hp);//hp不为空return hp->_size;
}

🙊 堆的判空

如果_size不为0,堆就不为空。

// 堆的判空
int HeapEmpty(Heap* hp)
{assert(hp);if (hp->_size == 0)//为空返回1return 1;else//不为空返回0return 0;
}

🙊 堆顶元素

堆顶元素在下标为0的位置。

// 取堆顶的数据
HPDataType HeapTop(Heap* hp)
{assert(hp);assert(hp->_size > 0);return hp->_a[0];
}

🙊 堆的销毁

堆的销毁实际上主要要做的就是回收在堆上开的空间,将相应的指针置为空,空间和元素大小置为0。

// 堆的销毁
void HeapDestory(Heap* hp)
{assert(hp);//hp不为空assert(hp->_size > 0);//元素个数要大于0free(hp->_a);//释放空间hp->_a = NULL;//将动态数组的指针置为空hp->_capacity = hp->_size = 0;//将空间和元素大小置为0
}

🙊 堆排序

由于小堆和大堆的堆顶元素都是最小或者最大的,所以我们pop堆顶元素,然后再剩下的元素调整,就能得到一个有序的序列。

🌺 版本1 创建堆,通过push、pop进行操作 (异地操作)

那我们该如何实现这个堆排序呢?下面的代码可以吗为什么?

void Heapsort(HPDataType* a, int n)
{Heap hp1;//假设我们建一个小堆HeapInit(&hp1);for (int i = 0; i < n; ++i){HeapPush(&hp1, a[i]);//插入对应元素}for (int i = 0; i < n; ++i){a[i] = HeapTop(&hp1);//将最小的元素依次给数组HeapPop(&hp1);//pop堆顶元素}
}

效果演示:

在这里插入图片描述

这种方法是不太好的,因为先暂且不谈时间复杂度的问题,我们为了去给数组排一个序,而建了一个堆,这个空间开销是没必要的,其实我们可以对数组原地建堆。

🌺 版本2 向上调整建堆(原地操作)
//堆排序,向上调整
void Heapsort2(HPDataType* a, int n)
{assert(a);for (int i = 1; i < n; i++)//我们向上调整,原地建一个小堆{AdjustUp(a,i);}for (int end = n - 1; end >= 0; --end){Swap(&a[0], &a[end]);//将堆顶元素放到堆最后面去AdjustDown(a, 0, end);//此时的end就代表我们的元素个数}
}

原地建堆,如果你想排降序,就需要建一个小堆,因为小堆的堆顶元素是最小的,我们原地操作,就可以将最小的和最后一个元素交换,这样没有破坏原来的结构,走一个向下调整就可以恢复堆结构。

在这里插入图片描述

时间复杂度:NlogN,向上调整建堆:NlogN,向下调整调整一次logN(一共调整了N次),所以总体的时间复杂度也是O(N*logN)。

🌺 版本3----向下调整建堆(原地操作)

但是这样就结束了吗,我们发现如果使用上面这种方法,既要写一个向上调整,也要写一个向下调整,我只是想排个序,需要这么复杂吗,难道就不能直接向下调整建堆吗?这样我就不用写两个了呀,调整那块肯定要用向下调整的,不然就破坏堆的结构了。

下面我们来介绍向下调整建堆

在这里插入图片描述
代码实现:

//堆排序,向下调整
void Heapsort3(HPDataType* a, int n)
{assert(a);for (int i = (n-1-1)/2; i >= 0; i--)//我们向上调整,原地建一个小堆{AdjustDown(a,i,n);}for (int end = n - 1; end >= 0; --end){Swap(&a[0], &a[end]);//将堆顶元素放到堆最后面去AdjustDown(a, 0, end);//此时的end就代表我们的元素个数}
}

运行结果:

在这里插入图片描述

🍀 向下调整建堆的时间复杂度分析

在这里插入图片描述

整体这种堆排序的时间复杂度也是O(N*logN),建堆的消耗是O(N),但是每一次调整的消耗还是logN,调整了次,量级还是和第二种一样,但是向下调整建堆只需要写一个向下调整,且向下调整建堆比向上调整建堆要更快。

🙊 topK问题

topK问题指的是让我们求第K大,第K小等问题,思考一下,在一个乱序的数组里面,如果我们想要这样来做,我们常规的解决办法是什么?排序!!!这样来做的时间复杂度是N*logN,我们只是求第K大或者第K小的数,有必要把这些数全部排一遍顺序吗,答案是不用,下面让我们来学习一下使用堆这种数据结构来解决经典的topK问题。

🌺 topK问题的分析

下面我们画图来分析一下topk问题具体的解决之道。

在这里插入图片描述

🌺 topK问题的代码实现

我们使用C语言的文件操作,造了100w个小于100w的数据(随机值),然后我们求前5个大的数,我们这样来验证,把文件里任意5个数改为大于100w的数,如果最后打印出来是我们改的5个数,证明我们的topk问题得到解决。

在这里插入图片描述

可以看到我们改了最后5个数,修改之后把创建数据的程序注释掉,防止它重新生成,我们的修改就没意义了。

代码实现:

void CreateNDate()
{// 造数据int n = 1000000;srand(time(0));const char* file = "data.txt";FILE* fin = fopen(file, "w");if (fin == NULL){perror("fopen error");return;}for (size_t i = 0; i < n; ++i){int x = rand() % 1000000;//造随机数据,可以让我们的测试程序得到更好的检查fprintf(fin, "%d\n", x);//将数据放入文件}fclose(fin);
}void PrintTopK(int k)
{const char* file = "data.txt";//定义文件FILE* flout = fopen(file, "r");if (flout == NULL){perror("fopen failed");exit(-1);}//开一个数组存放k个数据,开一个小堆HPDataType* minheap = (HPDataType*)malloc(sizeof(HPDataType) * k);if (minheap == NULL){perror("malloc failed");exit(-1);}for (int i = 0; i < k; i++)//将k个数放入数组中{fscanf(flout, "%d", &minheap[i]);}//向下调整建堆for (int i = (k - 1 - 1) / 2; i >= 0; i--){AdjustDown(minheap, i, k);}//遍历,如果大于小堆堆顶的数据的话,就和它换,并向下调整int x = 0;while (fscanf(flout, "%d", &x) != EOF){if (x > minheap[0]){Swap(&x, &minheap[0]);AdjustDown(minheap, 0,k);}}for (int i = 0; i < k; i++)//打印前k大的数{printf("%d ", minheap[i]);}fclose(flout);//关闭文件
}
int main()
{CreateNDate();//造10w个数据PrintTopK(5);return 0;
}

运行结果:

在这里插入图片描述

可以看到预期结果和我们修改的数是一致的,说明我们的代码应该没啥问题。

🌺 top问题的时间复杂度和空间复杂度分析

在这里插入图片描述

🌏 测试程序

下面我们来测试一下我们写的上述方法的正确性如何。

🐓 打印堆

我们可能需要把堆打印出来看结果。

//打印堆
void HeapPrint(Heap* hp)
{assert(hp);assert(hp->_size > 0);for (int i = 0; i < hp->_size; i++){printf("%d ", hp->_a[i]);}printf("\n");
}

🐓 验证是否是一个堆

我们应该如何验证我们的数据是否是一个小堆呢?其实很简单,数组是顺序存储的,我们的数据一定是完全二叉树,这个不用验证,关键是验证我们的数据是否都满足小堆的定义,即所有的根节点的值都比它的左子树的和右子树的根节点的值要小,我们可以走一个前序遍历来验证。

//验证是否是一个小堆
int is_Heap(HPDataType* a, int n,int root)
{if (root >= n)return 1;int leftchild = root * 2 + 1;//左孩子的下标int rightchild = root * 2 + 2;//右孩子的下标if (leftchild < n && a[root] > a[leftchild])//左孩子存在且小于根,返回0,并打印相关信息{printf("%d\n位置不满足小堆", root);return 0;}if (rightchild < n && a[root] > a[rightchild])//右孩子存在且小于根,返回0,并打印相关信息{printf("%d\n位置不满足小堆", root);return 0;}return is_Heap(a, n, leftchild) && is_Heap(a, n, rightchild);//左树和右树都满足小堆就返回1
}

🐓 测试程序

void Test2()
{Heap hp1;//建堆HeapInit(&hp1);//初始化堆//造10w个数据进堆int N = 100000;srand(time(NULL));//通过时间来初始化随机数种子for (int i = 0; i < N; ++i){HeapPush(&hp1, rand() % (10000000));//将数据存入堆里面}int i = is_Heap(hp1._a,N,0);//验证是否为堆if (i)//打印提示信息printf("是堆\n");elseprintf("不是堆\n");printf("堆的元素个数为%d\n", HeapSize(&hp1));//验证元素个数方法是否正确printf("堆顶元素为:%d\n", HeapTop(&hp1));//验证堆顶元素是否正确HeapPop(&hp1);//验证pop函数是否正确printf("堆的元素个数为%d\n", HeapSize(&hp1));printf("堆顶元素为:%d\n", HeapTop(&hp1));hp1._a[3] = -1;//破坏堆的结构i = is_Heap(hp1._a, N, 0);if (i)printf("是堆\n");elseprintf("不是堆\n");HeapDestory(&hp1);if (hp1._a == NULL)printf("销毁成功\n");
}

运行结果:

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/766587.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【SysBench】OLTP 基准测试示例

前言 本文采用 MySQL 沙盒实例作为测试目标&#xff0c;使用 sysbench-1.20 对其做 OLTP 基准测试。 有关 MySQL 沙盒的更多信息&#xff0c;请参阅 玩转 MySQL Shell 沙盒实例&#xff0c;【MySQL Shell】6.8 AdminAPI MySQL 沙盒 。 1、部署一个 MySQL 沙盒实例 使用 mysq…

指尖论文怎么用 #经验分享#学习方法

指尖论文是一款优秀的论文写作、查重降重工具&#xff0c;被广泛认可为高效、可靠、方便的辅助工具。那么&#xff0c;如何正确地使用指尖论文呢&#xff1f; 首先&#xff0c;用户需要注册一个指尖论文的账号&#xff0c;并登录到平台上。注册过程非常简单&#xff0c;只需要输…

瑞芯微RK3576|触觉智能:开启科技新篇章

更多产品详情可关注深圳触觉智能官网&#xff01; “瑞芯微&#xff0c;创新不止步&#xff01;”——全新芯片RK3576即将震撼登场。指引科技风潮&#xff0c;创造未来无限可能&#xff01;这款芯片在瑞芯微不断创新和突破的道路上&#xff0c;不仅是对过往成就的完美延续&…

V R元宇宙平台的未来方向|V R主题馆加 盟|游戏体验馆

未来&#xff0c;VR元宇宙平台可能会呈现出以下发展趋势和可能性&#xff1a; 全面融合现实与虚拟世界&#xff1a; VR元宇宙平台将更加无缝地融合现实世界和虚拟世界&#xff0c;用户可以在虚拟环境中进行各种活动&#xff0c;与现实世界进行互动&#xff0c;并且体验到更加逼…

FileZilla 链接服务器提示 20 秒连接超时

FileZilla 有个默认设置是如果 20 秒没有数据的话会自动中断链接。 Command: Pass: **************** Error: Connection timed out after 20 seconds of inactivity Error: Could not connect to server修改配置 这个配置是可以修改的&#xff0c;修改的步骤为&#xff1a; …

数据可视化-ECharts Html项目实战(5)

在之前的文章中&#xff0c;我们学习了如何设置滚动图例&#xff0c;工具箱设置和插入图片。想了解的朋友可以查看这篇文章。同时&#xff0c;希望我的文章能帮助到你&#xff0c;如果觉得我的文章写的不错&#xff0c;请留下你宝贵的点赞&#xff0c;谢谢 数据可视化-ECharts…

Vue3 + Django 前后端分离项目实现密码认证登录

1、功能需求 通常中小型前后端项目&#xff0c;对安全要求不高&#xff0c;也可以采用密码认证方案。如果只用django来实现非常简单。采用 Vue3 前后端分离架构&#xff0c;实现起来稍繁琐一点&#xff0c;好处是可以利用各种前端技术栈&#xff0c;如element-plus UI库来渲染…

Git Commit 提交规范,变更日志、版本发布自动化和 Emoji 提交标准

前言 Git Commit 是开发的日常操作, 一个优秀的 Commit Message 不仅有助于他人 Review, 还可以有效的输出 CHANGELOG, 对项目的管理实际至关重要, 但是实际工作中却常常被大家忽略&#xff0c;希望通过本文&#xff0c;能够帮助大家规范 Git Commit&#xff0c;并且展示相关 …

Mongodb入门到入土,安装到实战,外包半年学习的成果

这是我参与「第四届青训营 」笔记创作活动的的第27天&#xff0c;今天主要记录前端进阶必须掌握内容Mongodb数据库,从搭建环境到运行数据库,然后使用MongodB; 一、文章内容 数据库基础知识关系型数据库和非关系型数据库为什么学习Mongodb数据库环境搭建及运行MongodbMongodb命…

【进程概念】启动进程 | 查看进程 | 创建进程

目录 启动进程 查看进程 方法1&#xff1a;/proc 方法2&#xff1a;查看脚本 ​方法3&#xff1a;系统调用获取进程标示符❗❗ 终止进程 创建进程&#xff08;主fork) &#x1f642;查看父子进程的pid &#x1f642;进程创建/执行/终止 &#x1f642;多次重新启动进…

STM32 CAN的工作模式

STM32 CAN的工作模式 正常模式 正常模式下就是一个正常的CAN节点&#xff0c;可以向总线发送数据和接收数据。 静默模式 静默模式下&#xff0c;它自己的输出端的逻辑0数据会直接传输到它自己的输入端&#xff0c;逻辑1可以被发送到总线&#xff0c;所以它不能向总线发送显性…

鸿蒙开发实战:网络请求库【axios】

简介 [Axios] &#xff0c;是一个基于 promise 的网络请求库&#xff0c;可以运行 node.js 和浏览器中。本库基于[Axios]原库v1.3.4版本进行适配&#xff0c;使其可以运行在 OpenHarmony&#xff0c;并沿用其现有用法和特性。 http 请求Promise APIrequest 和 response 拦截器…

Stable Diffusion实现光影字效果

昨天下午有人在群里发光影图片&#xff0c;大家都觉得很酷&#xff0c;我没怎么在意。直到早上我在小红书看到有人发同款图片&#xff0c;只是一晚上的时间点赞就超过了8000&#xff0c;而且评论数也很高&#xff0c;也可以做文字定制变现。研究了一下发现这个效果不难实现&…

数据结构/C++:哈希表

数据结构/C&#xff1a;哈希表 哈希表概念哈希函数直接定址法除留余数法 哈希冲突闭散列 - 开放定址法基本结构查找插入删除总代码展示 开散列 - 哈希桶基本结构查找插入删除代码展示 哈希表概念 在顺序表中&#xff0c;查找一个数据的时间复杂度为O(N)&#xff1b;在平衡树这…

宋仕强论道之华强北科技创新说

宋仕强论道之华强北科技创新说&#xff0c;“创新”是深圳市和华强北灵魂&#xff0c;创新再加上敢想敢干永不言败&#xff0c;造就了深圳市经济奇迹和华强北财富神话&#xff01;首次在深圳市落槌的“土地拍卖”&#xff0c;华强北“一米柜台”赋予独立经营权&#xff0c;把最…

通过jsDelivr实现Github的图床CDN加速

最近小伙伴们是否发现访问我的个人博客http://xiejava.ishareread.com/图片显示特别快了&#xff1f; 我的博客的图片是放在github上的&#xff0c;众所周知的原因&#xff0c;github访问不是很快&#xff0c;尤其是hexo博客用github做图床经常图片刷不出来。一直想换图床&…

提面 | 面试抽题

学习到更新日期面试抽题-1.2案例分析的思维本质2024-3-23 1提面抽屉论述问题的分类 1.1案例分析占总论 1.2案例分析的思维本质

rabbitmq 3.9.29 docker mac 管理员页面无法打开

SyntaxError: Unexpected token ‘catch’ SyntaxError: Unexpected token ‘catch’ at EJS.Compiler.compile (http://127.0.0.1:15672/js/ejs-1.0.min.js:1:6659) at new EJS (http://127.0.0.1:15672/js/ejs-1.0.min.js:1:1625) at format (http://127.0.0.1:15672/js/main…

【docker系列】深入理解 Docker 容器管理与清理

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

llvm后端

SelectionDAGBuilder是LLVM&#xff08;Low Level Virtual Machine&#xff09;编译器中的一个重要组件&#xff0c;它负责将LLVM中间表示&#xff08;Intermediate Representation&#xff0c;IR&#xff09;转换为SelectionDAG&#xff08;选择有向无环图&#xff09;的形式。…