NzN的数据结构--二叉树part2

news2024/11/23 4:47:49

        上一章我们介绍了二叉树入门的一些内容,本章我们就要正式开始学习二叉树的实现方法,先三连后看是好习惯!!!

目录

一、二叉树的顺序结构及实现

1. 二叉树的顺序结构

2. 堆的概念及结构

3. 堆的实现

3.1 堆的创建

3.2 堆的插入

3.3 向下调整算法

3.4 堆的删除

3.5 总体代码实现

4. 堆操作的时间复杂度

二、堆的应用

1. 堆排序

2. TOP-K问题

三、二叉树链式结构的实现

1. 前置说明

2. 二叉树的遍历

2.1 前/中/后序遍历

2.2 层序遍历 

2.3 节点个数以及深度等

3. 二叉树的销毁


一、二叉树的顺序结构及实现

1. 二叉树的顺序结构

        普通的二叉树不适合用数组来存储,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储。

2. 堆的概念及结构

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

        大堆(大根堆):根节点最大;所有父亲都≥孩子

        小堆(小根堆):根节点最小;所有父亲都≤孩子

        堆的性质:

  • 堆中某个节点的值总是≥(或≤)其父节点的值,成为大堆(或小堆)
  • 堆总是一棵完全二叉树

注意:堆在数组里不是有序的,小()堆可以帮我们找到最小()值,即找到根节点。

3. 堆的实现

3.1 堆的创建

        我们给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过算法,把它构建成一个堆。根节点左右子树不是堆,我们怎么调整呢?这里我们从倒数的第一个非叶子节点的子树开始调整,一直调整到根节点的树,就可以调整成堆。

3.2 堆的插入

        先插入一个10到数组的尾上,再进行向上调整算法,直到满足堆。

3.3 向下调整算法

        我们给出一个数组,逻辑上看做一棵完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整成一个小堆。向下调整算法有一个前提:左右子树必须是一个堆,才能调整。

3.4 堆的删除

        删除堆是删除堆顶的数据,将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行向下调整算法。

3.5 总体代码实现

        先在头文件中对相关功能接口进行声明:

typedef int HPDataType;
//数组实现
typedef struct Heap
{
	HPDataType* a;//底层就是一个数组
	int size;
	int capacity;
}HP;
//堆的初始化和销毁
void HeapInit(HP* php);
void HeapInitArray(HP* php, HPDataType* a, int n);
void HeapDestroy(HP* php);
//向上调整算法
void AdjustUp(HPDataType* a, int child);
//往堆中插入数据
void HeapPush(HP* php, HPDataType x);
//向下调整算法
void AdjustDown(HPDataType* a, int size, int parent);
//删除堆顶(根节点)
void HeapPop(HP* php);
//输出根节点
HPDataType HeapTop(HP* php);
//计算堆的大小
size_t HeapSize(HP* php);
//判断堆是否为空
bool HeapEmpty(HP* php);
//堆排序
void HeapSort(int* a, int n);
//TOP-K问题
void PrintTopK(int* a, int n, int k);

        下面是接口的具体实现:

//堆的初始化和销毁
void HeapInit(HP* php)
{
	assert(php);
	php->a = NULL;
	php->size = 0;
	php->capacity = 0;
}

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

//将交换功能封装成Swap函数,以便复用
void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

//以小堆为例
void AdjustUp(HPDataType* a, int child)
{
	int parent = (child - 1) / 2;
	//确保不会移动根节点(因为根节点的索引为0,没有父节点)
	while (child > 0)
	{
		//孩子 < 父亲,就要交换
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			child = parent;//把父亲的位置给孩子
			parent = (child - 1) / 2;//再算新的父亲
		}
		else
		{
			break;
		}
	}
}

//往堆中插入数据
void HeapPush(HP* php, HPDataType x)
{
	assert(php);
	//空间不够需要扩容
	if (php->size == php->capacity)
	{
		//初始化时capacity=0,所以要先给他4个空间
		int newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HPDataType* tmp = (HPDataType*)realloc(php->a, newCapacity * sizeof(HPDataType));
		if (tmp == NULL)
		{
			perror("realloc");
			return;
		}
		//把原来的数组和容量更新成扩容之后的
		php->a = tmp;
		php->capacity = newCapacity;
	}
	php->a[php->size] = x;//在尾部插入数据
	php->size++;//插入一个数据后,size要+1
	//插入之后可能就不是堆了,通过向上调整算法把它变成堆
	AdjustUp(php->a, php->size - 1);
}

//向下调整算法
void AdjustDown(HPDataType* a, int size, int parent)
{
	int child = parent * 2 + 1;
	while (child < size)
	{
		//默认左孩子小,假设错误就更新一下
		if (child + 1 < size && a[child + 1] < a[child])//右孩子存在&&右孩子小于左孩子
		{
			++child;//child是左孩子,+1就是右孩子
		}

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

//删除堆顶(根节点)
void HeapPop(HP* php)
{
	assert(php);
	assert(php->size > 0);//至少要有一个节点

	//把堆顶的数据跟数组的尾元素交换位置
	//此时删除堆顶就变成了删除数组的尾元素
	Swap(&php->a[0], &php->a[php->size - 1]);
	php->size--;
	//删除操作就是控制size来完成的
	//其实我们并不是把这个数据删除,而是把他反复在最后就不用管它了
	//size-1后,想要访问这个数据就变成了数组越界访问
	AdjustDown(php->a, php->size, 0);
}

//输出根节点
HPDataType HeapTop(HP* php)
{
	assert(php);
	assert(php->size > 0);
	return php->a[0];
}

//计算堆的大小
size_t HeapSize(HP* php)
{
	assert(php);
	return php->size;
}

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

4. 堆操作的时间复杂度

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

       插入数据时间消耗主要在向上调整部分,最好情况就是不进行调整,最差情况就是调整树的高度次,因此插入数据的时间复杂度为

       删除数据时间消耗主要在向下调整部分,最好情况就是不进行调整,最差情况就是调整树的高度次,因此删除数据的时间复杂度为

//按照数组来初始化
void HeapInitArray(HP* php, HPDataType* a, int n)
{
	assert(php);
	php->a = (HPDataType*)malloc(sizeof(HPDataType) * n);
	if (php->a == NULL)
	{
		perror("realloc");
		return;
	}
	memcpy(php->a, a, sizeof(HPDataType) * n);//把数据放到堆上
	php->size = n;
	php->capacity = n;

	//建堆

	//模拟向上调整,理解为数据插入,时间复杂度O(N*logN)
	//for (int i = 1; i < php->size; i++)
	//{
	//	AdjustUp(php->a, i);
	//}
	//第二层最多向上调整1次,第三层最多向上调整2次……,最后一层最多向上调整h-1次
	//假设累计向上调整F(h)次,F(h)=2^1*1+2^2*2+…+2^(h-1)*(h-1)=2^h*(h-2)+2
	//把F(h)转换成F(N),h=log(N+1),因此F(N)=(N+1)*(log(N+1)-2)+2≈N*logN


	//模拟向下调整,时间复杂度O(N)
	//从倒数的第一个非叶子结点开始调整(最后一个节点的父亲),倒着往上调
	//最后一个节点的下标是php->size - 1,i求他的父亲然后开始倒着调整
	for (int i = (php->size - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(php->a, php->size, i);
	}
	//假设累计向下调整F(h)次,F(h)=2^(h-2)*1+2^(h-3)*2+…+2^0*(h-1)
	//F(N)=N-log(N+1)≈N
}

        对于向上调整,满二叉树最后一层结点数占整个树的一半,结点数量多的层,调整次数也多。而向下调整正好相反,结点数量多的调整次数少。因此两种方法时间复杂度相差较大,我们优先选择向下调整建堆。

二、堆的应用

1. 堆排序

void HeapSort(int* a, int n) 
{
	HP hp;
	HeapInitArray(&hp, a, n);
	int i = 0;
	while (!HeapEmpty(&hp))
	{
		a[i++] = HeapTop(&hp);//将堆顶赋给a[0]
		HeapPop(&hp);//删除堆顶元素,这样堆中第二大/小的元素就会被移动到堆顶
	}
	HeapDestroy(&hp);
}

        上面这种写法有两大缺陷,首先就是进行堆排序之前需要先创建堆的各种相关算法,其次就是空间复杂度为

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

        [1] 建堆

  •         升序:建大堆
  •         降序:建小堆

       [2]  利用堆删除思想来进行排序

        建堆和堆删除中都用到了向下调整,因此通过向下调整,就可以完成堆排序。

//大堆的向下调整算法
void AdjustDown(HPDataType* a, int size, int parent)
{
	int child = parent * 2 + 1;
	while (child < size)
	{
		//默认左孩子大,假设错误就更新一下  
		if (child + 1 < size && a[child] < a[child + 1]) //右孩子存在且右孩子大于左孩子  
		{
			++child; // child原本是左孩子,+1变成右孩子  
		}

		//如果子节点大于父节点则交换 
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

//升序建大堆,降序建小堆
//建小堆如果想要升序,堆顶元素在对应位置,剩余元素重新建小堆,时间复杂度大大增加
void HeapSort(int* a, int n)
{
	//对数组直接建堆
	for (int i = (n - 1 - 1) / 2; i >= 0; --i)
	{
		AdjustDown(a, n, i);
	}

	// O(N*logN)
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);//上面建的大堆,根节点最大,把大的数据往后放
		AdjustDown(a, end, 0);//新的根节点可能会破坏大堆性质,需要向下调整
		end--;
	}
}

 注意:我们需要根据升序或降序的要求,对向下调整算法进行修改。

2. TOP-K问题

        TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素。

        对于Top-K问题,最容易想到的方法就是排序,但是如果数据量非常大,排序就不可取了(可能数据不能一口气全部加载到内存中)。

        最佳的方式就是用堆来解决,基本思路如下:

        [1] 用数据集合中前K个元素来建堆

  •         求前k个最大的元素,则建小堆
  •         求前k个最小的元素,则建大堆

        [2] 用剩余的N-K个元素依次与堆顶比较,不满足则替换堆顶元素

        将剩余N-K个元素依次与堆顶元素比较之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。

        这种方法的时间复杂度是

//以求前K个最大的数据为例,则需要建小堆
void PrintTopK(int* a, int n, int k)
{
	HP php;
	HeapInit(&php);
	//数组中前k个元素来建堆
	for (int i = 0; i < k; ++i) 
	{
		HeapPush(&php, a[i]);
	}
	//用剩余的n-k个元素依次与堆顶比较
	for (int i = k; i < n; ++i) 
	{
		if (a[i] > HeapTop(&php)) //当前元素>堆顶,就要让他进堆
		{ 
			HeapPop(&php); //移除当前堆顶
			HeapPush(&php, a[i]); //将新的元素插入堆中
		}
	}
	for (int i = 0; i < k; ++i) {
		printf("%d ", php.a[i]);
	}
	printf("\n");
	HeapDestroy(&php);
}

三、二叉树链式结构的实现

1. 前置说明

        在学习二叉树的基本操作前,需先创建一棵二叉树,然后才能学习其相关操作。由于现在对二叉树结构掌握还不够深入,因此在此处手动快速创建一棵简单的二叉树,快速学习二叉树操作,等后面再来研究二叉树真正的创建方式。

        回顾下二叉树的概念,二叉树是:

  • 空树
  • 非空:根节点,根节点的左子树、根节点的右子树组成的

        从概念中可以看出,二叉树是递归定义的,因此后序基本操作中基本都是按照该概念实现的。

2. 二叉树的遍历

2.1 前/中/后序遍历

        学习二叉树结构,最简单的方式就是遍历。所谓二叉树遍历(Traversal)是按照某种特定的规则,依次对二叉树中的节点进行相应的操作,并且每个节点只操作一次。访问结点所做的操作依赖于具体的应用问题。 遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。

        按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历:

  • 前序遍历(先序遍历):根 左子树 右子树。
  • 中序遍历:左子树 根 右子树。
  • 后序遍历:左子树 右子树 根。
typedef struct BinTreeNode 
{
	struct BinTreeNode* left;
	struct BinTreeNode* right;
	int val;
}BTNode;

//前序遍历
void PreOrder(BTNode* root)
{
	//树为空
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	//树不为空,按照 根 左子树 右子树 的顺序递归打印
	printf("%d ", root->val);
	PreOrder(root->left);
	PreOrder(root->right);
}

//中序遍历
void InOrder(BTNode* root)
{
	//树为空
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	//树不为空,按照 左子树 根 右子树 的顺序递归打印
	InOrder(root->left);
	printf("%d ", root->val);
	InOrder(root->right);
}

//后序遍历
void PostOrder(BTNode* root)
{
	//树为空
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	//树不为空,按照 左子树 右子树 根 的顺序递归打印
	PostOrder(root->left);
	PostOrder(root->right);
	printf("%d ", root->val);
}

        前序遍历递归图解:

2.2 层序遍历 

        自上而下,自左至右逐层访问树的结点的过程就是层序遍历。

       层序遍历的实现需要借助队列(先进先出),实现思想是上一层带下一层。

void LevelOrder(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	//先入根节点
	if (root)
		QueuePush(&q, root);

	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		printf("%d ", front->val);
		//带入下一层
		if (front->left)
			QueuePush(&q, front->left);

		if (front->right)
			QueuePush(&q, front->right);
	}
	printf("\n");
	QueueDestroy(&q);
}

2.3 节点个数以及深度等

//求树的结点个数
static int size = 0;//定义成全局变量,每次调用前初始化为0
int TreeSize(BTNode* root)
{

	if (root == NULL)
		return 0;
	else
		++size;
	TreeSize(root->left);
	TreeSize(root->right);
	return size;
}

       但是这种写法会出现线程安全的风险,可以改成给函数传size的指针或者依靠分治递归思想(左子树返回的结点个数+右子树返回的结点个数+1,通过后序遍历)。

//求树的结点个数
int TreeSize(BTNode* root)
{
	return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}
//求树的深度
int TreeDepth(BTNode* root)
{
	if (root == NULL)
		return 0;
	//左子树和右子树分别计算深度,取最大的深度+1(根节点)
	int leftDepth = TreeDepth(root->left);
	int rightDepth = TreeDepth(root->right);
	return leftDepth > rightDepth ? leftDepth + 1 : rightDepth + 1;
}
int TreeDepth(BTNode* root)
{
	if (root == NULL)
		return 0;
	return TreeDepth(root->left) > TreeDepth(root->right) ? TreeDepth(root->left) + 1 : TreeDepth(root->right) + 1;
	//这种写法看似与上面的写法相差不大,但实际上这个写法的时间复杂度大打折扣
	//通过leftDepth和rightDepth会记录左右子树的深度,return时不需要再次计算,时间复杂度为O(N)
	//而这种写法每次使用TreeDepth(root->left)或TreeDepth(root->right)都需要重新计算一次
	//时间复杂度是一个等比数列和,根节点的找次其实是第二层节点找2次,是第三层找4次……
	//最后算到时间复杂度为O(2^N)
}
//求第k层的节点个数
//当前树的第k层节点个数=左子树的第k-1层节点个数+右子树的第k-1层节点个数
int TreeKLevel(BTNode* root, int k)
{
	assert(k > 0);
	if (root == NULL)
		return 0;
	//只有根节点
	if (k == 1)
		return 1;
	//k>1,说明第k层在子树里
	return TreeKLevel(root->left, k - 1) + TreeKLevel(root->right, k - 1);
}
//查找x所在的节点(找到1个就返回)
BTNode* TreeFind(BTNode* root, int x)
{
	if (root == NULL)
		return NULL;
	if (root->val == x)
		return root;
	//先在左子树找,没找到再去右子树找
	//如果要找两次,推荐用变量来接收
	BTNode* ret1 = TreeFind(root->left, x);
	if (ret1)
		return ret1;
	BTNode* ret2 = TreeFind(root->right, x);
	if (ret2)
		return ret2;
	return NULL;
}

3. 二叉树的销毁

void TreeDestroy(BTNode* root)
{
	if (root == NULL)
		return;
	TreeDestroy(root->left);
	TreeDestroy(root->right);
	free(root);
	//root = NULL;//这里置空对形参修改,实参不改变
	//但是想要修改root就需要二级指针,非常麻烦
	//因此可以调用完TreeDestroy函数后,手动将root置为NULL
}

        以上就是本文的全部内容,这个地方非常难理解,大家要注意看每段代码的注释部分,后面需要勤加练习,熟练掌握这些算法!!

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

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

相关文章

04-12 周五基于VS code + Python实现CSDN发布文章的自动生成

简介 之前曾经说过&#xff0c;在撰写文章之后&#xff0c;需要&#xff0c;同样需要将外链的图像转换为的形式&#xff0c;因此&#xff0c;可以参考 04-12 周五 基于VS Code Python 实现单词的自动提取 配置步骤 配置task 在vscode的命令面板configure task。配置如下的任…

python知识点汇总(十一)

python知识点总结 1、当Python退出时&#xff0c;是否会清除所有分配的内存&#xff1f;2、Python的优势有哪些&#xff1f;3、什么是元组的解封装4、Python中如何动态获取和设置对象的属性&#xff1f;5、创建删除操作系统上的文件6、主动抛出异常7、help() 函数和 dir() 函数…

人工智能基础部分26-基于知识推理引擎KIE算法的原理介绍与实际应用

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下人工智能基础部分26-基于知识推理引擎KIE算法的原理介绍与实际应用。知识推理引擎(Knowledge Inference Engine, KIE)是一种人工智能技术&#xff0c;其核心原理是基于规则和逻辑的方法来处理复杂的问题。它构建在业…

从前端角度防范XSS攻击的策略与实践

XSS&#xff08;Cross-Site Scripting&#xff0c;跨站脚本攻击&#xff09;是一种常见的网络安全威胁&#xff0c;它允许攻击者将恶意脚本注入到正常的网页中&#xff0c;从而在其他用户的浏览器上执行这些脚本。这可能导致数据泄露、会话劫持、甚至是对受害者计算机的完全控制…

探新路建“枢纽” 湖南深耕中非经贸合作“试验田”

湖南作为中国与非洲经贸合作的重要窗口&#xff0c;积极推动中非经贸关系的发展和深化。通过构建覆盖全产业链的高效运作模式&#xff0c;湖南企业能够在一周内将肯尼亚干制鳀鱼加工成为麻辣鲜香的劲仔深海小鱼并投入中国市场。此外&#xff0c;湖南还致力于推动非洲优质农产品…

【vue】watchEffect 自动侦听器

watchEffect&#xff1a;自动监听值的变化 获取旧值时&#xff0c;不是很方便&#xff0c;建议用watch <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevic…

数学基础:深度学习的语言

数学基础&#xff1a;深度学习的语言 概述 在深度学习的世界里&#xff0c;数学不仅仅是一套工具&#xff0c;它是构建、理解和优化深度学习模型的基石。从向量空间的概念到复杂的优化算法&#xff0c;数学的每一个分支都在深度学习的发展中扮演着关键角色。本文的目标是通过深…

解决cmd输入py文件路径不能执行,使用anaconda prompt 能执行

究其原因&#xff0c;是因为没有配置环境&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 第一步&#xff1a;配置环境变量 操作步骤如下&#xff1a; 1、右击此电脑 ---->属性 2、高级系统设置 3、点击环境变量 4、选择 …

网工内推 | 网络工程师,13薪,周末双休,华三、华为认证优先

01 路邦远大 招聘岗位&#xff1a;网络工程师 职责描述&#xff1a; 1、配合市场销售人员&#xff0c;做好产品的售后服务工作&#xff1b; 2、负责项目方案安装调试指导以及日常客户使用培训&#xff0c;对客户提出的问题提出解决方案&#xff1b; 3、为客户提供专业、规范的…

简析OpenHarmony软总线能力

分布式软总线是 OpenHarmony 的重要能力&#xff0c;设计目标是实现多设备间的通信方式。分布式软总线是分布式硬件和分布式软总线的重要基础&#xff0c;分布式软总线提供一种不区分链路的设备间发现、组网和传输的能力&#xff1a; 发现&#xff1a;应用 WiFi&#xff0c;蓝…

Baichuan-7B-chat WebDemo 部署调用

Baichuan-7B-chat WebDemo 部署调用 Baichuan2 介绍 Baichuan 2 是百川智能推出的新一代开源大语言模型&#xff0c;采用 2.6 万亿 Tokens 的高质量语料训练。在多个权威的中文、英文和多语言的通用、领域 benchmark 上取得同尺寸最佳的效果。 环境准备 在autodl平台中租一…

高并发高性能接口中,异步打印并采集业务日志的实现方案

一、背景 高并发接口中&#xff0c;为了提高接口的高性能&#xff0c;在需要保存审计及操作记录的时候&#xff0c;往往有以下常见方案&#xff1a; 保存到redis数据库异步保存到mysql/mongodb/es等数据库logger打印业务日志&#xff0c;采集与展示则交由elk模块 对于第一种…

【MATLAB源码-第4期】基于MATLAB的1024QAM误码率曲线,以及星座图展示。

1、算法描述 正交幅度调制&#xff08;QAM&#xff0c;Quadrature Amplitude Modulation&#xff09;是一种在两个正交载波上进行幅度调制的调制方式。这两个载波通常是相位差为90度&#xff08;π/2&#xff09;的正弦波&#xff0c;因此被称作正交载波。这种调制方式因此而得…

福建单航次最大批量汽车“出海”

3月12日这一天&#xff0c;在福州海关的严密监管下&#xff0c;共有4000辆上汽名爵品牌的汽车被高效有序地装载到“安吉智慧”号滚装船上&#xff0c;这批车辆即将启程前往荷兰、埃及、英国等多个海外市场。在这批出口汽车中&#xff0c;新能源车型占据了显著的比例&#xff0c…

FFmpeg 框架分析

1 概述 按照DirectShow 对播放器模块的划分&#xff0c;一个完整的播放器应该需要获得以下四个模块支持&#xff1a; Source Filter : 数据源&#xff0c;可以是本地文件fopen, 也可以是网络文件,http,rtp,rtmp 等等 Demux Fliter: 解复用&#xff0c;下载的数据是带容器封装…

Java | Leetcode Java题解之第18题四数之和

题目&#xff1a; 题解&#xff1a; class Solution {public List<List<Integer>> fourSum(int[] nums, int target) {List<List<Integer>> quadruplets new ArrayList<List<Integer>>();if (nums null || nums.length < 4) {return…

循环单链表算法库

学习贺老师数据结构 数据结构之自建算法库——循环单链表_循环单链表 csdn-CSDN博客​​​​​​ 整理总结出的循环单链表算法库 v1.0 : 基本实现功能 v2.0(2024.4.6): 修复Delete_SpecificLocate_CyclicList()删除节点函数bug,添加验证删除节点是否超范围判断 目录 1.主要功能…

CSS设置文本

目录 概述&#xff1a; text-aling: text-decoration: text-transform: text-indent: line-height: letter-spacing: word-spacing: text-shadow: vertical-align: white-space: direction: 概述&#xff1a; 在CSS中我们可以设置文本的属性&#xff0c;就像Word文…

测开面经(Git经典题目,Git入门)

1. GitHub是什么 a. Git是一个分布式版本控制系统&#xff0c;作用是跟踪、管理和协调软件开发项目中的代码更改。 b. 提供了一种有效的方式来管理代码的版本历史&#xff0c;以及多人协作开发的能力。 2. Git的作用有哪些 a. 版本控制&#xff1a;Git可以记录每次代码更改的…

Linux的学习之路:8、Linux调试器-gdb使用

摘要 本章主要是说一下gdb的使用&#xff0c;以及把使用指令放入放个指令手册。 目录 摘要 一、背景 二、使用 1、产生debug文件 2、进入gdb 3、使用指令 三、思维导图 一、背景 Linux调试器gdb的背景主要涉及到Linux程序发布方式和调试需求。 在Linux中&#xff0c…