数据结构——二叉树(堆)

news2024/11/20 7:26:49

大家好我是小峰,今天我们开始学习二叉树。

首先我们来学习什么是树?

树概念及结构

树是一种 非线性 的数据结构,它是由 n n>=0 )个有限结点组成一个具有层次关系的集合。 把它叫做树是因 为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的
有一个 特殊的结点,称为根结点 ,根节点没有前驱结点
除根节点外, 其余结点被分成 M(M>0) 个互不相交的集合 T1 T2 …… Tm ,其中每一个集合 Ti(1<= i <= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有 0 个或多个后继
因此, 树是递归定义 的。
树类似于下面的结构
注意:树形结构中,子树之间不能有交集,否则就不是树形结构(这种结构叫做图)

树的相关概念

我们下面来看看树的结构于概念

节点的度 :一个节点含有的子树的个数称为该节点的度; 如上图: A 的为 6
叶节点或终端节点 :度为 0 的节点称为叶节点; 如上图: B C H I... 等节点为叶节点
非终端节点或分支节点 :度不为 0 的节点; 如上图: D E F G... 等节点为分支节点
双亲节点或父节点 :若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图: A B 的父节点
孩子节点或子节点 :一个节点含有的子树的根节点称为该节点的子节点; 如上图: B A 的孩子节点
兄弟节点 :具有相同父节点的节点互称为兄弟节点; 如上图: B C 是兄弟节点
树的度 :一棵树中,最大的节点的度称为树的度; 如上图:树的度为 6
节点的层次 :从根开始定义起,根为第 1 层,根的子节点为第 2 层,以此类推;
树的高度或深度 :树中节点的最大层次; 如上图:树的高度为 4
堂兄弟节点 :双亲在同一层的节点互为堂兄弟;如上图: H I 互为兄弟节点
节点的祖先 :从根到该节点所经分支上的所有节点;如上图: A 是所有节点的祖先
子孙 :以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是 A 的子孙
森林 :由 m m>0 )棵互不相交的树的集合称为森林;
所以我们可得出结论:树的概念是基于人类亲缘关系的出来的,我们可以根据这一点来理解记忆

树的表示

树结构相对线性表就比较复杂了,要存储表示起来就比较麻烦了, 既然保存值域,也要保存结点和结点之间 的关系 ,实际中树有很多种表示方式如:双亲表示法,孩子表示法、孩子双亲表示法以及孩子兄弟表示法等。我们这里就简单的了解其中最常用的孩子兄弟表示法
这种方式的图解如下

思路是创建一个链表结构,节点中存储第一个孩子的节点,和下一个兄弟节点,这样不论有多少分支我们都可以表示出来。

接下来我们来学习一个特殊的树。

二叉树概念及结构

一棵二叉树是结点的一个有限集合,该集合 :
1. 或者为空
2. 由一个根节点加上两棵别称为左子树和右子树的二叉树组成
1. 二叉树不存在度大于 2 的结点
2. 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树
从上图我们可以看出二叉树都是由以下这几种情况合并而成

特殊的二叉树:

1. 满二叉树 :一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K ,且结点总数是2^k-1 ,则它就是满二叉树。
2. 完全二叉树 :完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为 K的,有n 个结点的二叉树,当且仅当其每一个结点都与深度为 K 的满二叉树中编号从 1 n 的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
下面我们来看看二叉树的一些性质
对于具有 n 个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从 0 开始编号,则对
于序号为 i 的结点有: 1. i>0 i 位置节点的双亲序号: (i-1)/2 i=0 i 为根节点编号,无双亲节点
2. 2i+1<n ,左孩子序号: 2i+1 2i+1>=n 否则无左孩子
3. 2i+2<n ,右孩子序号: 2i+2 2i+2>=n 否则无右孩子

二叉树的存储结构

1. 顺序存储
顺序结构存储就是使用 数组来存储 ,一般使用数组 只适合表示完全二叉树 ,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储,关于堆我们后面的章节会专门讲解。二叉树顺 序存储在物理上是一个数组,在逻辑上是一颗二叉树。
如下图
链式存储
二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。链式结构又分为二叉链和三叉链,当前我们学习中一般都是二叉链,后面学到高阶数据结构如红黑树等会用到三叉链。

二叉树的顺序结构及实现

我们通过上面的学习是不是对二叉树有了一定的理解现在我们用顺序结构来实现一个二叉树

我们来看看二叉树顺序结构的性质

在此之前我们先来看看堆的概念和结构

堆的概念及结构
如果有一个关键码的集合 K = { , , , ,},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足: <= 且 <= ( >= 且 >= ) i = 0, 1 ,2…,则称为小堆 ( 或大堆 ) 。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
堆的性质:
堆中某个节点的值总是不大于或不小于其父节点的值;
堆总是一棵完全二叉树。

大堆的父亲总是不大于孩子,小堆的父亲总是不小于孩子

现在我们来实现一个堆(这里我们举例实现大堆

堆的实现

因为每一个堆都是完全二叉树,所以我们用数组来实现一个堆,

这里我们要明白堆的物理结构是数组而堆的逻辑结构是二叉树,要时刻记住。

首先我们还是分装三个文件

堆的实现思路主要是数组,所以我们用顺序表来实现,顺序表的玩法我们都已经很了解了,所以不用啰嗦了我们直接上代码

初始化和销毁

//初始化
void Heapinit(Heap* hp) {
	assert(hp);
	hp->capacity = 0;
	hp->ps = NULL;
	hp->sz = 0;
}


//销毁
void HeapDestory(Heap* hp) {
	assert(hp);
	free(hp->ps);
	hp->ps = NULL;
	hp->capacity = 0;
	hp->sz = 0;
}

堆的插入

对于堆的插入我们知道顺序表插入的思路是直接插入,但是我们这里是堆插入我们要满足堆的概念(父亲总是不大于或不小于孩子),所以我们插入后要进行排序,

这里我们来看看逻辑图

我们来看看代码

//堆插入
void Heapush(Heap* hp, CMMlet n) {
	assert(hp);
	if (hp->sz == hp->capacity) {
		int net = hp->ps == NULL ? 4 : hp->capacity * 2;
		CMMlet* cur = (CMMlet*)realloc(hp->ps, sizeof(CMMlet) * net);
		if (cur == NULL) {
			printf("%s", strerror(errno));
			return;
		}
		hp->ps = cur;
		hp->capacity = net;
	}
	hp->ps[hp->sz] = n;
	hp->sz++;
	//向上堆排序
	Adjustup(hp->ps, hp->sz - 1);
}

下面是向上堆排序的代码

//替换函数
tety(CMMlet* p1, CMMlet* p2) {
	CMMlet n = *p1;
	   *p1 = *p2;
	   *p2 = n;
}
//向上堆排序
//ps待排数据地址,child待排数据下标
void Adjustup(CMMlet* ps, int child) {
	assert(ps);
	int n = (child - 1) / 2;
	while (child > 0) {
		if (ps[n] < ps[child]) {
			//替换函数
			tety(&ps[n], &ps[child]);
		}
		else {
			break;
		}
		child = n;
		n = (child - 1) / 2;
	}
}

判断堆为空(为空返回true)

这个已经是老玩法了我们直接上代码

//堆的判空
bool HeapEmpty(Heap* hp) {
	assert(hp);
	return hp->sz == 0;
}

堆的删除

删除堆是删除堆顶的数据,我们想想顺序表删除表头数据采用的是覆盖删除,如果我们这里也采用覆盖删除,覆盖删除后还要对所有元素重新排序,这种方法可行但不建议,因为所需的时间复杂度太高了,

所以我们这里采用的是先将堆顶元素与最后一个元素调换,删除了最后的元素后再进行向下排序是不是要简单得多啊?这里我们只需要对一个元素排序。

排序的逻辑如下

我们来看代码

//堆的删除
void Heappop(Heap* hp) {
	assert(hp);
	assert(!HeapEmpty(hp));
	//替换
	tety(&hp->ps[0], &hp->ps[hp->sz - 1]);
	//删除
	hp->sz--;
	//向下堆排序
	Ajustdown(hp->ps, hp->sz, 0);
}

下面是向下堆排序代码

//向下堆排序
//ps排序数据地址,size排序有效数据个数,mon待排数据下标
void Ajustdown(CMMlet* ps, int size, int mon) {
	int child = mon * 2 + 1;
	while (child<size) {
		//找出最大的孩子
		if (child+1<size && ps[child] < ps[child + 1]) {
			++child;
		}
		//判断是否符合堆的结构
		if (ps[mon] < ps[child]) {
			tety(ps[mon], ps[child]);
			mon = child;
			child = mon * 2 + 1;
		}
		else {
			break;
		}
	}

}

获取堆顶数据

老玩法了我们直接上代码

//获取堆顶数据
CMMlet Heaptop(Heap* hp) {
	assert(hp);
	assert(!HeapEmpty(hp));
	return hp->ps[0];
}

获取堆的有效数据个数

关门,我们直接上代码

//获取堆有效数据个数
int Heapsize(Heap* hp) {
	assert(hp);
	return hp->sz;
}

我们来测试一下我们的堆

void csheap() {
	Heap hp;
	int add[] = { 12,34,56,78,23,31,54,76,90,1,2, };
	int a=sizeof(add) / sizeof(add[0]);
	//初始化
	Heapinit(&hp);
	//插入
	for (int i = 0; i < a; i++) {
		Heapush(&hp, add[i]);
	}

	Heappop(&hp);
	Heappop(&hp);
	Heappop(&hp);
	Heappop(&hp);
	HeapDestory(&hp);
}

int main() {
	csheap();



	return 0;
}

执行插入

执行删除

我们调试看现象我们的堆是不是建成了。

堆的应用

堆排序

从我们实现堆的思路中我们可以看出堆的主要应用是排序

总结:升序:建大堆 降序:建小堆

对于排序的思路我们主要用利用堆删除思想来进行排序,我们主要使用向下堆排序法来排序,(向上堆排序法的复杂度太高了)

我们可以用代码实现一下

//排序数据
void addpx(int* add, int mon) {
	//用向下堆排序建堆
	for (int i = (mon-1-1)/2; i >=0; --i) {
		Ajustdown(add, mon, i);
	}
	//用堆的删除思路来排序
	int n = mon;
	while (n > 0) {
		int a = add[0];
		add[0] = add[n-1];
		add[n-1] = a;
		//用向下堆排序建堆再次排序
		n--;
		Ajustdown(add, n,0);
	}
}

TOP-K问题

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

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

对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。

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

1. 用数据集合中前K个元素来建堆 前k个最大的元素,则建小堆 前k个最小的元素,则建大堆

2. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素,将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。

我们可以写一个函数来实现

下面函数是在一万个数据中找到五个最大的。

void CreateNDate()
{
	// 造数据
	int n = 10000;
	srand(time(0));
	const char* file = "data.txt";
	FILE* fin = fopen(file, "w");
	if (fin == NULL)
	{
		perror("fopen error");
		return;
	}

	for (size_t i = 0; i < n; ++i)
	{
		int x = rand() % 1000000;
		fprintf(fin, "%d\n", x);
	}

	fclose(fin);
}

void PrintTopK(int k)
{
	const char* file = "data.txt";
	FILE* fout = fopen(file, "r");
	if (fout == NULL)
	{
		perror("fopen error");
		return;
	}

	int* kminheap = (int*)malloc(sizeof(int) * k);
	if (kminheap == NULL)
	{
		perror("malloc error");
		return;
	}

	for (int i = 0; i < k; i++)
	{
		fscanf(fout, "%d", &kminheap[i]);
	}

	// 建小堆
	for (int i = (k - 1 - 1) / 2; i >= 0; i--)
	{
		Ajustdown(kminheap, k, i);
	}

	int val = 0;
	while (!feof(fout))
	{
		fscanf(fout, "%d", &val);
		if (val > kminheap[0])
		{
			kminheap[0] = val;
			Ajustdown(kminheap, k, 0);
		}
	}

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

int main()
{
	CreateNDate();
	PrintTopK(5);

	return 0;
}

大家注意我们这建的是小堆所以我们的向下堆排序函数是建大堆的所以判断条件要改一下大于小于号。

这是一个测试函数我们写了一万个数据在文件中

我们看是不是找出来了,但由于数据太多了我们也不知道找出来的数据是不是最大的五个,

我们可以验证一下,

我直接更改文件中的数据,既然不知道找出的是不是最大的那我们就手动改五个最大的数不就行了吗

我们再运行试试

是不是找到了?

不知不觉本期内容已经将近尾声了,最后大家看看标准的二叉树大家参拜参拜,愿我们的数据结构都学得炉火纯青

下面是本期全部代码大家可以参考尝试一下

Heap.h

# define _CRT_SECURE_NO_WARNINGS 1
#pragma once

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



typedef struct heap Heap;
typedef int CMMlet;

struct heap {
	CMMlet* ps;
	int sz;//顺序表的元素个数
	int capacity;//顺序表容量
};

//初始化
void Heapinit(Heap* hp);
//销毁
void HeapDestory(Heap* hp);
//堆插入
void Heapush(Heap* hp,CMMlet n);
//向上堆排序
void Adjustup(CMMlet* ps, int child);
//ps待排数据地址,child待排数据下标

//堆的判空
bool HeapEmpty(Heap* hp);


//堆的删除
void Heappop(Heap* hp);

//向下堆排序
void Ajustdown(CMMlet* ps, int size, int mon);
//ps排序数据地址,size排序有效数据个数,mon待排数据下标


//获取堆顶数据
CMMlet Heaptop(Heap* hp);

//获取堆有效数据个数
int Heapsize(Heap* hp);

Heap.c

# include "heap.h"


//初始化
void Heapinit(Heap* hp) {
	assert(hp);
	hp->capacity = 0;
	hp->ps = NULL;
	hp->sz = 0;
}


//销毁
void HeapDestory(Heap* hp) {
	assert(hp);
	free(hp->ps);
	hp->ps = NULL;
	hp->capacity = 0;
	hp->sz = 0;
}

//替换函数
tety(CMMlet* p1, CMMlet* p2) {
	CMMlet n = *p1;
	   *p1 = *p2;
	   *p2 = n;
}
//向上堆排序
//ps待排数据地址,child待排数据下标
void Adjustup(CMMlet* ps, int child) {
	assert(ps);
	int n = (child - 1) / 2;
	while (child > 0) {
		if (ps[n] < ps[child]) {
			//替换函数
			tety(&ps[n], &ps[child]);
		}
		else {
			break;
		}
		child = n;
		n = (child - 1) / 2;
	}
}
//堆插入
void Heapush(Heap* hp, CMMlet n) {
	assert(hp);
	if (hp->sz == hp->capacity) {
		int net = hp->ps == NULL ? 4 : hp->capacity * 2;
		CMMlet* cur = (CMMlet*)realloc(hp->ps, sizeof(CMMlet) * net);
		if (cur == NULL) {
			printf("%s", strerror(errno));
			return;
		}
		hp->ps = cur;
		hp->capacity = net;
	}
	hp->ps[hp->sz] = n;
	hp->sz++;
	//向上堆排序
	Adjustup(hp->ps, hp->sz - 1);
}



//堆的判空
bool HeapEmpty(Heap* hp) {
	assert(hp);
	return hp->sz == 0;
}

//向下堆排序
//ps排序数据地址,size排序有效数据个数,mon待排数据下标
void Ajustdown(CMMlet* ps, int size, int mon) {
	int child = mon * 2 + 1;
	while (child<size) {
		//找出大的孩子
		if (child+1<size && ps[child] > ps[child + 1]) {
			++child;
		}
		//判断是否符合堆的结构
		if (ps[mon] > ps[child]) {
			tety(&ps[mon], &ps[child]);
			mon = child;
			child = mon * 2 + 1;
		}
		else {
			break;
		}
	}

}
//堆的删除
void Heappop(Heap* hp) {
	assert(hp);
	assert(!HeapEmpty(hp));
	//替换
	tety(&hp->ps[0], &hp->ps[hp->sz - 1]);
	//删除
	hp->sz--;
	//向下堆排序
	Ajustdown(hp->ps, hp->sz, 0);
}


//获取堆顶数据
CMMlet Heaptop(Heap* hp) {
	assert(hp);
	assert(!HeapEmpty(hp));
	return hp->ps[0];
}

//获取堆有效数据个数
int Heapsize(Heap* hp) {
	assert(hp);
	return hp->sz;
}

test.c

# include"heap.h"



排序数据
//void addpx(int* add, int mon) {
//	//用向下堆排序建堆
//	for (int i = (mon-1-1)/2; i >=0; --i) {
//		Ajustdown(add, mon, i);
//	}
//	//用堆的删除思路来排序
//	int n = mon;
//	while (n > 0) {
//		int a = add[0];
//		add[0] = add[n-1];
//		add[n-1] = a;
//		//用向下堆排序建堆再次排序
//		n--;
//		Ajustdown(add, n,0);
//	}
//}
//void csheap() {
//	Heap hp;
//	int add[] = { 12,34,56,78,23,31,54,76,90,1,2, };
//	int a=sizeof(add) / sizeof(add[0]);
//	addpx(add, a);
//}
//
//int main() {
//	csheap();
//
//
//
//	return 0;
//}





void CreateNDate()
{
	// 造数据
	int n = 10000;
	srand(time(0));
	const char* file = "data.txt";
	FILE* fin = fopen(file, "w");
	if (fin == NULL)
	{
		perror("fopen error");
		return;
	}

	for (size_t i = 0; i < n; ++i)
	{
		int x = rand() % 1000000;
		fprintf(fin, "%d\n", x);
	}

	fclose(fin);
}

void PrintTopK(int k)
{
	const char* file = "data.txt";
	FILE* fout = fopen(file, "r");
	if (fout == NULL)
	{
		perror("fopen error");
		return;
	}

	int* kminheap = (int*)malloc(sizeof(int) * k);
	if (kminheap == NULL)
	{
		perror("malloc error");
		return;
	}

	for (int i = 0; i < k; i++)
	{
		fscanf(fout, "%d", &kminheap[i]);
	}

	// 建小堆
	for (int i = (k - 1 - 1) / 2; i >= 0; i--)
	{
		Ajustdown(kminheap, k, i);
	}

	int val = 0;
	while (!feof(fout))
	{
		fscanf(fout, "%d", &val);
		if (val > kminheap[0])
		{
			kminheap[0] = val;
			Ajustdown(kminheap, k, 0);
		}
	}

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

int main()
{
	//CreateNDate();
	PrintTopK(5);

	return 0;
}

  以上就是全部内容了,如果有错误或者不足的地方欢迎大家给予建议。 

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

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

相关文章

canal部署

定义 canal组件是一个基于mysql数据库增量日志解析&#xff0c;提供增量数据订阅和消费&#xff0c;支持将增量数据投递到下游消费者&#xff08;kafka&#xff0c;rocketmq等&#xff09;或者存储&#xff08;elasticearch,hbase等&#xff09;canal感知到mysql数据变动&…

AI学习-线性回归推导

线性回归 1.简单线性回归2.多元线性回归3.相关概念熟悉4.损失函数推导5.MSE损失函数 1.简单线性回归 ​ 线性回归&#xff1a;有监督机器学习下一种算法思想。用于预测一个或多个连续型目标变量y与数值型自变量x之间的关系,自变量x可以是连续、离散&#xff0c;但是目标变量y必…

IpcRenderer.invoke Error: An object could not be cloned.

这个错误信息提示“Uncaught (in promise) Error: An object could not be cloned.”通常发生在使用 Electron 的 IPC 通信过程中&#xff0c;尝试通过 ipcRenderer.invoke 或 ipcMain.handle 发送不能被克隆的对象时。JavaScript 中一些特殊对象或包含循环引用的对象无法通过 …

SQL server 查询数据库中所有的表名及行数

SQL server 查询数据库中所有的表名及行数 select a.name,b.rows from sysobjects as ainner join sysindexes as bon a.id b.id where (a.type u)and (b.indid in (0, 1)) and b.rows<50 and b.rows>20 order by a.name, b.rows desc;

虚拟机 ubuntu 20.04 git 设置代理的方法

前言 ubuntu 20.04 虚拟机中 Git 访问 github 或者其他的 git 仓库&#xff0c;大部分情况下速度很慢&#xff0c;并且容易掉线 如果 主机上使用了代理软件&#xff0c;但是虚拟机 ubuntu 中 Git 访问 git 仓库依旧是很慢 【问题】如何设置 虚拟机 ubuntu 的 Git 代理&#x…

C# 批量删除Excel重复项

当从不同来源导入Excel数据时&#xff0c;可能存在重复的记录。为了确保数据的准确性&#xff0c;通常需要删除这些重复的行。 手动查找并删除可能会非常耗费时间&#xff0c;而通过编程脚本则可以实现在短时间内处理大量数据。本文将提供一个使用C# 快速查找并删除Excel重复项…

Beaver Builder Pro v2.8.0.6:最佳的WordPress页面构建器插件

如果你正在寻找一个能帮助你轻松创建具有专业外观的网站的工具&#xff0c;那么Beaver Builder Pro v2.8.0.6就是你的最佳选择。这个高级WordPress插件提供了一个直观的前端可视化页面构建器&#xff0c;让你可以通过拖放元素来快速构建无限的自定义帖子和页面。 Beaver Buil…

JAVAEE之IoCDI

Spring 是⼀个 IoC&#xff08;控制反转&#xff09;容器&#xff0c;作为容器, 那么它就具备两个最基础的功能&#xff1a; • 存 • 取 Spring 容器管理的主要是对象, 这些对象, 我们称之为"Bean". 我们把这些对象交由Spring管理, 由 Spring来负责对象的创建…

Spring Boot--文件上传和下载

文件上传和下载 前言文件上传1、以MultipartFile 接口流文件&#xff0c;流的名称需要和前台传过来的名称对应上2、获取到文件名称截取后缀3、为了放置文件名重复使用uuid来随机生成id后缀4、判断转存路径中是否有这个文件夹如果没有就创建5、将文件存储到转存的目录中 文件下载…

非小米电脑下载小米电脑管家

由于 小米电脑管家 现在新增了机型验证&#xff0c;本篇将分享非小米电脑用户如何绕过机型验证安装 小米电脑管家 首先到小米跨端智联官网 https://hyperos.mi.com/continuity 中下载小米电脑管家 打开官网链接后&#xff0c;直接滑动到底部&#xff0c;点击下载 下载完成后…

C语言编写Linux的Shell外壳

目录 一、输出命令行 1.1 了解环境变量 1.2 获取用户名、主机名、当前路径 1.3 缓冲区改进MakeCommandLine 二、获取用户命令 2.1 读取函数的选择 2.2 细节优化 2.3 返回值 三、指令和选项分割 3.1 strtok 函数 3.2 分割实现 四、执行命令 4.1 fork 方法 4.2 进…

iPhone GPU性能评估:优化移动应用开发

摘要 了解你的显卡对于在电脑上玩现代图形要求高的游戏非常重要。本文介绍了如何轻松查看你的显卡型号以及为什么显卡在玩电脑游戏时如此关键。 引言 随着电脑游戏的发展&#xff0c;现代游戏对硬件性能的要求越来越高。十年前发布的显卡已经无法满足当前游戏的需求。因此&…

【前端】CSS(引入方式+选择器+常用元素属性+盒模型)

文章目录 CSS一、什么是CSS二、语法规范三、引入方式1.内部样式表2.行内样式表3.外部样式 四、选择器1.选择器的种类1.基础选择器&#xff1a;单个选择器构成的1.标签选择器2.类选择器3.id 选择器4.通配符选择器 2.复合选择器1.后代选择器2.子选择器3.并集选择器4.伪类选择器 五…

深挖苹果Find My技术,伦茨科技ST17H6x芯片赋予产品功能

苹果发布AirTag发布以来&#xff0c;大家都更加注重物品的防丢&#xff0c;苹果的 Find My 就可以查找 iPhone、Mac、AirPods、Apple Watch&#xff0c;如今的Find My已经不单单可以查找苹果的设备&#xff0c;随着第三方设备的加入&#xff0c;将丰富Find My Network的版图。产…

Redis主从复制、哨兵模式、Cluster集群

目录 一、Redis主从复制 1、主从复制介绍 2、主从复制原理 ​编辑 3、主从复制的作用 4.Redis主从复制实验搭建 1. 关闭防火墙和安装依赖环境 2. 解压安装包 3. 编译并安装到指定目录 4. 执行脚本文件 5. 做软连接 6. 启动redis并查看端口 7. 重启redis 8. 修改主…

机器学习每周挑战——信用卡申请用户数据分析

数据集的截图 # 字段 说明 # Ind_ID 客户ID # Gender 性别信息 # Car_owner 是否有车 # Propert_owner 是否有房产 # Children 子女数量 # Annual_income 年收入 # Type_Income 收入类型 # Education 教育程度 # Marital_status 婚姻状况 # Housing_type 居住…

Win11 绕过 TPM 或 CPU 检测

方法 1&#xff1a;修改注册表绕过 TPM 或 CPU 检测&#xff08;升级安装&#xff09; 如果你的硬件不完全符合安装 Windows 11 的基本硬件要求&#xff0c;可以通过修改注册表&#xff0c;在至少拥有 TPM 1.2 和不支持的 CPU 上升级安装 Windows 11 系统. 适用场景&#xff…

【Layui】------ layui实现table表格拖拽行、列位置的示例代码

一、完整的示例代码&#xff1a;&#xff08;请使用layui v2.8.3的版本&#xff09;看懂就能用、不要照搬、照搬会出错误、拷贝重要代码改改符合你自己的需求。 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><…

【图论】知识点集合

边的类型 neighbors(邻居)&#xff1a;两个顶点有一条共同边 loop&#xff1a;链接自身 link&#xff1a;两个顶点有一条边 parallel edges&#xff1a;两个顶点有两条及以上条边 无向图 必要条件&#xff1a;删掉顶点数一定大于等于剩下的顶点数 设无向图G<V,E>是…

scRNA+bulk+MR:动脉粥样硬化五个GEO数据集+GWAS,工作量十分到位

今天给大家分享一篇JCR一区&#xff0c;单细胞bulkMR的文章&#xff1a;An integrative analysis of single-cell and bulk transcriptome and bidirectional mendelian randomization analysis identified C1Q as a novel stimulated risk gene for Atherosclerosis 标题&…