“二叉堆:不是,啊?”

news2024/9/21 16:49:20

目录

  • 前言
  • 一、堆的概念及结构
    • 堆的性质:
    • 堆的结构:
    • 最大堆
    • 最小堆
    • 堆顶
    • 注意
  • 二、堆的实现
    • 1.初始化堆
    • 2. 堆的插入
      • 什么是堆的向上调整算法?
    • 3.堆的删除
      • 什么是堆的向下调整算法?
    • 4.获取堆顶的数据
    • 5.获取堆的数据个数
    • 6.堆的判空
    • 7.堆的销毁
  • 三、建堆的时间复杂度
  • 四、堆排序
  • 五、二叉堆的应用——TopK问题

前言

普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结
构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统
虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。

在这里插入图片描述

一、堆的概念及结构

⼆叉堆本质上是⼀种 完全⼆叉树,它分为两个类型,最大堆最小堆

堆的性质:

1)堆中某个结点的值总是不大于或不小于其父结点的值;
2)堆总是一棵完全二叉树。

堆的结构:

在这里插入图片描述

最大堆

最⼤堆的任何⼀个⽗节点的值,都 ⼤于等于 它左、右孩⼦节点的值(如下图所示)。

在这里插入图片描述

最小堆

最⼩堆的任何⼀个⽗节点的值,都 ⼩于等于 它左、右孩⼦节点的值(如下图所示)。

在这里插入图片描述

堆顶

⼆叉堆的根节点叫作 堆顶

最⼤堆和最⼩堆的特点决定了:

1)最⼤堆的堆顶是整个堆中的 最⼤元素
2)最⼩堆的堆顶是整个堆中的 最⼩元素

注意

在实现堆的代码之前,我们还需要明确⼀点:⼆叉堆虽然是⼀个完全⼆叉树,但它的存储⽅式并不是链式存储,⽽是 顺序存储

换句话说,⼆叉堆的所有节点都存储在数组中(如下图所示)。
在这里插入图片描述

在数组中,在没有左、右指针的情况下,如何定位⼀个⽗节点的左孩⼦和右孩⼦呢?

我们可以通过数组下标来计算。

假 设 ⽗ 节 点 的 下 标 是 parent 。
那 么 它 的 左 孩 ⼦ 下 标 就 是 2 × p a r e n t + 1 ;
右 孩 ⼦ 下 标 就 是 2 × p a r e n t + 2 。

二、堆的实现

1.初始化堆

首先,创建一个堆的类型,该类型中需包含堆的基本信息:存储数据的数组堆中元素的个数 以及 当前堆的最大容量

typedef int HPDataType;

typedef struct Heap
{
	HPDataType* a;
	int size;
	int capacity;
}HP;

然后我们需要一个初始化函数,对刚创建的堆进行初始化。

void HPInit(HP* php)
{
	assert(php);
	php->a = NULL;
	php->capacity = php->size = 0;
}

2. 堆的插入

当⼆叉堆插⼊节点时,插⼊位置是完全⼆叉树的最后⼀个位置。

例如插⼊⼀个新节点,值是10(如下图所示)。
在这里插入图片描述
这时,新节点的⽗节点 28 ⽐ 10 ⼤,显然不符合 最⼩堆 的性质。于是让新节点 “上浮”,和⽗节点交换位置(如下图所示)。
在这里插入图片描述
继续⽤节点 10 和⽗节点 18 做⽐较,因为 10 ⼩于 18,则让新节点继续 “上浮”(如下图所示)。

在这里插入图片描述
继续⽐较,最终新节点 10 “上浮” 到了堆顶位置(如下图所示)。
在这里插入图片描述

这种方法叫做 堆的向上调整算法

什么是堆的向上调整算法?

在对二叉堆进行 插入数据 时,要使用 向上调整算法,它既可以构建 大堆,也可以构建 小堆

核心思路(以构建小堆为例):

1)将目标节点与其父节点比较。
2)若目标节点的值比其父节点的值小,则交换目标节点与其父节点的位置,并将原目标节点的父结点当作新的目标节点继续进行向上调整。
3)若目标节点的值比其父节点的值大,则停止向上调整,此时该完全二叉树已经是小堆了。

代码示例:

交换变量
void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = 0;
	tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

void AdjustUp(HPDataType* a, int child)
{
	//建小堆
	int parent = (child - 1) / 2;
	while (child > 0) 调整到根节点的位置截止
	{
		if (a[child] < a[parent]) 如果孩子节点的值小于父节点的值
		{
			Swap(&a[child], &a[parent]); 将父结点与孩子节点交换
			child = parent; 继续向上进行调整
			parent = (child - 1) / 2;
		}
		else
		{
			break; 已经是小堆了,直接跳出循环
		}
	}

}

ps: 向上调整构建大堆

其实构建大堆只是把上面构建小堆中 if 语句中的 小于 改成了 大于

既然 堆的插入 是向上调整,那我们直接用刚刚写好的函数就好了,这里以构建 小堆 为例。

代码示例:

void HPPush(HP* php, HPDataType x)
{
	assert(php);
	if (php->a == NULL)
	{
		//扩容
		int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HP* tmp = (HP*)realloc(php->a, sizeof(HPDataType) * newcapacity);
		if (tmp == NULL)
		{
			perror("realloc tail");
			exit(1);
		}
		php->a = tmp;
		php->capacity = newcapacity;
	}
	php->a[php->size++] = x;
	AdjustUp(php->a, php->size - 1);

}

3.堆的删除

⼆叉堆删除节点的过程和插⼊节点的过程正好相反,所删除的是处于堆顶的节点

例如删除 最⼩堆 的堆顶节点 1(如下图所示)。

在这里插入图片描述
这时,为了继续维持完全⼆叉树的结构,我们把堆的最后⼀个节点 10 临时补到原本堆顶的位置(如下图所示)。

在这里插入图片描述

接下来,让暂处堆顶位置的节点 10 和它的左、右孩⼦进⾏⽐较,如果左、右孩⼦节点中最⼩的⼀个(显然是节点 2 )⽐节点 10 ⼩,那么让节点 10 “下沉”(如下图所示)。

在这里插入图片描述
继续让节点 10 和它的左、右孩⼦做⽐较,左、右孩⼦中最⼩的是节点 7,由于 10 ⼤于 7,让节点 10 继续 “下沉”(如下图所示)。
在这里插入图片描述
这种方法叫 堆的向下调整算法

什么是堆的向下调整算法?

首先若想将其调整为小堆,那么根结点的左右子树必须都为小堆。

若想将其调整为大堆,那么根结点的左右子树必须都为大堆。

也就是说:

如果我们使用 向上调整算法 构建了一个 小堆,那么 向下调整算法 也必须只能调整 小堆。
如果我们使用 向上调整算法 构建了一个 大堆,那么 向下调整算法 也必须只能调整 大堆。

核心思路(以调整小堆为例):

1)从根结点处开始,选出左右孩子中值较小的孩子。
2)让小的孩子与其父亲进行比较。
3)若小的孩子比父亲还小,则该孩子与其父亲的位置进行交换。并将原来小的孩子的位置当成父亲继续向下进行调整,直到调整到叶子结点为止。
4)若小的孩子比父亲大,则不需处理了,调整完成,整个树已经是小堆了。

代码示例:

交换变量
void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = 0;
	tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

void AdjustDown(HPDataType* a, int n, int parent)
{
	//建小堆
	假设左孩子小
	int child = parent * 2 + 1;
	while(child < n)
	{
		在孩子存在的前提下,如果右孩子比【默认的左孩子】还要小,那么就把 child 指向右孩子;
		如果右孩子比左孩子大,那么就不进入if语句
		if ((child + 1) < n && a[child + 1] < a[child])
		{
			child++;
		}
			如果孩子小于父亲,则交换,并继续往下调整
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}

}

既然 堆的删除向下调整,那我们直接用刚刚写好的函数就好了。
上面的 插入 是构建的小堆,所以这里删除调整的应该也必须是 小堆

void HPPop(HP* php)
{
	assert(php);
	Swap(&php->a[0], &php->a[php->size - 1]); 把堆顶的数据和最后一个位置的数据互换
	php->size--; 删除最后一个节点(也就是删除原来堆顶的元素)
	AdjustDown(php->a, php->size, 0); 向下调整(调小堆)
}

4.获取堆顶的数据

获取堆顶的数据,即返回数组下标为 0 的数据。

代码示例:

// 取堆顶的数据
HPDataType HeapTop(HP* php) {
	assert(php);
	assert(php->size > 0);

	return php->a[0]; //返回堆顶数据
}

5.获取堆的数据个数

获取堆的数据个数,即返回堆结构体中的 size 变量。

代码示例:

// 堆的数据个数
int HeapSize(HP* php) {
	assert(php);

	return php->size; //返回堆中数据个数
}

6.堆的判空

堆的判空,即判断堆结构体中的 size 变量是否为 0

代码示例:

// 堆的判空
bool HeapEmpty(HP* php) {
	assert(php);
	
	//如果size等于0就为空,不等于0就不为空
	return php->size == 0;
}

7.堆的销毁

为了避免内存泄漏,使用完动态开辟的内存空间后都要及时释放该空间。

代码示例:

// 堆的销毁
void HeapDestory(HP* php) {
	assert(php);

	free(php->a); //释放动态开辟的数组
	php->a = NULL; //及时置空
	php->size = php->capacity = 0; //元素个数和容量置为0
}

三、建堆的时间复杂度

下面我们给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过算法,把它构建成一个堆。根结点左右子树不是堆,我们怎么调整呢?

这里我们从倒数的第一个非叶子结点的子树开始调整,一直调整到根结点的树,就可以调整成堆。

int a[] = {1,5,3,8,7,6};

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

从倒数第一个非叶子节点开始(最后一个节点的父亲)
n-1是最后一个节点的下标,(n-1-1)/2最后一个节点的父亲的下标
for (int i = (n - 1 - 1) / 2; i >= 0; --i) {
	AdjustDownBig(a, n, i);
}

那么建堆的时间复杂度又是多少呢?

因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明
(时间复杂度本来看的就是近似值,多几个结点不影响最终结果):

在这里插入图片描述

因此:建堆的时间复杂度为O(N)

总结一下:

堆的向下调整算法的时间复杂度:O ( log N)
建堆的时间复杂度:O ( N )

四、堆排序

堆排序的基本思想是:

1)将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;
(升序建大堆,降序建小堆)
2)将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;
3)重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。

堆排序代码(以升序为例):

void AdjustDownBig(HPDataType* a, int n, int parent)
{
	//建大堆
	//假设左孩子大
	int child = parent * 2 + 1;
	while(child < n)
	{
		if ((child + 1) < n && a[child + 1] > a[child])
		{
			child++;
		}

		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}

}

void HeapSort(int* a, int n) {
	建堆:使用向下调整 --> O(N)
	从倒数第一个非叶子节点开始(最后一个节点的父亲)
	n-1是最后一个节点的下标,(n-1-1)/2最后一个节点的父亲的下标
	for (int i = (n - 1 - 1) / 2; i >= 0; --i) {
		AdjustDownBig(a, n, i);
	}

	升序 --> 建大堆
	size_t end = n - 1; 最后一个元素的下标
	while (end > 0)
	{
		Swap(&a[0], &a[end]); 交换第一个元素和最后一个元素
		AdjustDownBig(a, end, 0);
		--end;
	}
}

总结一下

堆排序是一种选择排序,整体主要由 构建初始堆 + 交换堆顶元素和末尾元素并重建堆 两部分组成。

其中构建初始堆经推导复杂度为 O ( n ) ,在交换并重建堆的过程中,需交换 n − 1 次,而重建堆的过程中,根据完全二叉树的性质,[ l o g 2 ( n − 1 ) , l o g 2 ( n − 2 ) . . . 1 ] 递减,近似为 n * log n 。

所以堆排序时间复杂度一般认为就是O(nlogn)级。

堆排序使用堆来选数,效率就高了很多。
时间复杂度:O ( N ∗ l o g N )
空间复杂度:O ( 1 )
稳定性:不稳定

五、二叉堆的应用——TopK问题

什么是 TOP-K?

TOP-K 问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。

比如:专业前 10 名、世界 500 强、富豪榜、游戏中前 100 的活跃玩家等。

最佳思路: 用堆来解决。

1)先将数组的前 K 个数建为 小堆。
2)将数组剩下的 N-K 个数依次与堆顶元素进行比较,若比堆顶元素大,则将堆顶元素替换为该元素,然后再进行一次向下调整,使其仍为小堆。
3)最后堆里面的 K 个数就是最大的前 K 个。

代码实现:

//调整算法里面的交换
void Swap(HPDataType* pa, HPDataType* pb) {
	HPDataType tmp = *pa;
	*pa = *pb;
	*pb = tmp;
}

//向下调整算法 --> 构建小堆
void AdjustDownSmall(HPDataType* a, size_t size, size_t root) {
	size_t parent = root;
	size_t child = 2 * parent + 1; //默认左孩子最小

	while (child < size)
	{
		//1.找出左右孩子中小的那个
		//如果右孩子存在,且右孩子小于size(元素个数),那么就把默认小的左孩子修改为右孩子
		if (child+1 < size && a[child+1] < a[child]) {
			++child;
		}

		//2.把小的孩子去和父亲比较,如果比父亲小,就交换
		if (a[child] < a[parent]) {
			Swap(&a[child], &a[parent]);
			parent = child;
			child = 2 * parent + 1;
		}
		else {
			break; //如果孩子大于等于父亲,那么直接跳出循环
		}
	}
}

void PrintTopK(int* a, int n, int k) {
	//1.建堆 --> 用a中前k个元素建小堆
	int* kminHeap = (int*)malloc(sizeof(int) * k);
	if (kminHeap == NULL) {
		printf("malloc fail\n");
		exit(-1);
	}
	//把数组的前K个数放进去(放进去的就是数组的前K个数,大小不一)
	for (int i = 0; i < k; ++i) {
		kminHeap[i] = a[i];
	}

	//k-1是最后一个数的下标,(k-1-1)/2就是倒数的第一个非叶子节点,也就是最后一个节点的父亲节点
	for (int j = (k - 1 - 1) / 2; j >= 0; --j) {
		//建小堆
		AdjustDownSmall(kminHeap, k, j);
	}

	//2.将剩余的n-k个元素依次与堆顶元素比较
	for (int i = k; i < n; ++i) {
		if (a[i] > kminHeap[0]) {
			kminHeap[0] = a[i];
			AdjustDownSmall(kminHeap, k, 0);
		}
	}
	free(kminHeap);
}

这种算法的时间复杂度为:O ( K + log K ∗ ( N − K ) ) ,当 N 非常大时,并且 K 很小,那么基本就是 O ( N ) .

空间复杂度:O ( K ) .

这样内存的消耗就取决于 K 的值了,就算要在 100 亿个数里面找到最大的 100 个数据,也只需要 400 字节的内存空间了!

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

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

相关文章

逻辑分析仪的调试使用

调试软件下载&#xff1a;点击跳转 一、接线 逻辑分析仪 设备 GND --- GND CHX&#xff08;数据通道&#xff09; --- 通信引脚 二、数据采集 图中两个可以选择数字大小的地方分别表示 采样深度 &#xff08;10M Samples&a…

eBay运营账号防关联成功的关键因素是什么?

一、什么是eBay&#xff1f; eBay如今的发展现状呈现出积极且充满活力的态势。作为全球知名的在线拍卖和购物平台&#xff0c;随着全球消费者对线上购物的需求不断增长&#xff0c;这为卖家提供了广阔的市场空间和盈利机会&#xff0c;但多账号的运营若处理不好容易引起账号被关…

宝塔下新增站点 No Input File Specified.错误修复

今天明月收到了一个购买【站长必备在线工具源码含上百款工具-博客优化修复版】用户的求助&#xff0c;在宝塔里新增网站部署好工具源码后&#xff0c;访问出现“No input file specified.”的提示。其实出现这个提示一般都是 PHP 文件无法被解析造成的。 简单排查了一下宝塔相关…

521源码-免费下载-WordPress全能自动采集与发布插件 – WP-AutoPostPro 汉化版

更多网站源码学习教程&#xff0c;请点击&#x1f449;-521源码-&#x1f448;获取最新资源 本工具下载地址&#xff1a;WordPress全能自动采集与发布插件 – WP-AutoPostPro 汉化版 - 521源码 WP-AutoPostPro是一款出类拔萃的WordPress自动采集发布插件&#xff0c;凭借其卓…

剪画小程序:分享3个无字幕保存高清视频的方法!!!

视频怎么去水印&#xff1f; 视频制作是当下越来越流行的一种形式&#xff0c;但是很多时候我们会发现一些精美的视频却被水印所遮盖。 尤其是一些自媒体人想要进行二创时。。。 那么&#xff0c;该如何去除视频水印保存高清原视频呢&#xff1f; 今天&#xff0c;小编分享…

全国各城市间驾车耗时和距离矩阵数据集(更新至2022年)

数据简介&#xff1a;城市之间距离越远&#xff0c;耗时越长。经济发达地区的交通状况较好。各城市之间的驾车耗时和距离存在差异。有些城市之间的交通非常便捷&#xff0c;而有些城市之间的交通则较为不便。这表明中国的交通网络发展尚不平衡&#xff0c;需进一步优化。特别是…

解密 Alpha 勒索软件

Alpha 勒索软件很容易与 ALPHV 勒索软件混淆&#xff0c;但其实这是两个不同的勒索软件团伙。近期&#xff0c;Alpha 勒索软件团伙在暗网上建立了数据披露网站&#xff0c;并且对外公开了6个受害者。 通常来说&#xff0c;勒索软件运营者在启动数据披露网站前会保持攻击态势。一…

Yolov5保姆及入门-含源码【推荐】

前言 YOLO系列模型作为一种实时目标检测算法&#xff0c;自从YOLO1发布以来&#xff0c;就以其检测速度快、准确率高而受到广泛关注。随着技术的迭代&#xff0c;YOLO系列已经发展到了YOLO8。本文将详细介绍YOLO5的技术规格、应用场景、特点以及性能对比。 yolov5源码下载地址…

【软件测试】bug篇|软件测试的生命周期|描述bug的要素|bug的级别|bug的生命周期|高频面试题:与开发产⽣争执怎么处理

目录 一、软件测试的⽣命周期 二、BUG 2.1 bug的概念 2.2 描述bug的要素 2.3 bug级别 2.4 bug的⽣命周期 &#x1f4a1;2.5 与开发产⽣争执怎么办&#xff08;⾼频考题&#xff09; &#x1f4a1; 推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&…

四川省税务局CDH国产化替代实践

“传统数据仓库对于数据处理时效较低&#xff0c;且无法处理实时增量数据及数据变更&#xff0c;同时&#xff0c;在面对海量税务数据大规模进行查询分析等方面存在一些挑战。我们希望尽快寻找到一款能够替代CDH&#xff0c;并且具备灵活扩展能力的大数据解决方案&#xff0c;以…

Kubernetes 容器资源管理Resources和探针Probe

资源配额 Resources 在 Kubernetes 中&#xff0c;resources 配置用于设置容器的资源请求和限制&#xff0c;以确保集群中的资源&#xff08;如 CPU 和内存&#xff09;得到合理分配和使用。 在之前的pod中&#xff0c;不写 resources 字段。就意味着 Pod 对运行的资源要求“…

集智书童 | YOLOv10开源|清华用端到端YOLOv10在速度精度上都生吃YOLOv8和YOLOv9

本文来源公众号“集智书童”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;YOLOv10开源&#xff5c;清华用端到端YOLOv10在速度精度上都生吃YOLOv8和YOLOv9 在过去几年中&#xff0c;YOLO系列模型已成为实时目标检测领域的主导范式…

图像处理之基于标记的分水岭算法(C++)

图像处理之基于标记的分水岭算法&#xff08;C&#xff09; 文章目录 图像处理之基于标记的分水岭算法&#xff08;C&#xff09;前言一、基于标记点的分水岭算法应用1.实现步骤&#xff1a;2.代码实现 总结 前言 传统分水岭算法存在过分割的不足&#xff0c;OpenCV提供了一种…

图片提取表格要怎么做?7个软件教你快速进行图片识别

图片提取表格要怎么做&#xff1f;7个软件教你快速进行图片识别 要从图片中提取表格&#xff0c;您可以使用以下七款软件来快速进行图片识别和表格提取&#xff1a; 1.一键识别王&#xff1a;这是一款专业的OCR&#xff08;光学字符识别&#xff09;软件&#xff0c;可以帮助…

在通过跨网文件交换时,如何保障科研结构核心研究数据?

当今科研领域&#xff0c;数据如同生命线&#xff0c;支撑着每一个突破性发现的诞生。随着国际合作的加深&#xff0c;跨网文件交换成了常态&#xff0c;但这也为科研机构的核心研究数据安全带来了一系列挑战。想象一下&#xff0c;那些精心搜集和分析的宝贵数据&#xff0c;在…

【Typescript】通过变量的值即可获取变量的类型【typeof 变量】

注意&#xff1a;只要变量的类型准确,则typeof获取变量的类型就不会错 enum Test {a "a0",b "b0" }// 这里的a是一个变量的值 let a: Test.a "a0" as Test.a// 这里的typeof a是一个类型【Test.a】 let x: typeof a Test.a

【C++】开源:RabbitMQ安装与配置使用(SimpleAmqpClient)

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍。 无专精则不能成&#xff0c;无涉猎则不能通。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关注一下&#xff0c;下次更新不迷路&#x1…

Jlink卡死 JFlash keil 盗版JLINK

现象&#xff1a;用Keil打开Jlink配置页&#xff0c;会卡死。 解决方法&#xff1a;用旧版本的Jlink软件&#xff0c;因为淘宝买的很多JLINK下载器是盗版的&#xff0c;不支持新版本的JLINK软件。到https://www.segger.com/downloads/jlink下载旧版本的软件。 如果必须要用新版…

重量and体积,不要在傻傻的花冤枉钱寄快递了!

寄快递时有没有遇到过明明不重却被按体积收费的情况&#xff1f;别急&#xff0c;今天就来给大家揭秘快递收费的奥秘&#xff01; 实际重量和体积重量&#xff01; 首先&#xff0c;我们要明白两个概念&#xff1a;实际重量和体积重量。实际重量就是你看到的物品重量&#xf…

安装vllm的时候卡主:Collecting vllm-nccl-cu12<2.19,>=2.18 (from vllm)

按照vllm的时候卡主&#xff1a; ... Requirement already satisfied: typing-extensions in /home/wangguisen/miniconda3/lib/python3.10/site-packages (from vllm) (4.9.0) Requirement already satisfied: filelock>3.10.4 in /home/wangguisen/miniconda3/lib/python…