关于堆的介绍

news2024/9/22 18:15:39

1.堆的概念及结构

如果有一个关键码的集合,把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或者大根堆,根节点最小的堆叫做最小堆或小根堆。

堆的性质:1、堆中某个节点的值总是不大于或不小于其父节点的值;2、堆总是一棵完全二叉树。

2.堆的分类

堆主要分为两种类型:大根堆(最大堆)(Max Heap)和小根堆(最小堆)(Min Heap)。在大根堆中,父节点的值总是大于或等于其子节点的值,因此堆顶元素是整个堆中的最大值。相反,在小根堆中,父节点的值总是小于或等于其子节点的值,堆顶元素是整个堆中的最小值。

 堆通常使用数组来实现,数组中的每个元素对应堆中的一个节点。由于堆是完全二叉树,所以可以使用数组的下标关系来模拟树中父节点和子节点之间的关系。这种实现方式使得堆在插入、删除和查找最大(或最小)元素等操作中具有高效的性能。

3.父节点和子节点的下标关系

对于给定的下标为 i 的结点,其父结点、左子结点和右子结点的下标可以通过以下关系式计算:

  • 下标i元素的父结点下标:(i - 1) / 2(使用整数除法)
  • 下标i元素的左子结点下标:2 * i + 1
  • 下标i元素的右子结点下标:2 * i + 2

4.堆的实现

4.1 堆的向下调整算法(重点掌握)

现在我们给出一个数组,逻辑上看做一棵完全二叉树。通过从根节点开始的向下调整算法可以把它调整 成一个小堆。向下调整算法有一个前提:1、若想将其调整为小堆,根节点的左右子树必须都为小堆;2、若想将其调整为大堆,根节点的左右子树必须都为大堆。就根节点不满足。

向下调整算法的基本思想:1、从根节点处开始,选出左右子结点中值较小的子节点;2、让小的子结点与其父节点进行比较。我所说的这个是小堆的调整,大堆的话刚好相反

(1)若左右子节点中较小的子结点比父节点小,则让该子结点与其父节点的位置进行交换。并将原来较小的子结点的位置当作父节点继续向下进行调整,直到调整到叶子节点为止。

(2)如左右子结点中较小的子节点比父节点大,则就不需要处理了,调整完成,整个树就已经是小根堆了。

比如:int array[] = {27,15,19,18,28,34,65,49,25,37};以27为根的左右子树,都满足小堆的性质,只有根节点不满足,因此只需要将根节点往下调整到合适的位置即可形成堆。

void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}


void AdjustDown(HPDataType* a, int n, int parent)
{
	//默认左孩子节点较小
	int child = parent * 2 + 1;

	while (child < n)
	{
		//找出左右孩子中小的那一个
		//如果右孩子小于左孩子
        //这里child<n但是不能保证child+1<n
        //child+1<n是为了防止出现越界情况的发生
		if (child + 1 < n && a[child] > a[child + 1])
		{
			//将较小的子节点改为右孩子节点
			++child;
		}
		
		//如果孩子节点小于双亲节点则交换
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			//当小的孩子节点大于双亲节点,则就不需要往下调整了
			break;
		}
	}
}

当调用AdjustDown()这个函数时,需要传入:1、传入指向堆元素数组的指针(例如,如果有一个int类型的堆数组heap,则传入heap)。2、传入堆数组中当前元素的数量(即堆的大小)。3、需要向下调整的节点的索引(对于根节点来说通常是0)。

4.2 堆的向上调整算法

对于上图中的小根堆,当我们在当前的小根堆的最后一个空缺位置插入一个新的节点,这个新节点要影响的并不是整棵左子树,也不是右子树,而是它的父亲节点和祖先节点,因为对于堆来说,就是需要去比较它的父亲节点和孩子节点之间的关系。

对于上图中的小根堆,此时在末尾插入一个100,该节点比其父亲节点56还要大,则该节点当然也比其祖先节点要大。因此,我们无需再往上进行比较。

我们再看第二个小根堆,将标红的目标节点与其父节点进行比较,若目标节点的值比其父节点的值小,则交换目标节点与其父节点的位置,并将原目标节点的父节点当作新的目标节点继续进行向上调整。如目标节点的值比其父节点的值大,则停止向上调整,此时该树已经是小根堆了。

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;
		}
	}
}

为了正确调用AdjustUp()函数,需要传入堆的数组(指针),当前需要调整的节点的索引(通常是指新插入元素的索引)。

5.堆的数据结构各算法接口实现

5.1 结构体的定义及声明

我们首先介绍结构体的定义和声明,如下代码所示:

typedef struct Heap
{
	HPDataType* _a;
	int _size;
	int _capacity;
}Heap;

5.2 堆的初始化

这里的堆初始化是提供一个数组,使用向下调整算法将该数组调整成堆。

//提供一个数组a,并把该数组调整成一个堆
void HeapInit(Heap* php, HPDataType* a, int n)
{
	//1、开辟一个空间并将数组a中的数据拷贝到该空间中--使用memcpy函数
	php->_a = (HPDataType*)malloc(sizeof(HPDataType) * n);

	memcpy(php->_a, a, sizeof(HPDataType) * n);
	php->_size = n;
	php->_capacity = n;

	//2、使用向下调整算法构建堆
	for (int i = (n - 1 - 1) / 2; i >= 0; --i)
	{
		AdjustDown(php->_a, php->_size, i);
	}
}

5.3 堆的销毁

//注意:这里指针php指向的结构体Heap不需要我们去free,
//因为这个结构体不是malloc函数创建出来的。
void HeapDestroy(Heap* php)
{
	assert(php);
	free(php->_a);
	php->_a = NULL;
	php->_size = php->_capacity = 0;
}

5.4 向堆中插入数据

上面我们已经构建了一个基本堆,现在往堆中插入数据。对于数组我们都会去写一段数组扩容的逻辑,除了扩容逻辑之外,再底部还有一个向上调整算法,我们在插入新的元素之后要始终保持原先的堆是一个大根堆或者小根堆,所以要去进行一个向上调整。

//插入数据,借助向上调整算法来完成插入数据的操作
void HeapPush(Heap* php, HPDataType val)
{
	assert(php);

	//如果数组的大小和容量相等,则需要进行扩容
	if (php->_size == php->_size)
	{
		php->_capacity *= 2;
		HPDataType*tmp = (HPDataType*)realloc(php->_a, sizeof(HPDataType) * php->_capacity);

		php->_a = tmp;
	}
	php->_a[php->_size++] = val;
	AdjustUp(php->_a, php->_size - 1);
}

5.5 删除堆顶数据

删除堆顶元素的前提是不能破坏堆原有的结构,将堆顶的数据与堆中最后一个数据进行交换,将堆的大小减小即--size删除最后一个数据,也就是原堆顶数据,再进行一次堆的向下调整操作。

//删除堆顶的数据--不能破环堆原有的结构
//将堆顶的数据与最后一个数据进行交换,--size删除最后一个数据,即原堆顶数据
//再进行一次向下调整操作
void HeapPop(Heap* php)
{
	assert(php);
	assert(php->_size > 0);

	Swap(&php->_a[0], &php->_a[php->_size - 1]);
	php->_size--;

	AdjustDown(php->_a, php->_size, 0);
}

6.两种调整算法的时间复杂度刨析

6.1 向下调整算法的时间复杂度刨析

在本文的第4部分我们介绍了两种堆的调整算法,分别是向上调整算法和向下调整算法,在实现接口HeapPush和HeapPop时又用到了这两种算法,本文的核心就是围绕这两个调整算法来的。下面我们就分析这两种算法的时间复杂度。

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

由上图可知,

第1层:2^0个节点,需要向下移动h-1层;

第2层:2^1个节点,需要向下移动h-2层;

第3层:2^2个节点,需要向下移动h-3层;

...

第h-1层:2^(h-2)个节点,需要向下移动1层。

建堆的调用次数用T(N)表示:(从最后一个非叶子节点<也就是倒数第二层>开始,最极端的情况下:倒数第二层每个节点最多能向下调整1次;倒数第三层每个节点最多能向下调整2次;倒数第四层每个节点最多向下调整3次;依次类推。

向下调整建堆总的调整次数为:每层节点个数*极端情况下每个节点调整的次数;

即T(N)=2^(h-2) *1+2^(h-3)*2+...+2^1*(h-2)+2^0(h-1)   (1)

在公式(1)的左右两边同时乘以2,得到公式(2);

2T(N)=2*2^(h-2)*1+2*2^(h-3)*2+...+2*2^1*(h-2)+2*2^0(h-1)    (2)

公式(2)-(1)得:T(N)=2^h-1-h

计算时间复杂度:若规定根节点的层数是1,则深度为h的二叉树的最大节点数是2^h-1,则有h=log(N+1)。则有T(N)=N-log(N+1);即时间复杂度为O(N)。

6.2 向上调整算法的时间复杂度刨析

由上图可以得到向上调整建堆总的调整次数为:每层节点个数*极端情况下每个节点向上调整的次数;

T(N)=2^(h-1) *(h-1)+2^(h-2)*(h-2)+...2^2*2+2^1*1    (1)

则:2T(N)=2*2^(h-1)*(h-1)+2*2^(h-2)*(h-2)+...+2*2^2*2+2*2^1*1   (2)

则可得向上调整算法的时间复杂度为:NlogN。

很明显,向下调整算法更优一些,因为向下调整随着堆的层数增加结点数也会变多,可是结点越多调整得就越少,因为在一些大型数据处理场合我们会使用向下调整算法。

7.堆的实际应用

7.1 堆排序

下面我们介绍一种基于堆的排序算法--堆排序。

如果要排升序,是建大堆还是建小堆来进行排序?

首先建小堆可以实现升序,但是效率得不到保证。如果选择建小堆的话,每次建堆选出最小的数据(即栈顶元素),找出这个最小值后,后面剩余的节点还需要继续建小堆来找出次小的数据,即选出一个数据需要建一次小堆。每次向下调整建堆的时间复杂度是O(N),那么选完N个数据的时间复杂度是O(N^2)。

则可以考虑建大堆来实现升序,第一次建大根堆,堆顶元素为最大值,将该最大值与堆底末梢数据调换位置(此时建立大根堆的时间复杂度为O(N)),并同时将堆的size--,即除了最后一个叶子节点(原堆顶最大节点),将剩余的其他节点组成一棵二叉树,并使用向下调整算法对该树进行调堆建立大根树,找出次大的节点,由于此时该堆的左右子树都是大堆,只有根节点不满足,因此此时使用向下调整算法建立大根堆的时间复杂度是O(logN)。

时间复杂度总结:第一次建大根堆选出最小的数(时间复杂度为O(N)),后面需要使用向下调整算法选出次小以及后续的数据,一次向下调整算法的时间复杂度是logN,则N个数据需要被调整,则整体的时间复杂度是NlogN。总的时间复杂度为O(N+NlogN),取影响大的那个就是O(NlogN),这就是堆排序的时间复杂度。

//堆排序:
//给定一个数组a,对该数组进行堆排序,n为数组中元素的个数
//排降序建小堆
void HeapSort(int* a, int n)
{
	//1.建堆--利用向下调整算法建堆
	//从最后一个非叶子结点开始调整建堆,
	//即从最后一个叶子节点的父节点开始调整建堆
	for (int i = (n - 1 - 1) / 2; i >= 0; --i)
	{
		AdjustDown(a, n, i);
	}

	int end = n - 1;
	while (end>0)
	{
		Swap(&a[0], &a[end]);

		//再继续选次小的数
		AdjustDown(a, end, 0);
		--end;
	}
}

7.2 Top-K问题

7.2.1 Top-K问题的定义及思想

(1)Top-K问题的定义

求出一组数据中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。比如:专业前10名、世界500强、富豪榜等。我们以求n个数据中前K个最大的元素为例进行说明:(假设n=10000、K=10)。

(2)解决Top-K问题的思路

①排序法(不推荐使用)

对于Top-K问题,能想到的最简单直接的方法就是排序,我们可以把这个10000个数据排成降序,然后逐个取前10个数就是最大的10个数。即使用最快的排序算法,时间复杂度也是O(N*logN),空间复杂度是O(1)(直接在数组内对数据进行排序操作,所以空间复杂度是O(1),如果在排序的过程中另外新开辟了包含N个数据的空间,空间复杂度是O(N))。此外,如果数据量特别大可能数据都不能一下子全部加载到内存中。

②堆函数操作法(不推荐使用)

建立包含N个数据的大根堆(此时堆顶元素就是就是数组中的最大值),接着就可以不断的弹出当前堆顶元素,同时不断更新堆顶以保证当前堆一直是大根堆,这样我们所弹出的K个元素就是数组中前K个最大的元素。

时间复杂度:O(N+K*logK)(建大根堆的时间复杂度O(N)+从堆顶向下调整一次的高度是logN,一共需要调整K次,即需要Pop K次O(K*logK))。由于是直接在数组中建堆,则空间复杂度为:O(1)
当N非常大时,N远大于K ,比如100亿个数里面找出最大的前10个,上面的两种方法均不适用,即100亿个整数是放在磁盘中,也就是文件中。

③最优的解决思路

1. 用数据集合中的前K个元素来建堆

如果要找出前K个最大的元素,则建小堆;如果要找出前K个最小的元素,则建大堆。

2. 用剩余的N-K个元素依次与堆顶元素进行比较,满足要求则替换堆顶元素

假设找出前K个最大的元素,则建小根堆。并将剩余N-K个元素依次与堆顶元素进行比较,如果比堆顶数据大就替换堆顶。将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最大的元素。

用前K个数建立一个K个数的小堆(前K个数是随机的,我们只管建小堆),然后剩下的N-K个依次遍历 和堆顶最小的数据比较,如果比堆顶的数据大,就把大的数赋给堆顶,再向下调整,最后堆里面的K个数就是最大的K个 ,因为小堆的堆顶一定是最小的数,只要随便拿个数比他大就交换他俩,大的那个数进入堆后再建小堆,大的数就沉在最下面,所以最后堆里面一定是K个最大的数。

复杂度:

时间复杂度:建K个数据的小根堆的时间复杂度为:O(K),剩下的N-K个数和堆顶比较,比堆顶大就放进堆顶,并向下调整一次,向下调整一次最坏交换h次,h=logK;最坏情况是建完小根堆后,里面正好全是最小的数,则剩下N-K个数都比堆顶大,都要放进堆顶并向下调整,所以是O((N-K)*logK),加起来就是O(K+(N-K)*logK)。

空间复杂度:O(K)。

代码如下:

//1、交换函数
void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

//2、向下调整算法,将二叉树调整成大堆形式
void AdjustDown(HPDataType* a, int n, int root)
{
	//将根节点赋值给双亲节点
	int parent = root;
	//默认新定义的孩子是左孩子
	int child = parent * 2 + 1;

	while (child < n)
	{
		//找出左右孩子中大的那一个
		//如果右孩子大于左孩子
		if (child + 1 < n && a[child] < a[child + 1])
		{
			++child;
		}

		//如果孩子节点小于双亲节点则交换
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			//当小的孩子节点大于双亲节点,则就不需要往下调整了
			break;
		}
	}
}


//Top-K问题的代码实现
//3、Top-K问题--返回数组中前K个最小的值
int* GetLeastNumbers(int* arr, int arrSize, int k)
{
	//开辟一块空间用于存放数组中前K个最小的值,并返回该数组
	int* retArr = (int*)malloc(sizeof(int) * k);

	//建包含K个数据的大堆
	//首先将arr数组中前K个数据放入到数组retArr中
	for (int i = 0; i < k; ++i)
	{
		retArr[i] = arr[i];
	}

	//建大根堆
	for (int i = (k - 1 - 1) / 2; i >= 0; --i)
	{
		AdjustDown(retArr, k, i);
	}

	//除去前K个数,从第K+1个数开始与堆顶的数据比较
	//如果比堆顶的数据小,则进堆,并进行一次向下调整,重新建大根堆
	for (int j = k; j < arrSize; ++j)
	{
		if (arr[j] < retArr[0])
		{
			retArr[0] = arr[j];
			AdjustDown(retArr, k, 0);
		}
	}

	return retArr;
}

堆的完整代码:堆的向下/向上调整算法、堆排序、Top-K问题完整代码

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

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

相关文章

【Linux】:进程控制(创建、终止、等待、替换)

目录 1.进程创建 2.进程终止&#xff08;退出&#xff09; 2.1 什么是进程终止 2.2 进程退出的场景&#xff08;原因&#xff09; 2.3 进程退出码 2.4 错误码errno 2.5 进程常见的退出方法 正常终止 从main函数返回 调用库函数exit 系统接口_exit 3.进程等待 3.1 …

【Linux】简易线程池项目

线程池是一个可以巩固一些线程相关接口 && 加强理解的一个小项目。 注意&#xff1a;这里的线程池使用的线程并不是Linux原生接口&#xff0c;而是经过封装的&#xff0c;具体请看线程封装&#xff0c;为什么不使用原生接口&#xff1f; 因为原生接口一旦进行pthread…

2024最新easyrecovery 14中文破解版图文教程

使用EasyRecovery易恢复进行数据恢复非常简单。首先&#xff0c;用户需要选择需要恢复的数据类型&#xff0c;如文档、图片、视频等。然后&#xff0c;软件会对选定的存储设备进行全面扫描&#xff0c;以寻找可恢复的数据。在扫描过程中&#xff0c;用户可以预览部分已找到的文…

成化瓷器“制字衣横少越刀”--还有例外

孙瀛洲先生关于成化款瓷器的名言非常经典&#xff0c;但是&#xff0c;凡事总有以外。 图1&#xff0c;本人收藏成化斗彩鸡缸杯底款&#xff0c;制字的衣横越过双勾刀。 下面是两件台北故宫成化瓷器底款&#xff0c;制字下面的衣横也是越过刀了。 所以&#xff0c;凡事总有例外…

mysql5.7安装

1.创建一个software文件 2.先下载mysql的repo源 wget http://repo.mysql.com/mysql-community-release-el7-5.noarch.rpm 3安装源包 rpm -ivh mysql-community-release-el7-5.noarch.rpm 可能会报错 改成命令 rpm -ivh mysql-community-release-el7-5.noarch.rpm --nodeps…

优化图像处理:从旋转与缩放到水印添加

1. 旋转与缩放的仿射变换 在 OpenCV 中&#xff0c;cv2.getRotationMatrix2D() 函数可以生成旋转矩阵&#xff0c;该矩阵用于对图像进行旋转和缩放变换。旋转矩阵的主要参数是&#xff1a; Center&#xff1a;旋转中心点的坐标 (x, y)。 Angle&#xff1a;旋转角度&#xff0…

数据结构与算法--图的应用

文章目录 回顾提要连通图生成树最小生成树构造最小生成树的算法普里姆(Prim)算法克鲁斯卡尔(Kruskal)算法 最短路径狄杰斯特拉 (Dijkstra) 算法当前最短路径的更新拓扑排序拓扑排序方法拓扑排序示例总结 回顾 图的遍历方法&#xff1a; 深度优先遍历 (DFS)&#xff1a;从任意…

在centos7安装mysql

1.卸载旧环境 ps axj | grep mysql ps axj | grep mariabd 如果是这样就什么都不需要做。 如果不是 2.检查并卸载系统安装包 //检查安装包 rpm -qa | grep mysql//卸载安装包 rpm -qa | grep mysql | xargs yum -y remove 3.安装官方yum源 先查看系统的版本 比如我是7.9版…

力扣高频SQL 50题(基础版)第四十题之1164. 指定日期的产品价格

文章目录 力扣高频SQL 50题&#xff08;基础版&#xff09;第四十题1164. 指定日期的产品价格题目说明实现过程准备数据实现方式结果截图总结FIRST_VALUE()函数LAST_VALUE()函数NTH_VALUE()函数 LAST_VALUE()函数NTH_VALUE()函数 力扣高频SQL 50题&#xff08;基础版&#xff0…

YJ0043定制版抖音电商卷抢购系统带回收商城抖音电商优惠卷投资理财系统

系统是基于逍遥商城二开的系统&#xff0c;pc手机端都新增了邀请码验证 手机端重新定制的UI&#xff0c;前端产品不至于抖音卷也可以自行更改其他产品 用户前端下单&#xff0c;后台订单可以直接回收&#xff0c;后台支持设置默认邀请码和抢卷时间限制

动手学深度学习(pytorch)学习记录10-从零开始实现softmax回归[学习记录]

注&#xff1a;本代码在jupyter notebook上运行 封面图片来源 导包 import torch from IPython import display import torchvision from torchvision import transforms from torch.utils import data设置加载数据的线程数 def get_dataloader_workers(): ""&qu…

《学会 SpringBoot 系列 · spring.factories 详解》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…

机器人阻抗控制之设计方法

机器人阻抗控制的设计方法主要围绕调整机器人与环境之间的动态关系&#xff0c;使其等效为由弹簧-阻尼-质量组成的二阶系统。这一控制策略不是直接控制机器人的运动或其与外界的接触力&#xff0c;而是控制这二者之间的动态关系。以下是机器人阻抗控制设计方法的详细阐述&#…

Centos7系统上安装docker

centos7安装docker 安装之前&#xff0c;一定查看是否安装docker&#xff0c;如果有&#xff0c;卸载老版本 我是虚拟机装的Centos7&#xff0c;linux 3.10 内核&#xff0c;docker官方说至少3.8以上&#xff0c;建议3.10以上&#xff08;ubuntu下要linux内核3.8以上&#xff…

LVS详细配置

目录 LVS简介 LVS集群体系结构 LVS相关术语 lvs集群的类型 1、NAT模式 NAT简介 NAT模式数据逻辑 2、DR模式 DR模式简介 DR模式数据逻辑 DR模式的特点 3、TUN模式 TUN模式简介 TUN模式数据传输过程 TUN模式特点 4、fullnet模式 LVS模式总结 LVS调度算法 LVS静…

python从入门到精通:函数

目录 1、函数介绍 2、函数的定义 3、函数的传入参数 4、函数的返回值 5、函数说明文档 6、函数的嵌套调用 7、变量的作用域 1、函数介绍 函数是组织好的&#xff0c;可重复使用的&#xff0c;用来实现特定功能的代码段。 name "zhangsan"; length len(nam…

机器学习(1)--数据可视化

文章目录 数据可视化作用可视化方法实现可视化 总结 数据可视化 数据可视化是将数据以图形、图像、动画等视觉形式表示出来&#xff0c;以便人们能够更直观地理解、分析和交流数据中的信息。 作用 一个整理的好好的数据&#xff0c;我们为什么要将其可视化呢&#xff1f;将它…

深入理解指针

前言&#xff1a;对于指针我们已经有了初步的了解&#xff0c;并已能够简单使用。今天我们来深入理解指针。让我们的指针功力更上一层楼。 1 使用指针访问数组 有了指针的知识&#xff0c;再结合数组的特点&#xff0c;我们就可以使用指针来访问数组了。 #include<stdio.…

线程的进阶学习

线程结束方式: 1.pthread_exit //pthread_join 2.从线程执行函数中return //此时效果等价于pthread_exit 3.pthread_cancel //线程可以被取消 4.任何一个线程调用了exit 或者 主线程 (main函数) return都会造成 进程结束 线程资源回收 ---pthread_join int pthread_ca…

汤姆·克鲁斯对妮可·基德曼经常对粉丝提起他们以前的事感到恼火

妮可基德曼最近回忆了她与前夫汤姆克鲁斯和导演斯坦利库布里克在 1999 年的电影《大开眼戒》中合作的时光。这似乎是对她职业生涯中某个时刻的无伤大雅的回顾&#xff0c;但据报道&#xff0c;有一个人对她在纪念该电影上映 25 周年时的谈话感到不满。 据报道&#xff0c;克鲁…