数据结构与算法---树

数据结构可视化网址

  • Structure Visualization: https://www.cs.usfca.edu/~galles/visualization/
  • Totuma: https://www.totuma.cn/
  • Algorithm Visualizer: https://algorithm-visualizer.org/

构建二叉树

在这里插入图片描述

// C

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

typedef char T;


typedef struct TreeNode {
	T data;
	struct TreeNode* left;
	struct TreeNode* right;
}Node;


int main()
{
	int spaceSize = sizeof(Node);

	Node* a = (Node*)malloc(spaceSize);
	Node* b = (Node*)malloc(spaceSize);
	Node* c = (Node*)malloc(spaceSize);
	Node* d = (Node*)malloc(spaceSize);
	Node* e = (Node*)malloc(spaceSize);

	a->data = 'A';
	b->data = 'B';
	c->data = 'C';
	d->data = 'D';
	e->data = 'E';

	a->left = b;
	a->right = c;
	b->left = d;
	b->right = e;
	c->left = c->right = NULL;
	d->left = d->right = NULL;
	e->left = e->right = NULL;
	
	free(a);
	free(b);
	free(c);
	free(d);
	free(e);

	getchar();
	return 0;
}

遍历二叉树

0.基本概念

二叉树是一种特殊的树形数据结构,其中每个节点最多有两个子节点,通常称为左子节点和右子节点。

当我们遍历二叉树时,可以根据访问节点的顺序得到不同的遍历结果。

有以下四种遍历方式:

  1. 前序遍历
    • 访问根节点
    • 前序遍历左子树
    • 前序遍历右子树
    • 对于给定的二叉树,前序遍历的结果是一个节点序列,这个序列首先列出根节点,然后递归地列出左子树的所有节点,最后递归地列出右子树的所有节点
  2. 中序遍历
    • 中序遍历左子树
    • 访问根节点
    • 中序遍历右子树
    • 在二叉搜索树(BST)中,中序遍历的结果是一个有序的节点序列,因为它首先遍历最小的节点(在BST的左下方),然后遍历根节点,最后遍历最大的节点(在BST的右上方)
  3. 后序遍历
    • 后序遍历左子树
    • 后序遍历右子树
    • 访问根节点
    • 后序遍历的结果是一个节点序列,这个序列首先列出左子树的所有节点,然后列出右子树的所有节点,最后列出根节点
  4. 层序遍历
    • 从根节点开始
    • 访问当前层的所有节点
    • 对下一层的每个节点进行同样的操作
    • 层序遍历通常使用队列来实现,首先将根节点入,然后在循环中,出队一个节点并访问它,然后将它的子节点(如果存在)入队。这个过程一直持续到队列为空

1.前序遍历

在这里插入图片描述

  • 通过递归的方式实现
// C

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

typedef char T;


typedef struct TreeNode {
	T data;
	struct TreeNode* left;
	struct TreeNode* right;
}Node;


// 先序遍历
void preorder(Node* root)	// 参数:二叉树的根节点
{
	if (root == NULL)	return;
	printf("%c", root->data);
	preorder(root->left);
	preorder(root->right);
}


int main()
{
	int spaceSize = sizeof(Node);

	Node* a = (Node*)malloc(spaceSize);
	Node* b = (Node*)malloc(spaceSize);
	Node* c = (Node*)malloc(spaceSize);
	Node* d = (Node*)malloc(spaceSize);
	Node* e = (Node*)malloc(spaceSize);
	Node* f = (Node*)malloc(spaceSize);

	a->data = 'A';
	b->data = 'B';
	c->data = 'C';
	d->data = 'D';
	e->data = 'E';
	f->data = 'F';

	a->left = b;
	a->right = c;
	b->left = d;
	b->right = e;
	c->right = f;
	c->left = d->left = d->right = e->left = e->right = f->left = f->right = NULL;

	preorder(a);

	free(a);
	free(b);
	free(c);
	free(d);
	free(e);


	getchar();
	return 0;
}
  • 通过"循环+栈"的方式实现
// C

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

// 二叉树的节点
typedef char T;
typedef struct TreeNode {
	T data;
	struct TreeNode* left;
	struct TreeNode* right;
}TreeNode;


// 栈的节点
typedef struct StackNode {
	TreeNode* node;
	StackNode* next;
}StackNode;


// 栈的初始化
void initStack(StackNode* head)
{
	head->next = NULL;
}


// 入栈
bool pushStack(StackNode* head, TreeNode* node)
{
	StackNode* p = (StackNode*)malloc(sizeof(StackNode));
	if (p == NULL)	return false;

	p->node = node;
	p->next = head->next;
	head->next = p;

	return true;
}


// 出栈
TreeNode* popStack(StackNode* head)
{
	StackNode* temp = head->next;
	TreeNode* node = head->next->node;

	head->next = head->next->next;
	free(temp);

	return node;
}


// 栈是否为空
bool isEmpty(StackNode* head)
{
	return head->next == NULL;
}


// 先序遍历
void preorder(TreeNode* root)
{
	StackNode stack;		// 创建一个栈
	initStack(&stack);		// 初始化这个栈

	while (root || !isEmpty(&stack))		// 如果栈不为空,也会继续循环下去
	{
		// 先不断遍历左子树,直到没有为止
		while (root)
		{
			printf("%c", root->data);
			pushStack(&stack, root);		// 途中每经过一个节点,就将节点丢进栈中
			root = root->left;
		}

		// 处理右子树
		root = popStack(&stack);
		root = root->right;
	}
}

int main()
{
	int spaceSize = sizeof(TreeNode);

	TreeNode* a = (TreeNode*)malloc(spaceSize);
	TreeNode* b = (TreeNode*)malloc(spaceSize);
	TreeNode* c = (TreeNode*)malloc(spaceSize);
	TreeNode* d = (TreeNode*)malloc(spaceSize);
	TreeNode* e = (TreeNode*)malloc(spaceSize);
	TreeNode* f = (TreeNode*)malloc(spaceSize);

	a->data = 'A';
	b->data = 'B';
	c->data = 'C';
	d->data = 'D';
	e->data = 'E';
	f->data = 'F';

	a->left = b;
	a->right = c;
	b->left = d;
	b->right = e;
	c->right = f;
	c->left = d->left = d->right = e->left = e->right = f->left = f->right = NULL;

	preorder(a);	// 先序遍历

	free(a);
	free(b);
	free(c);
	free(d);
	free(e);


	getchar();
	return 0;
}

2.中序遍历

在这里插入图片描述

  • 通过递归的方式实现
// C

// 中序遍历
void inOrder(TreeNode* root)
{
	if (root == NULL) return;
	inOrder(root->left);		// 先完成全部左子树的遍历
	printf("%c", root->data);   // 等待左子树遍历完成之后再打印
	inOrder(root->right);		// 然后就是对右子树进行遍历
}
  • 通过"循环+栈"的方式实现
// C

// 中序遍历
void inOrder(TreeNode* root)
{
	StackNode stack;		// 创建一个栈
	initStack(&stack);		// 初始化这个栈

	while (root || !isEmpty(&stack))		// 如果栈不为空,也会继续循环下去
	{
		while (root)
		{
			pushStack(&stack, root);
			root = root->left;
		}
		root = popStack(&stack);
		printf("%c", root->data);   // 只需要将打印时机延后到左子树遍历完成
		root = root->right;
	}
}

3.后序遍历

// C

// 后序遍历
void postOrder(Node root){
    if(root == NULL) return;
    postOrder(root->left);
    postOrder(root->right);
    printf("%c", root->data);   // 时机延迟到最后
}

4.层序遍历

// C

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

typedef char E;


// 树的节点
struct TreeNode {
	E element;
	struct TreeNode* left;
	struct TreeNode* right;
	int flag;
};
typedef struct TreeNode* Node;


// 队列的节点
struct QueueNode {
	Node element;
	struct QueueNode* next;
};
typedef struct QueueNode* QNode;


// 队列
struct Queue {
	QNode front, rear;
};


// 初始化
bool initQueue(Queue* queue) {
	QNode node = (QNode)malloc(sizeof(struct QueueNode));
	if (node == NULL)   return false;

	queue->front = queue->rear = node;

	return true;
}


// 入队
bool offerQueue(Queue* queue, Node element) {
	QNode node = (QNode)malloc(sizeof(struct QueueNode));
	if (node == NULL) return false;

	node->element = element;
	queue->rear->next = node;
	queue->rear = node;

	return true;
}


// 出队
Node pollQueue(Queue* queue) {
	Node e = queue->front->next->element;
	QNode node = queue->front->next;

	queue->front->next = queue->front->next->next;
	if (queue->rear == node) queue->rear = queue->front;
	free(node);

	return e;
}


// 判断是否为空
bool isEmpty(Queue* queue) {
	return queue->front == queue->rear;
}


// 层序遍历
void levelOrder(Node root) {
	struct Queue queue;		// 创建一个队列
	initQueue(&queue);		// 初始化这个队列

	offerQueue(&queue, root);		// 先把根节点入队
	while (!isEmpty(&queue))		// 不断重复,直到队列空为止
	{
		Node node = pollQueue(&queue);		// 出队一个元素,打印值
		printf("%c", node->element);
		if (node->left)		// 如果存在左右孩子的话
			offerQueue(&queue, node->left);		// 记得将左右孩子入队,注意顺序,先左后右
		if (node->right)
			offerQueue(&queue, node->right);
	}
}


int main()
{
	int spaceSize = sizeof(TreeNode);

	TreeNode* a = (TreeNode*)malloc(spaceSize);
	TreeNode* b = (TreeNode*)malloc(spaceSize);
	TreeNode* c = (TreeNode*)malloc(spaceSize);
	TreeNode* d = (TreeNode*)malloc(spaceSize);
	TreeNode* e = (TreeNode*)malloc(spaceSize);
	TreeNode* f = (TreeNode*)malloc(spaceSize);

	a->element = 'A';
	b->element = 'B';
	c->element = 'C';
	d->element = 'D';
	e->element = 'E';
	f->element = 'F';

	a->left = b;
	a->right = c;
	b->left = d;
	b->right = e;
	c->right = f;
	c->left = d->left = d->right = e->left = e->right = f->left = f->right = NULL;

	levelOrder(a);	// 层序遍历

	free(a);
	free(b);
	free(c);
	free(d);
	free(e);


	getchar();
	return 0;
}

线索化二叉树

线索化概念

一棵二叉树的某些结点会存在NULL的情况,我们可以利用这些为NULL的指针,将其线索化为某一种顺序遍历的指向下一个按顺序的结点的指针,经过第一次遍历完成了线索化,以后我们再进行遍历的时候,就可以利用线索实现快速遍历。

  • 一棵经过线索化以后的二叉树的前序遍历顺序如下:

在这里插入图片描述

  • 一棵经过线索化以后的二叉树的中序遍历顺序如下:

在这里插入图片描述

  • 一棵经过线索化以后的二叉树的后序遍历顺序如下:

在这里插入图片描述

线索化规则

线索化的规则为:

  • 结点的左指针,指向其当前遍历顺序的前驱结点。
  • 结点的右指针,指向其当前遍历顺序的后继结点。

代码实现

// C

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

typedef char E;

// 树节点
typedef struct TreeNode {
	E element;
	struct TreeNode* left;
	struct TreeNode* right;
	struct TreeNode* parent;	// 指向双亲(父)结点 ------ 只是"后序遍历"需要用这个属性
	int leftTag, rightTag;		// 标志位,如果为1表示这一边指针指向的是线索,不为1就是正常的孩子结点
} *Node;


// 创建结点
Node createNode(E element) {
	Node node = (Node)malloc(sizeof(struct TreeNode));
	node->left = node->right = NULL;
	node->rightTag = node->leftTag = 0;
	node->element = element;
	return node;
}


// "前序遍历"线索化函数
Node pre1 = NULL;  // 这里我们需要一个pre1来保存后续结点的指向
void preOrderThreaded(Node root) {
	if (root == NULL) return;

	if (root->left == NULL) {   // 首先判断当前结点左边是否为NULL,如果是,那么指向上一个结点
		root->left = pre1;
		root->leftTag = 1;  // 记得修改标记
	}
	if (pre1 && pre1->right == NULL) {  // 然后是判断上一个结点的右边是否为NULL,如果是那么进行线索化,指向当前结点
		pre1->right = root;
		pre1->rightTag = 1;  // 记得修改标记
	}

	pre1 = root;   // 每遍历完一个,需要更新一下pre1,表示上一个遍历的结点

	if (root->leftTag == 0)   // 注意只有标志位是0才可以继续向下,否则就是线索了
		preOrderThreaded(root->left);
	if (root->rightTag == 0)
		preOrderThreaded(root->right);
}


// "前序遍历"一棵经过线索化的二叉树
void preOrder(Node root) {
	while (root) {
		printf("%c", root->element);   // 因为是前序遍历,所以直接按顺序打印就行了
		if (root->leftTag == 0)
			root = root->left;   // 如果是左孩子,那么就走左边
		else
			root = root->right;   // 如果左边指向的不是孩子,而是线索,那么就直接走右边,因为右边无论是线索还是孩子,都要往这边走了
	}
}


// "中序遍历"线索化函数
Node pre2 = NULL;  // 这里我们需要一个pre2来保存后续结点的指向
void inOrderThreaded(Node root) {
	if (root == NULL) return;
	if (root->leftTag == 0)
		inOrderThreaded(root->left);

	// ----------线索化 ---------
	if (root->left == NULL) {
		root->left = pre2;
		root->leftTag = 1;
	}
	if (pre2 && pre2->right == NULL) {
		pre2->right = root;
		pre2->rightTag = 1;
	}
	pre2 = root;
	// ----------线索化 ---------

	if (root->rightTag == 0)
		inOrderThreaded(root->right);
}


// "中序遍历"一棵经过线索化的二叉树
void inOrder(Node root) {

	while (root) {   // 因为中序遍历需要先完成左边,所以说要先走到最左边才行

		while (root && root->leftTag == 0)    // 如果左边一直都不是线索,那么就一直往左找,直到找到一个左边是线索的为止,表示到头了
			root = root->left;

		printf("%c", root->element);   // 到最左边了再打印,中序开始

		while (root && root->rightTag == 1) {   // 打印完就该右边了,右边如果是线索化之后的结果,表示是下一个结点,那么就一路向前,直到不是为止
			root = root->right;
			printf("%c", root->element);   // 注意按着线索往下就是中序的结果,所以说沿途需要打印
		}
		root = root->right;  // 最后继续从右结点开始,重复上述操作

	}
}


// "后序遍历"线索化函数
Node pre3 = NULL;  // 这里我们需要一个pre3来保存后续结点的指向
void postOrderThreaded(Node root) {

	if (root == NULL) return;
	if (root->leftTag == 0) {
		postOrderThreaded(root->left);
		if (root->left) root->left->parent = root;  // 左边完事之后,如果不为空,那么就设定父子关系
	}
	if (root->rightTag == 0) {
		postOrderThreaded(root->right);
		if (root->right) root->right->parent = root;   // 右边完事之后,如果不为空,那么就设定父子关系
	}

	// ------ 线索化 -------
	if (root->left == NULL) {
		root->left = pre3;
		root->leftTag = 1;
	}
	if (pre3 && pre3->right == NULL) {
		pre3->right = root;
		pre3->rightTag = 1;
	}
	pre3 = root;
	// ------ 线索化 -------
}


// "后序遍历"一棵经过线索化的二叉树
void postOrder(Node root) {

	Node last = NULL, node = root;  // 这里需要两个暂存指针,一个记录上一次遍历的结点,还有一个从root开始
	while (node) {
		while (node->left != last && node->leftTag == 0)    // 依然是从整棵树最左边结点开始,和前面一样,只不过这里加入了防无限循环机制,看到下面就知道了
			node = node->left;
		while (node && node->rightTag == 1) {   // 左边完了还有右边,如果右边是线索,那么直接一路向前,也是跟前面一样的
			printf("%c", node->element);   // 沿途打印
			last = node;
			node = node->right;
		}
		if (node == root && node->right == last) {
			// 上面的操作完成之后,那么当前结点左右就结束了,此时就要去寻找其兄弟结点了,我们可以
			// 直接通过parent拿到兄弟结点,但是如果当前结点是根结点,需要特殊处理,因为根结点没有父结点了
			printf("%c", node->element);
			return;   // 根节点一定是最后一个,所以说直接返回就完事
		}
		while (node && node->right == last) {    // 如果当前结点的右孩子就是上一个遍历的结点,那么一直向前就行
			printf("%c", node->element);   // 直接打印当前结点
			last = node;
			node = node->parent;
		}
		// 到这里只有一种情况了,是从左子树上来的,那么当前结点的右边要么是线索要么是右子树,所以直接向右就完事
		if (node && node->rightTag == 0) {  // 如果不是线索,那就先走右边,如果是,等到下一轮再说
			node = node->right;
		}
	}
}





int main() {
	// 一颗二叉树的所有节点
	Node a = createNode('A');
	Node b = createNode('B');
	Node c = createNode('C');
	Node d = createNode('D');
	Node e = createNode('E');

	// 节点相连,形成二叉树
	a->left = b;
	b->left = d;
	a->right = c;
	b->right = e;

	// 前序遍历
	preOrderThreaded(a);	// 线索化
	preOrder(a);			// 遍历

	// 中序遍历
	inOrderThreaded(a);		// 线索化
	inOrder(a);				// 遍历

	// 后序遍历
	postOrderThreaded(a);	// 线索化
	postOrder(a);			// 遍历

	// 释放内存空间
	free(a);
	free(b);
	free(c);
	free(d);
	free(e);

	// 程序暂停
	getchar();

	// 结束主函数
	return 0;
}

二叉查找树

二叉查找树也叫二叉搜索树或是二叉排序树,它具有一定的规则:

  • 左子树中所有结点的值,均小于其根结点的值
  • 右子树中所有结点的值,均大于其根结点的值
  • 二叉搜索树的子树也是二叉搜索树

在这里插入图片描述

平衡二叉树

  • 动画演示:https://www.cs.usfca.edu/~galles/visualization/AVLtree.html
  • 部分代码
#include<stdio.h>
#include<stdlib.h>

typedef int E;

typedef struct TreeNode {
    E element;
    struct TreeNode * left;
    struct TreeNode * right;
    int height;   // 每个结点需要记录当前子树的高度,便于计算平衡因子
} * Node;


Node createNode(E element){
    Node node = malloc(sizeof(struct TreeNode));
    node->left = node->right = NULL;
    node->element = element;
    node->height = 1;   // 初始化时,高度写为1就可以了
    return node;
}


int max(int a, int b){
    return a > b ? a : b;
}


int getHeight(Node root){
    if(root == NULL) return 0;
    return root->height;
}


Node leftRotation(Node root){  		// 左旋操作,实际上就是把左边结点拿上来
    Node newRoot = root->right;   	// 先得到左边结点
    root->right = newRoot->left;   	// 将左边结点的左子树丢到原本根结点的右边去
    newRoot->left = root;			// 现在新的根结点左边就是原本的跟结点了

    root->height = max(getHeight(root->right), getHeight(root->left)) + 1;
    newRoot->height = max(getHeight(newRoot->right), getHeight(newRoot->left)) + 1;
    return newRoot;
}


Node rightRotation(Node root){
    Node newRoot = root->left;
    root->left = newRoot->right;
    newRoot->right = root;

    root->height = max(getHeight(root->right), getHeight(root->left)) + 1;
    newRoot->height = max(getHeight(newRoot->right), getHeight(newRoot->left)) + 1;
    return newRoot;
}


Node leftRightRotation(Node root){
    root->left = leftRotation(root->left);
    return rightRotation(root);
}


Node rightLeftRightRotation(Node root){
    root->right = rightRotation(root->right);
    return leftRotation(root);
}


Node insert(Node root, E element){
    if(root == NULL) {    					// 如果结点为NULL,说明找到了插入位置,直接创建新的就完事
        root = createNode(element);
    }else if(root->element > element) {   // 和二叉搜索树一样,判断大小,该走哪边走哪边,直到找到对应插入位置
        root->left = insert(root->left, element);
        if(getHeight(root->left) - getHeight(root->right) > 1) {   	// 插入完成之后,需要计算平衡因子,看看是否失衡
            if(root->left->element > element) 			// 接着需要判断一下是插入了左子树的左边还是右边,如果是左边那边说明是LL,如果是右边那说明是LR
                root = rightRotation(root);   // LL型得到左旋之后的结果,得到新的根结点
            else
                root = leftRightRotation(root);    // LR型得到先左旋再右旋之后的结果,得到新的根结点
        }
    }else if(root->element < element){
        root->right = insert(root->right, element);
        if(getHeight(root->left) - getHeight(root->right) < -1){
            if(root->right->element < element)
                root = leftRotation(root);
            else
                root = rightLeftRightRotation(root);
        }
    }
    // 前面的操作完成之后记得更新一下树高度
    root->height = max(getHeight(root->left), getHeight(root->right)) + 1;
    return root;  	// 最后返回root到上一级
}

红黑树

  • 动画演示:https://www.cs.usfca.edu/~galles/visualization/RedBlack.html
  • 视频推荐:https://www.bilibili.com/video/BV1Xm421x7Lg/
  • 代码实现:https://blog.csdn.net/m0_75215937/article/details/133570625

B树

基本认识

B树 ( Balance Tree ),它是专门为磁盘数据读取设计的一种度为 m 的查找树(多用于数据库)

下面是一棵度为4的B树:

在这里插入图片描述

详细规则

  • 树中每个结点最多含有m个孩子(m >= 2)比如上面就是m为4的4阶B树,最多有4个孩子
  • 除根结点和叶子结点外,其它每个结点至少有⌈m/2⌉个孩子,同理键值数量至少有⌈m/2⌉-1个
  • 若根结点不是叶子结点,则至少有2个孩子
  • 所有叶子结点都出现在同一层
  • 一个结点的包含多种信息(P0,K1,P1,K2,…,Kn,Pn),其中P为指向子树的指针,K为键值(关键字)
  • Ki (i=1…n)为键值,也就是每个结点保存的值,且键值按顺序升序排序K(i-1)< Ki
  • Pi为指向子树的指针,且指针Pi指向的子树中所有结点的键值均小于Ki,但都大于K(i-1)
  • 键值的个数n必须满足: ⌈m/2⌉-1 <= n <= m-1

动画演示

https://www.cs.usfca.edu/~galles/visualization/BTree.html

B+树

基本认识

  • B+树是B树的一种变体,有着比B树更高的查询性能
  • B+树上的叶子结点存储关键字以及相应记录的地址,叶子结点以上各层作为索引使用

B+树的应用

在这里插入图片描述

动画演示

https://www.cs.usfca.edu/~galles/visualization/BPlusTree.html

代码演示

https://www.cnblogs.com/yangj-Blog/p/12992124.html

哈夫曼树

基本认识

  • 哈夫曼树(Huffman Tree)又称最优二叉树,是n个带权叶子结点构成的二叉树中,带权路径长度(WPL)最小的二叉树。
  • 哈夫曼编码是一种压缩编码的编码算法,是基于哈夫曼树的一种编码方式。
  • 在计算机数据处理中,哈夫曼编码使用变长编码表对源符号(如文件中的一个字母)进行编码,其中变长编码表是通过一种评估来源符号出现机率的方法得到的,出现机率高的字母使用较短的编码,反之出现机率低的则使用较长的编码,这便使编码之后的字符串的平均长度、期望值降低,从而实现无损压缩数据

详细内容

https://zhuanlan.zhihu.com/p/415467000

基本认识

  • 堆 (heap) 是计算机科学中一类特殊的数据结构统称
  • 堆通常是一个可以被看做一棵树的数组对象
  • 堆总是满足下列性质:
    • 堆中某个节点的值总是不大于或不小于其父节点的值
    • 堆总是一棵完全二叉树

详细内容

https://blog.csdn.net/xiaomucgwlmx/article/details/103522410

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

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

相关文章

题目:线性代数

问题描述&#xff1a; 解题思路&#xff1a; 列相乘&#xff0c;然后行相加。 注意点&#xff1a;由于元素数据范围最大为1e6&#xff0c;两个元素相乘乘积最大为1e12&#xff0c;如果元素类型为int则在乘的过程中就会爆炸&#xff0c;所以需要开long long类型。 AC代码…

【Linux网络】SSH服务

目录 一、SSH概述与使用 1.1 定义 1.2 优点 1.3 原理 1.4 命令登录 1.5 跳板登录 1.6 远程控制 二、SSH配置 2.1 常用的服务端配置 2.2 ssh服务最优配置 三、免密登录 3.1 操作原理 3.2 操作步骤 一、SSH概述与使用 1.1 定义 SSH&#xff08;Secure Shell&#…

【C++】滑动窗口:最大连续1的个数

1.题目 2.算法思路 其实在做这道题的时候并不需要真的把0翻转成1&#xff0c;只需要找到最长的子数组且该子数组中0的个数不大于K&#xff0c;就可以了&#xff01; 当然我们首先想到的是暴力穷举法&#xff1a; 找到所有符合题意的子数组&#xff0c;跳出最长的那个就可以了…

Integer中的缓存机制

先看一个示例&#xff1a; public static void main(String[] args) {Integer a127;Integer b127;System.out.println(ab);Integer c128;Integer d128;System.out.println(cd);} 输出&#xff1a; true false 为什么明明都是同一个数字进行比较&#xff0c;当数字等于127的…

JVM笔记-常用命令

1、jstat jstat是一个极强的监视JVM的工具&#xff0c;可以用来监视JVM的各种堆和非堆的大小以及内存使用量。 Usage: jstat -help|-optionsjstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]jstat的常用用法如图所示&#xff…

Matlab各个版本介绍、区别分析及推荐

MATLAB&#xff0c;由美国MathWorks公司出品&#xff0c;是一款广泛应用的商业数学软件。自其诞生之初&#xff0c;MATLAB便以其强大的矩阵计算能力、灵活的编程环境以及广泛的应用领域&#xff0c;赢得了全球科研工作者和工程师的青睐。本文将详细介绍MATLAB的各个版本&#x…

蓝桥杯练习系统(算法训练)ALGO-950 逆序数奇偶

资源限制 内存限制&#xff1a;256.0MB C/C时间限制&#xff1a;1.0s Java时间限制&#xff1a;3.0s Python时间限制&#xff1a;5.0s 问题描述 老虎moreD是一个勤于思考的青年&#xff0c;线性代数行列式时&#xff0c;其定义中提到了逆序数这一概念。不过众所周知我们…

力扣hot100:101. 对称二叉树(双指针以不同方式递归)

LeetCode&#xff1a;101. 对称二叉树 看了第一个样例&#xff0c;很容易直接层序遍历看每一层的前后是否相同。但接下来这个样例告诉你&#xff0c;不能这样做。 层序遍历 仔细思考会发现&#xff0c;层序遍历不能看本结点&#xff0c;但是可以看儿子结点是否对称&#xf…

RK3568平台(时间篇)看门狗

一.看门狗原理 在产品化的嵌入式系统中&#xff0c;为了使系统在异常情况下能自动复位&#xff0c;一般都需要引入看门狗。 看门狗其实就是一个可以在一定时间内被复位的计数器。当看门狗启动后&#xff0c;计数器开始自动计数&#xff0c;经过一定时间&#xff0c;如果没有被…

笔记-mathtype公式在PDF或打印出来显示不全

原文中的公式&#xff1a; 纸质版打印出来的公式有缺失 问题描述&#xff1a;mathtype公式编辑器所编辑的公式转成PDF或者打印出来有缺失 以下是解决方法的具体描述。 目录 一、准备工作二、操作步骤 一、准备工作 1、工具&#xff1a;mathtype、微软word 二、操作步骤 …

概念解析 | 互补学习系统

注1:本文系"概念解析"系列之一,致力于简洁清晰地解释、辨析复杂而专业的概念。本次辨析的概念是:互补学习系统(Complementary Learning Systems) 概念解析:互补学习系统 Paper Summary - “Complementary Learning Systems Theory Updated” | Rylan Schaeffer…

Linux下Palabos源码编译安装及使用

目录 软件介绍 基本依赖 其它可选依赖 一、源码下载 二、解压缩&#xff08;通过方式1下载源码.zip格式&#xff09; 三、编译安装 3.1 自带算例 ​编辑3.2 自行开发算例 四、简单使用 4.1 串行运行 4.2 并行运行 4.3 查看结果 软件介绍 Palabos是一款基于LBM&…

前端面试和一些建议

最近公司在招前端&#xff0c;我有跟着一起参与面试。我们主要负责面试的人&#xff0c;不会问那些什么闭包&#xff0c;原型链&#xff0c;他觉得那些东西在我们日常开发中用不到&#xff0c;问的基本都是一些工作中的问题。这些问题不是每次都问&#xff0c;但也就问这些了。…

[论文阅读] 测试时间自适应TTA

最初接触 CVPR2024 TEA: Test-time Energy Adaptation [B站]&#xff08;1:35:00-1:53:00&#xff09;https://www.bilibili.com/video/BV1wx4y1v7Jb/?spm_id_from333.788&vd_source145b0308ef7fee4449f12e1adb7b9de2 实现&#xff1a; 读取预训练好的模型参数设计需要更…

C语言写一个终端进度条

C语言写一个终端进度条 这个功能挺简单的&#xff0c;主要有以下两点&#xff1a; 如何获取终端宽度如何让字符在原地闪烁 如何获取终端宽度 这里用到了设备控制接口函数ioctl()&#xff0c;下面简单的介绍一下这个函数的用法&#xff1a; ioctl是一个在Unix和类Unix系统中…

怎样通过Java语言实现远程控制8路控制器/断路器

怎样通过Java语言实现远程控制8路控制器/断路器呢&#xff1f; 本文描述了使用Java语言调用HTTP接口&#xff0c;实现控制8路控制器/断路器&#xff0c;支持8路输出&#xff0c;均可独立控制&#xff0c;可接入各种电器。 可选用产品&#xff1a;可根据实际场景需求&#xff0…

【数据库主从架构】

【数据库主从架构】 1. 什么是数据库的主从架构1.1 主从复制1.1.1 MySQL的主从主从复制技术三级目录 1. 什么是数据库的主从架构 随着公司业务线的增多&#xff0c;各种数据都在迅速增加&#xff0c;并且数据的读取流量也大大增加&#xff0c;就面临着数据安全问题&#xff0c;…

手把手教你实现通讯录

整体构思 我们现在要实现一个通讯录 它应该有以下的功能 通讯录可以用来存储1000个人的信息&#xff0c;每个人的信息包括&#xff1a;姓名、性别、年龄、电话、住址 提供方法&#xff1a; 1.添加联系人信息 2.删除指定联系人信息 3.查找指定联系人信息 4.修改指定联系人信…

如何删除BigKey2

例2&#xff1a;假如有hash类型的key&#xff0c;其中有100万对field和value&#xff0c;field是自增id&#xff0c;这个key存在什么问题&#xff1f;如何优化&#xff1f; keyfieldvaluesomeKeyid:0value0..........id:999999value999999 存在的问题&#xff1a; hash的ent…

BJFUOJ-C++程序设计-实验2-类与对象

A 评分程序 答案&#xff1a; #include<iostream> #include<cstring>using namespace std;class Score{ private:string name;//记录学生姓名double s[4];//存储4次成绩&#xff0c;s[0]和s[1]存储2次随堂考试&#xff0c;s[2]存储期中考试&#xff0c;s[3]存储期…