【数据结构】堆的实现(向下调整和向上调整法)和堆排序的实现

news2024/11/19 14:28:55

 

目录

一、堆的概念引入

二、小堆的实现

首先,我们会跟线性表一样建立一个动态数组来存堆的数据

①、堆的初始化--HeapInit

②、小堆的向下调整法的实现

③、堆排序的实现

 ④、堆的插入和向上调整法

 ⑤、删除堆顶数据

⑥、获取堆顶

三、时间复杂度总结:


注:本文logN表示以2为底N的对数,其次本文只实现小堆,因为大小堆的实现方式极其相似

一、堆的概念引入

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

 堆的概念及结构:

大堆(大根堆):1、完全二叉树  2、每个父亲>=孩子

小堆(小根堆):1、完全二叉树  2、每个父亲<=孩子

将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

堆就像一个完全二叉树+数组来存储,只是在这个基础上分大根堆和小根堆(简称大堆和小堆)

而关于左右孩子谁大谁小完全无关


堆的意义:

用来选数,因为比如大堆,父亲就是最大的,以后讲的堆排序就是用的这个特征,大堆特点:根(堆顶)就是最大值,小堆特点:根(堆顶)就是最小值。实际应用比如选出哪个地方好评最好的食品,就用到了堆。

堆的性质:

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

例题:

 下列关键字序列为堆的是(A)

A、100,60,70,50,32,65

B、60,70,65,50,32,100

C、65,100,70,32,50,60

D、70,65,100,32,50,60

思路:因为堆的完全二叉树按照数组来存储,则从第一个往后每一层的节点数为1、2、4、6......

比如A:

二、小堆的实现

本质就是一个完全二叉树用数组来存,我们用一个动态数组就好

分为三个文件:Heap.h    Heap.c    test.c

关键:

逻辑结构是完全二叉树,物理结构是数组,本质上逻辑结构只是想象出来的,物理结构才是我们真实操作的,所以针对数组,我们真实的是操作它的下标。

如果想构建小堆:

首先,我们会跟线性表一样建立一个动态数组来存堆的数据

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

①、堆的初始化--HeapInit

①、数组应该存堆的数据,而数据是源于你传入的数据,用动态数组拷贝数据,才方便后续操作

②、数组建堆要用向下调整法,满足树中所有父亲节点均小于孩子,向下调整法在后面有讲

void HeapInit(Heap* php, HPDataType* a, int n)
{
	//php: hp代表heap,前面多个p表示指针
	php->_a = (HPDataType*)malloc(sizeof(HPDataType) * n);
	if (php->_a == NULL)
	{//一般malloc很少会失败,为了严谨最好检查一下
		printf("malloc fail!\n");
		exit(-1);
	}
	memcpy(php->_a, a, sizeof(HPDataType) * n);
	//把需要的数据拷贝到堆中,拷贝后才方便动态进行
	//因为传给我们的数组a,它是静态的,不便于后续操作
	php->_size = n;//堆的特点就是本来堆中就有n个数据
	php->_capacity = n;

	//构建堆
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{//从最后的叶子结点的父节点开始向下调整
		AdjustDown(php->_a, php->_size, i);
	}

}

②、小堆的向下调整法的实现

向下调整法:

  • 前提:如果一棵树中左右子树都是小堆,只有根节点不满足,那么就可使用向下调整算法。(这个方法很重要,以后讲的堆排序都要用到这个算法)数组建堆主要依赖的就是向下调整法
  • 基础知识:对于完全二叉树而言,已知父亲节点的下标为i,则他的左孩子下标为2*i+1,右孩子下标为2*i+2
  • 思路:找出左右孩子中小的那一个,然后与父亲节点交换,然后再找下一个父亲和孩子,再次找出左右孩子中小的那一个,不断比较并交换,直到最后的下标超出了数组的范围。

下面是只有一个左子树的情况元素个数n=10,此时数值为37的下标为9,9+1会造成越界

 因为向下调整法是构建在左右子树都是小堆的前提下,那怎么使左右子树是小堆

只有一个节点时既可看作小堆也可看作大堆,那对于一个完全二叉树,只看叶子结点肯定保证是小堆了(可看做是小堆),那只需从叶子结点的父亲开始调整为小堆,其下标为(n-1-1)/2(因为向下调整法只要在左右子树是小堆,根节点不保证是小堆的下用,那么叶子结点可以看做是左右子树),然后在判断它的前一个元素(只需要下标-1就找到上一个元素了),不断往上调整,直到调完最上面的根节点就结束了,最上面的根节点的下标为0

综上,调整终止条件有两个:

  • 当二叉树是满二叉树:child < n循环(调整)才会继续
  • 当二叉树的最后某一节点只有左子树(不可能只有右子树,因为是完全二叉树),child+1<n循环(调整)才会继续,否则这种情况会越界
  • 交换过程中满足孩子大于父亲了,说明此次无需交换了
//向下调整算法。前提:左右子树都是小堆
void AdjustDown(HPDataType* a, int n, int root)
{
	//找出左右孩子中小的那一个,默认认为左孩子是小的那一个,否则就加以调整即可
	int parent = root;
	int child = parent * 2 + 1;//先默认child是左孩子,我们的目的是让child成为小的那一个
	while (child < n)
	{//当孩子的下标<n的时候才会一直比较交换,越界就说明堆构建完了
		if (child + 1 < n && a[child + 1] < a[child])//判断还要有一个只有左子树的情况
		{//如果右孩子比左孩子还小,就让child变成右孩子,即小标+1即可
			child++;
		}
		//如果孩子小于父亲就交换
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;//进行下一次的比较判断
			child = parent * 2 + 1;
		}
		else
		{
			break;//因为孩子已经>=父亲了,满足小堆的条件了,就无须继续往下判断,
			//因为在调整的过程中可能就存在在越界之前,孩子>=父亲的情况
            //谨记向下调整法用于只有堆顶不满足,而左右子树满足堆的性质的时候使用
		}
		//仅交换一次还不能够满足小堆,应该持续比较并交换,所以应该是个循环
	}

}

当然也可以用递归来写,但是一般除了练习,最好不要用递归,最好用迭代

向下调整法的时间复杂度:

因为对于堆来说,按最糟糕的情况来算,那就是每一层就要交换一次节点,那么整个堆就要交换高度次,即log(N+1) (以2为底N+1的对数),故时间复杂度:O(logN)

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

③、堆排序的实现

利用向下调整法建小堆可以找出最小的一个,但为了排序,如何找到次小的?正常逻辑是除了这个最大的其他节点再次建小堆,因为再次建小堆就可以再次找到最小的,但是这个方法时间复杂度比较大,为O(N*N)

排降序:建小堆

排升序:建大堆

那么如何减少时间复杂度?

思路:

 已知建堆的时间复杂度为O(N),而在排序过程中,由于要每次选出剩余数中最大的数,并保存到每次最后的节点,并要再执行一次向下调整算法,总共需要进行N次,而向下调整算法的时间复杂度为O(log2N),进行N次就是O(N*log2N),即堆排序的时间复杂度

void HeapSort(int* a, int n)
{
	//1、数组建堆
	for (int i = (n - 1 - 1) / 2; i >= 0; --i)
	{
		AdjustDown(a, n, i);
	//当然i也可初始化为n-1,即从叶子结点开始调,但是这么做肯定没有从叶子结点
	//的父节点开始调高效
	}
	//2、找次小,排序
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		
		//再继续选次小的
		AdjustDown(a, end, 0);
		--end;
//没有真的删除最后一个数据,只是说我下次再找小交换,最后一个数据
//不被看作堆里面的,不造成影响
	}
}

 ④、堆的插入和向上调整法

 插入数据,为了保持堆(小堆或大堆)的性质,它与普通线性表的插入还不完全相同,因为它插入的数据可能会使原来是小堆变成不是小堆,即它比他的父亲还小,就需要重新调整。

头插可以吗?头插会导致关系变乱,原来是父子关系的两节点,会出现父子变兄弟,第二,头插需要挪动数据(因为用的是数组),会导致效率低下,所以用尾插。尾插需要用到向上调整法

向上调整法思路如下: 

 也就是从最后插入的数据跟它的父亲比大小,比父亲小就交换,比父亲大就说明不用交换了,满足小堆

void AdjustUp(HPDataType* a, int n, int child)//child表示从最后底开始的下标,因为是从下往上调
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{//这里的循环判断条件不应该看父节点,父节点不管怎么都可以>=0,比如child=0,(0-1)/2=0
	//如果以父亲<0作为循环终止条件是判断不出来的
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;//孩子比父亲大,就无须调整了
		}
	}
}
void HeapPush(Heap* php, HPDataType x)
{
	assert(php);
	if (php->_capacity == php->_size)
	{
		php->_capacity *= 2;
		HPDataType* tmp = (HPDataType*)realloc(php->_a, sizeof(HPDataType) * php->_capacity);
		if (tmp != NULL)
		{
			php->_a = tmp;
		}
	}
	php->_a[php->_size++] = x;//先插入x
	//然后利用向上调整法再调节为小堆
	AdjustUp(php->_a, php->_size, php->_size - 1);
}

 ⑤、删除堆顶数据

直接删堆顶不行,因为关系会变乱,比如原来的父子关系会变成兄弟,那这里和堆排序的思路差不多,第一个数据与最后一个交换,但是这里最后一个数据是真删,然后再向下调整法,就可以把堆顶删掉了。这里不需要尾删,因为简单且没意义,我们一般都会删除堆顶,不断找次小的数。

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);//再次向下调整法,使其为小堆
}

⑥、获取堆顶

HPDataType HeapTop(Heap* php)
{
	assert(php);
	assert(php->_size > 0);

	return php->_a[0];
}

Heap.h:

#pragma once

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>

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

//堆的初始化
void HeapInit(Heap* php, HPDataType* a, int n);
//堆的销毁
void HeapDestroy(Heap* php);
//数据插入
void HeapPush(Heap* php,HPDataType x);//与线性表不同的是插入数据后,要保持堆的特性
//删除堆顶的数据
void HeadPop(Heap* php);//并不是删除那么简单,删除完数据还是要保持堆的特性
//获得堆顶的数据
HPDataType HeapTop(Heap* php);

Heap.c:

#define _CRT_SECURE_NO_WARNINGS 1
#include"Heap.h"

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

//向下调整算法。前提:左右子树都是小堆
void AdjustDown(HPDataType* a, int n, int root)
{
	//找出左右孩子中小的那一个,默认认为左孩子是小的那一个,否则加以调整即可
	int parent = root;
	int child = parent * 2 + 1;//先默认child是左孩子的下标,我们的目的是让child成为小的那一个
	while (child < n)
	{//当孩子的下标(每一轮循环child下标都会被更新为左孩子的下标)<n的时候才会一直比较交换,越界就说明堆构建完了
		if (child + 1 < n && a[child + 1] < a[child])//判断还要有一个只有左子树的情况
		{//如果右孩子比左孩子还小,就让child变成右孩子,即小标+1即可
			child++;
		}
		//如果孩子小于父亲就交换
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;//进行下一次的比较判断
			child = parent * 2 + 1;//再次假设小的孩子是左孩子,进行下一次循环
		}
		else
		{
			break;//满足小堆的条件了,无须继续往下判断,
			//因为在调整的过程中,可能出现孩子>=父亲的情况
		}
		//仅交换一次还不能够满足小堆,应该持续比较并交换,所以应该是个循环
	}

}

//向上调整法
void AdjustUp(HPDataType* a, int n, int child)//child表示从最后底开始的下标,因为是从下往上调
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{//这里的循环判断条件不应该看父节点,父节点不管怎么都可以>=0,比如child=0,(0-1)/2=0
	//如果以父亲<0作为循环终止条件是判断不出来的
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;//孩子比父亲大,就无须调整了
		}
	}
}
void HeapInit(Heap* php, HPDataType* a, int n)
{
	//php: hp代表heap,前面多个p表示指针
	php->_a = (HPDataType*)malloc(sizeof(HPDataType) * n);
	if (php->_a == NULL)
	{//一般malloc很少会失败,为了严谨最好检查一下
		printf("malloc fail!\n");
		exit(-1);
	}
	memcpy(php->_a, a, sizeof(HPDataType) * n);
	//把需要的数据拷贝到堆中,拷贝后才方便动态进行
	//因为传给我们的数组a,它是静态的,不便于后续操作
	php->_size = n;//堆的特点就是本来堆中就有n个数据
	php->_capacity = n;

	//构建堆
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{//从最后的叶子结点的父节点开始向下调整
		AdjustDown(php->_a, php->_size, i);
	}

}

//堆的销毁
void HeapDestroy(Heap* php)
{
	assert(php);
	free(php->_a);
	php->_a = NULL;
	php->_capacity = php->_size = 0;
}

//堆排序的实现
void HeapSort(int* a, int n)
{
	//1、数组建堆
	for (int i = (n - 1 - 1) / 2; i >= 0; --i)
	{
		AdjustDown(a, n, i);
	//当然i也可初始化为n-1,即从叶子结点开始调,但是这么做肯定没有从叶子结点
	//的父节点开始调高效
	}
	//2、找次小,排序
	int end = n - 1;
	while (end > 0)
	{//最后临界条件是end=0,即只剩下一个元素,就无须再排了
		Swap(&a[0], &a[end]);//把建好的堆的最小元素换到和最后的一个元素换
		
		//再继续选次小的
		AdjustDown(a, end, 0);//传入了n-1个元素,相当于把最后的一个元素除去了
		--end;
	}
}

//从堆尾部插入数据
void HeapPush(Heap* php, HPDataType x)
{
	assert(php);
	if (php->_capacity == php->_size)
	{
		php->_capacity *= 2;
		HPDataType* tmp = (HPDataType*)realloc(php->_a, sizeof(HPDataType) * php->_capacity);
		if (tmp != NULL)
		{
			php->_a = tmp;
		}
	}
	php->_a[php->_size++] = x;//先插入x
	//然后利用向上调整法再调节为小堆
	AdjustUp(php->_a, php->_size, php->_size - 1);
}

//从删除堆顶数据
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);//再次向下调整法,使其为小堆
}

//获取堆顶
HPDataType HeapTop(Heap* php)
{
	assert(php);
	assert(php->_size > 0);

	return php->_a[0];
}

test.c:

#define _CRT_SECURE_NO_WARNINGS 1
#include"Heap.h"

int main()
{
	int a[] = { 27,15,19,18,28,34,65,49,25,37 };
	Heap hp;
	HeapInit(&hp, a, sizeof(a) / sizeof(a[0]));
	HeapPush(&hp, 19);
	printf("堆顶为%d\n", HeapTop(&hp));
	HeapDestroy(&hp);

	return 0;
}

三、时间复杂度总结:

向上调整法复杂度=向下调整法时间复杂度=O(logN)

向下建堆的时间复杂度:O(N)

堆排序的时间复杂度:O(N*logN)

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

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

相关文章

C# PaddleInference OCR识别 学习研究Demo

说明 基于开源项目 https://github.com/sdcb/PaddleSharp VS2022.net4.8 OpenCvSharp4Sdcb.PaddleInference 效果 项目 代码 using Sdcb.PaddleInference.Native; using Sdcb.PaddleInference; using System; using System.Collections.Generic; using OpenCvSharp.Extensi…

15年前的手机并没有jvm虚拟机,为何可以运行Java游戏

2000年代初期&#xff0c;随着移动通信技术的发展&#xff0c;手机逐渐普及。那个时代的手机功能相对比较单一&#xff0c;主要用于打电话和发送短信。但是&#xff0c;随着技术的进步&#xff0c;人们开始在手机上玩游戏&#xff0c;而其中最受欢迎的游戏就是Java游戏。在那个…

ChatLaw,开源了!

公众号关注 “GitHubDaily” 设为 “星标”&#xff0c;每天带你逛 GitHub&#xff01; 最近这段时间&#xff0c;AI 的整体热度有所下降&#xff0c;但是 AI 技术在各行各业的探索脚步&#xff0c;却一直没有停止。 在 ChatGPT 刚发布时&#xff0c;有不少业内人士认为&#x…

【浏览器篇】记录下浏览器保存PDF文件不同方式的小区别

【浏览器篇】记录下浏览器保存PDF文件不同方式的小区别 以前不太注意这些&#xff0c;最近搞文档比较多才发现为何保存的一部分PDF文件里面字体可以复制可以搜索&#xff0c;一部分保存的PDF里面的字体却无法复制、无法搜索等&#xff0c;发现是不同保存方式得到的文档权限不一…

SQL注入攻击原理 实战

我来进行实战了&#xff0c;总在看教程。 文章目录 前言一&#xff0c;网站是否存在sql漏洞二、判断一下字段3. 判断显点4.查找相关信息1.查询数据库2.版本3.数据库表名4.字段名5,查询 总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 前言&#xff…

华为OD机试真题 Python 实现【学校的位置】【2023Q1 100分】,附详细解题思路

目录 一、题目描述二、输入描述三、输出描述四、Python算法源码五、效果展示1、输入2、输出3、说明 一、题目描述 为了解决新学期学生暴涨的问题&#xff0c;小乐村要建所新学校。考虑到学生上学安全问题&#xff0c;需要所有学生家到学校距离最短。假设学校和所有的学生家&am…

unity+pico neo3入门教程1-基础传送

tips&#xff1a;之前入门教程如果没有左手柄&#xff0c;查看一下自己的手柄设置&#xff0c;左右手柄&#xff0c; Helloworld型 1.基础传送&#xff0c;调式地面传送功能&#xff0c;通过手柄默认的“握手键”&#xff0c;瞬移&#xff0c; VR头显&#xff0c;添加Teleport…

Go语言远程调试

Go语言远程调试 1、安装dlv # 安装dlv $ go install github.com/go-delve/delve/cmd/dlvlatest$ dlv version Delve Debugger Version: 1.20.1 Build: $Id: 96e65b6c615845d42e0e31d903f6475b0e4ece6e $2、命令行远程调试 我们远程(Linux服务器)有如下代码&#xff1a; [ro…

(四)Kafka 消费者

文章目录 1. Kafka 消费者相关概念消费者和消费者组&#xff08;1&#xff09;横向伸缩消费者&#xff08;2&#xff09;横向伸缩消费者组 分区再平衡再均衡的类型&#xff08;1&#xff09;主动再均衡&#xff08;2&#xff09;协作再均衡&#xff08;增量再均衡&#xff09; …

MyBatisAnnotationSqlInjection.ql学习

源码位置 java\ql\src\experimental\Security\CWE\CWE-089 源代码 /*** name SQL injection in MyBatis annotation* description Constructing a dynamic SQL statement with input that comes from an* untrusted source could allow an attacker to modify …

【UE5 Cesium】14-Cesium for Unreal 加载服务器上的倾斜摄影

目录 前言 步骤 一、下载安装tomcat 10 二、下载安装JDK 三、启动Tomcat 四、Tomcat加载倾斜摄影 五、UE中加载Tomcat上的倾斜摄影 前言 上一篇文章&#xff08;【UE5 Cesium】13-Cesium for Unreal 加载本地倾斜摄影&#xff09;介绍了如何在UE中加载本地倾斜摄影&am…

链表专题1—24. 两两交换链表中的节点 234.回文链表 143.重排链表 141.环形链表 142.环形链表II 160.链表相交 C++实现

文章目录 24. 两两交换链表中的节点234.回文链表链表转数组统计长度反转后半部分链表 快慢指针 143. 重排链表数组 双指针 超时双队列反转和插入链表 141. 环形链表142.环形链表II160.链表相交 24. 两两交换链表中的节点 迭代法&#xff0c;时间复杂度&#xff1a; O ( n ) O(n…

App store里简单好用的便签app有哪些?

作为一个打工人&#xff0c;我经常需要一个简单而又好用的便签应用来记录我的各种事务和备忘。我曾在App Store里尝试了许多便签应用&#xff0c;但有一款应用真正让我留下了深刻的印象——敬业签。 敬业签的简单和易用性让我爱不释手。无论是添加新的便签&#xff0c;设置提醒…

基础大模型能像人类一样标注数据吗?

自从 ChatGPT 出现以来&#xff0c;我们见证了大语言模型 (LLM) 领域前所未有的发展&#xff0c;尤其是对话类模型&#xff0c;经过微调以后可以根据给出的提示语 (prompt) 来完成相关要求和命令。然而&#xff0c;直到如今我们也无法对比这些大模型的性能&#xff0c;因为缺乏…

为什么程序员更容易抑郁?是因为...

【1】 前段时间&#xff0c;有一位朋友&#xff0c;在后台留言&#xff1a; 《谢谢你听我吐槽&#xff0c;说出来感觉好了很多》 这位程序员朋友在深圳大厂&#xff0c;35岁&#xff0c;10年研发经验&#xff0c;倍感抑郁&#xff0c;吐露了自己的近况&#xff1a; &#xff08…

IDE /skipping incompatible xxx_d.dll when searching for -lxxx_d

文章目录 概述场景复现用以测试的代码编译器位数不匹配导致?保持编译器类型一致再验证编译器位数的影响MingW下调用OS的库咋不告警?以mingW下使用winSocket为例MingW下网络编程的头文件分析该环境下链接的ws2_32库文件在哪里&#xff1f;mingW为啥可以兼容window下的动态库 概…

MySQL自治平台建设的内核原理及实践(下)

总第566篇 2023年 第018篇 本文整理自美团技术沙龙第75期的主题分享《美团数据库攻防演练建设实践》&#xff0c;系超大规模数据库集群保稳系列&#xff08;内含4个议题的PPT及视频&#xff09;的第4篇文章。 本文作者在演讲后根据同学们的反馈&#xff0c;补充了很多技术细节&…

【Web狗自虐系列1】Pwn入门之初级ROP

0x0 栈介绍 栈式一种典型的后进先出的数据结构&#xff0c;其操作主要有压栈(push)与出栈(pop)两种操作 压栈与出栈都是操作的栈顶 高级语言在运行时都会被转换为汇编程序&#xff0c;在汇编程序运行过程中&#xff0c;充分利用了这一数据结构。每个程序在运行时都有虚拟地址…

国产化适配再进一步,融云完成欧拉、TDSQL、优炫等多方适配

近期&#xff0c;融云完成了与开源操作系统欧拉&#xff08;openEuler&#xff09;、企业级数据库 TDSQL 和优炫的适配工作&#xff0c;国产化上下游生态适配之路再次迈进坚实一步。关注【融云 RongCloud】&#xff0c;了解协同办公平台更多干货。 欧拉&#xff08;openEuler&a…

DoTween 学习

部分参考&#xff1a;DOTween中文详解&#xff08;持续更新&#xff09;_一条爱玩游戏的咸鱼的博客-CSDN博客 官方文档&#xff1a;DOTween - Documentation (demigiant.com) 什么是Tween&#xff08;补间&#xff09; 补间&#xff0c;一般指补间动画&#xff0c;例如uni…