二叉树 -- 堆(详解)

目录

1、堆的概念及结构

2、堆的实现(附代码)

2.1、向下调整算法建堆

3、堆的应用(附代码)

3.1、堆排序

3.2、TOP-K问题


1、堆的概念及结构

如果有一个关键码的集合K = { k0,k1 ,k2 ,…,k(n-1) },把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:ki<=k(2*i+1) 且 ki<=k(2*i+2) ( ki>=k(2*i+1) 且ki>=k(2*i+2) ) (i = 0,1,2…,)则称为小堆(或大堆)。

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

通俗一点讲就是:

小堆:任意一个父结点的值都小于或者等于其子结点。

大堆:任意一个父结点的值都大于或者等于其子结点。

至于同一个父结点的两个子结点谁大谁小都无所谓。

堆的性质:

(1)堆中某个节点的值总是不大于或不小于其父节点的值。

(2)堆总是一棵完全二叉树。

补充一个问题:

堆的意义是什么?

答:选数,选最大值或最小值 -- 时间复杂度为O(logN)。

ex:

(1)堆排序,时间复杂度为O(N*logN)。 -- 选出每个节点所需时间为O(logN),选出N个节点所需的总时间为O(N*logN)。

(2)top k问题 -- ex:从100万个值中找出最大的10个值或者说找出最小的10个值。

2、堆的实现(附代码)

对于堆的创建有两种方法,一种是插入法(使用向上调整,时间复杂度为N*logN),一种是向下调整法(也叫筛选法)(向下调整建堆,时间复杂度为O(N))。

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

堆的底层看起来像是顺序表,但和顺序表是不一样的,从逻辑结构上,要把堆看作是一颗完全二叉树。

Heap.h:

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>
#include <time.h>

typedef int HPDataType;

typedef struct Heap
{
	HPDataType* array;
	int size;
	int capacity;
} HP;
// 堆的底层看起来像是顺序表,但和顺序表是不一样的,从逻辑结构上,要把堆看作是一颗完全二叉树。

// 堆的初始化
void HeapInit(HP* php);

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

// 判断是否需要扩容
void CheckCapacity(HP* php); 

// 交换
void Swap(HPDataType* p1, HPDataType* p2);

// 获取堆顶的元素
HPDataType HeapTop(HP* php);

// 获取堆的大小
int HeapSize(HP* php);

// 判空
bool HeapEmpty(HP* php);

// 小堆的插入
void SmallHeapPush(HP *php, HPDataType x);

// 小堆的向下调整
void SmallAdjustDown(HPDataType *a, int size, int parent);

// 小堆的删除(规定删除栈顶元素)
void SmallHeapPop(HP *php);

// 大堆的插入
void BigHeapPush(HP* php, HPDataType x);

// 大堆向下调整
void BigAdjustDown(HPDataType* a, int size, int parent);

// 大堆的删除(规定删除栈顶元素)
void BigHeapPop(HP* php);

Heap.c:

#include "Heap.h"

// 堆的初始化
void HeapInit(HP* php)
{
	assert(php);

	php->array = NULL;
	php->capacity = php->size = 0;
}

// 堆的销毁
void HeapDestory(HP* php)
{
	assert(php);
	free(php->array); // 连续的空间,直接free即可,不用像链表那种,还要去一个一个节点的free
	php->array = NULL;
	php->capacity = php->size = 0;
}

void CheckCapacity(HP* php) // 判断是否需要扩容
{
	if (php->size == php->capacity)
	{
		int newcapacity = php->capacity ? 2 * php->capacity : 4;
		HPDataType* tmp = (HPDataType*)realloc(php->array, sizeof(HPDataType) * newcapacity);
		if (tmp == NULL)
		{ 
			perror("realloc fail");
			exit(-1);
		}
		php->array = tmp;
		php->capacity = newcapacity;
	}
}

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

// 获取堆顶的元素
HPDataType HeapTop(HP* php)
{
	assert(php);
	assert(php->size);

	return php->array[0];
}

// 获取堆的大小
int HeapSize(HP* php)
{
	assert(php);
	return php->size;
}

// 判空
bool HeapEmpty(HP* php)
{
	assert(php);
	return php->size == 0;
}

void AdjustUp(HPDataType *a, int child) // child至少是0
{
	int parent = (child - 1) / 2;
	while (a[child] < a[parent]) // 把这里改成 > 就是实现大堆了
	{
		Swap(&a[child], &a[parent]);
		child = parent;
		parent = (parent - 1) / 2; // parent怎么变都不会变成负数
	}
}

// 堆的插入(建堆)--时间复杂度为log(N)--主要的时间消耗在于向上调整的时间消耗--最坏的情况要调整log(N)次
void HeapPush(HP *php, HPDataType x)
{
	assert(php);

	CheckCapacity(php);

	php->array[php->size++] = x;

	AdjustUp(php->array, php->size - 1); // 因为size至少是1
}

// 向下调整--删除栈顶元素之后进行的调整--也可用于堆的创建,时间复杂度为O(N),比向上调整建堆更优
// 思路:
// 因为交换完根结点和尾结点,并删除栈顶元素之后,根结点的左子树和右子树仍旧符合堆的规定
// 所以如下
void AdjustDown(HPDataType *a, int size, int parent) // 将里面的孩子与父亲的大小比较改成>就变成是大堆的调整了
{
	// 分情况讨论法
	int leftchild = parent * 2 + 1;
	int rightchild = parent * 2 + 2;
	while (leftchild < size)
	{
		// 如果左右孩子一样大,跟谁换都行(前提是左右孩子都存在)
		if (rightchild < size && a[leftchild] <= a[rightchild] && a[parent] > a[leftchild]) // 比左孩子大,就跟左孩子交换
		{
			Swap(&a[parent], &a[leftchild]);
			parent = leftchild;
		}
		else if (rightchild < size && a[leftchild] > a[rightchild] && a[parent] > a[rightchild]) // 比右孩子大,就跟右孩子交换
		{
			Swap(&a[parent], &a[rightchild]);
			parent = rightchild;
		}
		else if (a[leftchild] < a[parent]) // 一定要注意要讨论只有左孩子,没有右孩子的情况,不然就出现越界访问了,Bug
		{
			Swap(&a[parent], &a[leftchild]);
			parent = leftchild;
		}
		else // 如果孩子都比父结点大,那就不用再换了
		{
			break;
		}
		leftchild = parent * 2 + 1;
		rightchild = parent * 2 + 2;
	}

	// 假设法
	// int child = parent * 2 + 1;//就假设是左孩子更小
	// while(child < size)
	//{
	//	if(child+1 < size && a[child+1] < a[child]) //这里可能出现越界,需要加一个判断child+1<size
	//                                               //因为存在那种只有左孩子但没有右孩子的情况,这时child+1的值就已经和size相等了
	//                                               //此时a[child+1]就已经是越界访问了
	//   {
	//		++child;
	//	}
	//   if(a[child] < a[parent])
	//   {
	//		Swap(&a[parent],&a[child]);
	//		parent = child;
	//		child = parent * 2 + 1;
	//   }
	//	else
	//	{
	//		break;
	//   }
	// }
}

// 堆的删除(规定删除栈顶元素)
void HeapPop(HP *php)
{
	assert(php);
	assert(php->size);

	// 第一步,先将根结点和尾结点进行交换,然后删除栈顶元素
	Swap(&php->array[0], &php->array[php->size - 1]);
	php->size--;

	AdjustDown(php->array, php->size, 0);
}

2.1、向下调整算法建堆

因为向下调整需要先保证该节点的左右子树已经是大/小堆了,才能向下调整,所以先从尾开始调整(思路就是从尾开始调整,从最后一个叶结点的父结点开始调整,叶结点无需调整,因为叶结点可以看作是一种特殊的子树,既可以当作大堆,也可以当作小堆)。

for (int i = (size - 1 - 1) / 2; i >= 0; i--) // (size-1-1)/2是最后一个叶结点的父结点的下标
{
    AdjustDown(a, size, i); // a是一个完全二叉树
}

注:向下调整算法有一个前提:左右子树必须是一个堆,才能调整。

注:向下调整算法的两大优势:

  1. 在进行堆排序或者解决TOP-K问题时,写一个向下调整就能解决。
  2. 时间复杂度为O(N),效率更高。

补充:为什么用向下调整算法建堆的时间复杂度是O(N)?

答:经过计算时间复杂度的值是一个等比数列乘于等差数列的求和,结果为2^h-1-h(h为树的高度),因为2^h-1==N,所以时间复杂度为O(N)。

3、堆的应用(附代码)

3.1、堆排序

堆排序即利用堆的思想来进行排序,总共分为两个步骤:

1.建堆

升序:建大堆--注:建大堆,父节点是跟更大的孩子交换

降序:建小堆--注:建小堆,父节点是跟更小的孩子交换

2.利用堆删除思想来进行排序 -- 交换头尾,然后并不是真的删除掉尾,只是把除了尾之外的其他数看成新的堆,再对新的堆进行调整

总结:建堆和堆删除中都可以用向下调整,因此掌握了向下调整,就可以完成堆排序。

3.2、TOP-K问题

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

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

对于Top-K问题,能想到的最简单直接的方式就是排序。--空间复杂度大(可能导致内存不够用),时间复杂度还不好说

但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。

所以:最佳的方式就是用堆来解决。--空间复杂度很小,时间复杂度仅为O(N(logK))--比较次数为N-K(K可以忽略不计),调整次数最多为logK次

基本思路如下:

第一步:用数据集合中前K个元素来建堆

要找前k个最大的元素,则建小堆

要找前k个最小的元素,则建大堆

第二步:用剩余的N-K个元素依次与堆顶元素来比较

如果是找前K个最大的元素,将剩余N-K个元素依次与堆顶元素比较,若比堆顶元素大,则替换堆顶元素(替换完后要进行一下向下调整)。

如果是找前K个最小的元素,将剩余N-K个元素依次与堆顶元素比较,若比堆顶元素小,则替换堆顶元素(替换完后要进行一下向下调整)。

最后堆中剩余的K个元素就是所求的前K个最大或者最小的元素。

代码示例:

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

void Print(int* a, int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}

// 堆排序即利用堆的思想来进行排序,总共分为两个步骤:
// 1. 建堆
// 升序:建大堆
// 降序:建小堆
// 2. 利用堆删除思想来进行排序 --交换头尾,然后并不是真的删除掉尾,只是把除了尾之外的其他数看成新的堆,再对新的堆进行调整
// 总结:建堆和堆删除中都可以用向下调整,因此掌握了向下调整,就可以完成堆排序。

void HeapSort_ascending(int* a, int size) // 这里排升序
{
	assert(a);
	// 第一步把数组建成堆 --时间复杂度为N*log(N)
	// 思路:直接把数组的第一个元素看作是一个堆,从第二个元素开始看作是要插入(Push)的数据
	// 这里演示大堆的建成 
	for (int i = 1; i < size; ++i)
	{
		int child = i;
		int parent = (child - 1) / 2;
		while (a[child] > a[parent])
		{
			// 交换
			int tmp = a[child];
			a[child] = a[parent];
			a[parent] = tmp;

			child = parent;
			parent = (child - 1) / 2;
		}
	}

	// 第二步,交换并进行向下调整--时间复杂度为N*logN
	for (int end = size - 1; end > 0; --end)
	{
		// 先交换头尾
		int tmp = a[0];
		a[0] = a[end];
		a[end] = tmp;

		// 再进行向下调整。这里用的是假设法,就假设左孩子更大,建大堆就是跟更大的孩子换,建小堆则是跟更小的孩子换
		int j = 0;
		int k = j * 2 + 1;
		while (k < end)
		{
			if (k + 1 < end && a[k] < a[k + 1])
			{
				k++;
			}
			if (a[j] < a[k])
			{
				int tmp = a[j];
				a[j] = a[k];
				a[k] = tmp;
			}
			else
			{
				break;
			}
			j = k;
			k = j * 2 + 1;
		}
	}
}



void HeapSort_descending(int* a, int size) // 这里排降序
{
	assert(a);

	// 假设第一个元素自身是一个小堆,将从第二个元素开始之后的元素都视为新插入的元素
	for (int i = 1; i < size; ++i)
	{
		int child = i;
		int parent = (child - 1) / 2;
		while (a[child] < a[parent])
		{
			// 交换
			int tmp = a[child];
			a[child] = a[parent];
			a[parent] = tmp;

			child = parent;
			parent = (child - 1) / 2;
		}
	}

	// 开始排序,就是先交换头尾,再进行向下调整
	for (int i = size - 1; i > 0; --i)
	{
		int tmp = a[i];
		a[i] = a[0];
		a[0] = tmp;
		
		int parent = 0;
		int leftchild = parent * 2 + 1;
		int rightchild = parent * 2 + 2;

		while (rightchild < i || leftchild < i)
		{
			if (rightchild < i && a[rightchild] < a[leftchild] && a[rightchild] < a[parent])
			{
				int tmp = a[rightchild];
				a[rightchild] = a[parent];
				a[parent] = tmp;

				parent = rightchild;
			}
			else if (a[leftchild] < a[parent])
			{
				int tmp = a[leftchild];
				a[leftchild] = a[parent];
				a[parent] = tmp;

				parent = leftchild;
			}
			else
				break;
			leftchild = parent * 2 + 1;
			rightchild = parent * 2 + 2;
		}
	}
}





// TOP-K问题
// 基本思路如下:
// 第一步:用数据集合中前K个元素来建堆
// 要找前k个最大的元素,则建小堆
// 要找前k个最小的元素,则建大堆
// 第二步:用剩余的N-K个元素依次与堆顶元素来比较
// 如果是找前K个最大的元素,将剩余N-K个元素依次与堆顶元素比较,若比堆顶元素大,则替换堆顶元素(替换完后要进行一下向下调整)。
// 如果是找前K个最小的元素,将剩余N-K个元素依次与堆顶元素比较,若比堆顶元素小,则替换堆顶元素(替换完后要进行一下向下调整)。
// 最后堆中剩余的K个元素就是所求的前K个最大或者最小的元素。
// 补充:
// 当N不大时(N太大,会有内存不够的问题),可以选择建个大小为N的堆,然后 pop K次即可。 // 但不管怎样,这个思路都不如上面的好。

// 造数据
void CreateData()
{
	int n = 1000000;
    FILE* pf = fopen("data.txt", "w");
	if (!pf)
	{
		perror("fopen fail");
		exit(-1);
	}
	for (int i = 0; i < n; i++)
	{
		int x = (rand() + i) % 100000; // 注意rand()产生的随机数是有范围的,范围为0到RAND MAX(32767)之间
		fprintf(pf, "%d ", x);
	}
	fclose(pf);
	pf = NULL; // 好习惯,置不置空都行,因为出了函数就自动销毁了
}

void PrintfTopK(int k)
{
	FILE* pf = fopen("data.txt", "r");
	if (!pf)
	{
		perror("fopen fail");
		return;
	}

	// 第一步,建堆(这里建小堆,找大数)
    int* minheap = (int*)malloc(sizeof(int) * k);
	if (!minheap)
	{
		perror("malloc fail");
		return;
	}
	for (int i = 0; i < k; i++)
	{
		// 边读边建堆
		int x = 0;
		if (fscanf(pf, "%d", &x) != EOF) // fscanf的用法,笔记那里没写例子!!
		{
			minheap[i] = x; 
			int j = i;
			while (minheap[j] < minheap[(j - 1) / 2]) // 向上调整
			{
				int tmp = minheap[j];
				minheap[j] = minheap[(j - 1) / 2];
				minheap[(j - 1) / 2] = tmp;
				j = (j - 1) / 2;
			}
		}
	}

	int x = 0;
	while (fscanf(pf, "%d", &x) != EOF)
	{
		if (x > minheap[0])
		{
			// 手动打个条件断点 --因为该例子中,数据太多了,一个一个调试,那得调试一辈子
			// if (x > 100000)
			// {
			//	 int condition = 0; // 因为断点不能打在空语句上,这里只是随便写一句代码,让断点打一下
			// }

			minheap[0] = x;
			// 开始向下调整
			int L = 0;
			int y = L * 2 + 1;
			while (L * 2 + 1 < k)
			{
				if (y + 1 < k && minheap[y + 1] < minheap[y])
				{
					y++;
				}
				if (minheap[L] > minheap[y])
				{
					int tmp = minheap[L];
					minheap[L] = minheap[y];
					minheap[y] = tmp;
				}
				else
				{
					break;
				}
				L = y;
				y = L * 2 + 1;
			}
		}
	}

	for (int i = 0; i < k; i++)
	{
		printf("%d ", minheap[i]);
	}
	printf("\n");

	free(minheap);
	fclose(pf);
}

int main()
{
	srand((unsigned int)time(NULL));

	// CreateData();
	int k = 0;
	printf("请输入你想获取的最大数的个数:\n");
	scanf("%d", &k);
	PrintfTopK(k);

	//int n = 10;
	//int a[10] = { 0 };
	//for (int i = 0; i < n; i++)
	//{
	//	a[i] = rand() % 100 + 1;
	//}

	//HeapSort_ascending(a, sizeof(a) / sizeof(int)); // 堆排序
	//Print(a, sizeof(a) / sizeof(int));
	//HeapSort_descending(a, sizeof(a) / sizeof(int));
	//Print(a, sizeof(a) / sizeof(int));
	
	return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

windows环境下pytorch安装踩坑

目录 1 前言2 安装Anaconda3 安装CUDA4 创建Python3.9环境5 安装Pytorch环境5.1 conda方式5.2 pip方式 6 验证是否安装成功7 注意事项7.1 no module named torch问题7.12 torch.cuda.is_available()返回False问题 8 最佳实践9 总结 1 前言 这两天由于要使用Genesis&#xff0c;…

Linux系统命令基础

Linux命令⾏ [pypylinux ~]$ 普通⽤户py&#xff0c;登陆后 [rootpylinux ~]# 超级⽤户root&#xff0c;登录后root代表当前登录的⽤户 分隔符pylinux 主机名~ 当前的登录的位置&#xff0c;此时是家⽬录# 超级⽤户身份提示符 $ 普通⽤户身份提示符操作系统⽬录分隔符 Linux目录…

PHP木马编写

一、最简单的一句话木马 <?php eval($_REQUEST[cmd]); ?> 1. <?php 和 ?> <?php 和 ?> 是 PHP 代码的开始和结束标记&#xff0c;表示 PHP 代码块的范围。 2. eval() eval() 是 PHP 中的一个内建函数&#xff0c;用来执行字符串类型的 PHP 代码。…

[Unity]【图形渲染】【游戏开发】Shader数学基础4-更多矢量运算

在计算机图形学和着色器编程中,矢量运算是核心的数学工具之一。矢量用于描述空间中的位置、方向、速度等各种物理量,并在图形变换、光照计算、纹理映射等方面起着至关重要的作用。本篇文章将详细讲解矢量和标量之间的乘法与除法、矢量的加法与减法、矢量的模与单位矢量、点积…

基于JSP动漫论坛的设计与实现【源码+文档】

目录 摘 要 Abstract 1. 绪论 1.1 课题背景 1.2 国内外现状 1.3 动漫论坛系统特点 1.4 发展前景 1.5 所做的主要工作 2. 可行性分析及需求分析 2.1 可行性分析 2.1.1 经济可行性 2.1.2 技术可行性 2.1.3 运行可行性 2.2 需求分析 2.2.1 功能需求 …

VCU--新能源汽车VCU电控开发

课程目标 信号采集的原理 使用simulink处理信号 做一个MIL仿真测试 零、参考 构建Simulink模型——CAN通信 | chans Bloggerrrrr基于Simulink实现CAN报文解析(unpack)与打包(pack)任务_RichardsZ_-开放原子开发者工作坊 一、功能概述 1.硬线信号 定义&#xff1a;通过物…

nodejs搭配express网站开发后端接口设计需要注意事项

nodejs搭配express网站开发后端接口设计需要注意事项&#xff01;为了回避一些常见的误区&#xff0c;今天和大家汇总一下&#xff0c;最近我遇到的一些错误信息&#xff0c;虽然都是小问题&#xff0c;但是还是需要分享一下&#xff0c;以免大家再次犯错。 1&#xff1a;第一个…

【时间之外】IT人求职和创业应知【71】-专利费

目录 2025 ICT产业趋势年会召开&#xff0c;2024年度ICT十大新闻重磅揭晓 海纳致远数字科技申请定制化插件驱动的数据分析专利 阿波罗智联取得语音数据的处理方法、装置、设备和存储介质专利 心勿贪&#xff0c;贵知足。 感谢所有打开这个页面的朋友。人生不如意&#xff0…

问题小记-达梦数据库报错“字符串转换出错”处理

最近遇到一个达梦数据库报错“-6111: 字符串转换出错”的问题&#xff0c;这个问题主要是涉及到一条sql语句的执行&#xff0c;在此分享下这个报错的处理过程。 问题表现为&#xff1a;一样的表结构和数据&#xff0c;执行相同的SQL&#xff0c;在Oracle数据库中执行正常&…

数据结构——队列的模拟实现

大家好&#xff0c;上一篇博客我带领大家进行了数据结构当中的栈的模拟实现 今天我将带领大家实现一个新的数据结构————队列 一&#xff1a;队列简介 首先来认识一下队列&#xff1a; 队列就像我们上学时的排队一样&#xff0c;有一个队头也有一个队尾。 有人入队的话就…

前端面试汇总(不定时更新)

目录 HTML & CSS1. XML、HTML、XHTML 有什么区别&#xff1f;⭐2. XML和JSON的区别&#xff1f;3. 是否了解W3C的规范&#xff1f;⭐4. 什么是语义化标签&#xff1f;⭐⭐5. 行内元素和块级元素的区别&#xff1f;⭐6. 行内元素和块级元素的转换&#xff1f;⭐7. 常用的块级…

在UE5中调用ImGui图形界面库

ImGui是一个小巧灵活、简洁美观的图形界面库 首先我们直接参考Github https://github.com/SLSNe/Unreal5-ImGui 把项目下载下来后 打开项目目录或者引擎目录 项目根目录/Plugins/ImGui/ 或 UE5引擎根目录/Engine/Plugins/ 如果没有Plugins文件夹就新建一个 把项目放里面…

数据结构与算法:稀疏数组

前言 此文以整型元素的二维数组为例&#xff0c;阐述稀疏数组的思想。其他类型或许有更适合压缩算法或者其他结构的稀疏数组&#xff0c;此文暂不扩展。 稀疏数组的定义 在一个二维数据数组里&#xff0c;由于大量的元素的值为同一个值&#xff0c;比如 0或者其他已知的默认值…

【蓝桥杯】43699-四平方和

四平方和 题目描述 四平方和定理&#xff0c;又称为拉格朗日定理&#xff1a; 每个正整数都可以表示为至多 4 个正整数的平方和。如果把 0 包括进去&#xff0c;就正好可以表示为 4 个数的平方和。 比如&#xff1a; 502021222 712121222; 对于一个给定的正整数&#xff0c;可…

ECharts散点图-SymbolShapeMorph,附视频讲解与代码下载

引言&#xff1a; ECharts散点图是一种常见的数据可视化图表类型&#xff0c;它通过在二维坐标系或其它坐标系中绘制散乱的点来展示数据之间的关系。本文将详细介绍如何使用ECharts库实现一个散点图&#xff0c;包括图表效果预览、视频讲解及代码下载&#xff0c;让你轻松掌握…

会话控制(cookie、session 和 token)

1. 介绍 所谓会话控制就是 对会话进行控制HTTP 是一种无状态的协议&#xff0c;它没有办法区分多次的请求是否来自于同一个客户端&#xff0c; 无法区分用户&#xff0c;而产品中又大量存在的这样的需求&#xff0c;所以我们需要通过 会话控制 来解决该问题。 常见的会话控制…

「九」HarmonyOS 5 端云一体化实战项目——「M.U.」应用云侧开发云数据库

1 立意背景 M. 代表 “我”&#xff0c;U. 代表 “你”&#xff0c;这是一款用于记录情侣从相识、相知、相恋、见家长、订婚直至结婚等各个阶段美好记忆留存的应用程序。它旨在为情侣们提供一个专属的空间&#xff0c;让他们能够将一路走来的点点滴滴&#xff0c;如初次相遇时…

双臂机器人

目录 一、双臂机器人简介 二、双臂机器人系统的组成 三、双臂机器人面临的主要挑战 3.1 协调与协同控制问题 3.2 力控制与柔顺性问题 3.3 路径规划与轨迹优化问题 3.4 感知与环境交互 3.5 人机协作问题 3.6 能源与效率问题 3.7 稳定性与可靠性问题 四、双臂机器人…

Lua语言入门 - Lua 面向对象

Lua 面向对象 面向对象编程&#xff08;Object Oriented Programming&#xff0c;OOP&#xff09;是一种非常流行的计算机编程架构&#xff0c;通过创建和操作对象来设计应用程序。 以下几种编程语言都支持面向对象编程&#xff1a; CJavaObjective-CSmalltalkC#Ruby Lua 是…

电子电器架构 ---整车区域控制器

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 所谓鸡汤,要么蛊惑你认命,要么怂恿你拼命,但都是回避问题的根源,以现象替代逻辑,以情绪代替思考,把消极接受现实的懦弱,伪装成乐观面对不幸的…