二叉树知识小结

news2024/9/28 3:19:20

思维导图:

一,树

树,这是一种对计算机里的某种数据结构的形象比喻。比如这种:

这种:

这种:

这几种都是树形结构。在百度百科中对树形结构的定义如下:

树形结构指的是数据元素之间存在着“一对多”的树形关系的数据结构,是一类重要的非线性数据结构。

在树形结构中,树根结点没有前驱结点,其余每个结点有且只有一个前驱结点。叶子结点没有后续结点,其余每个结点的后续节点数可以是一个也可以是多个。

二,树形结构的相关概念

 1.根:根节点是每一个树节点的祖宗。所以这个节点是没有父节点的。这个节点就是树型结构最上面的节点。比如:

2.度:在一个树形结构里的一个节点含有多少个子节点那这个节点的度便是多少。一棵树的度是节点最大的度。比如:

 这棵树的度分别是3,2,1,0。这棵树中最大的度便是3,所以这棵树的度便是3。

 除了这两个概念以外,关于树形结构的概念还有很多我在这里就不一一列举了。

三,树型结构的结构定义

我们知道了树形结构是什么这就够了吗?显然不是,我们知道了这种结构以后我们便要自己能够定义这种结构来给自己所用。但是树形结构如此复杂我们该如何对这些树的节点的结构进行定义呢?在这里有四种方式:

1.明确了树的度:

比如这棵树的度为3,那我们便定义节点的结构为:

typedef NodeType int;
struct Node
{
  struct Node* first;
  struct Node* second;
  struct Node* third; 
  NodeType val;
};

​

因为树的度是3,所以节点的度的最大值便是三。定义一个在这样的结构绝对可以满足任何一个节点的需求。

2.用顺序表储存孩子

这种方法我还没有试过,感兴趣的小伙伴可以自己捣鼓一下。

3.双亲表示法

只存父亲的下标而对孩子不管不问:

typedef NodeType int;
structur Node
{
  int parenti;
  NodeType val;
}

​

4.左孩子右兄弟表示法

只存第一个孩子然后通过兄弟节点来找到每一个子节点。

typedef NodeType int;
struct Node{
  struct Node* firstChild;
  struct Node* brother;
  NodeType val;
};

这个结构是如何工作的呢?以下面的树形结构为例:

用左孩子右兄弟表示法便是这样的:

通过这个结构便可以拿捏任何一个树形结构。这个结构也是一个定义树形结构的王者结构。

三,二叉树

都说二叉树二叉树的,那什么是二叉树呢?

1.二叉树首先是一种树形结构。

2.其次二叉树满足二叉树的度小于等于2。

例如以下便是二叉树:

 可以看到这几个树形结构的度都是小于等于2的。在这几棵树中后两颗树即3,4棵树是二叉树中特殊的两棵树:1.满二叉树,2.完全二叉树。

满二叉树定义:满二叉树是每一层都是满了的二叉树,也就是每一层的节点个数都是2^(h-1)个

完全二叉树的定义:完全二叉树是前h-1层是满的,最后一层是连续的二叉树树。

例如:

在这个图中,第一棵是完全二叉树,第二棵就不是完全二叉树。因为第二棵的最后一层不是连续的(先NULL再有节点) 。

3.2堆

在明白了完全二叉树与满二叉树的区别以后,便可以来看一种叫做堆的数据结构。这种数据结构物理结构上便是一种数组但在逻辑上便可以看做是一种完全二叉树。

堆的分类:

1.小堆:父节点的值小于子节点的值的堆

2.大堆:父节点的值大于子节点的堆

堆的定义:

根据堆的本质以及堆的分类我们便可以得到堆的定义:

堆是一种父节点与子节点有序的完全二叉树。

堆的建立:

1. 堆的结构:

typedef int HeapType ;//数组类型
typedef struct HeapNode {
	HeapType* a;//定义数组
	int size;//当前的数组长度
	int capacity;//数组容量
}HeapNode;

2.堆的初始化

void HeapInit(HeapNode* heap)
{
	assert(heap);//断言,防止传入NULL
	heap->a = NULL;
	heap->size = 0;
	heap->capacity = 0;
}

3。堆的数据插入

void HeapPush(HeapNode* heap,HeapType x)
{
	assert(heap);
	if (heap->size == heap->capacity)
	{
		int newcapacity = heap->capacity == 0 ? 4 : 2 * heap->capacity;
		heap->a = (HeapType*)realloc(heap->a,newcapacity*sizeof(HeapType));
		if (heap->a == NULL)
		{
			perror("realloc fail");
			return;
		}
		heap->capacity = newcapacity;
   }
		heap->a[heap->size] = x;
		heap->size++;
}

使用这段代码插入如下数据:

5,9,8,1,7,2

得到heap:

很明显,这不是一个堆。所以我们要对这个结构的数值进行调整。

4.向上调整算法:

void swap(HeapType* p1, HeapType* p2)
{
	HeapType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
void AdjustUp(HeapType* 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;
	}
}

将这个算法加入HeapPush中:

void HeapPush(HeapNode* heap,HeapType x)
{
	assert(heap);
	if (heap->size == heap->capacity)
	{
		int newcapacity = heap->capacity == 0 ? 4 : 2 * heap->capacity;
		heap->a = (HeapType*)realloc(heap->a,newcapacity*sizeof(HeapType));
		if (heap->a == NULL)
		{
			perror("realloc fail");
			return;
		}
		heap->capacity = newcapacity;
   }
		heap->a[heap->size] = x;
		AdjustUp(heap->a, heap->size);//加入向上调整算法
		heap->size++;
}

效果:

建堆完成。并且是个小堆,想要建立大堆改一下条件便可。

5.堆的删除操作

这里需要注意的一点是堆的删除操作可不是删除堆的最后一个数据,相反这个操作要删除的是堆堆顶元素。但是直接将堆顶元素删除的话是很难办的。所以这里采取这样的思路:

1.将堆顶元素与最后一个元素交换,然后删除最后一个元素

2.交换后堆将不堆(堆可能就不是堆)所以要实现一个向下调整算法调整建堆

代码:

bool Empty(HeapNode* heap)//判空函数
{
	return heap->size == 0;
}
void AdjustDown(HeapNode* heap, int n, int parent)//向下调整函数
{
	int child = 2 * parent + 1;
	while (child < n)
	{
		if (child+1<n&&heap->a[child + 1] < heap->a[child])
		{
			child++;
		}
		if (heap->a[child] < heap->a[parent])
		{
			swap(&heap->a[child], &heap->a[parent]);
			parent = child;
			child = 2 * parent + 1;
		}
		else
		{
			break;
		}

	}

}
void HeapPop(HeapNode* heap)
{
	assert(heap);//断言防止传入NULL
	assert(!Empty(heap));//当堆是NULL时不能删
	swap(&heap->a[0], &heap->a[heap->size - 1]);//交换首尾元素
	heap->size--;//删除最后一个元素
	AdjustDown(heap,heap->size, 0);
}

 6.取堆顶元素

代码:

HeapType HeapTop(HeapNode* heap)
{
	assert(heap);
	HeapType top = heap->a[0];
	HeapPop(heap);
	return top;
}

7.销毁堆

void HeapDestory(HeapNode* heap)
{
	assert(heap);
	free(heap->a);
	heap->a = NULL;
	heap->size = heap->capacity = 0;
}

堆排序

在堆中有一个算法是非常厉害的,那就是堆排序。堆排序的思路:

1.建堆:这里的建堆有两种方式,分别是向上调整建堆以及向下调整建堆。建堆的方式的不同导致算法的时间复杂度的不同。排不同的序建的堆也不同:1.排升序建大堆。2.排降序建小堆。

2.排序:在将堆建完以后,我们便要对堆内的元素进行排序了。这里要用的是向下调整算法来排序交换。

1.建堆

void HeapSort(HeapType* a,int n)
{
	assert(a);
	for (int i = n - 1;i > 0;i--)//向上调整建堆
	{
		AdjustUp(a, i);
	}

	for (int i =(n-1-1)/2;i >=0;i--)//向下调整建堆
	{
		AdjustDown(a,n ,i);
	}

}

两种建堆方法的时间复杂度: 

向上调整建堆:

设有一个满二叉树的堆,层数为h:

当情况在最坏时,每一层的节点都需要向上调整h-1次。即:

那调整的总次数就是每一层的节点个数(2^(h-1)个)乘上每个节点的调整次数的和:

F(h) = 2^0*0+2^1*1+2^2* 2+2^3*3+……2^(h-2)*(h-2)+2^(h-1)*(h-1) = 2^h*(h-2)+2

设节点的总个数为N,所以N = 2^h-1。所以h = log(N+1)//底数为2.将h = log(N+1)代入F(h) = 2^H*(h-2)+2中得到:F(h) = (N+1)*(logN-1)+2 。所以向上调整建堆的时间复杂度为O(N*logN)。

 向下调整建堆:

满二叉树的堆,层数为h:

 当情况最坏时,每层调整的次数(最后一层不用调整,一个节点天生就是堆):

调整的总次数为:

F(h) = 2^0*(h-1)+2^1*(h-2)+2^3*(h-3)+……+2^(h-2)*1+2^(h-1)*0 = 2^h-h-1

令N为节点的总数,则h = 1og(N+1),F(h) = N-h。所以向下调整的时间复杂度为O(N)

通过以上分析可知,向下调整建堆的效率会比向上调整建堆的方式好很多。所以我们一般采取向下调整建堆。 

 排序:

要记住排升序建大堆,排降序建小堆。

在这里排序的步骤有两个:

1.找到首尾位置

2.交换首尾的值

3.向下调整

代码:

void HeapSort(HeapType* a,int n)
{
	assert(a);
	//for (int i = n - 1;i > 0;i--)//向上调整建堆
	//{
	//	AdjustUp(a, i);
	//}
	for (int i =(n-1-1)/2;i >=0;i--)//向下调整建堆
	{
		AdjustDown(a,n ,i);
	}
	int end = n - 1;//初始末位置,首位置都是下标为0的位置
	while (end > 0)
	{
		swap(&a[end], &a[0]);//交换首尾
		AdjustDown(a, end, 0);//向下调整
		end--;//重新找末位置
	}
}

TopK算法

TopK算法的用处就在于利用堆来从一堆的大数据中找到前K大的或者前K小的数据。TopK算法的精髓在于:

1.创建一个能够装的下K个数据的堆,这K个数据的初始值是全部数据的前K个。

注意点:要找到前K大的数需要建立的是小堆,要找到前K小的数据需要建大堆。

2.比较替换:用后面的N-K个数据分别与堆的第一个元素比较,满足条件时(大于或小于堆顶元素)替换进堆。

3.对新建立的堆进行向下调整。

代码:

首先创建数据:
void CreatData()
{
	srand(time(0));//随机数种子
	int n = 10000;
	const char* file = "data.txt";
	FILE* fin = fopen(file, "w");
	if (fin == NULL)
	{
		perror("fopen fail");
		return;
	}
	for (int i = 0;i < n;i++)
	{
		int val = rand() % 10000;//在文件中写入的是随机的数据
		fprintf(fin, "%d\n", val);
	}
	fclose(fin);

}

 找前K大的数据:

void TopK(int k)
{
	const char* file = "data.txt";
	FILE* fout = fopen(file, "r");//打开文件

	int* a = (int*)malloc(sizeof(int) * k);//建立能够容纳K个数据的数组

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

	AdjustDown(a, k, 0);//将数组变成小堆
	int val = 0;

	while (!feof(fout))//当feof没有读到末尾时返回0,读到末尾时返回EOF(-1)
	{
		fscanf(fout, "%d", &val);
		if (val > a[0])
		{
			swap(&val, &a[0]);//交换末尾数据
			AdjustDown(a, k, 0);
		}
	}

	fclose(fout);//关闭文件

	for (int i = 0;i < k;i++)//答应前K大的数据
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}

二叉树的创建以及各项操作

二叉树的创建

要进行二叉树的操作的讲解就要来创建一棵二叉树。于是我们的第一步便是创建一颗二叉树,而这棵二叉树是一棵链式二叉树。

节点的结构定义代码:

typedef int NodeType;
 typedef struct BTreeNode {
	 struct BTreeNode* left;
	 struct BTreeNode* right;
	 NodeType val;
}BTreeNode;

代码:

BTreeNode* BuyNode(NodeType x)//创建二叉树的节点
{
	struct BTreeNode* newnode = (struct BTreeNode*)malloc(sizeof(struct BTreeNode));
	if (newnode == NULL)
	{
		perror("malloc fail\n");
		return NULL;
	}
	newnode->val = x;
	newnode->left = NULL;
	newnode->right = NULL;

	return newnode;
}
int main()
{
	BTreeNode* n1 = BuyNode(1);
	BTreeNode* n2 = BuyNode(2);
	BTreeNode* n3 = BuyNode(3);
	BTreeNode* n4 = BuyNode(4);
	BTreeNode* n5 = BuyNode(5);
	BTreeNode* n6 = BuyNode(6);
	BTreeNode* n7 = BuyNode(7);


	n1->left = n2;
	n1->right = n3;
	n2->left = n4;
	n4->right = n5;
	n3->left = n6;
	n3->right = n7;

	return 0;
}

在经过以上操作以后我们便可以得到一棵树,这棵树便长这样:

接下来我们便要来进行有关于这棵二叉树的各项操作。 

1.遍历:

 二叉树的便历有三种形式,第一种便是前序遍历,第二种是中序遍历,最后一种是后序遍历。

1.前序遍历:根->左->右

2.中序遍历:左->根->右

3.后序遍历:左->右->根

按照这个顺序写成代码便是下面这样:

1.前序遍历:

void InOrder(BTreeNode* root)
{
	if (root == NULL)
	{
		printf("N->");//遍历到空树时答应N
		return NULL;
	}

	PreOrder(root->left);//先遍历左子树
	printf("%d->", root->val);//再将根节点遍历打印
	PreOrder(root->right);//最后遍历右子树
}

2.中序遍历:

void InOrder(BTreeNode* root)
{
	if (root == NULL)
	{
		printf("N->");//遍历到空树时答应N
		return NULL;
	}
    InOrder(root->left);//先遍历左子树
	printf("%d->", root->val);//再将根节点遍历打印
	InOrder(root->right);//最后遍历右子树
}

3.后序遍历:

void PostOrder(BTreeNode* root)
{
	if (root == NULL)
	{
		printf("N->");//遍历到空树时答应N
		return NULL;
	}

	PostOrder(root->left);//先遍历左子树
	PostOrder(root->right);//再遍历右子树
	printf("%d->", root->val);//最后将根节点遍历打印
}

2.计算节点个数

 计算节点的个数的方法有两种。一种是计数法(也要用到递归),另一种是递归法。鉴于计数法太挫,所以在这里只展示递归法。

int BTNodeSize(BTreeNode* root)
{
	if (root == NULL)
	{
		return 0;
	}

	return BTNodeSize(root->left) + //计算左子树和右子树的节点的和再加上自己
		BTNodeSize(root->right) + 1;
}

 这段代码是如何执行的呢?

画图便可知晓:递归过程逻辑图

3.计算叶子节点的个数

要计算叶子节点的个数就必须要知道什么是叶子节点。叶子节点就是左子树和右子树为NULL的节点。所以遇到左孩子和有孩子为NULL的节点时这个节点便是叶子节点,叶子节点的个数便可以加1。

代码:

int BTLeafSize(BTreeNode* root)
{
	if (root == NULL)
	{
		return 0;
	}

	if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}

	return BTLeafSize(root->left) + BTLeafSize(root->right);
}

 4.计算二叉树的深度

计算二叉树的深度便要涉及到一个比较。因为二叉树的深度只能是左右子树中较高的那一棵。而涉及到比较,就要涉及到记录,记录了才能提高效率。

代码:

int BTHeight(BTreeNode* root)
{
	if (root == NULL)
	{
		return 0;
	}

	int leftHeight = BTHeight(root->left);//记录左子树的高度
	int rightHeight = BTHeight(root->right);//记录右子树的高度

	return leftHeight > rightHeight ? leftHeight+1 : rightHeight + 1;//比较左右树高度返回较大值
}

5.计算二叉树每一层的节点个数

计算每一层的节点个数需要用到的思想就是把第K层通过递归变成第一层。当K递归成1时返回1。当为NULL节点1时返回0。

代码:

int BTKSize(BTreeNode* root,int k)
{
	assert(k > 0);//K必须大于0
	if (root == NULL)
	{
		return 0;
	}
	if (k == 1)//当层数k=1时返回1,即这一层就是我们要求的那一层。
	{
		return 1;
	}

	return BTKSize(root->left, k - 1) + BTKSize(root->right, k - 1);//第K层的个数是左右子树的第K层的和。
}

6.二叉树的层序遍历

所谓层序遍历,就是要一层一层的对二叉树进行遍历打印。像我们这棵二叉树:

层序遍历的结果便是:1 2 3 4 6 7 5。

那我们该如何来做到层序遍历呢?这就需要我们使用另一种数据结构——队列。

为什么要使用队列呢?这是因为队列有一个特点叫先进先出,利用这个特点我们便可以实现层序遍历。

代码:

void LevelOrder(BTreeNode* root)
{
	Queue pq;//创造一个队列
	QueueInit(&pq);//初始化队列
	
	QueuePush(&pq, root);//先将一个根节点插入到队列中
	
	while (!isEmpty(&pq))
	{
		BTreeNode* front = QueueFront(&pq);//接收队列中第一个元素
		printf("%d->", front->val);
		QueuePop(&pq);//将第一个元素清出队列
 
		if (front->left)
			QueuePush(&pq, front->left);//插入左节点
		if (front->right)
			QueuePush(&pq, front->right);//插入右节点
	}
	QueueDestory(&pq);//销毁队列
}

详细解释可以看向这里:

http://t.csdn.cn/Tbm9F

 7.判断完全二叉树

判断完全二叉树的算法和层序遍历的算法实际上差不多。不过是将while循环中的条件改变了,以及增加了一个判断的条件。写成如下代码:

代码:

bool CompleteBTree(BTreeNode* root)
{
	Queue q;
	QueueInit(&q);
	QueuePush(&q, root);
	while (!isEmpty(&q))
	{
		BTreeNode* front = QueueFront(&q);
		QueuePop(&q);
        //当该节点为NULL时就跳出该循环到下一循环进行判断
		if (front == NULL)
			break;
        //当树节点的某一子节点为NULL时也需要插入队列中
		QueuePush(&q,front->left);
		QueuePush(&q,front->right);
		
	}
	while (!isEmpty(&q))
	{
		BTreeNode* front = QueueFront(&q);
		QueuePop(&q);
        //若该队列中的元素不完全是NULL节点那这棵二叉树就不是完全二叉树
		if (front)
		{
            QueueDestory(&q);
			return false;
		
		}
	}
	QueueDestory(&q);
	return true;
}

 详细解释可以看向这里:http://t.csdn.cn/XMQXa

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

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

相关文章

津津乐道设计模式 - 建造者模式详解(教你如何构造一个专属女友)

&#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Micro麦可乐的博客 &#x1f425;《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程&#xff0c;入门到实战 &#x1f33a;《RabbitMQ》…

接口测试断言详解(Jmeter)

接口测试是目前最主流的自动化测试手段&#xff0c;它向服务器发送请求&#xff0c;接收和解析响应结果&#xff0c;通过验证响应报文是否满足需求规约来验证系统逻辑正确性。接口的响应类型通过Content-Type指定&#xff0c;常见的响应类型有&#xff1a; • text/html &…

Android Jetpack Compose之轻松添加分隔线:Divider组件

引言&#xff1a; 在构建用户界面时&#xff0c;有效地组织和分隔内容是至关重要的。这就是Android Jetpack Compose的Divider组件派上用场的地方。在这篇博客中&#xff0c;我们将详细了解Divider组件的功能和用法&#xff0c;并通过示例展示如何将其融入您的Compose UI。 Je…

自动化测试和性能测试面试题精选

自动化测试相关 包含 Selenium、Appium 和接口测试。 1. 自动化代码中&#xff0c;用到了哪些设计模式&#xff1f; 单例模式工厂模式PO模式数据驱动模式 2. 什么是断言&#xff1f; 检查一个条件&#xff0c;如果它为真&#xff0c;就不做任何事&#xff0c;用例通过。如果…

8年资深测试总结,自动化测试成功实施,你不知道的都在这...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 什么项目&#xf…

python:并发编程(二十七)

前言 本文将和大家一起探讨python并发编程的实际项目&#xff1a;Locust性能测试&#xff08;篇一&#xff0c;共N篇&#xff09;&#xff0c;系列文章将会从零开始构建项目&#xff0c;并逐渐完善项目&#xff0c;最终将项目打造成适用于高并发场景的应用。 本文为python并发…

分支定价算法求解VRPTW问题(代码非原创)

参考文献&#xff1a;微信公众号“程序猿声”关于分支定价求解VRPTW的代码 A tutorial on column generation and branch-and-price for vehicle routing problems 框架 对于VRPTW问题&#xff0c;先做线性松弛&#xff0c;调用列生成算法&#xff08;一种解决大型线性规划问…

Docker网络之Network Namespace

Docker网络中相关的命令非常少&#xff0c;但需要掌握的底层原理却又非常多。 1.Network Namespace Docker网络底层原理是Linux的Network Namespace&#xff0c;所以说对于Linux Network Namespace的理解对Docker网络底层原理的理解就显得尤为重要了。 2.需求 通过手工的方式…

ICC2与INNOVUS命令对照表

ICC2与INNOVUS命令对照表 TargetICC2INNOVUS设置多CPU set_host_options -max_cores16 setMultiCpuUsage -localCpu 16 获得物体的属性 get_attribute

DSP,国产C2000横空出世,QX320F280049,替代TI 的 TMS320F280049,支持国产

一、特性参数 1、独立双核&#xff0c;32位CPU&#xff0c;单核主频400MHz 2、IEEE 754 单精度浮点单元 &#xff08;FPU&#xff09; 3、三角函数单元 &#xff08;TMU&#xff09; 4、1MB 的 FLASH &#xff08;ECC保护&#xff09; 5、1MB 的 SRAM &#xff08;ECC保护&…

全网最全,Selenium自动化测试POM模式总结(详细)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 在UI自动化测试中…

Python+Selenium+Unittest 之selenium7--元素定位6-CSS定位1(定位所有、定位class、定位id、tag定位)

目录 一、CSS简介 二、 定位方式 三、实践操作 1、*&#xff08;定位所有元素&#xff09; 2、. &#xff08;定位class属性&#xff09; 3、#&#xff08;定位id属性&#xff09; 4、tag定位 一、CSS简介 CSS属于是一种计算机语言&#xff0c;主要是用来为结构化文档的外…

软件测试期末速成(背题家出列!)

文章目录 一、前言二、选择题&#xff08;15 X 2&#xff09;1、概述2、相关概念3、黑盒测试4、白盒测试5、单元测试6、集成测试7、系统测试8、自动化测试9、实用软件测试技术 三、判断题&#xff08;10 X 1’&#xff09;四、简答题&#xff08;4 X 5&#xff09;1、软件测试生…

一文学会Nginx做图片服务器

Nginx做图片服务器 前言&#xff1a; Nginx是一个高性能的HTTP和反向代理web服务器,以及负载均衡器。根据nginx是高性能的http服务器&#xff0c;因此可以用作图片服务器使用。 本案例是在docker安装nginx来操作的。 什么是Nginx? Nginx是一款高性能的Web服务器和反向代理服…

linux入门之浅谈shell及权限的概念

文章目录 目录 文章目录 一、shell命令以及运行原理 二、linux权限的概念 1.Linux权限管理 a.文件访问者的分类&#xff08;人&#xff09; b.文件类型和访问权限 1&#xff09;文件类型 2&#xff09;基本权限 3&#xff09;文件权限值的表示方法 4)文件访问权限的相…

docker快速部署oracle19c、oracle12c,测试环境问题复现demo快速搭建笔记

Oracle 19c测试环境快速搭建 安装 # 下载镜像 19.3.0.0.0 docker pull registry.cn-hangzhou.aliyuncs.com/laowu/oracle:19c # 创建文件 mkdir -p /mymount/oracle19c/oradata # 授权&#xff0c;不授权会导致后面安装失败 chmod 777 /mymount/oracle19c/oradatadocker run …

01背包思路解析+代码

01背包 题目链接&#xff1a;01背包 思路&#xff1a;题目要求是获取背包能装的最大重量。一个物品有体积和重量两个属性。而当我们判断一个物品是否要放进背包&#xff0c;第一取决于他的体积是否足以放进背包&#xff0c;第二取决于他的重量是否足以让我们取出已经放入的一部…

buuctf 你有没有好好看网课? 解析

打开文件得到两个压缩包&#xff0c;第一个压缩包flag2需要密码&#xff0c;第二个压缩包flag3打开后在备注可以获得提示 使用arc爆破&#xff0c;得到6位数字密码 解压压缩包得到一个视频和文档 文档内容包含6个数字&#xff0c;结合视频猜测是关键信息藏在这两个时间节点上 …

XSS注入——DOM型XSS

DOM型xss XSS根据恶意脚本的传递方式可以分为3种&#xff0c;分别为反射型、存储型、DOM型&#xff0c;前面两种恶意脚本都会经过服务器端然后返回给客户端&#xff0c;相对DOM型来说比较好检测与防御&#xff0c;而DOM型不用将恶意脚本传输到服务器在返回客户端&#xff0c;这…

如何优化Nginx服务进程(详细教程)

目录 一、了解Nginx服务配置内容 ① 外框架 ② 内框架 ③ 三个主模块 二、Nginx服务进程 访问信息的组成 Web服务的监听配置 LNMP架构 三、Nginx优化 隐藏版本号 可以查询指定地址的服务信息 更改配置文件内容 检查语法错误 重启服务 再次查看版本号是否隐藏 自…