二叉树深度解析:从基础概念到算法实现与应用
一、二叉树的本质定义与核心特性
(一)递归定义与逻辑结构
二叉树是一种 严格有序的树结构,其递归定义为:
- 空树:不含任何结点的集合,是二叉树的特殊形态。
- 非空二叉树:由以下三部分组成:
- 根结点(Root):唯一的顶层结点,没有父结点。
- 左子树(Left Subtree):以根结点左子结点为根的二叉树。
- 右子树(Right Subtree):以根结点右子结点为根的二叉树。
关键特性:
- 度的限制:每个结点最多有 2 个子结点(度为 0、1 或 2),不存在度大于 2 的结点。
- 有序性:左右子树顺序严格区分,例如仅有左子结点的树与仅有右子结点的树视为不同结构。
- 五种基本形态:
(二)结点关系与术语
- 父结点与子结点:若结点 A 的子结点为 B,则 A 是 B 的父结点,B 是 A 的左 / 右子结点。
- 兄弟结点:具有相同父结点的结点互称兄弟,如 B 和 C 是兄弟。
- 叶子结点与分支结点:
- 叶子结点:度为 0 的结点(无任何子结点)。
- 分支结点:度为 1 或 2 的结点(至少有一个子结点)。
- 层次与高度:
- 层次:根结点为第 1 层,子结点为第 2 层,依此类推。
- 高度:树中结点的最大层次,如高度为 3 的二叉树最多有 7 个结点(满二叉树)。
二、特殊二叉树:满二叉树与完全二叉树的对比分析
(一)满二叉树(Perfect Binary Tree)
- 数学定义:高度为 h 的满二叉树,每层结点数达到最大值 2h−1,结点总数为 2h−1。
- 结构特点:
- 所有叶子结点位于最后一层(第 h 层)。
- 不存在度为 1 的结点,每个分支结点都有左右两个子结点。
- 示例(高度 3):
结点数:2*3−1=7,每层结点数分别为 1、2、4。
(二)完全二叉树(Complete Binary Tree)
- 定义:高度为 h、结点数为 n 的二叉树,其结点与高度为 h 的满二叉树中前 n 个结点一一对应(按层序编号)。
- 结构特点:
- 前 h−1 层是满二叉树,第 h 层结点从左到右连续排列,不允许中间有空缺。
- 满二叉树是特殊的完全二叉树,但完全二叉树不一定是满二叉树。
- 示例(高度 3,结点数 5):
与满二叉树前 5 个结点(1,2,3,4,5)位置一致,第 3 层仅有左子结点。
(三)核心区别对比
特性 | 满二叉树 | 完全二叉树 |
---|---|---|
结点分布 | 每层满结点 | 前 h−1 层满,第 h 层左连续 |
度为 1 的结点 | 不存在(必为 0 或 2) | 最多一个,且只能在最后一层 |
结点数公式 | 2^h-1 | n∈[2^(h−1),2^h−1] |
三、存储结构:顺序存储 vs 链式存储的适用场景与实现细节
(一)顺序存储(数组实现,适合完全二叉树)
- 存储逻辑:用一维数组按层序存储结点,下标对应结点编号:
- 根结点:下标 0
- 结点 i 的左子结点:2i+1(若存在)
- 结点 i 的右子结点:2i+2(若存在)
- 结点 i 的父结点:(i−1)/2(i>0 时)
- 示例(完全二叉树):
数组存储:[1, 2, 3, 4, 5]
- 非完全二叉树的空间浪费:
若树结构为单链(仅有左子结点),高度为 h 的树需 2h−1 个存储空间,实际仅用 h 个,空间利用率低。
(二)链式存储(二叉链表,适合任意二叉树)
- 结点结构:
typedef int BTDataType; typedef struct BinaryTreeNode {struct BinaryTreeNode* left; // 左子结点指针struct BinaryTreeNode* right; // 右子结点指针BTDataType val; // 数据域 } BTNode;
- 创建与连接结点:
// 创建新结点 BTNode* BuyBTNode(BTDataType val) {BTNode* newNode = (BTNode*)malloc(sizeof(BTNode));if (!newNode) { perror("malloc failed"); return NULL; }newNode->val = val;newNode->left = newNode->right = NULL; // 初始左右子结点为空return newNode; }// 构建二叉树示例:1为根,左子2,右子4,2的左子3,4的左子5、右子6 BTNode* CreateTree() {BTNode* n1 = BuyBTNode(1);BTNode* n2 = BuyBTNode(2);BTNode* n3 = BuyBTNode(3);BTNode* n4 = BuyBTNode(4);BTNode* n5 = BuyBTNode(5);BTNode* n6 = BuyBTNode(6);n1->left = n2; n1->right = n4; // 根结点连接左右子n2->left = n3; // 2号结点仅有左子n4->left = n5; n4->right = n6; // 4号结点有左右子return n1; // 返回根结点 }
- 优缺点:
- 优点:动态灵活,无空间浪费,适合频繁插入 / 删除。
- 缺点:需额外指针空间(每个结点两个指针),实现复杂度高于顺序存储。
四、遍历方式:递归遍历与层序遍历的实现与原理
(一)深度优先遍历(DFS,递归实现)
1. 前序遍历(根→左→右)
- 访问顺序:先访问根结点,再递归前序遍历左子树,最后递归前序遍历右子树。
- 代码实现:
void PreOrder(BTNode* root) {if (root == NULL) { printf("N "); // 空结点标记为"N"return; }printf("%d ", root->val); // 访问根PreOrder(root->left); // 递归左子树PreOrder(root->right); // 递归右子树 }
- 示例输出(树结构同上):
1 2 3 N N N 4 5 N N 6 N N
(注:N 表示空结点,实际应用中可省略,此处为展示结构)
2. 中序遍历(左→根→右)
- 访问顺序:先递归中序遍历左子树,再访问根结点,最后递归中序遍历右子树。
- 代码实现:
void InOrder(BTNode* root) {if (root == NULL) { printf("N "); return; }InOrder(root->left); // 递归左子树printf("%d ", root->val); // 访问根InOrder(root->right); // 递归右子树 }
- 示例输出:
N 3 N 2 N 1 N 5 N 4 N 6 N
(实际非空结点中序:3 2 1 5 4 6)
3. 后序遍历(左→右→根)
- 访问顺序:先递归后序遍历左子树,再递归后序遍历右子树,最后访问根结点。
- 代码实现:
void PostOrder(BTNode* root) {if (root == NULL) { printf("N "); return; }PostOrder(root->left); // 递归左子树PostOrder(root->right); // 递归右子树printf("%d ", root->val); // 访问根 }
- 示例输出:
N N 3 N N 2 N N 5 N 6 4 1
(实际非空结点后序:3 2 5 6 4 1)
(二)广度优先遍历(BFS,层序遍历)
- 实现原理:借助队列,按层从上到下、每层从左到右访问结点。
- 代码步骤:
- 根结点入队。
- 循环取出队首结点,访问后将其左右子结点(若存在)入队。
- 直到队列为空。
- 代码实现(假设队列已实现):
void LevelOrder(BTNode* root) {Queue q;QueueInit(&q); // 初始化队列if (root) QueuePush(&q, root); // 根结点入队(非空时)while (!QueueEmpty(&q)) {BTNode* node = QueueFront(&q); // 取队首结点QueuePop(&q); // 出队printf("%d ", node->val); // 访问结点// 左右子结点入队if (node->left) QueuePush(&q, node->left);if (node->right) QueuePush(&q, node->right);}QueueDestroy(&q); // 销毁队列,释放内存 }
- 示例输出(树结构同上):
1 2 4 3 5 6
五、堆:基于完全二叉树的高效数据结构
(一)堆的定义与性质
- 定义:满足 “堆性质” 的完全二叉树,分为两种类型:
- 大根堆:父结点值 ≥ 子结点值(根结点为最大值)。
- 小根堆:父结点值 ≤ 子结点值(根结点为最小值)。
- 存储结构:用数组实现,下标对应完全二叉树结点编号。
- 堆性质:对于数组下标 i,有:
- 大根堆:a[i]≥a[2i+1] 且 a[i]≥a[2i+2](若子结点存在)。
- 小根堆:a[i]≤a[2i+1] 且 a[i]≤a[2i+2]。
(二)核心操作:向上调整与向下调整
1. 向上调整(插入新结点时恢复堆性质)
- 场景:新结点插入堆尾(数组末尾),可能破坏堆性质,需与父结点比较并交换。
- 算法步骤:
- 计算新结点下标 child,父结点 parent=(child−1)/2。
- 若 child>0 且新结点值大于父结点(大根堆),交换两者,更新 child=parent,重复直至堆性质满足。
- 代码实现(大根堆):
void AdjustUp(HPDataType* 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;} else {break; // 已满足堆性质,停止}} }
2. 向下调整(删除堆顶时恢复堆性质)
- 场景:堆顶元素删除后,将堆尾元素移至堆顶,需与子结点比较并交换。
- 算法前提:左右子树已满足堆性质。
- 算法步骤:
- 设当前结点为 parent,左子结点 child=2parent+1。
- 找到左右子结点中较大者(大根堆),若较大子结点值大于父结点,交换两者,更新 parent=child,重复直至堆性质满足。
- 代码实现(大根堆):
void AdjustDown(HPDataType* a, int n, int parent) {int child = 2 * parent + 1; // 左子结点下标while (child < n) { // 子结点存在// 若右子结点存在且更大,选右子结点if (child + 1 < n && a[child + 1] > a[child]) {child++;}// 若子结点大于父结点,交换if (a[child] > a[parent]) {Swap(&a[child], &a[parent]);parent = child;child = 2 * parent + 1;} else {break; // 已满足堆性质,停止}} }
(三)堆排序:高效的原地排序算法
- 步骤(升序排序,构建大根堆):
- 建堆:对数组从最后一个分支结点开始,自底向上调用
AdjustDown
,时间复杂度 O(n)。 - 排序:每次将堆顶(最大值)与堆尾元素交换,堆大小减 1,对新堆顶调用
AdjustDown
,重复直至堆空。
- 建堆:对数组从最后一个分支结点开始,自底向上调用
- 代码框架:
void HeapSort(int* a, int n) {// 1. 建大根堆for (int i = (n-1-1)/2; i >= 0; i--) { // 最后一个分支结点的父结点AdjustDown(a, n, i);}// 2. 排序:交换堆顶与堆尾,调整堆int end = n - 1;while (end > 0) {Swap(&a[0], &a[end]); // 最大值放到末尾AdjustDown(a, end, 0); // 调整前end个元素为堆end--;} }
- 时间复杂度:建堆 O(n) + 排序 O(nlogn),总复杂度 O(nlogn)。
六、二叉树的重要性质与经典问题求解
(一)核心性质推导
1. 叶子结点数公式:n0=n2+1
- 推导过程:
设二叉树中度为 0、1、2 的结点数分别为 n0、n1、n2,总结点数 n=n0+n1+n2。
二叉树边数 E=n−1(每个结点除根外有且仅有一个父结点),同时边数等于各结点度之和:E=0⋅n0+1⋅n1+2⋅n2。
联立得:n0+n1+n2−1=n1+2n2,化简得 n0=n2+1。
2. 完全二叉树高度计算
- 公式:高度 h=⌊log2n⌋+1,其中 n 为结点数。
- 推导:高度为 h 的完全二叉树结点数范围:2h−1≤n≤2h−1,取对数得 h−1≤log2n<h,故 h=⌊log2n⌋+1。
(二)经典例题解析
例题 1:某二叉树有 399 个结点,其中 199 个度为 2 的结点,求叶子结点数。
- 解答:根据 n0=n2+1=199+1=200
例题 2:判断完全二叉树(层序序列 ABCDEFGH)的前序序列。
- 分析:完全二叉树结构如下:
前序遍历顺序:A → B → D → H → E → C → F → G
七、实战应用:判断完全二叉树与销毁二叉树
(一)判断完全二叉树(层序遍历法)
- 算法思路:
- 层序遍历二叉树,遇到第一个空结点后,后续结点必须全为空。
- 用队列记录结点,当某结点为空时,标记 “已进入空结点阶段”,后续若再遇到非空结点,返回 false。
- 代码实现:
bool IsCompleteBinaryTree(BTNode* root) {Queue q;QueueInit(&q);if (root) QueuePush(&q, root); // 根结点入队(非空时)bool hasNull = false; // 是否已遇到空结点while (!QueueEmpty(&q)) {BTNode* node = QueueFront(&q);QueuePop(&q);if (node == NULL) {hasNull = true; // 标记进入空结点阶段} else {if (hasNull) {// 空结点之后出现非空结点,非完全二叉树QueueDestroy(&q);return false;}// 非空结点,左右子结点入队(空结点也需入队,用于标记)QueuePush(&q, node->left);QueuePush(&q, node->right);}}QueueDestroy(&q);return true; }
(二)销毁二叉树(递归释放内存)
- 注意事项:二叉树结点通过动态内存分配创建,需递归释放每个结点及其子树。
- 代码实现:
void DestroyBinaryTree(BTNode** root) {if (*root == NULL) return; // 空树直接返回// 先销毁左子树和右子树DestroyBinaryTree(&(*root)->left);DestroyBinaryTree(&(*root)->right);// 释放当前结点内存free(*root);*root = NULL; // 防止野指针 }
八、总结与拓展方向
(一)核心价值
二叉树是理解树结构的基础,其递归定义和层次特性支撑了大量算法:
- 理论层面:满二叉树与完全二叉树的数学性质为算法分析提供依据。
- 应用层面:堆排序、TOP-K 问题、文件系统目录结构、编译器语法树等均依赖二叉树思想。
(二)拓展学习建议
- 算法题实践:
对称二叉树、二叉树的中序遍历 、 二叉树的前序遍历 、相同的树 、 另一棵树的子树
- 数据结构进阶:
- 二叉搜索树(BST)、AVL 树、红黑树(自平衡二叉树)。
- 哈夫曼树(带权路径最短的二叉树,用于压缩算法)。
- 工程实现:
- 用 C++ 模板实现链式二叉树,支持动态插入、删除、遍历。
- 用 Python 实现堆结构,解决实际场景中的 TOP-K 问题。
附录
二叉树
//Queue.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>//typedef int QDataType;
typedef struct BinaryTreeNode* QDataType;
typedef struct QListNode
{struct QListNode* next;QDataType data;
}QNode;// 队列的结构
typedef struct Queue
{QNode* front;QNode* rear;
}Queue;// 初始化队列
void QueueInit(Queue* q);
// 队尾入队列
void QueuePush(Queue* q, QDataType data);
// 队头出队列
void QueuePop(Queue* q);
// 获取队列头部元素
QDataType QueueFront(Queue* q);
// 获取队列队尾元素
QDataType QueueBack(Queue* q);
// 获取队列中有效元素个数
int QueueSize(Queue* q);
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
bool QueueEmpty(Queue* q);
// 销毁队列
void QueueDestroy(Queue* q);
//Tree.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
#include<math.h>
typedef char BTDataType;
typedef struct BinaryTreeNode
{BTDataType data;struct BinaryTreeNode* left;struct BinaryTreeNode* right;
}BTNode;
BTNode* BuyBTNode(BTDataType x);
void PreOrder(BTNode* root);
void InOrder(BTNode* root);
void PostOrder(BTNode* root);
int BTNSize(BTNode* root);
int BTLSize(BTNode* root);
int BTLKSize(BTNode* root, int k);
int BTDepth(BTNode* root);
BTNode* BTFind(BTNode* root, BTDataType x);
void BTDestroy(BTNode**root);
void LevelOrder(BTNode* root);
bool BTComplete(BTNode* root);
//Queue.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"Queue.h"
// 初始化队列
void QueueInit(Queue* q)
{q->front = q->rear = NULL;
}
// 队尾入队列
void QueuePush(Queue* q, QDataType data)
{QNode* newnode = (QNode*)malloc(sizeof(QNode));if (newnode == NULL){perror("malloc fail1");exit(1);}newnode->data = data;newnode->next = NULL;if (q->front == NULL){q->front = q->rear = newnode;}else{q->rear->next = newnode;q->rear = newnode;}
}
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
bool QueueEmpty(Queue* q)
{assert(q);return q->front == NULL;
}
// 队头出队列
void QueuePop(Queue* q)
{assert(!QueueEmpty(q));if (q->front == q->rear){free(q->front);q->front = q->rear = NULL;}else{QNode* tmp = q->front->next;free(q->front);q->front = tmp;}
}
// 获取队列头部元素
QDataType QueueFront(Queue* q)
{assert(!QueueEmpty(q));return q->front->data;
}
// 获取队列队尾元素
QDataType QueueBack(Queue* q)
{assert(!QueueEmpty(q));return q->rear->data;}
// 获取队列中有效元素个数
int QueueSize(Queue* q)
{assert(q);int count = 0;QNode* tmp = q->front;while (tmp != NULL){count++;tmp = tmp->next;}return count;
}
// 销毁队列
void QueueDestroy(Queue* q)
{assert(q);QNode* pcur = q->front;while (q->front){QNode* tmp = q->front->next;free(q->front);q->front = tmp;}q->front = q->rear = NULL;
}
//Tree.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"Tree.h"
#include"Queue.h"
BTNode* BuyBTNode(BTDataType x)
{BTNode* tmp = (BTNode*)malloc(sizeof(BTNode));if (tmp == NULL){perror("malloc fail");exit(1);}tmp->data = x;tmp->left = tmp->right = NULL;return tmp;
}
void PreOrder(BTNode* root)
{if (root == NULL){printf("NULL ");return ;}printf("%c ", root->data);PreOrder(root->left);PreOrder(root->right);
}
void InOrder(BTNode* root)
{if (root == NULL){printf("NULL ");return ;}InOrder(root->left);printf("%c ", root->data);InOrder(root->right);
}
void PostOrder(BTNode* root)
{if (root == NULL){printf("NULL ");return ;}PostOrder(root->left);PostOrder(root->right);printf("%c ", root->data);
}
int BTNSize(BTNode* root)
{if (root == NULL){return 0;}return 1 + BTNSize(root->left) + BTNSize(root->right);
}
int BTLSize(BTNode* root)
{if (root == NULL){return 0;}if ((root->left == NULL) && (root->right == NULL)){return 1;}return BTLSize(root->left) + BTLSize(root->right);
}
int BTLKSize(BTNode* root, int k){if (root == NULL){return 0;}if (k == 1){return 1;}return BTLKSize(root->left, k--) + BTLKSize(root->right, k--);
}
int BTDepth(BTNode* root)
{if (root == NULL){return 0;}int ld = BTDepth(root->left);int rd = BTDepth(root->right);return 1 + (ld > rd ? ld : rd);
}
BTNode* BTFind(BTNode* root, BTDataType x)
{if (root == NULL){return NULL;}if (root->data == x){return root;}BTNode* left=BTFind(root->left,x);if (left){return left;}BTNode* right=BTFind(root->right,x);if (right){return right;}return NULL;
}
void BTDestroy(BTNode** root)
{if (root == NULL){return;}BTDestroy(&((*root)->left));BTDestroy(&((*root)->right));free(*root);*root = NULL;
}
void LevelOrder(BTNode* root)
{Queue q;QueueInit(&q);QueuePush(&q, root);while (!QueueEmpty(&q)){BTNode* top = QueueFront(&q);QueuePop(&q);printf("%c ", top->data);if (top->left)QueuePush(&q, top->left);if (top->right)QueuePush(&q, top->right);}QueueDestroy(&q);
}
bool BTComplete(BTNode* root)
{Queue q;QueueInit(&q);QueuePush(&q, root);while (!QueueEmpty(&q)){BTNode* top = QueueFront(&q);QueuePop(&q);if (top == NULL){break;}QueuePush(&q, top->left);QueuePush(&q, top->right);}while (!QueueEmpty(&q)){BTNode* top = QueueFront(&q);QueuePop(&q);if (top != NULL){QueueDestroy(&q);return false;}}QueueDestroy(&q);return true;
}
堆
//Heap.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int HPDataType;
typedef struct Heap
{HPDataType* arr;int size;int capacity;
}HP;
// 堆的初始
void HPInit(HP* ph);
// 堆的销毁
void HPDestroy(HP* ph);
// 堆的插入
void HPPush(HP* ph, HPDataType x);
// 堆的删除
void HPPop(HP* ph);
// 取堆顶的数据
HPDataType HeapTop(HP* ph);
// 堆的数据个数
int HPSize(HP* ph);
// 堆的判空
int HPEmpty(HP* ph);
//void HPPrint(HP* ph);
void HPPrint(HPDataType* arr, int size);
void HPjustUp(HPDataType* arr, int child);
//void HPjustDown(HPDataType* arr, int n);
void HPjustDown(HPDataType* arr, int parent, int n);
void Swap(HPDataType* a, HPDataType* b);
//Heap.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"Heap.h"
void HPInit(HP* ph)
{assert(ph);ph->arr = NULL;ph->capacity = ph->size = 0;
}
void HPDestroy(HP* ph)
{assert(ph);if (ph->arr == NULL)free(ph->arr);ph->arr = NULL;ph->capacity = ph->size = 0;
}
void Swap(HPDataType* a, HPDataType* b)
{HPDataType tmp = *a;*a = *b;*b = tmp;
}
void HPjustUp(HPDataType* arr,int child)
{int parent = (child - 1) / 2;while (child>0){if (arr[child] < arr[parent]){Swap(&arr[child], &arr[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}
}
void HPPush(HP* ph, HPDataType x)
{assert(ph);if (ph->capacity == ph->size){int newcapacity = ph->capacity == 0 ? 1 : 2 * ph->capacity;HPDataType* tmp = (HPDataType*)realloc(ph->arr, newcapacity * sizeof(HPDataType));if (tmp == NULL){perror("relloc fail");exit(1);}ph->arr = tmp;ph->capacity = newcapacity;}ph->arr[ph->size] = x;HPjustUp(ph->arr, ph->size);++ph->size;
}
int HPEmpty(HP* ph)
{assert(ph);return ph->size==0;
}
void HPjustDown(HPDataType* arr,int parent, int n)
{int child = parent * 2 + 1;while (child < n){if (child + 1 < n && arr[child] > arr[child + 1]){child++;}if (arr[child] < arr[parent]){Swap(&arr[child], &arr[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}
void HPPop(HP* ph)
{assert(!HPEmpty(ph));Swap(&ph->arr[0], &ph->arr[ph->size-1]);ph->size--;HPjustDown(ph->arr,0 , ph->size);
}
HPDataType HeapTop(HP* ph)
{assert(!HPEmpty(ph));return ph->arr[0];
}
int HPSize(HP* ph)
{assert(!HPEmpty(ph));return ph->size;
}
//void HPPrint(HP* ph)
//{
// assert(!HPEmpty(ph));
// int tmp = 0;
// while (tmp<ph->size)
// {
// printf("%d ", ph->arr[tmp]);
// tmp++;
// }
// printf("\n");
//}
void HPPrint(HPDataType* arr, int size)
{int tmp = 0;while (tmp < size){printf("%d ", arr[tmp]);tmp++;}printf("\n");
}
相关文章:
二叉树深度解析:从基础概念到算法实现与应用
一、二叉树的本质定义与核心特性 (一)递归定义与逻辑结构 二叉树是一种 严格有序的树结构,其递归定义为: 空树:不含任何结点的集合,是二叉树的特殊形态。非空二叉树:由以下三部分组成&#x…...
Model Context Protocol(MCP)模型上下文协议
Model Context Protocol(MCP)模型上下文协议 前言一、什么是MCP二、MCP的作用三、MCP与Function call对比四、构建一个简单的MCP DEMO环境准备实现MCP Server运行 ServerMCP Client端配置验证 总结 前言 在Agent时代,将Agent确立为大模型未来…...
代码随想录算法训练营第十六天
LeetCode题目: 530. 二叉搜索树的最小绝对差501. 二叉搜索树中的众数236. 二叉树的最近公共祖先3272. 统计好整数的数目(每日一题) 其他: 今日总结 往期打卡 530. 二叉搜索树的最小绝对差 跳转: 530. 二叉搜索树的最小绝对差 学习: 代码随想录公开讲解 问题: 给你一个二叉搜…...
类似东郊到家的上门按摩预约服务系统小程序APP源码全开源
🔥 为什么上门按摩正在席卷全国? 万亿蓝海市场爆发 2024年中国按摩市场规模突破8000亿,上门服务增速达65% 90后成消费主力,**72%**白领每月至少使用1次上门按摩(数据来源:艾媒咨询) 传统痛点…...
MySQL 5.7.30 Linux 二进制安装包详解及安装指南
MySQL 5.7.30 Linux 安装包详解 mysql-5.7.30-linux-glibc2.12-x86_64.tar 是 MySQL 服务器 5.7.30 版本的 Linux 二进制发行包。 mysql-5.7.30-linux-glibc2.12-x86_64.tar 安装包下载 链接:https://pan.quark.cn/s/2943cd209ca5 包信息 版本: MySQL 5.7.30 平…...
C语言超详细指针知识(二)
在上一篇有关指针的博客中,我们介绍了指针的基础知识,如:内存与地址,解引用操作符,野指针等,今天我们将更加深入的学习指针的其他知识。 1.指针的使用和传址调用 1.1strlen的模拟实现 库函数strlen的功能是…...
Java集合框架详解:核心类、使用场景与最佳实践
文章目录 一、Java集合框架概览二、核心集合类详解1. List接口(有序、可重复)**ArrayList****LinkedList****List对比表** 2. Set接口(无序、唯一)**HashSet****TreeSet****Set对比表** 3. Queue接口(队列)…...
模板引擎语法-标签
模板引擎语法-标签 文章目录 模板引擎语法-标签[toc]一、用于进行判断的{% if-elif-else-endif %}标签二、关于循环对象的{% for-endfor %}标签三、关于自动转义的{% autoescape-endautoescape %}标签四、关于循环对象的{% cycle %}标签五、关于检查值是否变化的{% ifchange %}…...
刘火良FreeRTOS内核实现与应用学习之7——任务延时列表
在《刘火良FreeRTOS内核实现与应用学习之6——多优先级》的基础上:关键是添加了全局变量:xNextTaskUnblockTime ,与延时列表(xDelayedTaskList1、xDelayedTaskList2)来高效率的实现延时。 以前需要在扫描就绪列表中所…...
基于红外的语音传输及通信系统设计
标题:基于红外的语音传输及通信系统设计 内容:1.摘要 本设计聚焦于基于红外的语音传输及通信系统,以解决传统通信方式在特定场景下的局限性为背景,旨在开发一种高效、稳定且具有一定抗干扰能力的语音传输系统。方法上,采用红外技术作为语音信…...
解锁AI未来,开启创新之旅——《GPTs开发详解》与《ChatGPT 4应用详解》两本书的深度解析
前言 在这个数字化时代,AI技术正在以前所未有的速度改变我们的生活和工作方式。作为一名AI爱好者和从业者,我深知了解并掌握先进技术的重要性。今天,我想向大家推荐两本极具价值的书籍:《GPTs开发详解》和《ChatGPT 4应用详解》。…...
Linux进程通信入门:匿名管道的原理、实现与应用场景
Linux系列 文章目录 Linux系列前言一、进程通信的目的二、进程通信的原理2.1 进程通信是什么2.2 匿名管道通讯的原理 三、进程通讯的使用总结 前言 Linux进程间同通讯(IPC)是多个进程之间交换数据和协调行为的重要机制,是我们学习Linux操作系…...
[SpringMVC]上手案例
创建工程 新建项目,选择maven工程,原型(Archetype)选择maven的webapp,注意名称头尾。会使用到tomcat(因为是javaWeb)。 新建的项目结构目录如下,如果没有java目录,需要自…...
kubernetes 入门篇之架构介绍
经过前段时间的学习和实践,对k8s的架构有了一个大致的理解。 1. k8s 分层架构 架构层级核心组件控制平面层etcd、API Server、Scheduler、Controller Manager工作节点层Kubelet、Kube-proxy、CRI(容器运行时接口)、CNI(网络插件&…...
说一说 Spring 中的事务
什么是事务? 事务就是用户定义的一系列执行SQL语句的操作, 这些操作要么完全地执行,要么完全地都不执行, 它是一个不可分割的工作执行单元。 Spring 中的事务是怎么实现的? Spring事务底层是基于数据库事务和AOP机制的首先对于…...
docker容器安装的可道云挂接宿主机的硬盘目录:解决群晖 威联通 飞牛云等nas的硬盘挂接问题
基于Docker部署可道云(KodCloud)时,通过挂载宿主机其他磁盘目录可实现高效、安全的数据管理。具体而言,使用绑定挂载(Bind Mounts)将宿主机目录(如/data/disk2)映射到容器内的可道云…...
Oracle 23ai Vector Search 系列之5 向量索引(Vector Indexes)
文章目录 Oracle 23ai Vector Search 系列之5 向量索引Oracle 23ai支持的向量索引类型内存中的邻居图向量索引 (In-Memory Neighbor Graph Vector Index)磁盘上的邻居分区矢量索引 (Neighbor Partition Vector Index) 创建向量索引HNSW索引IVF索引 向量索引示例参考 Windows 环…...
GPT模型架构与文本生成技术深度解析
核心发现概述 本文通过系统分析OpenAI的GPT系列模型架构,揭示其基于Transformer解码器的核心设计原理与文本生成机制。研究显示,GPT模型通过自回归机制实现上下文感知的序列生成,其堆叠式解码器结构配合创新的位置编码方案,可有效…...
【读者求助】如何跨行业进入招聘岗位?
文章目录 读者留言回信岗位细分1. 中介公司的招聘岗位2. 猎头专员3. 公司的招聘专员选择建议 面试建议1. 请简单介绍你过去 3 年的招聘工作经历,重点说下你负责的岗位类型和规模2. 你在招聘流程中最常用的渠道有哪些?如何评估渠道效果?3. 当你…...
2025蓝桥杯省赛C++B组解题思路
由于题面还没出来,现在先口胡一下思路 填空题直接打表找规律或者乱搞一下就能出,从大题开始说。 1,题意: 给你一个数组,这个数组里有几个数可以被一个连续递增的数字区间求和得出 思路:诈骗题,显…...
springcloud整理
问题1.服务拆分后如何进行服务之间的调用 我们该如何跨服务调用,准确的说,如何在cart-service中获取item-service服务中的提供的商品数据呢? 解决办法:Spring给我们提供了一个RestTemplate的API,可以方便的实现Http请…...
游戏引擎学习第220天
介绍 今天的工作主要是进行一些代码整理和清理,目的是将我们之前写过的代码重新整合在一起,使它们能够更好地协同工作。现在的阶段,我们的任务并不是进行大规模的功能开发,而是集中精力对现有的代码进行整合和思考,确…...
OceanBase企业版单机部署:obd命令行方式
OceanBase企业版单机部署:obd命令行方式 安装包准备服务器准备最低资源配置是否部署ODP组件?仲裁服务器 服务器配置操作系统内核参数BIOS设置磁盘挂载网卡设置 obd部署前配置obd部署单机版安装obd配置obd部署OB集群部署后检查 环境清理与集群销毁 本文介…...
KWDB创作者计划—KWDB认知引擎:数据流动架构与时空感知计算的范式突破
引言:数据智能的第三范式 在数字化转型进入深水区的2025年,企业数据系统正面临三重悖论:数据规模指数级增长与实时决策需求之间的矛盾、多模态数据孤岛与业务连续性要求之间的冲突、静态存储范式与动态场景适配之间的鸿沟。KWDB(K…...
车载通信系统中基于ISO26262的功能安全与抗辐照协同设计研究
摘要:随着智能网联汽车的快速发展,车载通信系统正面临着功能安全与抗辐照设计的双重挑战。在高可靠性要求的车载应用场景下,如何实现功能安全标准与抗辐照技术的协同优化,构建满足ISO26262安全完整性等级要求的可靠通信架构&#…...
Oracle OCP认证考试考点详解083系列03
题记: 本系列主要讲解Oracle OCP认证考试考点(题目),适用于19C/21C,跟着学OCP考试必过。 11. 第11题: 题目 解析及答案: 关于 RMAN(恢复管理器)多路复用备份集,以下哪…...
Spring
一.Ioc&DI 1.类的五种控制反转注解 这五个注解作用都一样,只是意义不一样,用来提高代码的可读性。 Controller:控制层,接收请求,对请求进⾏处理,并进⾏响应。 Servie:业务逻辑层࿰…...
基于开源链动2+1模式、AI智能名片与S2B2C商城小程序源码的体验式关系深化与商业转化研究
摘要:本文探讨了通过体验过程中的共同经历强化关系,促使KOC(关键意见消费者)为品牌背书的机制,并深入分析了开源链动21模式、AI智能名片以及S2B2C商城小程序源码在其中的创新应用。研究发现,这些新模式和技…...
【区块链安全 | 第三十九篇】合约审计之delegatecall(一)
文章目录 外部调用函数calldelegatecall call 与 delegatecall 的区别示例部署后初始状态调用B.testCall()函数调用B.testDelegatecall()函数区别总结 漏洞代码代码审计攻击代码攻击原理解析攻击流程修复建议审计思路 外部调用函数 在 Solidity 中,常见的两种底层外…...
Kingbase 常用运维命令总结
一、数据库连接与基础操作 连接指定服务器数据库 ksql -h 主机IP -p 端口号 -U 用户名 -d 数据库名 -W # 示例:连接 IP 为 192.168.1.100 的数据库 ksql -h 192.168.1.100 -p 54321 -U system -d test -W 断开数据库连接 \q 或 exit 查看数据库列表及详细信息…...
从零开始的C++编程 2(类和对象下)
目录 1.构造函数初始化列表 2.类型转换 3.static成员 4.友元 5.内部类 6.匿名对象 1.构造函数初始化列表 ①之前我们实现构造函数时,初始化成员变量主要使⽤函数体内赋值,构造函数初始化还有⼀种⽅式,就是初始化列表,初始化…...
Java---抽象类与接口
抽象类与接口 前言一、抽象类1.抽象类的概念2.抽象类的语法3.抽象类的特点4.抽象类的操作5.抽象类的作用 二、接口1.接口的概念2.接口语法3.接口的使用与特性4.实现多个接口5.接口之间的继承6.接口的实例(1).对象大小的比较(1).Comparable接口(2).Comparator接口 (2).实现类的克…...
玩转Docker | 使用Docker部署linkding书签管理工具
玩转Docker | 使用Docker部署linkding书签管理工具 前言一、linkding介绍简介主要特点二、系统要求环境要求环境检查Docker版本检查检查操作系统版本三、部署linkding服务下载镜像创建容器检查容器状态检查服务端口设置登录账号与密码安全设置四、访问linkding服务访问linkding…...
K8s 集群网络疑难杂症:解决 CNI 网络接口宕机告警的完整指南
引言 在 Kubernetes 集群运维过程中,网络问题往往是最棘手的故障之一。当你收到一条 [CRITICAL] 网络接口宕机 (172.18.109.55:9100) 的告警,并且告警内容显示 172.18.109.55:9100 的网络接口 cni0 已宕机5分钟 时,这通常意味着你的 Kubernetes 集群中有一个节点的容器网络…...
程序员/运维绘图工具---Mermaid
效果 介绍 Mermaid 是一种基于文本的图表生成工具,通过类似 Markdown 的简洁语法快速创建流程图、甘特图、类图等各类专业图表 应用场景 程序员绘图 系统架构图&代码逻辑可视化 项目管理图 数据可视化 AI辅助生成:LLM生成mermaid代码然后去渲染成…...
《MATLAB实战训练营:从入门到工业级应用》趣味入门篇-用MATLAB画一朵会动的3D玫瑰:从零开始的浪漫编程之旅
《MATLAB实战训练营:从入门到工业级应用》趣味入门篇-🌹用MATLAB画一朵会动的3D玫瑰:从零开始的浪漫编程之旅 你是否想过用代码创造一朵永不凋谢的玫瑰?今天,我将带你走进MATLAB的奇妙世界,用数学公式和编…...
激光院董事长龚赤坤到北京研发中心检查指导工作
4月11日,激光院党委书记、董事长龚赤坤到北京研发中心检查指导工作。 龚赤坤详细了解了北京研发中心的建设情况和科研进展,充分肯定所取得的成绩,对发展寄予厚望,龚赤坤指出北京研发中心的成立正处于激光院加速发展与产业进化的关…...
AbortController:让异步操作随时说停就停
AbortController:让异步操作随时说停就停 一、什么是 AbortController? AbortController 是 JavaScript 在浏览器和部分 Node.js 环境中提供的全局类,用来中止正在进行或待完成的异步操作(如 fetch() 请求、事件监听、可写流、数…...
leetcode572 另一棵树的子树
1.与100、101解法相同 递归: class Solution { private:bool compare(TreeNode* p, TreeNode* q){if(!p && !q) return true;else if(!p || !q) return false;else if(p->val ! q->val) return false;bool leftside compare(p->left, q->lef…...
再看 MPTCP 时的思考
2022 年夏,居家办公时,第一次接手 mptcp 就觉得它不靠谱,以至于我后来搞了 mpudp for DC,再后来我调研了很多 mptcp-based 方案,发现它们都是向善而来,最终灰头土脸而终。mptcp 实则一个坑,业内…...
将三维非平面点集拆分为平面面片的MATLAB实现
将三维非平面点集拆分为平面面片的MATLAB实现 要将三维空间中不在同一平面上的点集拆分为多个平面面片,可以采用以下几种方法: 1. 三角剖分法 (Delaunay Triangulation) 最简单的方法是将点集进行三角剖分,因为三个点总是共面的࿱…...
Python(10.2)Python可变与不可变类型内存机制解密:从底层原理到工程实践
目录 一、类型特性引发的内存现象1.1 电商促销活动事故分析1.2 内存机制核心差异 二、内存地址追踪实验2.1 基础类型验证2.2 复合对象实验 三、深度拷贝内存分析3.1 浅拷贝陷阱3.2 深拷贝实现 四、函数参数传递机制4.1 默认参数陷阱4.2 安全参数模式 五、内存优化最佳实践5.1 字…...
华为hcie证书的有效期怎么判断?
在ICT行业,华为HCIE证书堪称含金量极高的“敲门砖”,拥有它往往意味着在职场上更上一层楼。然而,很多人在辛苦考取HCIE证书后,却对其有效期相关事宜一知半解。今天,咱们就来好好唠唠华为HCIE证书的有效期怎么判断这个关…...
【前端】CSS Grid 布局详解
CSS Grid 布局详解(通俗易懂版) 一、概述 CSS Grid 是一种二维布局系统,可以同时控制行和列,相比 Flex(一维布局),更适合用在整体页面布局或复杂模块结构中。 二、基础概念 Grid 容器&#x…...
物美“外贸转内销”极速绿色通道正式开启
「TMT星球」获悉,在国家“提振消费、扩大内需”及“内外贸一体化”战略指引下,物美集团依托自身零售生态优势,打造“云超绿通”专项通道,助力中国优质外贸企业实现“出口转内销”的高效转型,通过极速绿通、线上线下全渠…...
【说明书#1】Node.js 和 npm安装与使用
系统提示 npm 不是内部或外部命令,也不是可运行的程序或批处理文件,也就是 npm 命令无法识别。这个错误通常是因为 Node.js 和 npm 没有正确安装,或者它们的路径没有添加到系统的环境变量中。 解决方法如下: 1. 安装 Node.js 和 npm: 如果你还没有安装 Node.js,可以从…...
【触想智能】安卓工业平板电脑和普通商业平板电脑的区别
安卓工业平板电脑是基于ARM架构开发的一种工业平板电脑,它在自助终端、智能制造、产线车间、智慧物流、商业金融等诸多领域有着广泛的应用。 触想安卓工业平板电脑TPC-A2系列 安卓工业平板电脑和普通商业平板电脑在一些方面存在一些区别,包括设计、硬件规…...
Java基于SSM的课程答疑微信小程序【附源码、文档说明】
博主介绍:✌IT徐师兄、7年大厂程序员经历。全网粉丝15W、csdn博客专家、掘金/华为云//InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取源码联系🍅 👇🏻 精彩专栏推荐订阅👇dz…...
模板引擎语法-变量
模板引擎语法-变量 文章目录 模板引擎语法-变量(一)在Django框架模板中使用变量的代码实例(二)在Django框架模板中使用变量对象属性的代码实例(三)在Django框架模板中使用变量显示列表 (一&…...
1260 最大公约数
1260 最大公约数 ⭐️难度:中等 🌟考点:GCD 📖 📚 import java.util.Scanner; import java.util.Arrays;public class Main {public static void main(String[] args) {Scanner sc new Scanner(System.in);int t …...