数据结构C语言描述3(图文结合)--双链表、循环链表、约瑟夫环问题
前言
- 这个专栏将会用纯C实现常用的数据结构和简单的算法;
- 有C基础即可跟着学习,代码均可运行;
- 准备考研的也可跟着写,个人感觉,如果时间充裕,手写一遍比看书、刷题管用很多,这也是本人采用纯C语言实现的原因之一;
- 欢迎收藏 + 关注,本人将会持续更新。
文章目录
- 双向链表
- 简介
- 双链表实现
- 循环链表
- 循环链表约瑟夫环问题
双向链表
简介
🚡 双向链表,对比单链表来说,顾名思义,就是指向指向有两个指针,指向前后节点。
🌾 结合单链表,单链表有无头和有头之分,双向链表也是一样,这里是无头双链表,采用再封装写法,不是二级指针写法,再封装写法比较写起来比较容易,个人比较偏爱🤠🤠🤠🤠
双链表图示:
🉑 从ADT抽象中来说:
总结起来一句话:增删改查,🤠🤠🤠,假设双向链表是一个结合,这这个结合功能有:
- 增加元素
- 删除元素
- 拿取元素
- 查询元素
- 修改元素
- …………………………
双链表实现
这里采用再封装的方法,实现无头链表,有头和无头再单链表的那一节以及讲过了,这里采用无头实现
💠 节点封装
在双链表中,对于每一个节点来说,都有一个指向前、指向后节点的指针。
typedef int DataType;typedef struct Node {DataType data;struct Node* prev; // 前struct Node* next; // 后
}Node;
⚜️ 链表封装
这一步封装的作用是,可以更好的操作链表的一些操作,这个size的一个再封装写法的核心,无头与有头再实现的过程中,核心就在于第一个头节点的处理,无头如果没有任何节点,则插入的节点则作为第一个节点,但是这样会改变指针的指向,这也是因为需要传递二级指针的原因,而再封装写法则很好的解决了这个问题。
typedef struct List {Node* headNode;Node* tailNode;int size;
}List;
🍨 创建节点
采用calloc
创建节点,这样可以自动赋值为0
。
Node* create_node(DataType data)
{Node* node = (Node*)calloc(1, sizeof(Node));assert(node);node->data = data;return node;
}
🍤 创建链表
List* create_list()
{List* list = (List*)calloc(1, sizeof(List));assert(list);return list;
}
🤕 插入–头插
- 情况判断:是否为空链表
- 空链表:插件节点,作为头
- 不为空:头插,如图:
void push_front(List* list, DataType data)
{if (list == NULL) {return;}Node* node = create_node(data);if (list->size == 0) { // 这种写法的优势,不用传递二级指针list->tailNode = node;}else {node->next = list->headNode;list->headNode->prev = node;}list->headNode = node;list->size++;
}
🌮 插入–尾插入
- 情况判断:是否为空链表
- 为空:插入,作为头
- 不为空:找到尾节点,插入
void push_back(List* list, DataType data)
{if (list == NULL) {return;}Node* node = create_node(data);if (list->size == 0) {list->headNode = node;}else {list->tailNode->next = node;node->prev = list->tailNode;}list->tailNode = node;list->size++;
}
💠 插入–任意位置
- 规则: 在元素后位置插入
- 情况:3种
- 没有该元素
- 尾插
- 任意插
void insert(List* list, DataType posData, DataType data)
{if (list == NULL || list->size == 0) { // size != 0 保证有头return;}Node* t = list->headNode;while (t->next != NULL && t->data != posData) {t = t->next;}if (t->data != posData) { // 没有该元素return;}else if (t->next == NULL && t->data == posData) { // 尾插Node* node = create_node(data);node->prev = t;t->next = node;list->tailNode = node; // 尾巴移位}else{Node* node = create_node(data);node->next = t->next;t->next->prev = node;node->prev = t;t->next = node;}list->size++;
}
🎧 删除–头删
注意点:释放后指针指向问题:
- 如果说就一个元素,则删除后,在封装头需要指向NULL
- 如果不是,则下一个元素的prev指针需要赋值为NULL
void pop_front(List* list)
{if (list == NULL || list->size == 0) {return;}Node* node = list->headNode;list->headNode = node->next;free(node);(list->headNode) ? (list->headNode->prev = NULL) : (list->tailNode = NULL); // 判断是否只有一个节点的情况node = NULL;
}
🚖 删除–尾删除
注意点:释放后指针指向问题:
- 如果说就一个元素,则删除后,在封装头需要指向NULL
- 如果不是,则上一个元素的next指针需要赋值为NULL
void pop_back(List* list)
{if (list == NULL || list->size == 0) {return;}Node* node = list->tailNode;list->tailNode = list->tailNode->prev;free(node);(list->tailNode) ? (list->tailNode->next = NULL) : (list->headNode = NULL);list->size--;
}
🦅 删除–任意位置删除
四种情况:
- 没有找到
- 找到了
- 头删
- 尾删
- 任意位置删
void erase(List* list, DataType posData)
{if (list == NULL || list->size == 0) {return;}Node* cur = list->headNode;while (cur->next != NULL && cur->data != posData) {cur = cur->next;}// 没有找到if (cur->data != posData) {return;}else if (cur->next == NULL) { // 尾删除pop_back(list);}else if (cur->prev == NULL) { // 头删pop_front(list);}else {Node* t = cur;cur->prev->next = cur->next;cur->next->prev = cur->prev;free(t);t = NULL;list->size--;}
}
🌐 万金油函数
bool empty(List* list)
{if (list == NULL) {return true;}return list->size == 0;
}size_t size(List* list)
{if (list == NULL) {return 0;}return list->size;
}
📤 向前遍历
void travel_front(List* list)
{if (list == NULL) {return;}Node* cur = list->headNode;while (cur) {printf("%d ", cur->data);cur = cur->next;}printf("\n");
}
🎉 向后遍历
void travel_back(List* list)
{if (list == NULL) {return;}Node* cur = list->tailNode;while (cur) {printf("%d ", cur->data);cur = cur->prev;}printf("\n");
}
⏰ 总代码
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>typedef int DataType;typedef struct Node {DataType data;struct Node* prev;struct Node* next;
}Node;typedef struct List {Node* headNode;Node* tailNode;int size;
}List;Node* create_node(DataType data)
{Node* node = (Node*)calloc(1, sizeof(Node));assert(node);node->data = data;return node;
}List* create_list()
{List* list = (List*)calloc(1, sizeof(List));assert(list);return list;
}void push_front(List* list, DataType data)
{if (list == NULL) {return;}Node* node = create_node(data);if (list->size == 0) {list->tailNode = node;}else {node->next = list->headNode;list->headNode->prev = node;}list->headNode = node;list->size++;
}void push_back(List* list, DataType data)
{if (list == NULL) {return;}Node* node = create_node(data);if (list->size == 0) {list->headNode = node;}else {list->tailNode->next = node;node->prev = list->tailNode;}list->tailNode = node;list->size++;
}void insert(List* list, DataType posData, DataType data)
{if (list == NULL || list->size == 0) { // size != 0 保证有头return;}Node* t = list->headNode;while (t->next != NULL && t->data != posData) {t = t->next;}if (t->data != posData) { // 没有该元素return;}else if (t->next == NULL && t->data == posData) { // 尾插Node* node = create_node(data);node->prev = t;t->next = node;list->tailNode = node; // 尾巴移位}else{Node* node = create_node(data);node->next = t->next;t->next->prev = node;node->prev = t;t->next = node;}list->size++;
}void pop_front(List* list)
{if (list == NULL || list->size == 0) {return;}Node* node = list->headNode;list->headNode = node->next;free(node);(list->headNode) ? (list->headNode->prev = NULL) : (list->tailNode = NULL); // 判断是否只有一个节点的情况node = NULL;
}void pop_back(List* list)
{if (list == NULL || list->size == 0) {return;}Node* node = list->tailNode;list->tailNode = list->tailNode->prev;free(node);(list->tailNode) ? (list->tailNode->next = NULL) : (list->headNode = NULL);list->size--;
}void erase(List* list, DataType posData)
{if (list == NULL || list->size == 0) {return;}Node* cur = list->headNode;while (cur->next != NULL && cur->data != posData) {cur = cur->next;}// 没有找到if (cur->data != posData) {return;}else if (cur->next == NULL) { // 尾删除pop_back(list);}else if (cur->prev == NULL) { // 头删pop_front(list);}else {Node* t = cur;cur->prev->next = cur->next;cur->next->prev = cur->prev;free(t);t = NULL;list->size--;}
}bool empty(List* list)
{if (list == NULL) {return true;}return list->size == 0;
}size_t size(List* list)
{if (list == NULL) {return 0;}return list->size;
}void travel_front(List* list)
{if (list == NULL) {return;}Node* cur = list->headNode;while (cur) {printf("%d ", cur->data);cur = cur->next;}printf("\n");
}void travel_back(List* list)
{if (list == NULL) {return;}Node* cur = list->tailNode;while (cur) {printf("%d ", cur->data);cur = cur->prev;}printf("\n");
}int main()
{List* list = create_list();for (int i = 1; i < 4; i++) {push_front(list, i);}for (int i = 1; i < 4; i++) {push_back(list, i * 10);}travel_front(list);travel_back(list);insert(list, 3, 33);insert(list, 30, 300);travel_front(list);travel_back(list);pop_front(list);travel_front(list);travel_back(list);pop_back(list);travel_front(list);travel_back(list);erase(list, 33);erase(list, 30);erase(list, 10);travel_front(list);travel_back(list);return 0;
}
循环链表
循环链表分为循环单链表,循环双链表,单链表和双链表又分为有头和无头链表,这里是有头循环双链表。
双向循环链表(Doubly Circular Linked List)是一种数据结构,其中每个节点都包含两个指针,一个指向前一个节点,一个指向后一个节点。与普通链表不同的是,双向循环链表的最后一个节点的下一个指针指向头节点,而头节点的前一个指针指向最后一个节点,形成一个循环。
🤕 节点封装
typedef int DataType;typedef struct Node {DataType data;struct Node* prev;struct Node* next;
}Node;
🍨 创建节点
Node* create_node(DataType data)
{Node* node = (Node*)calloc(1, sizeof(Node));assert(node);node->data = data;return node;
}
⚜️ 创建链表
这里需要构建一个循环节点(链表),如图:
Node* create_list()
{Node* list = (Node*)calloc(1, sizeof(Node));assert(list);list->next = list;list->prev = list;return list;
}
🎧 插入–头插
双向头删就很容易了,如图:
void push_front(Node* list, DataType data)
{assert(list);Node* node = create_node(data);node->next = list->next;list->next->prev = node; // 难点,不用担心 next,prev为空的时候node->prev = list;list->next = node;
}
🌮 插入–尾插
尾巴删除也很容易,因为头和尾巴互相找到,如图:
void push_back(Node* list, DataType data)
{assert(list);Node* node = create_node(data);node->prev = list->prev;list->prev->next = node;node->next = list;list->prev = node;
}
⛹️♀️ 插入–任意位置
任意位置也不难,找到要插入的位置,要注意的是找不到的情况。
// 找到要插入那个节点的位置节点
void insert(Node* list, DataType posData ,DataType data)
{assert(list);Node* cur = list->next;while (cur->next != list && cur->data != posData) {cur = cur->next;}if (cur->data != posData) { // 思考,为什么不能 cur->next != list ?????return;}else {Node* node = create_node(data);node->next = cur->next;cur->next->prev = node;node->prev = cur;cur->next = node;}}
👟 删除–头删
注意: 一个节点的头节点指向不同。
两种情况:
- 如果一个元素,这个时候删除,头节点指向修改和两个元素以上删除不同,这个时候头节点需要指向自己
- 两个元素及上
void pop_front(Node* list)
{assert(list);Node* cur = list->next;if (cur == list) { // 无节点return;}else if(cur->next == list) { // 一个节点list->prev = list;list->next = list;}else {list->next = cur->next; // 两个节点以上cur->next->prev = list;}free(cur);cur = NULL;
}
📑 删除–尾删
这个也是简单的,因为可以通过头节点直接找到尾节点,这个时候就只需要一种情况即可,因为创建双链表有一个很好的特性,
void pop_back(Node* list)
{assert(list);Node* cur = list->prev; // 因为可以获取尾节点if (cur == list) {return;}else {cur->prev->next = list; // 哪怕是一个节点,也和普通情况也是一样list->prev = cur->prev; // 这个也是一样,free(cur);cur = NULL;}}
🚴♀ 删除–任意位置
很简单,因为这个也不用记录前驱节点,也不用找尾节点了,只需要考虑两种情况:
- 没有找到
- 找到了
void erase(Node* list, DataType posData)
{assert(list);Node* cur = list->next;while (cur->next != list && cur->data != posData) {cur = cur->next;}if (cur->data != posData) {return;}else {cur->prev->next = cur->next;cur->next->prev = cur->prev;free(cur);cur = NULL;}
}
🔱 遍历
void travel_front(Node* list)
{assert(list);Node* cur = list->next;while (cur != list) {printf("%d ", cur->data);cur = cur->next;}printf("\n");
}void travel_back(Node* list)
{assert(list);Node* cur = list->prev;while (cur != list) {printf("%d ", cur->data);cur = cur->prev;}printf("\n");
}
⚗️ 总代码
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>// 有头链表实现,简单点typedef int DataType;typedef struct Node {DataType data;struct Node* prev;struct Node* next;
}Node;Node* create_node(DataType data)
{Node* node = (Node*)calloc(1, sizeof(Node));assert(node);node->data = data;return node;
}Node* create_list()
{Node* list = (Node*)calloc(1, sizeof(Node));assert(list);list->next = list;list->prev = list;return list;
}void push_front(Node* list, DataType data)
{assert(list);Node* node = create_node(data);node->next = list->next;list->next->prev = node; // 难点,不用担心 next,prev为空的时候node->prev = list;list->next = node;
}void push_back(Node* list, DataType data)
{assert(list);Node* node = create_node(data);node->prev = list->prev;list->prev->next = node;node->next = list;list->prev = node;
}
// 找到要插入那个节点的位置节点
void insert(Node* list, DataType posData ,DataType data)
{assert(list);Node* cur = list->next;while (cur->next != list && cur->data != posData) {cur = cur->next;}if (cur->data != posData) { // 思考,为什么不能 cur->next != list ?????return;}else {Node* node = create_node(data);node->next = cur->next;cur->next->prev = node;node->prev = cur;cur->next = node;}}void pop_front(Node* list)
{assert(list);Node* cur = list->next;if (cur == list) {return;}else if(cur->next == list) {list->prev = list;list->next = list;}else {list->next = cur->next;cur->next->prev = list;}free(cur);cur = NULL;
}void pop_back(Node* list)
{assert(list);Node* cur = list->prev;if (cur == list) {return;}else {cur->prev->next = list;list->prev = cur->prev;free(cur);cur = NULL;}}void erase(Node* list, DataType posData)
{assert(list);Node* cur = list->next;while (cur->next != list && cur->data != posData) {cur = cur->next;}if (cur->data != posData) {return;}else {cur->prev->next = cur->next;cur->next->prev = cur->prev;free(cur);cur = NULL;}
}void travel_front(Node* list)
{assert(list);Node* cur = list->next;while (cur != list) {printf("%d ", cur->data);cur = cur->next;}printf("\n");
}void travel_back(Node* list)
{assert(list);Node* cur = list->prev;while (cur != list) {printf("%d ", cur->data);cur = cur->prev;}printf("\n");
}int main()
{Node* list = create_list();push_front(list, 1);push_front(list, 2);push_front(list, 3);travel_front(list);travel_back(list);push_back(list, 11);push_back(list, 22);push_back(list, 33);travel_front(list);travel_back(list);insert(list, 2, 20);insert(list, 3, 30);insert(list, 33, 330);travel_front(list);travel_back(list);pop_front(list);travel_front(list);travel_back(list);pop_back(list);travel_front(list);travel_back(list);erase(list, 33);travel_front(list);travel_back(list);return 0;
}
循环链表约瑟夫环问题
讲一个比较有意思的故事:约瑟夫是犹太军队的一个将军,在反抗罗马的起义中,他所率领的军队被击溃,只剩下残余的部队40余人,他们都是宁死不屈的人,所以不愿投降做叛徒。一群人表决说要死,所以用一种策略来先后kill所有人。
于是约瑟夫建议:每次由其他两人一起kill一个人,而被kill的人的先后顺序是由抽签决定的,约瑟夫有预谋地抽到了最后一签,在kill了除了他和剩余那个人之外的最后一人,他劝服了另外一个没死的人投降了罗马。
我们这个规则是这么定的:
- 在一间房间总共有n个人(下标0~n-1),只能有最后一个人活命。
- 按照如下规则去排除人:
- 所有人围成一圈
- 顺时针报数,每次报到q的人将被排除掉
- 被排除掉的人将从房间内被移走
- 然后从被kill掉的下一个人重新报数,继续报q,再清除,直到剩余一人
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>/*
* 用上一个链表,也可以。这里采用无头循环双链表实现
* 无头采用再次封装的写法
*/typedef struct Node {int data;struct Node* prev;struct Node* next;
}Node;typedef struct List {Node* headNode;
}List;// 每一个节点创建都是循环
Node* create_node(int data)
{Node* node = (Node*)calloc(1, sizeof(Node));assert(node);node->data = data;node->prev = node;node->next = node;return node;
}void push_back(List* list, int data)
{assert(list);Node* node = create_node(data);if (list->headNode == NULL) {list->headNode = node;}else {Node* cur = list->headNode->prev;node->next = list->headNode;list->headNode->prev = node;cur->next = node;node->prev = cur;}
}void erase(List* list, Node* node)
{assert(list);assert(node);// 一个节点if (node->next == node) {free(node);node = NULL;list->headNode = NULL;}else {node->prev->next = node->next;node->next->prev = node->prev;if (list->headNode == node) { // 防止删除头list->headNode = node->next;}free(node);node = NULL;}
}void travel(List* list)
{Node* cur = list->headNode;while (cur->next != list->headNode) {printf("%d ", cur->data);cur = cur->next;}printf("%d ", cur->data);printf("\n");
}// 只做演示,不考虑内存问题
void game_run(int n, int m)
{if (n < 0 || m < 0) {return;}List list = { NULL };for (int i = 1; i <= n; i++) {push_back(&list, i);}travel(&list);Node* cur = list.headNode;while (n > 1) {// 报数for (int i = 1; i < m; i++) {cur = cur->next;}Node* next = cur->next;erase(&list, cur);// 重置报数cur = next;n--;}printf("天选之子: %d\n", cur->data);
}int main()
{game_run(10, 3);return 0;
}
相关文章:

数据结构C语言描述3(图文结合)--双链表、循环链表、约瑟夫环问题
前言 这个专栏将会用纯C实现常用的数据结构和简单的算法;有C基础即可跟着学习,代码均可运行;准备考研的也可跟着写,个人感觉,如果时间充裕,手写一遍比看书、刷题管用很多,这也是本人采用纯C语言…...
第二十五章 TCP 客户端 服务器通信 - TCP 设备的 READ 命令
文章目录 第二十五章 TCP 客户端 服务器通信 - TCP 设备的 READ 命令TCP 设备的 READ 命令READ 修改 $ZA 和 $ZB$ZA 和 READ 命令 第二十五章 TCP 客户端 服务器通信 - TCP 设备的 READ 命令 TCP 设备的 READ 命令 从服务器或客户端发出 READ 命令以读取客户端或服务器设置的…...

【C++】哈希表的实现详解
哈希表的实现详解 一、哈希常识1.1、哈希概念1.2、哈希冲突1.3、哈希函数(直接定执 除留余数)1.4、哈希冲突解决闭散列(线性探测 二次探测)开散列 二、闭散列哈希表的模拟实现2.1、框架2.2、哈希节点状态的类2.3、哈希表的扩容2…...

高阶C语言之五:(数据)文件
目录 文件名 文件类型 文件指针 文件的打开和关闭 文件打开模式 文件操作函数(顺序) 0、“流” 1、字符输出函数fputc 2、字符输入函数fgetc 3、字符串输出函数fputs 4、 字符串输入函数fgets 5、格式化输入函数fscanf 6、格式化输出函数fpr…...

服务器上部署并启动 Go 语言框架 **GoZero** 的项目
要在服务器上部署并启动 Go 语言框架 **GoZero** 的项目,下面是一步步的操作指南: ### 1. 安装 Go 语言环境 首先,确保你的服务器上已安装 Go 语言。如果还没有安装,可以通过以下步骤进行安装: #### 1.1 安装 Go 语…...

【Java SE 】继承 与 多态 详解
🔥博客主页🔥:【 坊钰_CSDN博客 】 欢迎各位点赞👍评论✍收藏⭐ 目录 1. 继承 1.1 继承的原因 1.2 继承的概念 1.3 继承的语法 2. 子类访问父类 2.1 子类访问父类成员变量 2.1.1 子类与父类不存在同名成员变量 2.1.2 子类…...

【大语言模型】ACL2024论文-16 基于地图制图的罗马尼亚自然语言推理语料库的新型课程学习方法
【大语言模型】ACL2024论文-16 基于地图制图的罗马尼亚自然语言推理语料库的新型课程学习方法 目录 文章目录 【大语言模型】ACL2024论文-16 基于地图制图的罗马尼亚自然语言推理语料库的新型课程学习方法目录摘要:研究背景:问题与挑战:如何解…...
秋招大概到此结束了
1、背景 学院本,软工,秋招只有同程,快手和网易面试,后两家kpi(因为面试就很水),秋招情况:哈啰(实习转正ing),同程测开offer。 2、走测开的原因 很…...
华为OD机试真题---字符串化繁为简
华为OD机试真题中的“字符串化繁为简”题目是一个涉及字符串处理和等效关系传递的问题。以下是对该题目的详细解析: 一、题目描述 给定一个输入字符串,字符串只可能由英文字母(a~z、A~Z)和左右小括号((、)࿰…...

概念解读|K8s/容器云/裸金属/云原生...这些都有什么区别?
随着容器技术的日渐成熟,不少企业用户都对应用系统开展了容器化改造。而在容器基础架构层面,很多运维人员都更熟悉虚拟化环境,对“容器圈”的各种概念容易混淆:容器就是 Kubernetes 吗?容器云又是什么?容器…...
初识Arkts
创建对象: 类: 类声明引入一个新类型,并定义其字段、方法和构造函数。 定义类后,可以使用关键字new创建实例 可以使用对象字面量创建实例 在以下示例中,定义了Person类,该类具有字段name和surname、构造函…...

基本的SELECT语句
1.SQL概述 SQL(Structured Query Language)是一种用于管理和操作关系数据库的编程语言。它是一种标准化的语言,用于执行各种数据库操作,包括创建、查询、插入、更新和删除数据等。 SQL语言具有简单、易学、高效的特点,…...

51c自动驾驶~合集30
我自己的原文哦~ https://blog.51cto.com/whaosoft/12086789 #跨越微小陷阱,行动更加稳健 目前四足机器人的全球市场上,市场份额最大的是哪个国家的企业?A.美国 B.中国 C.其他 波士顿动力四足机器人 云深处 绝影X30 四足机器人 …...
Python Tutor网站调试利器
概述 本文主要是推荐一个网站:Python Tutor. 网站首页写道: Online Compiler, Visual Debugger, and AI Tutor for Python, Java, C, C++, and JavaScript Python Tutor helps you do programming homework assignments in Python, Java, C, C++, and JavaScript. It contai…...
h5小游戏实现获取本机图片
h5小游戏实现获取本机图片 本文使用cocos引擎 1.1 需求 用户通过文件选择框选择图片。将图片内容转换为Cocos Creator的纹理 (cc.Texture2D),将纹理设置到 cc.SpriteFrame 并显示到节点中。 1.2 实现步骤 创建文件输入框用于获取文件 let input document.createElement(&quo…...
前端 javascript a++和++a的区别
前端 javascript a和a的区别 a 是先执行表达式后再自增,执行表达式时使用的是a的原值。a是先自增再执行表达示,执行表达式时使用的是自增后的a。 var a0 console.log(a); // 输出0 console.log(a); // 输出1var a0 console.log(a); // 输出1 console.l…...

OceanBase V4.x应用实践:如何排查表被锁问题
DBA在日常工作中常常会面临以下两种常见情况: 业务人员会提出问题:“表被锁了,导致业务受阻,请帮忙解决。” 业务人员还会反馈:“某个程序通常几秒内就能执行完毕,但现在却运行了好几分钟,不清楚…...

ctfshow-web入门-SSRF(web351-web360)
目录 1、web351 2、web352 3、web353 4、web354 5、web355 6、web356 7、web357 8、web358 9、web359 10、web360 1、web351 看到 curl_exec 函数,很典型的 SSRF 尝试使用 file 协议读文件: urlfile:///etc/passwd 成功读取到 /etc/passwd 同…...
【日常记录-Git】如何为post-checkout脚本传递参数
1. 简介 在Git中,post-checkout 钩子是一个在git checkout 或git switch命令成功执行后自动调用的脚本。该脚本不接受任何来自Git命令的直接参数,因为Git设计该钩子是为了在特定的版本控制操作后执行一些预定义的任务,而不是作为一个通用的脚…...

《机器人控制器设计与编程》考试试卷**********大学2024~2025学年第(1)学期
消除误解,课程资料逐步公开。 复习资料: Arduino-ESP32机器人控制器设计练习题汇总_arduino编程语言 题-CSDN博客 试卷样卷: 开卷考试,时间: 2024年11月16日 001 002 003 004 005 ……………………装………………………...

测试微信模版消息推送
进入“开发接口管理”--“公众平台测试账号”,无需申请公众账号、可在测试账号中体验并测试微信公众平台所有高级接口。 获取access_token: 自定义模版消息: 关注测试号:扫二维码关注测试号。 发送模版消息: import requests da…...

《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》
引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...

vscode(仍待补充)
写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh? debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...

UE5 学习系列(三)创建和移动物体
这篇博客是该系列的第三篇,是在之前两篇博客的基础上展开,主要介绍如何在操作界面中创建和拖动物体,这篇博客跟随的视频链接如下: B 站视频:s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…...

面向无人机海岸带生态系统监测的语义分割基准数据集
描述:海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而,目前该领域仍面临一个挑战,即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...

力扣热题100 k个一组反转链表题解
题目: 代码: func reverseKGroup(head *ListNode, k int) *ListNode {cur : headfor i : 0; i < k; i {if cur nil {return head}cur cur.Next}newHead : reverse(head, cur)head.Next reverseKGroup(cur, k)return newHead }func reverse(start, end *ListNode) *ListN…...
LRU 缓存机制详解与实现(Java版) + 力扣解决
📌 LRU 缓存机制详解与实现(Java版) 一、📖 问题背景 在日常开发中,我们经常会使用 缓存(Cache) 来提升性能。但由于内存有限,缓存不可能无限增长,于是需要策略决定&am…...

MacOS下Homebrew国内镜像加速指南(2025最新国内镜像加速)
macos brew国内镜像加速方法 brew install 加速formula.jws.json下载慢加速 🍺 最新版brew安装慢到怀疑人生?别怕,教你轻松起飞! 最近Homebrew更新至最新版,每次执行 brew 命令时都会自动从官方地址 https://formulae.…...
WebRTC从入门到实践 - 零基础教程
WebRTC从入门到实践 - 零基础教程 目录 WebRTC简介 基础概念 工作原理 开发环境搭建 基础实践 三个实战案例 常见问题解答 1. WebRTC简介 1.1 什么是WebRTC? WebRTC(Web Real-Time Communication)是一个支持网页浏览器进行实时语音…...

Rust 开发环境搭建
环境搭建 1、开发工具RustRover 或者vs code 2、Cygwin64 安装 https://cygwin.com/install.html 在工具终端执行: rustup toolchain install stable-x86_64-pc-windows-gnu rustup default stable-x86_64-pc-windows-gnu 2、Hello World fn main() { println…...