C 语言数据结构(二):顺序表和链表
目录
1. 线性表
2. 顺序表
2.1 概念及结构
2.1.1 静态顺序表(不常用)
2.1.2 动态顺序表(常用)
编辑
2.2 练习
2.2.1 移除元素
2.2.2 删除有序数组中的重复项
2.2.3 合并两个有序数组
2.3 顺序表存在的问题
3. 链表
3.1 概念及结构
3.2 链表的分类
编辑
3.2.1 常用链表
3.2.1.1 无头单向非循环链表
3.2.1.2 带头双向循环链表
3.3 无头单向非循环链表的实现
3.4 带头双向循环链表的实现
4. 对比顺序表和链表
💬 :如果你在阅读过程中有任何疑问或想要进一步探讨的内容,欢迎在评论区畅所欲言!我们一起学习、共同成长~!
👍 :如果你觉得这篇文章还不错,不妨顺手点个赞、加入收藏,并分享给更多的朋友噢~!
1. 线性表
线性表(linear list)是 n 个具有相同特性的数据元素的有限序列。它是一种在实际中广泛使用的数据结构。
常见的线性表包括:顺序表、链表、栈、队列、字符串等。
线性表在逻辑上是线性结构,即一条连续的直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
2. 顺序表
2.1 概念及结构
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般采用数组存储。在数组上完成数据的增删查改。
顺序表一般可以分为:静态顺序表,动态顺序表。
2.1.1 静态顺序表(不常用)
静态顺序表使用定长数组存储元素。存储数据前,需要预先确定数组的长度,且在程序运行过程中数组长度不能改变。
示例:
#include <stdio.h>
#define N 7// int 类型重命名为 SLDataType
typedef int SLDataType;// 定义静态顺序表结构体
typedef struct SeqList // 此结构体类型起了一个别名 SeqList
{SLDataType array[N]; // 定长数组size_t size; // size_t 用于表示无符号整数类型,通常用于表示数组的索引、对象的大小等// size :当前有效数据的个数} SeqList;void SeqListInit(SeqList* psl)
{psl->size = 0;
}// 向静态顺序表尾部插入数据
int SeqListPushBack(SeqList* psl, SLDataType x)
{if (psl->size >= N) {printf("顺序表已满,无法插入\n");return 0;}psl->array[psl->size] = x;psl->size++; // 表示成功插入了一个数据return 1;
}// 打印静态顺序表
void SeqListPrint(SeqList* psl)
{for (size_t i = 0; i < psl->size; i++) {printf("%d ", psl->array[i]);}printf("\n");
}int main()
{SeqList sl;// 声明一个SeqList类型的变量sl,用于表示一个静态顺序表SeqListInit(&sl);SeqListPushBack(&sl, 1);SeqListPushBack(&sl, 2);SeqListPushBack(&sl, 3);SeqListPrint(&sl);return 0;
}
2.1.2 动态顺序表(常用)
动态顺序表使用动态开辟的数组存储元素。可根据实际需要动态地增减数组的容量,以适应数据量的变化。
示例:
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>typedef int SLDataType;
// int 类型重命名为 SLDataTypetypedef struct SeqList // 此结构体类型起了一个别名 SeqList
{SLDataType* array; // 指向动态开辟的数组size_t size; // 顺序表中有效数据的个数size_t capacity; // 顺序表当前容量大小
} SeqList;// 顺序表初始化
void SeqListInit(SeqList* psl)
{// 检查传入的顺序表指针是否为空,若为空则终止程序assert(psl);// 初始化时先不分配空间,将数组指针置为 NULLpsl->array = NULL;psl->size = 0;psl->capacity = 0;
}// 检查空间,如果满了,进行增容
void CheckCapacity(SeqList* psl)
{assert(psl);// 当有效数据个数等于容量时,说明顺序表已满,需要增容if (psl->size == psl->capacity){// 若当前容量为 0,先给一个初始容量,这里设为 4size_t newCapacity = psl->capacity == 0 ? 4 : psl->capacity * 2;// 重新分配内存空间,大小为新容量乘以每个数据元素的大小SLDataType* tmp = (SLDataType*)realloc(psl->array, newCapacity * sizeof(SLDataType));// 检查内存分配是否成功,若失败则终止程序assert(tmp);// 更新数组指针为新分配的内存地址psl->array = tmp;psl->capacity = newCapacity;}
}// 顺序表尾插
void SeqListPushBack(SeqList* psl, SLDataType x)
{assert(psl);// 插入元素前先检查是否需要增容CheckCapacity(psl);// 在顺序表的末尾插入新元素psl->array[psl->size] = x;// 有效数据个数加 1,表示插入一个元素psl->size++;
}// 顺序表尾删
void SeqListPopBack(SeqList* psl)
{assert(psl);// 检查顺序表中是否有数据,若没有则终止程序assert(psl->size > 0);// 表示删除一个元素psl->size--;
}// 顺序表头插
void SeqListPushFront(SeqList* psl, SLDataType x)
{assert(psl);// 插入元素前先检查是否需要增容CheckCapacity(psl);// 将顺序表中所有元素依次向后移动一位for (int i = psl->size; i > 0; i--){psl->array[i] = psl->array[i - 1];}psl->array[0] = x;psl->size++;
}// 顺序表头删
void SeqListPopFront(SeqList* psl)
{assert(psl);// 检查顺序表中是否有数据,若没有则终止程序assert(psl->size > 0);// 将顺序表中所有元素依次向前移动一位for (int i = 0; i < psl->size - 1; i++){psl->array[i] = psl->array[i + 1];}psl->size--;
}// 顺序表查找
int SeqListFind(SeqList* psl, SLDataType x)
{assert(psl);for (size_t i = 0; i < psl->size; i++){// 若找到与目标元素相等的元素,返回其下标if (psl->array[i] == x){return (int)i;}}return -1;
}// 顺序表在 pos 位置插入 x
void SeqListInsert(SeqList* psl, size_t pos, SLDataType x)
{assert(psl);// 检查插入位置是否合法,即不能小于 0 且不能大于当前有效数据个数assert(pos >= 0 && pos <= psl->size);// 插入元素前先检查是否需要增容CheckCapacity(psl);// 将 pos 位置及之后的元素依次向后移动一位for (size_t i = psl->size; i > pos; i--){psl->array[i] = psl->array[i - 1];}psl->array[pos] = x;psl->size++;
}// 顺序表删除 pos 位置的值
void SeqListErase(SeqList* psl, size_t pos)
{assert(psl);assert(pos >= 0 && pos < psl->size);for (size_t i = pos; i < psl->size - 1; i++){psl->array[i] = psl->array[i + 1];}psl->size--;
}// 顺序表销毁
void SeqListDestory(SeqList* psl)
{assert(psl);// 释放动态分配的数组内存free(psl->array);// 将数组指针置为 NULL,防止悬空指针psl->array = NULL;psl->size = 0;psl->capacity = 0;
}void SeqListPrint(SeqList* psl)
{assert(psl);for (size_t i = 0; i < psl->size; i++){printf("%d ", psl->array[i]);}printf("\n");
}int main()
{SeqList sl;// 声明一个SeqList类型的变量sl,用于表示一个动态顺序表// 表尾插SeqListInit(&sl);SeqListPushBack(&sl, 1);SeqListPushBack(&sl, 2);SeqListPushBack(&sl, 3);SeqListPrint(&sl);// 表头插SeqListPushFront(&sl, 0);SeqListPrint(&sl);// 查找int index = SeqListFind(&sl, 2);if (index != -1){printf("元素 2 在顺序表中的下标是: %d\n", index);}else{printf("未找到元素 2\n");}// 表尾删SeqListPopBack(&sl);SeqListPrint(&sl);// 表头删SeqListPopFront(&sl);SeqListPrint(&sl);// 指定位置插入值SeqListInsert(&sl, 1, 10);SeqListPrint(&sl);// 指定位置删除值SeqListErase(&sl, 1);SeqListPrint(&sl);// 顺序表销毁SeqListDestory(&sl);return 0;
}
2.2 练习
2.2.1 移除元素
有一个数组和一个值 val
,要求:
- 原地移除所有等于
val
的元素 - 返回数组中不等于
val
(剩余)的元素的数量 k - 新数组前 k 个元素包含不等于 val 的元素
“原地”:仅使用常数级别的额外空间,即空间复杂度为 O(1)。其算法直接在原始数据存储的内存位置上进行修改,而不是创建一个与原始数据规模相当的新数据结构来存储处理后的结果。
#include <stdio.h>int removeElement(int* nums, int numsSize, int val)
{int k = 0;for (int i = 0; i < numsSize; i++) {if (nums[i] != val) {// 如果不等于 val,则将该元素放到数组的前 k 个位置nums[k] = nums[i];k++;}}return k;
}int main()
{int nums[] = {3, 2, 2, 3};int numsSize = sizeof(nums) / sizeof(nums[0]);int val = 3;int newSize = removeElement(nums, numsSize, val);printf("剩余元素的数量: %d\n", newSize);printf("移除指定元素后的数组: ");for (int i = 0; i < newSize; i++) {printf("%d ", nums[i]);}printf("\n");return 0;
}
只遍历数组一次,所以时间复杂度 O(n) ,n 为数组长度。
2.2.2 删除有序数组中的重复项
原地删除一个非严格递增排列的数组 nums 中重复的元素,使每个元素只出现一次,保持元素的相对顺序不变,返回新数组的长度 k ,新数组前 k 个元素包含唯一元素。
#include <stdio.h>int removeDuplicates(int* nums, int numsSize)
{if (numsSize == 0) {return 0;}int slow = 0;for (int fast = 1; fast < numsSize; fast++) {if (nums[fast] != nums[slow]) {slow++;nums[slow] = nums[fast];}}return slow + 1;
}int main()
{int nums[] = {1, 1, 2, 2, 2, 3, 4, 4, 5};int numsSize = sizeof(nums) / sizeof(nums[0]);int newLength = removeDuplicates(nums, numsSize);printf("删除重复元素后数组的新长度: %d\n", newLength);printf("删除重复元素后的数组: ");for (int i = 0; i < newLength; i++) {printf("%d ", nums[i]);}printf("\n");return 0;
}
只遍历数组一次,所以时间复杂度 O(n) ,n 为数组长度。
2.2.3 合并两个有序数组
给定两个非递减排序的整数数组 nums1
和 nums2
,及整数 m
和 n
。
要求:
nums1
初始长度是m + n
,前m
个是待合并元素,后n
个为 0 ;nums2
初始长度为n;
nums2
合并到nums1
里,合并后数组保持非递减顺序,合并后结果存于nums1
中。
#include <stdio.h>void merge(int* nums1, int m, int* nums2, int n)
{int p1 = m - 1; // nums1 中最后一个“有效”元素的索引int p2 = n - 1; // nums2 中最后一个元素的索引int p = m + n - 1; // 合并后数组最后一个元素的索引// 从后往前比较两个数组的元素while (p1 >= 0 && p2 >= 0) {if (nums1[p1] > nums2[p2]) {nums1[p] = nums1[p1];p1--;}else {nums1[p] = nums2[p2];p2--;}p--;}// 其中一个数组遍历完后,若 nums2 还有剩余元素,将其复制到 nums1 前面while (p2 >= 0) {nums1[p] = nums2[p2];p2--;p--;}
}int main()
{int nums1[] = { 1, 2, 3, 0, 0, 0, 0, 0 };int m = 3;int nums2[] = { 0, 2, 5, 6, 7 };int n = 5;merge(nums1, m, nums2, n);for (int i = 0; i < m + n; i++) {printf("%d ", nums1[i]);}printf("\n");return 0;
}
对比 | nums1[p] | 每次比较后的数组 |
---|---|---|
nums1[7] = 7 | 1,2,3,0,0,0,0,7 | |
nums1[6] = 6 | 1,2,3,0,0,0,6,7 | |
nums1[5] = 5 | 1,2,3,0,0,5,6,7 | |
nums1[4] = 3 | 1,2,3,0,3,5,6,7 | |
nums1[3] = 2 | 1,2,3,2,3,5,6,7 | |
nums1[2] = 2 | 1,2,2,2,3,5,6,7 | |
nums1[1] = 1 | 1,1,2,2,3,5,6,7 |
然后 p1 = -1,p2 = 0,p = 0,执行第二个 while 循环,nums1[0] = nums2[0] = 0 ,得到合并数组{0,1,2,2,3,5,6,7} 。
另外,只遍历数组 nums1
和 nums2
一次,所以时间复杂度为 O(m+n) 。
2.3 顺序表存在的问题
顺序表存在以下问题:
- 进行中间或表头插入、删除操作时,时间复杂度达 O (N)。
- 增容要申请新空间、拷贝数据、释放旧空间,消耗大。
- 扩容一般扩大为原来的 2 倍,若插入数据少,造成空间浪费。
为解决这些问题,下面介绍链表结构。
3. 链表
3.1 概念及结构
链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
链表在逻辑上是连续的,但是——链表节点一般从堆上申请空间,而堆上空间的分配依据一定策略进行,导致两次申请的空间可能连续,也可能不连续——所以链表的物理存储地址不一定连续。
3.2 链表的分类
- 单向/双向
- 带头/不带头
- 循环/非循环
3.2.1 常用链表
3.2.1.1 无头单向非循环链表
结构简单,一般不会单独用来存数据。实际中更多作为其他数据结构的子结构,如哈希桶、图的邻接表等。另外这种结构在笔试面试中较常见。
3.2.1.2 带头双向循环链表
结构最复杂,一般用来单独存储数据。实际中使用的链表数据结构多是带头双向循环链表。
3.3 无头单向非循环链表的实现
#include <stdio.h>
#include <stdlib.h>typedef int SLTDateType;// 定义链表节点结构体
typedef struct SListNode
{SLTDateType data; // 节点存储的数据struct SListNode* next; // 指向下一个节点的指针
}SListNode;// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x)
{// 为新节点分配内存空间SListNode* newNode = (SListNode*)malloc(sizeof(SListNode));if (newNode == NULL) {perror("malloc");return NULL;}// 初始化新节点的数据newNode->data = x;// 新节点的下一个节点初始化为空newNode->next = NULL;return newNode;
}void SListPrint(SListNode* plist)
{SListNode* cur = plist;// plist 是指向链表头节点的指针while (cur != NULL) // 无头单向非循环链表的末尾节点的 next 指针始终被设置为 NULL{// 打印当前节点的数据printf("%d -> ", cur->data);// 移动到下一个节点cur = cur->next;}// 打印链表结束标志printf("NULL\n");
}// 尾插
void SListPushBack(SListNode** pplist, SLTDateType x)
{// 创建新节点SListNode* newNode = BuySListNode(x);if (*pplist == NULL) {// 如果链表为空,新节点就是链表的头节点*pplist = newNode;}else {// 找到链表的尾节点SListNode* tail = *pplist;while (tail->next != NULL) {tail = tail->next;}tail->next = newNode;}
}// 头插
void SListPushFront(SListNode** pplist, SLTDateType x)
{SListNode* newNode = BuySListNode(x);// 新节点的下一个节点指向原来的头节点newNode->next = *pplist;*pplist = newNode;
}// 尾删
void SListPopBack(SListNode** pplist)
{if (*pplist == NULL) // 若链表为空 {return; // 函数立即终止执行}if ((*pplist)->next == NULL) {// 如果链表只有一个节点,释放该节点并将头指针置空free(*pplist);*pplist = NULL;}else {SListNode* prev = NULL;SListNode* tail = *pplist;while (tail->next != NULL) {prev = tail;tail = tail->next;}// 最后一次循环中,prev 被赋为 tail 的前一个节点,tail 移到了尾节点free(tail);// 倒数第二个节点的下一个节点置空prev->next = NULL;}
}// 头删
void SListPopFront(SListNode** pplist)
{if (*pplist == NULL) {return;}// 保存原来的头节点SListNode* first = *pplist;// 更新头节点为原来头节点的下一个节点*pplist = first->next;// 释放原来的头节点free(first);
}// 查找
SListNode* SListFind(SListNode* plist, SLTDateType x)
{// 遍历链表,查找值为x的节点SListNode* cur = plist;while (cur != NULL) {if (cur->data == x) {return cur;}cur = cur->next;}return NULL;
}// 在pos的后一个位置插入值 x
void SListInsertAfter(SListNode* pos, SLTDateType x)
{if (pos == NULL) {return;}SListNode* newNode = BuySListNode(x);newNode->next = pos->next;pos->next = newNode;
}// 删除pos后一个位置的值
void SListEraseAfter(SListNode* pos)
{if (pos == NULL || pos->next == NULL) // 如果pos为空或者pos是尾节点 {return;}// 保存pos节点的下一个节点SListNode* nextNode = pos->next;// pos节点的下一个节点指向要删除节点的下一个节点pos->next = nextNode->next;free(nextNode);
}int main()
{SListNode* list = NULL; // 声明并初始化一个指向 SListNode 类型结构体的指针 list// 用于表示初始为空的链表// 尾插SListPushBack(&list, 1);SListPushBack(&list, 2);SListPushBack(&list, 3);SListPrint(list);// 头插SListPushFront(&list, 0);SListPrint(list);// 尾删SListPopBack(&list);SListPrint(list);// 头删SListPopFront(&list);SListPrint(list);// 查找SListNode* found = SListFind(list, 2);if (found) {printf("Found: %d\n", found->data);}else {printf("Not found\n");}// 在pos的后一个位置插入值SListInsertAfter(list, 4);SListPrint(list);// 删除pos后一个位置的值SListEraseAfter(list);SListPrint(list);return 0;
}
“无头单向非循环链表的末尾节点的 next 指针始终被设置为 NULL”:
- 节点创建:新节点创建时,
next
指针初始化为NULL
。 - 尾插操作:尾插新节点时,若链表为空,新节点成头节点,
next
为NULL
;若不为空,找到尾节点,将其next
指向新节点(新节点next
已初始化为NULL
)。 - 尾删操作:尾删时,若链表仅一个节点,释放后链表为空;若有多个节点,释放尾节点,将倒数第二个节点的
next
置为NULL
。
思考:为什么在pos的后一个位置插入值,而非前一个位置?为什么不删除 pos 位置节点?
(1)单向链表只能从前向后遍历,没有指向前一节点的指针,无法直接获取 pos
节点的前一个节点,因此只能从头节点开始依次遍历链表,直到找到 pos
节点的前一个节点,时间复杂度达 O(n);而在 pos
后插入,只改两个指针,时间复杂度为 O(1)。
(2)删除 pos 位置节点需找到其前一节点,时间复杂度达 O(n);而删除 pos 后节点,只改一个指针,时间复杂度为 O(1)。
3.4 带头双向循环链表的实现
#include <stdio.h>
#include <stdlib.h>typedef int LTDataType;typedef struct ListNode
{LTDataType _data; // 节点存储的数据struct ListNode* next; // 指向下一个节点的指针struct ListNode* prev; // 指向前一个节点的指针
}ListNode;// 创建返回链表的头结点
ListNode* ListCreate()
{// 为头节点分配内存ListNode* phead = (ListNode*)malloc(sizeof(ListNode));if (phead == NULL) {perror("malloc fail");return NULL;}// 头节点的 next 和 prev 都指向自身,形成循环phead->next = phead;phead->prev = phead;return phead;
}// 双向链表销毁
void ListDestory(ListNode* plist)
{// 从头节点的下一个节点开始遍历ListNode* cur = plist->next;while (cur != plist) {// 保存当前节点的下一个节点ListNode* next = cur->next;free(cur);cur = next;}// 最后释放头节点的内存free(plist);
}void ListPrint(ListNode* plist)
{ListNode* cur = plist->next;while (cur != plist) {printf("%d ", cur->_data);cur = cur->next;}printf("\n");
}// 尾插
void ListPushBack(ListNode* plist, LTDataType x)
{// 创建新节点ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));if (newnode == NULL) {perror("malloc fail");return;}newnode->_data = x;// 找到尾节点ListNode* tail = plist->prev;// 插入新节点tail->next = newnode;newnode->prev = tail;newnode->next = plist;plist->prev = newnode;
}// 尾删
void ListPopBack(ListNode* plist)
{// 检查链表是否为空if (plist->next == plist) {return;}// 找到尾节点ListNode* tail = plist->prev;// 找到尾节点的前一个节点ListNode* prev = tail->prev;free(tail);// 更新指针prev->next = plist;plist->prev = prev;
}// 头插
void ListPushFront(ListNode* plist, LTDataType x)
{ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));if (newnode == NULL) {perror("malloc fail");return;}newnode->_data = x;// 保存头节点的下一个节点ListNode* first = plist->next;// 插入新节点plist->next = newnode;newnode->prev = plist;newnode->next = first;first->prev = newnode;
}// 头删
void ListPopFront(ListNode* plist)
{// 检查链表是否为空if (plist->next == plist) {return;}// 保存头节点的下一个节点ListNode* first = plist->next;// 保存头节点的下下一个节点ListNode* second = first->next;// 释放头节点的下一个节点的内存free(first);// 更新指针plist->next = second;second->prev = plist;
}// 查找
ListNode* ListFind(ListNode* plist, LTDataType x)
{// 从头节点的下一个节点开始遍历ListNode* cur = plist->next;while (cur != plist) {if (cur->_data == x) {return cur;}cur = cur->next;}return NULL;
}// 在pos的前一个位置插入
void ListInsert(ListNode* pos, LTDataType x)
{ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));if (newnode == NULL) {perror("malloc fail");return;}newnode->_data = x;// 找到 pos 节点的前一个节点ListNode* prev = pos->prev;// 插入新节点prev->next = newnode;newnode->prev = prev;newnode->next = pos;pos->prev = newnode;
}// 删除pos位置的节点
void ListErase(ListNode* pos)
{ListNode* prev = pos->prev;ListNode* next = pos->next;free(pos);// 更新指针prev->next = next;next->prev = prev;
}int main()
{ListNode* plist = ListCreate();// 调用 ListCreate 创建带头双向循环链表,并将返回的头节点指针赋值给 plistListPushBack(plist, 1);ListPushBack(plist, 2);ListPushBack(plist, 3);ListPrint(plist);ListPushFront(plist, 0);ListPrint(plist);ListPopBack(plist);ListPrint(plist);ListPopFront(plist);ListPrint(plist);ListNode* pos = ListFind(plist, 2);if (pos) {ListInsert(pos, 4);ListPrint(plist);}pos = ListFind(plist, 4);if (pos) {ListErase(pos);ListPrint(plist);}ListDestory(plist);return 0;
}
4. 对比顺序表和链表
对比维度 | 顺序表 | 链表 |
---|---|---|
存储空间 | 物理存储上一定连续 | 逻辑上数据元素连续,但物理内存中不一定连续,节点通过指针相互连接 |
随机访问 | 支持随机访问,通过下标可直接定位元素,时间复杂度为 O (1) | 需从表头开始,逐个通过指针遍历查找元素,时间复杂度为 O (N) |
插入和删除操作 | 在任意位置插入或删除元素时,可能需移动后续元素,效率较低,时间复杂度为 O (N) | 插入或删除元素时,仅需修改相关节点的指针指向,操作相对简便 |
插入特性 | 动态顺序表空间不足时,需进行扩容,涉及重新分配内存、数据拷贝等步骤 | 没有容量限制,可随时动态创建和删除节点 |
应用场景 | 适合元素高效存储且频繁访问的场景,如数组实现的线性表 | 适用于数据插入和删除操作频繁的场景,如实现队列、栈等数据结构 |
缓存利用率 | 物理存储连续,符合局部性原理,读取数据时相邻数据易一同加载到缓存中,缓存利用率高 | 节点物理位置分散,难以充分利用缓存,缓存利用率较低 |
相关文章:
C 语言数据结构(二):顺序表和链表
目录 1. 线性表 2. 顺序表 2.1 概念及结构 2.1.1 静态顺序表(不常用) 2.1.2 动态顺序表(常用) 编辑 2.2 练习 2.2.1 移除元素 2.2.2 删除有序数组中的重复项 2.2.3 合并两个有序数组 2.3 顺序表存在的问题 3. 链表 …...
TDengine 服务无法启动常见原因
taosd 是 TDengine 的核心服务进程,如果无法启动将导致整个数据库无法使用,了解常导致无法启动的原因,可以帮你快速解决问题。 1. 如何查找日志 无法启动的原因记录在日志中,日志文件默认在 /var/log/taos 的 taosdlog.0 或者 t…...
在 UniApp 中实现stream流式输出 AI 聊天功能,AI输出内容用Markdown格式展示
在 UniApp 中实现流式 AI 聊天功能 介绍 在现代 Web 开发中,流式 API 响应能够显著提升用户体验,尤其是在与 AI 聊天接口进行交互时。本文将介绍如何在 UniApp 中使用 Fetch API 实现一个流式响应的 AI 聊天功能,包括实时更新聊天内容和滚动…...
数据库SQL的配置和练习题
一、MySQL的安装 1.安装包下载 下载地址:https://downloads.mysql.com/archives/community/ 2.解压软件包 将MySQL软件包解压在没有中文和空格的目录下 3.设置配置文件 在解压目录下创建my.ini文件并添加内容如下: [client] #客户端设置&…...
Pytorch的一小步,昇腾芯片的一大步
Pytorch的一小步,昇腾芯片的一大步 相信在AI圈的人多多少少都看到了最近的信息:PyTorch最新2.1版本宣布支持华为昇腾芯片! 1、 发生了什么事儿? 在2023年10月4日PyTorch 2.1版本的发布博客上,PyTorch介绍的beta版本…...
AI+办公 Task1
作业 题目1:提示词除了三要素“角色”、“背景”、“要求”之外,还有哪些关键要素 提示词有一个框架叫CO-STAR框架,还有的关键要素有风格、任务、响应格式等。 要素适用场景实际案例Context需要限定领域或场景的任务"作为医学助手&…...
文件系统调用─── linux第17课
目录 linux 中man 2和man 3的区别 文件内容介绍 C语言文件接口 示例: 输出信息到显示器,你有哪些方法 总结: 系统文件I/O 文件类的系统调用接口介绍 示例 open 函数具体使用哪个,和具体应用场景相关, write read close lseek ,类比C文件相关接…...
概念|RabbitMQ 消息生命周期 待消费的消息和待应答的消息有什么区别
目录 消息生命周期 一、消息创建与发布阶段 二、消息路由与存储阶段 三、消息存活与过期阶段 四、消息投递与消费阶段 五、消息生命周期终止 关键配置建议 待消费的消息和待应答的消息 一、待消费的消息(Unconsumed Messages) 二、待应答的消息…...
Javaweb后端文件上传@value注解
文件本地存储磁盘 阿里云oss准备工作 阿里云oss入门程序 要重启一下idea,上面有cmd 阿里云oss案例集成 优化 用spring中的value注解...
DeepSeek技术演进与发展前瞻
如果喜欢可以订阅专栏哟(^U^)ノ~YO,至少更新6年 以下DeepSeek未来发展的技术分析框架及核心内容示范 # -*- coding: utf-8 -*- """ DeepSeek技术演进模拟器(概念验证代码) 本代码展示动态架构调整的核心逻辑 """class DynamicArchitect…...
Java常见面试技术点整理讲解——后端框架(整理中,未完成)
前言: 对于后端常用框架的技术整理,其实框架在平时就是会用就行,但面试时多半需要描述实现原理,这个要靠自己理解,不推荐死记硬背。 这篇和另外几篇文章区分开,主要用于规整Java后端各种框架,…...
目标检测YOLO实战应用案例100讲-基于毫米波雷达的多目标检测 (续)
目录 3.2 改进的CFAR目标检测算法 3.3 算法步骤描述 3.4 实验结果与分析 基于VGG16-Net的毫米波雷达目标检测算法 4.1 VGG16-Net网络模型 4.2 改进VGG16-Net网络的目标检测算法 4.3 算法步骤描述 4.4 实验结果与分析 知识拓展 基于毫米波雷达的多目标检测:使…...
python爬虫:Android自动化工具Auto.js的详细使用
更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 1. Auto.js 简介2. 安装与配置2.1 安装 Auto.js2.2 安装 Python 环境2.3 安装 ADB 工具3. Python 与 Auto.js 结合3.1 通过 ADB 执行 Auto.js 脚本3.2 通过 Python 控制 Auto.js3.3 通过 Python 与 Auto.js 交互4. 常用…...
MyBatis-Plus 注解大全
精心整理了最新的面试资料和简历模板,有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 MyBatis-Plus 注解大全 MyBatis-Plus 是基于 MyBatis 的增强工具,通过注解简化了单表 CRUD 操作和复杂查询的配置。以下是常用注解的分类及详细说…...
牛客周赛 Round 84——小红的陡峭值(四)
牛客竞赛_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ 小红的陡峭值(四) 题目: 思路: 题目告诉我们关于树的陡峭值的定义,那一开始看起来无从下手,但是当我们选取某一个节点为根节点时&#…...
Redis 内存淘汰策略深度解析
Redis 作为高性能的内存数据库,其内存资源的高效管理直接关系到系统的稳定性和性能。当 Redis 的内存使用达到配置的最大值(maxmemory)时,新的写入操作将触发内存淘汰机制(Eviction Policy),以释…...
微前端之 Garfish.js 的基础使用教程和进阶配置
前言 在现代前端开发中,微前端架构逐渐成为一种流行的解决方案。它允许将大型应用拆分成多个小型独立的子应用,从而提高开发效率和可维护性。Garfish.js 是一个强大的微前端框架,可以帮助我们轻松实现这一架构。在本文中,通过一个…...
Rabbitmq--延迟消息
13.延迟消息 延迟消息:生产者发送消息时指定一个时间,消费者不会立刻收到消息,而是在指定时间之后才会收到消息 延迟任务:一定时间之后才会执行的任务 1.死信交换机 当一个队列中的某条消息满足下列情况之一时,就会…...
Webshell原理与利用
本文内容仅用于技术研究、网络安全防御及合法授权的渗透测试,严禁用于任何非法入侵、破坏或未经授权的网络活动。 1. WebShell的定义与原理 定义:WebShell是一种基于Web脚本语言(如PHP、ASP、JSP)编写的恶意后门程序,…...
Android 内存泄漏实战:从排查到修复的完整指南
通过实战示例和工具使用,帮助开发者理解、排查和修复 Android 应用中的内存泄漏问题 1. 什么是内存泄漏? 定义:内存泄漏是指程序中已动态分配的内存由于某种原因未能释放,导致系统内存的浪费,最终可能导致应用崩溃或性…...
Liunx系统 : 进程间通信【IPC-Shm共享内存】
文章目录 System V共享内存创建共享内存shmget 控制共享内存shmctl shm特性 System V System V是Liunx中的重要的进程间通信机制,它包括(shm)共享内存,(msg)消息队列和(sem)信号量。…...
c语言笔记 数组指针
数组指针是指针类型的一种,一般数组指针跟二维数组,一维数组结合比较多,下面我们通过图片来探讨一下数组指针的使用以及结合起来的联系。 1.数组指针与一维数组 int a[3]; //一维数组 int aa[2][3];//二维数组 数组元素类型 int [3] int (*p…...
SpringBoot + vue 管理系统
SpringBoot vue 管理系统 文章目录 SpringBoot vue 管理系统 1、成品效果展示2、项目准备3、项目开发 3.1、部门管理 3.1.1、前端核心代码3.1.2、后端代码实现 3.2、员工管理 3.2.1、前端核心代码3.2.2、后端代码实现 3.3、班级管理 3.3.1、前端核心代码3.3.2、后端代码实现 …...
Python语法核心架构与核心知识点:从理论到实践
一、Python的核心设计哲学 Python以“简洁优雅”为核心理念,遵循以下原则: # Zen of Python(输入 import this 可查看) >>> import this The Zen of Python, by Tim Peters ... Simple is better than complex. Readab…...
OpenHarmony子系统开发 - 编译构建Kconfig可视化配置指导
OpenHarmony子系统开发 - 编译构建Kconfig可视化配置指导 概述 功能简介 该功能基于Kconfiglib与Kconfig实现,方便用户个性化配置OpenHarmony产品子系统部件。 基于Kconfig实现的可视化配置功能具有以下优点: 能直观且全面地展示软件的部件选项。可…...
管中窥豹数字预失真(DPD)
管中窥豹数字预失真(DPD) 数字预失真在通信领域发挥了巨大的作用,对提高功放效率、改善误码率起了不可忽略的作用,广泛运用与通信、雷达等各种领域。但是对于普通用户,它显得及其高深神秘。今天就用这个短文ÿ…...
spring-boot-starter和spring-boot-starter-web的关联
maven的作用是方便jar包的管理,所以每一个依赖都是对应着相应的一个或者一些jar包,从网上看到很多对spring-boot-starter的描述就是“这是Spring Boot的核心启动器,包含了自动配置、日志和YAML。”没看太明白,所参与的项目上也一直…...
梯度计算中常用的矩阵微积分公式
标量对向量求导的常用数学公式 设标量函数 y f ( x ) y f(\boldsymbol{x}) yf(x),其中 x ( x 1 , x 2 , ⋯ , x n ) T \boldsymbol{x} (x_1, x_2, \cdots, x_n)^{\rm T} x(x1,x2,⋯,xn)T是一个 n n n维列向量。标量 y y y对向量 x \boldsymbol{x} x的导数…...
vim 编写/etc/docker/daemon.json文件时,E212: 无法打开并写入文件
目录 问题描述 解决方法 1、创建/etc/docker目录 2、打开/etc/docker目录 3、创建daemon.json文件 4、vim 编辑daemon.json文件 问题描述 当我们输入代码:vim /etc/docker/daemon.json时,报E212: 无法打开并写入文件错误,如下图 vim /e…...
http 模块的概念及作用详细介绍
目录 1. http 模块概述 2. http 模块的作用 3. http 服务器代码示例 运行代码 4. http 客户端代码示例 运行代码 5. 总结 1. http 模块概述 http 模块是 Node.js 内置的核心模块之一,它用于创建 HTTP 服务器和客户端,支持处理 HTTP 请求和响应。…...
重生之我在学Vue--第5天 Vue 3 路由管理(Vue Router)
重生之我在学Vue–第5天 Vue 3 路由管理(Vue Router) 文章目录 重生之我在学Vue--第5天 Vue 3 路由管理(Vue Router)前言一、路由配置与导航1.1 什么是 Vue Router?1.2 安装 Vue Router1.3 基本路由配置步骤代码示例 1…...
常见排序算法深度评测:从原理到10万级数据实战
常见排序算法深度评测:从原理到10万级数据实战 摘要 本文系统解析冒泡排序、选择排序、插入排序、希尔排序、归并排序、快速排序、堆排序和基数排序8种经典算法,通过C语言实现10万随机数排序并统计耗时。测试显示:快速排序综合性能最优&…...
搭建BOA服务器
BOA服务器是嵌入式常用的服务器类型,嵌入式程序作为后端时候如果想配合网页进行显示,利用BOA服务器搭建网络界面是不错的选择 首先下载boa官方安装包 Boa Webserver 下载后传输到Ubuntu随便文件夹,解压 tar -xvf boa-0.94.13.tar.gz 进入…...
JSON.parse(JSON.stringify())深拷贝不会复制函数
深拷贝 是指创建一个新对象,并递归地复制原对象中所有层级的属性和值,从而确保新对象与原对象完全独立 深拷贝的实现方法 : 1. 使用 JSON.parse(JSON.stringify()) 函数会被忽略复制,比如,下面的对象的forma…...
debug_unpack_ios failed: Exception: Failed to codesign 解决方案(亲测有效)
debug_unpack_ios failed: Exception: Failed to codesign 解决方案(亲测有效) 背景原因解决方案tipsresult 背景 执行flutter doctor全通过后run项目依然报错 原因 1、检查flutter Mac的flutter项目在哪个文件夹内 2、检查flutter Sdk在哪个文件夹内 …...
Docker篇
1.docker环境搭建: 1.1软件仓库的配置rhel9: #cd/etc/yum.repos.d #vim docker.repo [docker] namedocker-ce baseurlhttps://mirrors.aliyun.com/docker-ce/linux/rhel/9/x86_64/stable gpgcheck0 1.2安装docker并且启动服务 yum install -y dock…...
【Linux】基本命令
目录 🔥一、基础命令 1.sudo su(superuser do) 2.pwd(print working directory) 3.ls(list) 4.cd(change directory) 5.mkdir(make directoryÿ…...
win10电脑鼠标速度突然变的很慢?
电脑鼠标突然变很慢,杀毒检测后没问题,鼠标设置也没变,最后发现可能是误触鼠标的“DPI”调节键。 DPI调节键在鼠标滚轮下方,再次点击即可恢复正常鼠标速度。 如果有和-的按键,速度变快,-速度变慢。 图源&…...
前端(vue)学习笔记(CLASS 3):生命周期工程化开发入门
1、生命周期 Vue生命周期:一个Vue实例从创建到销毁的整个过程 生命周期四个阶段:创建、挂载、更新、销毁 1、创建阶段:响应式数据 2、挂载阶段:渲染模板 3、更新阶段:数据修改、更新视图(执行多次&…...
Python写一个查星座的小程序,适合初学者练手——字典和if语句练习
一、界面预览 二、完整代码 # 导入必要的库 import tkinter as tk from tkinter import ttk # 导入ttk模块用于更现代的控件 from PIL import Image, ImageTk # 用于处理图片 import os # 用于文件路径操作class ZodiacApp:def __init__(self, root):self.root rootself.r…...
云上特权凭证攻防启示录:从根账号AK泄露到安全体系升级的深度实践
事件全景:一场持续17分钟的云上攻防战 2025年3月9日15:39,阿里云ActionTrail日志突现异常波纹——根账号acs:ram::123456789:root(已脱敏)从立陶宛IP(164.92.91.227)发起高危操作。攻击者利用泄露的AccessKey(AK)在17分钟内完成侦察→提权→持久化攻击链,完整操作序列…...
blazemeter工具使用--用于自动生成jmeter脚本并进行性能测试
1、安装blazemeter(网上有很多详情的教程) 2、开始录制:设置号你的文件名称后开始录制 3、录制完成后保存为jmeter(jmx)文件 4、在jmeter中打开文件 5、添加一个后置处理器:查看结果树,后运行看看能否成功…...
TypeScript系列07-类型声明文件
在现代前端开发中,TypeScript已成为提升代码质量和开发体验的利器。对于React和React Native项目,合理利用类型声明文件不仅能提供更好的智能提示和类型检查,还能显著减少运行时错误。本文将深入探讨类型声明文件的编写与使用。 1. 声明文件…...
【社交+陪玩服务】全场景陪玩系统源码 小程序+H5双端 社群互动+即时点单+搭建教程
内容目录 一、详细介绍二、效果展示1.部分代码2.效果图展示 三、学习资料下载 一、详细介绍 找搭子系统源码,圈子源码、社交源码、陪玩源码,亲测 100% 可用,跟市场上卖 1w的那款一模一样,功能非常齐全,企业级别运营的…...
【Java并发】【synchronized】适合初学者体质入门的synchronized
👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD 🔥 2025本人正在沉淀中… 博客更新速度 👍 欢迎点赞、收藏、关注,跟上我的更新节奏 📚欢迎订阅专栏…...
经销商管理系统选型解析:8款产品详评
本文主要介绍了以下8款经销商管理系统:1.纷享销客; 2.用友T6经销商管理系统; 3.金蝶经销商管理系统; 4.鼎捷经销商管理系统; 5.浪潮经销商管理系统; 6.销售易; 7.SAP Business One Distributor …...
基于STM32的逻辑分析仪
目录 制约性能因素协议命令下位机回复CMD_ID的回复CMD_METADATA命令的回复上报的采样数 设置使用开源软件PulseView设置操作1.设置采样数2.设置采样频率3.使能或禁止通道4.设置通道的触发条件 实现准备汇编指令精确测量时间 程序C语言初实现采集数据上报…...
mapbox高阶,结合threejs(threebox)添加管道
👨⚕️ 主页: gis分享者 👨⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨⚕️ 收录于专栏:mapbox 从入门到精通 文章目录 一、🍀前言1.1 ☘️mapboxgl.Map 地图对象1.2 ☘️mapboxgl.Map style属性1.3 ☘️threebox Tube静态对象二、🍀使用thr…...
使用PySpark进行大数据处理与机器学习实战指南
1. 技术介绍 1.1 PySpark概述 PySpark是Apache Spark的Python API,它结合了Python的易用性和Spark的分布式计算能力,能够高效处理PB级数据集。Spark基于内存计算的特性使其比传统Hadoop MapReduce快10-100倍,支持流处理、SQL查询、机器学习…...
注意力机制-学习
1. 自注意力机制 句子:"The animal didnt cross the street because it was too tired." 在这个句子中,“it”指的是“animal”. 在自注意力机制中,当我们处理到“it”这个词时,模型会计算“it”与句子中其他所有词的…...