数据结构C语言描述3(图文结合)--双链表、循环链表、约瑟夫环问题

news2024/11/17 11:07:05

前言

  • 这个专栏将会用纯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;
	}

}

👟 删除–头删

注意: 一个节点的头节点指向不同。

两种情况:

  1. 如果一个元素,这个时候删除,头节点指向修改和两个元素以上删除不同,这个时候头节点需要指向自己
  2. 两个元素及上
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;
	}

}

🚴‍♀ 删除–任意位置

很简单,因为这个也不用记录前驱节点,也不用找尾节点了,只需要考虑两种情况:

  1. 没有找到
  2. 找到了
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;
}

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

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

相关文章

7.高可用集群架构Keepalived双主热备原理

一. 高可用集群架构Keepalived双主热备原理 (1)主机+备机keepalived配置(192.168.1.171) ! Configuration File for keepalivedglobal_defs {# 路由id:当前安装keepalived节点主机的标识符,全局唯一router_id keep_101 } #计算机节点(主机配置) vrrp_instance VI_1 {</

深入理解接口测试:实用指南与最佳实践5.0(二)

✨博客主页&#xff1a; https://blog.csdn.net/m0_63815035?typeblog &#x1f497;《博客内容》&#xff1a;.NET、Java.测试开发、Python、Android、Go、Node、Android前端小程序等相关领域知识 &#x1f4e2;博客专栏&#xff1a; https://blog.csdn.net/m0_63815035/cat…

TypeScript简介:TypeScript是JavaScript的一个超集

官方描述&#xff1a;TypeScript 是 JavaScript 的一个超集 GitHub官网&#xff1a;https://github.com/Microsoft/TypeScript TypeScript is a superset of JavaScript that compiles to clean JavaScript output. TypeScript 是 JavaScript 的一个超集&#xff0c;支持 EC…

【计算机网络】UDP网络程序

一、服务端 1.udpServer.hpp 此文件负责实现一个udp服务器 #pragma once#include <iostream> #include <string> #include <cstdlib> #include <cstring> #include <functional> #include <strings.h> #include <unistd.h> #incl…

【启明智显分享】5G CPE与5G路由器到底有什么区别?

5G路由器和5G CPE在功能和应用场景上存在很明显的差异&#xff0c;小编做了详细比较&#xff0c;希望能帮助到你进一步了解他们的区别及应用。 一、定义与功能 5G路由器 5G路由器是一个将5G网络连接转换为Wi-Fi信号的设备&#xff0c;使多个Wi-Fi设备可以通过5G网络进行连接…

对称加密与非对称加密:密码学的基石及 RSA 算法详解

对称加密与非对称加密&#xff1a;密码学的基石及 RSA 算法详解 在当今数字化的时代&#xff0c;信息安全至关重要。对称加密和非对称加密作为密码学中的两种基本加密技术&#xff0c;为我们的数据安全提供了强大的保障。本文将深入探讨对称加密和非对称加密的特点、应用场景&…

爬虫——数据解析与提取

第二节&#xff1a;数据解析与提取 在网络爬虫开发中&#xff0c;获取网页内容&#xff08;HTML&#xff09;是第一步&#xff0c;但从这些内容中提取有用的数据&#xff0c;才是爬虫的核心部分。HTML文档通常结构复杂且充满冗余信息&#xff0c;因此我们需要使用高效的解析工…

我们是如何实现 TiDB Cloud Serverless 的 - 成本篇

作者&#xff1a; shiyuhang0 原文来源&#xff1a; https://tidb.net/blog/fbedeea4 背景 Serverless 数据库是云原生时代的产物&#xff0c;它提供全托管&#xff0c;按需付费&#xff0c;自动弹性的云数据库服务&#xff0c;让客户免于繁重的数据库运维工作。关于 Serve…

Linux——环境基础开发工具使用2(正在更新中...)

1.自动化构建-make/Makefile 1.1 认识make和Makefile make是一个命令&#xff1b; Makefile是一个文件。 1.2 理解 其中在第一个图片中&#xff0c;第一行的 mytest:test.c 叫做依赖关系&#xff1b;第二行的 gcc test.c -o mytest 叫做依赖方法。 依赖关系和依赖方法共同…

微服务链路追踪skywalking安装

‌SkyWalking是一个开源的分布式追踪系统&#xff0c;主要用于监控和分析微服务架构下的应用性能。‌ 它提供了分布式追踪、服务网格遥测分析、度量聚合和可视化一体化解决方案&#xff0c;特别适用于微服务、云原生架构和基于容器的环境&#xff08;如Docker、K8s、Mesos&…

品牌如何利用大数据工具,进行消费者洞察分析?

存量竞争的时代&#xff0c; 消费者聆听是品牌持续增长的关键&#xff0c;借助大数据的消费者数据洞察&#xff0c;可以帮助品牌分析消费者的所思所想及行为特征&#xff0c;获取消费者对产品的需求痛点、使用感受&#xff0c;对品牌的评价口碑等&#xff0c;从而帮助品牌更好地…

模拟实现优先级队列

目录 定义 特点 构造函数 常用方法 关于扩容的问题 关于建堆的问题 向上调整和向下调整的比较 &#xff08;向上调整&#xff09;代码 &#xff08;向下调整&#xff09;代码 关于入队列和出队列问题 模拟实现优先级队列代码 关于堆排序的问题 堆排序代码 关于对…

【4】GD32H7xx ADC采样

目录 1. GD32H7xx ADC1.1 ADC外设资源1.2 采样时间1.3 片上硬件过采样 2. ADC DMA规则多通道采样程序3. 程序测试 1. GD32H7xx ADC 1.1 ADC外设资源 GD32H7xx 有3个ADC外设&#xff1a; ADC0 20个外部通道&#xff0c;1个内部通道&#xff08;DAC0_OUT0通道&#xff09;32位…

GitLab 如何跨版本升级?

本分分享 GitLab 跨版本升级的一些注意事项。 众所周知&#xff0c;GitLab 的升级必须要严格遵循升级路径&#xff0c;否则就会出现问题&#xff0c;导致升级失败。因此&#xff0c;在 GitLab 升级之前需要做好两件事情&#xff1a; 当前版本的确认升级路径的确认 极狐GitLa…

【咕泡P5人工智能CV 技术NLP项目实战】

人工智能核心代码&#xff1a; 一、什么是人工智能技术&#xff1a; 人工智能&#xff08;Aritificial Intelligence&#xff09;&#xff0c;英文缩写AI&#xff0c;人工智能是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系统的一门新的技术科学。 人工智…

R门 - rust第一课陈天 -内存知识学习笔记

内存 #mermaid-svg-1NFTUW33mcI2cBGB {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-1NFTUW33mcI2cBGB .error-icon{fill:#552222;}#mermaid-svg-1NFTUW33mcI2cBGB .error-text{fill:#552222;stroke:#552222;}#merm…

C# x Unity 从玩家控制类去分析命令模式该如何使用

本文部分内容出自游戏编程模式一书,游戏编程模式,有兴趣的小伙伴可以去看看,虽然不是unity x c#写的 但是思路挺好的 目录 目录 0.先说结论 发现问题 命令模式如何解耦 打个断点更利于分析 怎么实现延迟命令? 如何撤销命令? 脚本整体一览 不足分析(AI) 0.先说结论 …

【C++】—— stack和queue的模拟实现

前言 ​ stack 和 queue使用起来都非常简单&#xff0c;现在来模拟实现一下&#xff0c;理解其底层的原理。 ​ 在实现之前&#xff0c;应该知道&#xff0c;stack 和 queue 都是容器适配器&#xff0c;通过看官网文件也可以看出来&#xff1b;其默认的容器都是deque&#xff…

探索高效的 Prompt 框架:RBTR 提示框架的奥秘与优势

前言 在当今数字化的时代&#xff0c;人工智能&#xff08;AI&#xff09;已经成为我们生活和工作中不可或缺的一部分。而 Prompt 作为与 AI 交互的关键工具&#xff0c;其质量直接影响着我们获取信息的准确性和有用性。今天&#xff0c;我们将深入探讨一个通用的 Prompt 框架…

动态规划-完全背包问题——322.零钱兑换

1.题目解析 题目来源 322.零钱兑换——力扣 测试用例 2.算法原理 1.状态表示 这里需要寻找硬币使总面值等于一个值求出所需硬币的最小个数&#xff0c;所以不妨设置一个二维dp表&#xff0c;即dp[i][j]&#xff1a;在[1,i]个硬币中选择的硬币总面值完全等于j时所需要的最小硬…