数据结构(线性表)
1线性表的定义与操作
1.1线性表的定义
线性表是一种基础的数据结构,其主要特点是:数据元素之间存在一种线性关系(一对一)。线性表的每一个数据元素都有一个唯一的前驱和后继,除了第一个元素没有前驱,最后一个元素没有后继
- 有限性:线性表中数据元素的个数是有限的。
- 序列性:数据元素之间存在着先后顺序,即线性表中的数据元素是有顺序的。
- 相同数据类型:线性表中的数据元素具有相同的数据类型。
线性表(Linear List)是由n个数据元素组成的有限序列,可以用以下形式表示:
- 顺序线性表:数据元素在内存中占据连续的存储空间。
- 链式线性表:数据元素在内存中不必连续,但每个元素都包含指向下一个元素的地址(指针)
1.2线性表的基本操作
-
初始化操作
- 功能:创建一个空的线性表,为后续的操作做好准备。
- 实现:通常分配一定的存储空间,并将线性表的长度设置为 0。
-
插入操作
- 功能:在指定位置插入一个新的数据元素。
- 实现:首先要判断插入位置是否合法,如果合法,则从插入位置开始,将后面的元素依次向后移动一位,然后在指定位置插入新元素,并更新线性表的长度。
- 例如,在线性表 [1, 2, 3] 中,在位置 2 插入元素 4,结果为 [1, 2, 4, 3]。
-
删除操作
- 功能:删除指定位置的数据元素。
- 实现:首先判断删除位置是否合法,如果合法,则将删除位置后面的元素依次向前移动一位,覆盖被删除的元素,并更新线性表的长度。
- 例如,在线性表 [1, 2, 3, 4] 中,删除位置 2 的元素,结果为 [1, 2, 4]。
-
查找操作
- 功能:在线性表中查找指定的数据元素,并返回其位置。
- 实现:从线性表的第一个元素开始,依次比较每个元素与目标元素是否相等,直到找到目标元素或遍历完整个线性表。
- 例如,在线性表 [1, 2, 3, 4] 中,查找元素 3,返回位置 2。
-
遍历操作
- 功能:依次访问线性表中的每个数据元素。
- 实现:使用循环从线性表的第一个元素开始,依次访问每个元素,直到遍历完整个线性表。
- 例如,对于线性表 [1, 2, 3, 4],遍历输出为 “1 2 3 4”。
-
获取长度操作
- 功能:返回线性表中数据元素的个数。
- 实现:直接返回存储线性表长度的变量值。
-
判断空操作
- 功能:判断线性表是否为空。
- 实现:如果线性表的长度为 0,则为空,返回 true;否则返回 false。
2线性表的顺序存储
2.1顺序表的定义与初始化
一、顺序表的定义
顺序表,又称线性表的顺序存储结构,是用一组地址连续的存储单元依次存储线性表中的数据元素,从而使得逻辑上相邻的两个元素在物理位置上也相邻。在顺序表中,各个表项的逻辑顺序与其存放的物理顺序一致,即第i个表项存储于第i个物理位置(1≤i≤n)。
顺序表具有以下特点:
- 随机访问:可通过首地址和元素序号在单位时间O(1)内找到指定的元素。
- 存储密度高:因为每个结点存储空间只用来存储数据元素,没有额外的开销。
- 物理位置相邻:逻辑上相邻的元素在物理位置上也相邻,因此插入和删除元素需要移动大量元素,比较耗时。
二、顺序表的初始化
-
确定顺序表的存储结构
- 通常可以使用数组来实现顺序表。定义一个数组来存储数据元素,并记录顺序表的当前长度。
-
分配存储空间
- 根据实际需求确定数组的大小,并在内存中分配相应的空间。可以在程序运行时动态分配空间,也可以使用固定大小的数组。
-
设置初始状态
- 将顺序表的长度初始化为 0,表示当前没有数据元素。同时,可以将数组的所有元素初始化为特定的值,如 0 或 null。
以下是用 C 语言实现顺序表初始化的示例代码:
方式一:静态数组定义和初始化
这是最简单的定义和初始化方式,适用于固定大小的顺序表。
#include <stdio.h>#define MAXSIZE 100 // 定义顺序表的最大容量
typedef int ElemType; // 定义顺序表中元素的类型typedef struct {ElemType data[MAXSIZE]; // 存储数据的静态数组int length; // 当前表长
} SqList; // 定义顺序表的结构体类型// 初始化顺序表
void InitList(SqList *L) {L->length = 0; // 初始化表长为0
}int main() {SqList L; // 声明一个顺序表变量InitList(&L); // 初始化顺序表// ... 其他操作return 0;
}
方式二:动态数组定义和初始化
这种方式允许在运行时动态分配内存,适用于不确定顺序表大小的情况。
#include <stdio.h>
#include <stdlib.h>typedef int ElemType; // 定义顺序表中元素的类型typedef struct {ElemType *data; // 存储数据的动态数组int length; // 当前表长int maxSize; // 数组的最大容量
} SqList; // 定义顺序表的结构体类型// 初始化顺序表
void InitList(SqList *L, int maxSize) {L->data = (ElemType *)malloc(maxSize * sizeof(ElemType)); // 动态分配内存if (L->data == NULL) {printf("内存分配失败\n");exit(1);}L->length = 0; // 初始化表长为0L->maxSize = maxSize; // 设置最大容量
}// 释放顺序表的内存
void FreeList(SqList *L) {free(L->data);L->data = NULL;L->length = 0;L->maxSize = 0;
}int main() {SqList L; // 声明一个顺序表变量InitList(&L, 100); // 初始化顺序表,最大容量为100// ... 其他操作FreeList(&L); // 释放顺序表的内存return 0;
}
方式三:内存分配与初始化函数结合
这种方式将内存分配和初始化结合在一起,适用于需要初始化默认值的情况。
#include <stdio.h>
#include <stdlib.h>typedef int ElemType; // 定义顺序表中元素的类型typedef struct {ElemType *data; // 存储数据的动态数组int length; // 当前表长int maxSize; // 数组的最大容量
} SqList; // 定义顺序表的结构体类型// 初始化顺序表并分配内存
SqList* CreateList(int maxSize) {SqList *L = (SqList *)malloc(sizeof(SqList)); // 动态分配顺序表结构体内存if (L == NULL) {printf("内存分配失败\n");exit(1);}L->data = (ElemType *)malloc(maxSize * sizeof(ElemType)); // 动态分配数组内存if (L->data == NULL) {printf("内存分配失败\n");free(L);exit(1);}L->length = 0; // 初始化表长为0L->maxSize = maxSize; // 设置最大容量return L;
}// 释放顺序表的内存
void DestroyList(SqList *L) {free(L->data);free(L);
}int main() {SqList *L = CreateList(100); // 创建顺序表,最大容量为100// ... 其他操作DestroyList(L); // 释放顺序表的内存return 0;
}
2.2顺序表的基本操作
1插入运算
插入运算的步骤
- 检查位置合法性:
- 确保插入位置
pos
在有效范围内,即1 ≤ pos ≤ length + 1
,其中length
是顺序表的当前长度。 - 如果
pos
小于 1 或大于length + 1
,则插入位置非法,通常返回错误或抛出异常。
- 确保插入位置
- 检查容量:
- 确保顺序表的容量(即数组的大小)足够容纳新元素。
- 如果当前容量不足,需要进行扩容操作(通常是分配一个更大的数组,并将原数组的内容复制到新数组中)。
- 插入元素:
- 从插入位置
pos
开始,将顺序表中该位置及其后的所有元素向后移动一个位置,以腾出空间插入新元素。 - 将新元素插入到
pos - 1
的位置(因为数组索引从 0 开始)。
- 从插入位置
- 更新长度:
- 将顺序表的长度
length
增加 1。
- 将顺序表的长度
以下是一个简单的 c语言实现,展示了如何在顺序表中插入元素
#include <stdio.h>
#include <stdlib.h>#define MAXSIZE 100 // 定义顺序表的最大容量
typedef int ElemType; // 定义顺序表中元素的类型typedef struct {ElemType data[MAXSIZE]; // 存储数据的数组int length; // 当前表长
} SqList; // 定义顺序表的结构体类型// 初始化顺序表
void InitList(SqList *L) {L->length = 0; // 初始化表长为0
}// 插入元素到顺序表的指定位置
int InsertList(SqList *L, int pos, ElemType e) {if (L->length >= MAXSIZE) {printf("顺序表已满,无法插入元素 %d\n", e);return 0; // 插入失败}if (pos < 1 || pos > L->length + 1) {printf("插入位置 %d 不合法\n", pos);return 0; // 插入失败}// 从后向前移动元素,为新元素腾出位置for (int i = L->length; i >= pos; i--) {L->data[i] = L->data[i - 1];}L->data[pos - 1] = e; // 插入新元素L->length++; // 更新当前表长return 1; // 插入成功
}// 打印顺序表中的所有元素
void DisplayList(const SqList *L) {for (int i = 0; i < L->length; i++) {printf("%d ", L->data[i]);}printf("\n");
}int main() {SqList L; // 声明一个顺序表变量InitList(&L); // 初始化顺序表// 插入1到10的数字到顺序表中for (int i = 1; i <= 10; i++) {InsertList(&L, i, i);}// 打印顺序表中的所有元素printf("顺序表中的元素:");DisplayList(&L);// 在位置3插入一个新元素 99if (InsertList(&L, 3, 99)) {printf("插入元素后的顺序表:");DisplayList(&L);}return 0;
}
2删除运算
删除运算的步骤
- 检查位置合法性:
- 确保删除位置
pos
在有效范围内,即1 ≤ pos ≤ length
,其中length
是顺序表的当前长度。 - 如果
pos
小于 1 或大于length
,则删除位置非法,通常返回错误或抛出异常。
- 确保删除位置
- 删除元素:
- 从删除位置
pos
开始,将顺序表中该位置之后的所有元素向前移动一个位置,以填补被删除元素留下的空位。 - 注意,不需要为被删除元素分配新的存储空间或进行其他特殊处理,因为顺序表的内存是连续的,只需调整后续元素的位置即可。
- 从删除位置
- 更新长度:
- 将顺序表的长度
length
减少 1。
- 将顺序表的长度
// 删除顺序表中指定位置的元素
int deleteElem(SeqList *L, int pos) {if (L->length == 0) {return 0; // 顺序表为空,删除失败}if (pos < 1 || pos > L->length) {return 0; // 位置不合法,删除失败}for (int i = pos; i < L->length; i++) {L->data[i - 1] = L->data[i];}L->length--;return 1; // 删除成功
}
以下是一个完整的 C 代码示例,包括顺序表的定义、初始化、删除运算以及显示顺序表内容的功能。
#include <stdio.h>
#include <stdlib.h>#define MAXSIZE 100 // 定义顺序表的最大容量
typedef int ElemType; // 定义顺序表中元素的类型typedef struct {ElemType data[MAXSIZE]; // 存储数据的数组int length; // 当前表长
} SqList; // 定义顺序表的结构体类型// 初始化顺序表
void InitList(SqList *L) {L->length = 0; // 初始化表长为0
}// 插入元素到顺序表的指定位置
int InsertList(SqList *L, int pos, ElemType e) {if (L->length >= MAXSIZE) {printf("顺序表已满,无法插入元素 %d\n", e);return 0; // 插入失败}if (pos < 1 || pos > L->length + 1) {printf("插入位置 %d 不合法\n", pos);return 0; // 插入失败}// 从后向前移动元素,为新元素腾出位置for (int i = L->length; i >= pos; i--) {L->data[i] = L->data[i - 1];}L->data[pos - 1] = e; // 插入新元素L->length++; // 更新当前表长return 1; // 插入成功
}// 删除顺序表中指定位置的元素
int DeleteList(SqList *L, int pos, ElemType *e) {if (L->length == 0) {printf("顺序表为空,无法删除元素\n");return 0; // 删除失败}if (pos < 1 || pos > L->length) {printf("删除位置 %d 不合法\n", pos);return 0; // 删除失败}*e = L->data[pos - 1]; // 保存被删除的元素// 从前向后移动元素,覆盖被删除的元素for (int i = pos - 1; i < L->length - 1; i++) {L->data[i] = L->data[i + 1];}L->length--; // 更新当前表长return 1; // 删除成功
}// 打印顺序表中的所有元素
void DisplayList(const SqList *L) {for (int i = 0; i < L->length; i++) {printf("%d ", L->data[i]);}printf("\n");
}int main() {SqList L; // 声明一个顺序表变量InitList(&L); // 初始化顺序表// 插入1到10的数字到顺序表中for (int i = 1; i <= 10; i++) {InsertList(&L, i, i);}// 打印顺序表中的所有元素printf("顺序表中的元素:");DisplayList(&L);// 从顺序表中删除位置3的元素ElemType deletedElem;if (DeleteList(&L, 3, &deletedElem)) {printf("删除了元素 %d\n", deletedElem);printf("删除元素后的顺序表:");DisplayList(&L);}return 0;
}
3按值查找
按值查找的步骤
-
遍历顺序表:从顺序表的第一个元素开始,逐个检查每个元素的值是否等于指定的值。
-
记录位置:如果找到了与指定值相等的元素,则记录该元素的位置(索引)。注意,根据具体实现,位置可能是从0开始还是从1开始的索引。
-
返回结果:如果遍历完整个顺序表后找到了元素,则返回记录的位置;如果未找到,则返回一个表示失败的值。
// 按值查找元素在顺序表中的位置
int searchElem(SeqList *L, int elem) {for (int i = 0; i < L->length; i++) {if (L->data[i] == elem) {return i + 1;}}return -1; // 未找到元素
}
以下是一个简单的 c语言实现,展示了如何在顺序表中找到具体元素
#include <stdio.h>
#include <stdlib.h>#define MAXSIZE 100 // 定义顺序表的最大容量
typedef int ElemType; // 定义顺序表中元素的类型typedef struct {ElemType data[MAXSIZE]; // 存储数据的数组int length; // 当前表长
} SqList; // 定义顺序表的结构体类型// 初始化顺序表
void InitList(SqList *L) {L->length = 0; // 初始化表长为0
}// 插入元素到顺序表的指定位置
int InsertList(SqList *L, int pos, ElemType e) {if (L->length >= MAXSIZE) {printf("顺序表已满,无法插入元素 %d\n", e);return 0; // 插入失败}if (pos < 1 || pos > L->length + 1) {printf("插入位置 %d 不合法\n", pos);return 0; // 插入失败}// 从后向前移动元素,为新元素腾出位置for (int i = L->length; i >= pos; i--) {L->data[i] = L->data[i - 1];}L->data[pos - 1] = e; // 插入新元素L->length++; // 更新当前表长return 1; // 插入成功
}// 按值查找顺序表中的元素
int LocateElem(const SqList *L, ElemType e) {for (int i = 0; i < L->length; i++) {if (L->data[i] == e) {return i + 1; // 返回元素位置(从1开始)}}return 0; // 未找到元素
}// 打印顺序表中的所有元素
void DisplayList(const SqList *L) {for (int i = 0; i < L->length; i++) {printf("%d ", L->data[i]);}printf("\n");
}int main() {SqList L; // 声明一个顺序表变量InitList(&L); // 初始化顺序表// 插入1到10的数字到顺序表中for (int i = 1; i <= 10; i++) {InsertList(&L, i, i);}// 打印顺序表中的所有元素printf("顺序表中的元素:");DisplayList(&L);// 按值查找元素 5int pos = LocateElem(&L, 5);if (pos) {printf("元素 5 的位置是 %d\n", pos);} else {printf("未找到元素 5\n");}// 按值查找元素 20pos = LocateElem(&L, 20);if (pos) {printf("元素 20 的位置是 %d\n", pos);} else {printf("未找到元素 20\n");}return 0;
}
3线性表的链式存储
优点
- 动态大小:
- 链表不需要在创建时指定大小,它可以根据需要动态地增长和缩小。
- 高效插入和删除:
- 在链表中,插入和删除操作(特别是在已知节点位置的情况下)通常可以在O(1)时间复杂度内完成,因为只需要调整相邻节点的指针。
- 无需连续内存:
- 链表节点可以分散在内存中的任何位置,这有助于减少内存碎片问题。
- 灵活性:
- 链表可以通过各种方式(如双向链表、循环链表等)进行扩展,以满足不同的需求。
缺点
- 访问速度慢:
- 链表不支持随机访问,因此查找特定位置的元素需要从头节点开始顺序遍历,时间复杂度为O(n)。
- 额外空间开销:
- 每个链表节点都需要额外的空间来存储指针,这增加了内存的使用量。
- 内存管理复杂:
- 链表节点的动态分配和释放需要仔细管理,以避免内存泄漏和碎片化。
- 缓存性能较差:
- 由于链表节点在内存中通常不是连续存储的,因此可能无法充分利用CPU缓存,导致访问速度下降。
应用场景
- 链表适用于需要频繁进行插入和删除操作,但对随机访问要求不高的场景。
- 双向链表和循环链表等扩展形式可以用于需要双向遍历或循环遍历的场景。
- 在实现某些数据结构(如栈、队列、哈希表等)时,链表也可以作为底层存储结构。
3.1单向链表的结构
一、结构特点
- 节点组成:
- 单向链表由一系列节点(Node)组成,每个节点包含两个部分:数据域(Data)和指针域(Next)。
- 数据域用于存储节点的数据,可以是任意类型,由编程者自行指定。
- 指针域用于存储下一个节点的地址(或指针),指向链表中的下一个节点。
- 链接方向:
- 单向链表的链接方向是单向的,即只能从头节点开始,通过指针域依次访问后续节点。
- 头节点与头指针:
- 头节点是为了操作方便而设立的,通常放在第一个元素节点之前,其数据域一般无实际意义(但也可以用来存放链表长度等信息)。
- 头指针是指向链表头节点的指针,无论链表是否为空,头指针均不为空,是链表的必要元素。
- 无头与有头链表:
- 无头链表:不设立头节点,头指针直接指向第一个元素节点(如果存在)。
- 有头链表:设立头节点,头指针指向头节点,头节点的指针域指向第一个元素节点。
二单链表的定义与初始化
一、单链表的定义
typedef struct ListNode {int data;struct ListNode* next;
} ListNode;
二、单链表的初始化
ListNode* initList() {// 创建一个空的头节点ListNode* head = (ListNode*)malloc(sizeof(ListNode));head->data = 0; // 头节点的数据域可以存储一些特殊信息,这里初始化为 0head->next = NULL;return head;
}
以下是一个用C语言初始化单链表的示例(包含头节点):
#include <stdio.h>
#include <stdlib.h>// 定义链表节点
typedef int ElemType; // 定义链表中元素的类型typedef struct Node {ElemType data; // 数据域struct Node *next; // 指针域,指向下一个节点
} Node, *LinkedList;// 初始化链表
LinkedList InitList() {LinkedList L = (LinkedList)malloc(sizeof(Node)); // 申请头节点空间if (!L) {printf("内存分配失败\n");exit(1); // 内存分配失败,退出程序}L->next = NULL; // 初始化头节点,指针域为空return L; // 返回初始化后的链表
}// 测试初始化链表
int main() {LinkedList L = InitList(); // 初始化链表if (L->next == NULL) {printf("链表初始化成功,头节点指向 NULL\n");}// 这里可以继续对链表进行插入、删除等操作free(L); // 释放头节点的内存return 0;
}
3.2单链表的基本操作
1插入结点建立单链表
在单链表中插入节点是一个常见的操作,它允许我们在链表的特定位置添加一个新的节点。
- 在链表头部插入节点:新节点成为链表的新头节点。
- 在链表尾部插入节点:新节点成为链表的最后一个节点。
- 在链表中间插入节点:在指定位置插入新节点,新节点位于指定位置的节点之前。
单链表的定义
#include <stdio.h>
#include <stdlib.h>// 定义链表节点的类型
typedef int ElemType; // 定义链表中元素的类型typedef struct Node {ElemType data; // 数据域struct Node *next; // 指针域,指向下一个节点
} Node, *LinkedList;
初始化单链表
// 初始化链表
LinkedList InitList() {LinkedList L = (LinkedList)malloc(sizeof(Node)); // 申请头节点空间if (!L) {printf("内存分配失败\n");exit(1); // 内存分配失败,退出程序}L->next = NULL; // 初始化头节点,指针域为空return L; // 返回初始化后的链表
}
在链表头部插入节点
// 在链表头部插入节点
void InsertHead(LinkedList L, ElemType e) {Node *s = (Node *)malloc(sizeof(Node)); // 申请新节点空间if (!s) {printf("内存分配失败\n");exit(1); // 内存分配失败,退出程序}s->data = e; // 新节点数据域赋值s->next = L->next; // 新节点的指针域指向原头节点的下一个节点L->next = s; // 头节点的指针域指向新节点
}
在链表尾部插入节点
// 在链表尾部插入节点
void InsertTail(LinkedList L, ElemType e) {Node *p = L;// 找到链表的最后一个节点while (p->next) {p = p->next;}Node *s = (Node *)malloc(sizeof(Node)); // 申请新节点空间if (!s) {printf("内存分配失败\n");exit(1); // 内存分配失败,退出程序}s->data = e; // 新节点数据域赋值s->next = NULL; // 新节点的指针域指向NULLp->next = s; // 原最后一个节点的指针域指向新节点
}
在指定位置插入节点
// 在链表的第pos个位置插入节点
int InsertAtPosition(LinkedList L, int pos, ElemType e) {Node *p = L;int i = 0;// 找到插入位置的前一个节点while (p && i < pos - 1) {p = p->next;i++;}if (!p || i > pos - 1) {printf("插入位置不合法\n");return 0; // 插入失败}// 创建新节点Node *s = (Node *)malloc(sizeof(Node));if (!s) {printf("内存分配失败\n");exit(1); // 内存分配失败,退出程序}s->data = e; // 新节点数据域赋值s->next = p->next; // 新节点的指针域指向插入位置的下一个节点p->next = s; // 插入位置前一个节点的指针域指向新节点return 1; // 插入成功
}
打印链表
// 打印链表中的所有元素
void DisplayList(LinkedList L) {Node *p = L->next;while (p) {printf("%d ", p->data);p = p->next;}printf("\n");
}
主函数
int main() {LinkedList L = InitList(); // 初始化链表// 在链表头部插入节点InsertHead(L, 1);InsertHead(L, 2);// 在链表尾部插入节点InsertTail(L, 3);InsertTail(L, 4);// 在指定位置插入节点InsertAtPosition(L, 3, 5); // 在第3个位置插入元素5// 打印链表中的所有元素printf("链表中的元素:");DisplayList(L);return 0;
}
2有头节点的单链表
在有头节点的单链表中,链表包含一个额外的节点,称为头节点(或哑节点、哨兵节点)。头节点不存储实际的数据,而是作为链表的起点,其next
指针指向链表的第一个实际数据节点。头节点的存在可以简化链表的操作,特别是在插入和删除节点时,因为它允许我们在链表头部进行这些操作而无需检查是否为空链表。
定义
在C语言中,有头节点的单链表可以通过定义一个结构体来表示链表节点,其中包含一个数据域和一个指向下一个节点的指针域。头节点也是这个结构体的一个实例,但其数据域通常不会被使用。
typedef struct Node { int data; // 数据域,可以存储任意类型的数据,这里以int为例 struct Node* next; // 指针域,指向下一个节点
} Node;
初始化
初始化有头节点的单链表时,需要为头节点分配内存,并将其next
指针设置为NULL
,表示链表当前为空。以下是一个初始化有头节点的单链表的函数示例:
Node* initLinkedList() { Node* head = (Node*)malloc(sizeof(Node)); // 为头节点分配内存 if (head == NULL) { // 内存分配失败,可以打印错误信息或进行其他错误处理 printf("内存分配失败\n"); return NULL; // 返回NULL表示初始化失败 } head->next = NULL; // 初始化头节点的next指针为NULL,表示链表为空 return head; // 返回头节点的指针
}
3求单链表的长度
#include <stdio.h>
#include <stdlib.h>// 定义链表节点的类型
typedef int ElemType;typedef struct Node {ElemType data; // 数据域struct Node *next; // 指针域,指向下一个节点
} Node, *LinkedList;// 初始化有头节点的单链表
LinkedList InitList() {LinkedList L = (LinkedList)malloc(sizeof(Node)); // 申请头节点空间if (!L) {printf("内存分配失败\n");exit(1);}L->next = NULL; // 初始化头节点的下一个节点为空return L;
}// 在链表尾部插入节点
void InsertTail(LinkedList L, ElemType e) {Node *p = L;while (p->next) {p = p->next;}Node *s = (Node *)malloc(sizeof(Node)); // 申请新节点空间if (!s) {printf("内存分配失败\n");exit(1);}s->data = e; // 新节点数据域赋值s->next = NULL; // 新节点的指针域指向NULLp->next = s; // 原最后一个节点的指针域指向新节点
}// 求链表的长度
int GetLength(LinkedList L) {int length = 0; // 初始化长度为0Node *p = L->next; // 从头节点的下一个节点开始遍历while (p) {length++; // 计数p = p->next; // 移动到下一个节点}return length; // 返回链表的长度
}// 打印链表中的所有元素
void DisplayList(LinkedList L) {Node *p = L->next;while (p) {printf("%d ", p->data);p = p->next;}printf("\n");
}// 主函数
int main() {LinkedList L = InitList(); // 初始化链表// 插入一些元素到链表中InsertTail(L, 10);InsertTail(L, 20);InsertTail(L, 30);InsertTail(L, 40);// 打印链表printf("链表中的元素:");DisplayList(L);// 求链表的长度int length = GetLength(L);printf("链表的长度是:%d\n", length);return 0;
}
4输出表中所有元素
#include <stdio.h>
#include <stdlib.h>// 定义链表节点的类型
typedef int ElemType;typedef struct Node {ElemType data; // 数据域struct Node *next; // 指针域,指向下一个节点
} Node, *LinkedList;// 初始化有头节点的单链表
LinkedList InitList() {LinkedList L = (LinkedList)malloc(sizeof(Node)); // 申请头节点空间if (!L) {printf("内存分配失败\n");exit(1);}L->next = NULL; // 初始化头节点的下一个节点为空return L;
}// 在链表尾部插入节点
void InsertTail(LinkedList L, ElemType e) {Node *p = L;while (p->next) {p = p->next;}Node *s = (Node *)malloc(sizeof(Node)); // 申请新节点空间if (!s) {printf("内存分配失败\n");exit(1);}s->data = e; // 新节点数据域赋值s->next = NULL; // 新节点的指针域指向NULLp->next = s; // 原最后一个节点的指针域指向新节点
}// 打印链表中的所有元素
void DisplayList(LinkedList L) {Node *p = L->next; // 从头节点的下一个节点开始遍历while (p) {printf("%d ", p->data); // 输出当前节点的数据域p = p->next; // 移动到下一个节点}printf("\n");
}// 主函数
int main() {LinkedList L = InitList(); // 初始化链表// 插入一些元素到链表中InsertTail(L, 10);InsertTail(L, 20);InsertTail(L, 30);InsertTail(L, 40);// 打印链表中的所有元素printf("链表中的元素:");DisplayList(L);return 0;
}
5删除指定元素
#include <stdio.h>
#include <stdlib.h>// 定义链表节点的类型
typedef int ElemType;typedef struct Node {ElemType data; // 数据域struct Node *next; // 指针域,指向下一个节点
} Node, *LinkedList;// 初始化有头节点的单链表
LinkedList InitList() {LinkedList L = (LinkedList)malloc(sizeof(Node)); // 申请头节点空间if (!L) {printf("内存分配失败\n");exit(1);}L->next = NULL; // 初始化头节点的下一个节点为空return L;
}// 在链表尾部插入节点
void InsertTail(LinkedList L, ElemType e) {Node *p = L;while (p->next) {p = p->next;}Node *s = (Node *)malloc(sizeof(Node)); // 申请新节点空间if (!s) {printf("内存分配失败\n");exit(1);}s->data = e; // 新节点数据域赋值s->next = NULL; // 新节点的指针域指向NULLp->next = s; // 原最后一个节点的指针域指向新节点
}// 删除链表中指定的元素
void DeleteElement(LinkedList L, ElemType e) {Node *p = L; // p 指向头节点Node *q; // q 用于指向要删除的节点// 遍历链表,找到目标元素的前一个节点while (p->next && p->next->data != e) {p = p->next;}// 如果找到了目标元素if (p->next) {q = p->next; // q 指向要删除的节点p->next = q->next; // 修改前一个节点的指针域,跳过目标节点free(q); // 释放目标节点的内存空间printf("元素 %d 已删除\n", e);} else {printf("链表中不存在元素 %d\n", e);}
}// 打印链表中的所有元素
void DisplayList(LinkedList L) {Node *p = L->next; // 从头节点的下一个节点开始遍历while (p) {printf("%d ", p->data); // 输出当前节点的数据域p = p->next; // 移动到下一个节点}printf("\n");
}// 主函数
int main() {LinkedList L = InitList(); // 初始化链表// 插入一些元素到链表中InsertTail(L, 10);InsertTail(L, 20);InsertTail(L, 30);InsertTail(L, 40);// 打印链表中的所有元素printf("删除前的链表:");DisplayList(L);// 删除指定元素DeleteElement(L, 30);// 打印删除后的链表printf("删除后的链表:");DisplayList(L);return 0;
}
6查找特定元素
1. 无头结点的单链表查找代码
struct ListNode {int value; // 节点的值struct ListNode* next; // 指向下一个节点的指针
};// 查找特定元素的函数
struct ListNode* search_element(struct ListNode* head, int target) {struct ListNode* current = head; // 从头节点开始查找while (current != NULL) { // 遍历链表直到结束if (current->value == target) { // 如果当前节点的值等于目标值return current; // 找到元素,返回当前节点}current = current->next; // 移动到下一个节点}return NULL; // 未找到元素,返回 NULL
}
2有头结点的单链表查找代码
struct ListNode {int value; // 节点的值struct ListNode* next; // 指向下一个节点的指针
};// 查找特定元素的函数
struct ListNode* search_element(struct ListNode* head, int target) {struct ListNode* current = head->next; // 从头节点的下一个节点开始查找while (current != NULL) { // 遍历链表直到结束if (current->value == target) { // 如果当前节点的值等于目标值return current; // 找到元素,返回当前节点}current = current->next; // 移动到下一个节点}return NULL; // 未找到元素,返回 NULL
}
3完整代码
#include <stdio.h>
#include <stdlib.h>// 定义链表节点的类型
typedef int ElemType;typedef struct Node {ElemType data; // 数据域struct Node *next; // 指针域,指向下一个节点
} Node, *LinkedList;// 初始化有头节点的单链表
LinkedList InitList() {LinkedList L = (LinkedList)malloc(sizeof(Node)); // 申请头节点空间if (!L) {printf("内存分配失败\n");exit(1);}L->next = NULL; // 初始化头节点的下一个节点为空return L;
}// 在链表尾部插入节点
void InsertTail(LinkedList L, ElemType e) {Node *p = L;while (p->next) {p = p->next;}Node *s = (Node *)malloc(sizeof(Node)); // 申请新节点空间if (!s) {printf("内存分配失败\n");exit(1);}s->data = e; // 新节点数据域赋值s->next = NULL; // 新节点的指针域指向NULLp->next = s; // 原最后一个节点的指针域指向新节点
}// 查找链表中指定的元素
int FindElement(LinkedList L, ElemType e) {Node *p = L->next; // 从头节点的下一个节点开始遍历int index = 1; // 记录元素的索引位置// 遍历链表,查找目标元素while (p) {if (p->data == e) {return index; // 返回元素的索引位置}p = p->next; // 移动到下一个节点index++; // 索引位置加1}return -1; // 如果未找到,返回-1
}// 打印链表中的所有元素
void DisplayList(LinkedList L) {Node *p = L->next; // 从头节点的下一个节点开始遍历while (p) {printf("%d ", p->data); // 输出当前节点的数据域p = p->next; // 移动到下一个节点}printf("\n");
}// 主函数
int main() {LinkedList L = InitList(); // 初始化链表// 插入一些元素到链表中InsertTail(L, 10);InsertTail(L, 20);InsertTail(L, 30);InsertTail(L, 40);// 打印链表中的所有元素printf("链表中的元素:");DisplayList(L);// 查找指定元素ElemType target = 30;int index = FindElement(L, target);if (index != -1) {printf("元素 %d 在链表中的位置是 %d\n", target, index);} else {printf("链表中不存在元素 %d\n", target);}return 0;
}
7判断单链表是否为空
1. 无头结点的单链表判断是否为空
struct ListNode {int value; // 节点的值struct ListNode* next; // 指向下一个节点的指针
};// 判断无头结点单链表是否为空
int is_empty(struct ListNode* head) {return head == NULL; // 头节点为 NULL 则链表为空
}
2. 有头结点的单链表判断是否为空
struct ListNode {int value; // 节点的值struct ListNode* next; // 指向下一个节点的指针
};// 判断有头结点单链表是否为空
int is_empty(struct ListNode* head) {return head->next == NULL; // 如果头节点的下一个节点为 NULL,则链表为空
}
8销毁单链表
1. 无头结点的单链表销毁
#include <stdlib.h>struct ListNode {int value; // 节点的值struct ListNode* next; // 指向下一个节点的指针
};// 销毁无头结点单链表的函数
void destroy_list(struct ListNode* head) {struct ListNode* current = head;struct ListNode* next_node;while (current != NULL) { // 遍历链表直到结束next_node = current->next; // 保存下一个节点free(current); // 释放当前节点current = next_node; // 移动到下一个节点}
}
2. 有头结点的单链表销毁
#include <stdlib.h>struct ListNode {int value; // 节点的值struct ListNode* next; // 指向下一个节点的指针
};// 销毁有头结点单链表的函数
void destroy_list(struct ListNode* head) {struct ListNode* current = head;struct ListNode* next_node;while (current != NULL) { // 遍历链表直到结束next_node = current->next; // 保存下一个节点free(current); // 释放当前节点current = next_node; // 移动到下一个节点}head = NULL; // 销毁后将头节点指针置为 NULL,避免野指针
}
9清空单链表
1. 无头结点的单链表清空
#include <stdlib.h>struct ListNode {int value; // 节点的值struct ListNode* next; // 指向下一个节点的指针
};// 清空无头结点单链表的函数
void clear_list(struct ListNode** head) {struct ListNode* current = *head;struct ListNode* next_node;while (current != NULL) { // 遍历链表直到结束next_node = current->next; // 保存下一个节点free(current); // 释放当前节点current = next_node; // 移动到下一个节点}*head = NULL; // 清空后将头节点指针置为 NULL,表示链表为空
}
2. 有头结点的单链表清空
#include <stdlib.h>struct ListNode {int value; // 节点的值struct ListNode* next; // 指向下一个节点的指针
};// 清空有头结点单链表的函数
void clear_list(struct ListNode* head) {struct ListNode* current = head->next;struct ListNode* next_node;while (current != NULL) { // 遍历链表直到结束next_node = current->next; // 保存下一个节点free(current); // 释放当前节点current = next_node; // 移动到下一个节点}head->next = NULL; // 清空后将头节点的 next 指针置为 NULL,表示链表为空
}
3.3循环链表
循环链表是一种特殊的链表结构,其特点是链表中的最后一个节点指向头节点,形成一个闭环。这种结构使得链表的遍历可以从任何节点开始,并且在遍历到链表末尾时可以无缝地回到链表的开头。循环链表可以是单向的(每个节点只有一个指针指向下一个节点)或双向的(每个节点有两个指针,分别指向前一个节点和后一个节点)。
循环链表的特点
- 闭环结构:链表的最后一个节点指向头节点,形成一个闭环。
- 遍历灵活:可以从任何节点开始遍历,并且在遍历到链表末尾时可以回到链表的开头。
- 节省空间:相比于双向链表,单向循环链表只需要一个指针域,节省了空间。
- 应用场景:常用于需要循环操作的场景,如循环队列、约瑟夫环问题等。
循环链表的基本操作
1. 初始化循环链表
初始化一个空的循环链表,创建一个头节点,并使其指向自身。
#include <stdio.h>
#include <stdlib.h>// 定义链表节点的类型
typedef int ElemType;typedef struct Node {ElemType data; // 数据域struct Node *next; // 指针域,指向下一个节点
} Node, *CircularLinkedList;// 初始化循环链表
CircularLinkedList InitCircularList() {CircularLinkedList L = (CircularLinkedList)malloc(sizeof(Node)); // 申请头节点空间if (!L) {printf("内存分配失败\n");exit(1);}L->next = L; // 头节点的下一个节点指向自身,形成闭环return L;
}
2. 插入节点
在循环链表中插入节点,可以选择在链表的头部或尾部插入。
// 在链表尾部插入节点
void InsertTail(CircularLinkedList L, ElemType e) {Node *p = L;while (p->next != L) { // 找到链表的最后一个节点p = p->next;}Node *s = (Node *)malloc(sizeof(Node)); // 申请新节点空间if (!s) {printf("内存分配失败\n");exit(1);}s->data = e; // 新节点数据域赋值s->next = L; // 新节点的指针域指向头节点p->next = s; // 原最后一个节点的指针域指向新节点
}
3. 删除节点
删除循环链表中的指定节点。
// 删除链表中指定的元素
void DeleteElement(CircularLinkedList L, ElemType e) {Node *p = L; // p 指向头节点Node *q; // q 用于指向要删除的节点// 遍历链表,找到目标元素的前一个节点while (p->next != L && p->next->data != e) {p = p->next;}// 如果找到了目标元素if (p->next != L) {q = p->next; // q 指向要删除的节点p->next = q->next; // 修改前一个节点的指针域,跳过目标节点free(q); // 释放目标节点的内存空间printf("元素 %d 已删除\n", e);} else {printf("链表中不存在元素 %d\n", e);}
}
4. 查找节点
查找循环链表中的指定元素。
// 查找链表中指定的元素
int FindElement(CircularLinkedList L, ElemType e) {Node *p = L->next; // 从头节点的下一个节点开始遍历int index = 1; // 记录元素的索引位置// 遍历链表,查找目标元素while (p != L) {if (p->data == e) {return index; // 返回元素的索引位置}p = p->next; // 移动到下一个节点index++; // 索引位置加1}return -1; // 如果未找到,返回-1
}
5. 打印链表
遍历并打印循环链表中的所有元素。
// 打印链表中的所有元素
void DisplayList(CircularLinkedList L) {Node *p = L->next; // 从头节点的下一个节点开始遍历while (p != L) {printf("%d ", p->data); // 输出当前节点的数据域p = p->next; // 移动到下一个节点}printf("\n");
}
主函数示例
// 主函数
int main() {CircularLinkedList L = InitCircularList(); // 初始化循环链表// 插入一些元素到链表中InsertTail(L, 10);InsertTail(L, 20);InsertTail(L, 30);InsertTail(L, 40);// 打印链表中的所有元素printf("链表中的元素:");DisplayList(L);// 查找指定元素ElemType target = 30;int index = FindElement(L, target);if (index != -1) {printf("元素 %d 在链表中的位置是 %d\n", target, index);} else {printf("链表中不存在元素 %d\n", target);}// 删除指定元素DeleteElement(L, 30);// 打印删除后的链表printf("删除后的链表:");DisplayList(L);return 0;
}
3.4双向链表
双向链表是一种链表结构,与单向链表不同,双向链表中的每个节点除了包含数据域外,还包含两个指针,分别指向前一个节点和后一个节点。这种结构使得链表的操作更加灵活,但也增加了空间的开销。
双向链表的特点
- 双向遍历:可以从任何一个节点开始向前或向后遍历链表。
- 灵活性高:插入和删除操作更加方便,因为可以直接访问前一个节点和后一个节点。
- 空间开销大:每个节点包含两个指针,相比于单向链表,空间开销更大。
- 应用场景:适用于需要频繁插入和删除操作的场景,如文本编辑器中的撤销/重做功能。
双向链表的基本操作
1. 初始化双向链表
初始化一个空的双向链表,创建一个头节点,并使其前后指针都指向自身(如果链表为空)。
#include <stdio.h>
#include <stdlib.h>// 定义链表节点的类型
typedef int ElemType;typedef struct Node {ElemType data; // 数据域struct Node *prev; // 指针域,指向前一个节点struct Node *next; // 指针域,指向下一个节点
} Node, *DoubleLinkedList;// 初始化双向链表
DoubleLinkedList InitDoubleList() {DoubleLinkedList L = (DoubleLinkedList)malloc(sizeof(Node)); // 申请头节点空间if (!L) {printf("内存分配失败\n");exit(1);}L->prev = L; // 头节点的前指针指向自身L->next = L; // 头节点的后指针指向自身return L;
}
2. 插入节点
在双向链表中插入节点,可以选择在链表的头部或尾部插入。
// 在链表尾部插入节点
void InsertTail(DoubleLinkedList L, ElemType e) {Node *p = L;while (p->next != L) { // 找到链表的最后一个节点p = p->next;}Node *s = (Node *)malloc(sizeof(Node)); // 申请新节点空间if (!s) {printf("内存分配失败\n");exit(1);}s->data = e; // 新节点数据域赋值s->next = L; // 新节点的后指针指向头节点s->prev = p; // 新节点的前指针指向原最后一个节点p->next = s; // 原最后一个节点的后指针指向新节点L->prev = s; // 头节点的前指针指向新节点
}
3. 删除节点
删除双向链表中的指定节点。
// 删除链表中指定的元素
void DeleteElement(DoubleLinkedList L, ElemType e) {Node *p = L->next; // p 指向第一个节点// 遍历链表,找到目标元素while (p != L && p->data != e) {p = p->next;}// 如果找到了目标元素if (p != L) {p->prev->next = p->next; // 修改前一个节点的后指针p->next->prev = p->prev; // 修改后一个节点的前指针free(p); // 释放目标节点的内存空间printf("元素 %d 已删除\n", e);} else {printf("链表中不存在元素 %d\n", e);}
}
4. 查找节点
查找双向链表中的指定元素。
// 查找链表中指定的元素
int FindElement(DoubleLinkedList L, ElemType e) {Node *p = L->next; // 从第一个节点开始遍历int index = 1; // 记录元素的索引位置// 遍历链表,查找目标元素while (p != L) {if (p->data == e) {return index; // 返回元素的索引位置}p = p->next; // 移动到下一个节点index++; // 索引位置加1}return -1; // 如果未找到,返回-1
}
5. 打印链表
遍历并打印双向链表中的所有元素。
// 打印链表中的所有元素
void DisplayList(DoubleLinkedList L) {Node *p = L->next; // 从第一个节点开始遍历while (p != L) {printf("%d ", p->data); // 输出当前节点的数据域p = p->next; // 移动到下一个节点}printf("\n");
}
主函数示例
// 主函数
int main() {DoubleLinkedList L = InitDoubleList(); // 初始化双向链表// 插入一些元素到链表中InsertTail(L, 10);InsertTail(L, 20);InsertTail(L, 30);InsertTail(L, 40);// 打印链表中的所有元素printf("链表中的元素:");DisplayList(L);// 查找指定元素ElemType target = 30;int index = FindElement(L, target);if (index != -1) {printf("元素 %d 在链表中的位置是 %d\n", target, index);} else {printf("链表中不存在元素 %d\n", target);}// 删除指定元素DeleteElement(L, 30);// 打印删除后的链表printf("删除后的链表:");DisplayList(L);return 0;
}
总结:
比较项目 | 顺序表 | 链表 |
---|---|---|
存储方式 | 顺序存储,用一段连续的内存空间存储数据元素。 | 链式存储,通过指针将各个数据元素链接起来。 |
随机访问 | 支持随机访问,可以在 O (1) 时间内访问任意位置的元素。 | 不支持随机访问,访问特定位置元素需要从链表头开始遍历,时间复杂度为 O (n)。 |
插入和删除操作 | 在中间位置插入和删除元素时,需要移动大量元素,时间复杂度为 O (n)。 | 在中间位置插入和删除元素只需修改指针,时间复杂度为 O (1)。 |
内存占用 | 需要预先分配连续的内存空间,如果空间不足需要进行扩容,可能导致大量数据的移动;空间较为紧凑,没有额外指针占用。 | 按需分配内存,不会出现内存浪费,但每个节点都需要额外的指针空间,相对来说空间占用可能稍大。 |
空间复杂度 | 如果预先分配的空间过大,可能会造成空间浪费;如果空间不足进行扩容,也会有一定开销。一般为 O (n)。 | 每个节点都需要额外指针,空间开销相对稳定。一般也为 O (n)。 |
适用场景 | 适合频繁访问元素,而插入和删除操作较少的情况。 | 适合频繁进行插入和删除操作的情况。 |
比较项目 | 单链表 | 循环链表 | 单向循环链表 | 双向循环链表 |
---|---|---|---|---|
存储方式 | 通过指针将各个数据元素链接起来,尾节点指向 NULL。 | 首尾相接形成环状结构。 | 与循环链表类似,只是单向循环链表更强调其单向性。 | 通过两个指针分别指向前驱和后继,首尾相接形成环状结构。 |
遍历方式 | 从链表头开始,顺着指针依次访问每个节点,直到尾节点(指向 NULL)。 | 从链表中任意一个节点开始,顺着指针可以循环访问所有节点。 | 与循环链表类似,单向遍历。 | 从链表中任意一个节点开始,可以顺着指针向前或向后循环访问所有节点。 |
随机访问 | 不支持随机访问,访问特定位置元素需要从链表头开始遍历,时间复杂度为 O (n)。 | 不支持随机访问,时间复杂度为 O (n)。 | 不支持随机访问,时间复杂度为 O (n)。 | 不支持随机访问,时间复杂度为 O (n)。 |
插入操作 | 在中间位置插入元素需修改前后节点的指针,时间复杂度为 O (1)(已知插入位置的情况下)。 | 在中间位置插入元素需修改前后节点的指针,时间复杂度为 O (1)(已知插入位置的情况下)。 | 与循环链表类似,时间复杂度为 O (1)。 | 在中间位置插入元素需修改前后节点的指针,时间复杂度为 O (1)(已知插入位置的情况下)。 |
删除操作 | 在中间位置删除元素需修改前后节点的指针,时间复杂度为 O (1)(已知删除位置的情况下)。 | 在中间位置删除元素需修改前后节点的指针,时间复杂度为 O (1)(已知删除位置的情况下)。 | 与循环链表类似,时间复杂度为 O (1)。 | 在中间位置删除元素需修改前后节点的指针,时间复杂度为 O (1)(已知删除位置的情况下)。 |
适用场景 | 适用于插入和删除操作相对较少,对遍历顺序没有特殊要求的情况。 | 适用于需要循环遍历的场景,如某些算法中需要反复遍历链表。 | 与循环链表类似,在需要单向循环遍历的场景中适用。 | 适用于需要双向遍历以及频繁在链表中进行插入和删除操 |
相关文章:

数据结构(线性表)
1线性表的定义与操作 1.1线性表的定义 线性表是一种基础的数据结构,其主要特点是:数据元素之间存在一种线性关系(一对一)。线性表的每一个数据元素都有一个唯一的前驱和后继,除了第一个元素没有前驱,最后…...

ArcGIS Pro SDK (十八)栅格
ArcGIS Pro SDK (十八)栅格 环境:Visual Studio 2022 + .NET6 + ArcGIS Pro SDK 3.0 栅格 1 在文件夹中打开栅格数据集 // 使用文件夹路径创建 FileSystemConnectionPath 对象。 FileSystemConnectionPath connectionPath = new FileSystemConnectionPath(new System...

c++ 对象作用域
在 C 中,对象的作用域(scope)指的是对象的生命周期以及对象在程序中可以访问的范围。作用域影响对象的创建、使用和销毁,主要有以下几种类型: 1. 局部作用域(Local Scope) 局部作用域的对象是…...

【无标题】海尔AI英语面试
1.自我介绍 Good morning. I am delighted to have this English interview. My name is fu guilin. I graduated from CDUT with a degree in Information engineering. During my university years, I have laid a solid foundation in my professional knowledge. I posses…...

软件设计模式------概述
一:简述 目的:为了可重用代码,代码更容易被他人理解,提高代码的可靠性。 定义:是一套被反复使用,多数人知晓,经过分类编目的,代码设计经验的总结。 (通俗来说…...

刷题/学习网站推荐
前言: 最近没怎么学习,荒芜生活,学不进去,太累了,就喜欢翻翻网站有没有好用的东西分享给大家,正好看到一些刷题的网站(其实也是学习的网站吧),相比学程序的很多都是力扣…...

OQE-OPTICAL AND QUANTUM ELECTRONICS
文章目录 一、征稿简介二、重要信息三、服务简述四、投稿须知五、联系咨询 一、征稿简介 二、重要信息 期刊官网:https://ais.cn/u/3eEJNv 三、服务简述 四、投稿须知 1.在线投稿:由艾思科蓝支持在线投稿,请将文章全文投稿至艾思科蓝投稿系…...

在 Spring MVC 应用程序中使用 WebMvcTest 注释有什么用处?
大家好,我是锋哥。今天分享关于【在 Spring MVC 应用程序中使用 WebMvcTest 注释有什么用处?】面试题?希望对大家有帮助; 在 Spring MVC 应用程序中使用 WebMvcTest 注释有什么用处? 1000道 互联网大厂Java工程师 精选…...

Chromium html<textarea>c++接口定义
<textarea>:文本区域元素 <textarea> HTML 元素是一个多行纯文本编辑控件,适用于允许用户输入大量自由格式文本的场景。 例子: <!DOCTYPE html> <html> <head> <meta charset"utf-8"> &l…...

OpenCV高级图形用户界面(13)选择图像中的一个矩形区域的函数selectROI()的使用
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 允许用户在给定的图像上选择一个感兴趣区域(ROI)。 该功能创建一个窗口,并允许用户使用鼠标来选择一个 ROI。…...

《计算机视觉》—— 基于dlib库的人检检测
文章目录 一、dlib库的安装1. 通过PyCharm的Settings安装2. 通过Anaconda安装(适用于Windows等操作系统)3. 通过命令行安装4.懒人安装 二、基于dlib库的人检测1.对图像进行人脸检测2.打开电脑摄像头,检测人脸 一、dlib库的安装 在PyCharm中&…...

分布式数据库安全可靠测评名录之平凯数据库(TiDB企业版)
作者: 数据源的TiDB学习之路 原文来源: https://tidb.net/blog/d052ee0b 2024 年 9 月 30 日,中国信息安全测评中心公布安全可靠测评结果公告(2024年第2号),其中包含 6 款集中式数据库和 11 款分布式数据…...

动态规划之打家劫舍
大纲 题目思路第一步:确定下标含义第二步:确定递推公式第二步:dp数组如何初始化第三步:确定遍历顺序第四步:举例推导dp数组 总结 最近有人询问我 LeetCode 「打家劫舍」系列问题(英文版叫 House Robber&…...

嵌入式入门学习——8基于Protues仿真Arduino+SSD1306液晶显示数字时钟
0 系列文章入口 嵌入式入门学习——0快速入门,Let‘s Do It! SSD1306 1 Protues查找SSD1306器件并放置在画布,画好电气连接(这里VCC和GND画反了,后面仿真出错我才看见,要是现实硬件估计就烧毁了…...

盘点现代浏览器的各种神奇能力,功能令人惊讶
盘点现代浏览器的各种神奇能力,功能令人惊讶😮 浏览器的进化 一个运行在浏览器里面的操作系统。一个炫酷的量子纠缠网页。内嵌在浏览器里面的AI大模型。 随着web技术的迅猛发展,现代浏览器已经不仅仅是一个浏览网页的工具了。它的功能早已进…...

人工智能停滞:人工智能投资与人工智能采用之间的差距
关注公众号网络研究观获取更多内容。 人工智能继续影响着云战略,但人工智能的实施速度比大多数人预测的要慢。这让在人工智能上押下重注的技术提供商感到沮丧。到底发生了什么? Censuswide 代表 Red Hat 近期开展了一项调查,调查对象为英国…...

高效容器化技术(3)---docker镜像仓库
1.镜像仓库 Docker镜像仓库是存储和管理Docker镜像的地方。它允许用户上传、下载和共享Docker镜像,从而方便在不同的主机上部署和运行应用程序。 常见的Docker镜像仓库包括: Docker Hub:官方的Docker镜像仓库,包含了大量的公共镜…...

使用docker搭建lnmp运行WordPress
一,部署目的 使用 Docker 技术在单机上部署 LNMP 服务(Linux Nginx MySQL PHP)。部署并运行 WordPress 网站平台。掌握 Docker 容器间的互联及数据卷共享。 二,部署环境 操作系统:CentOS 7Docker 版本࿱…...

【设计模式】深入理解Python中的桥接模式(Bridge Pattern)
深入理解Python中的桥接模式(Bridge Pattern) 在软件开发中,我们常常会遇到一个类随着功能的扩展,继承层次越来越复杂,导致系统僵化,难以维护。桥接模式(Bridge Pattern)提供了一种…...

YOLOv11改进策略【卷积层】| SAConv 可切换的空洞卷积 二次创新C3k2
一、本文介绍 本文记录的是利用SAConv优化YOLOv11的目标检测网络模型。空洞卷积是一种在不增加参数量和计算量的情况下,通过在卷积核元素之间插入空洞来扩大滤波器视野的技术。并且为了使模型能够适应不同尺度的目标,本文利用SAConv将不同空洞率卷积结果进行结合,来获取更全…...

Javaweb基础-axios
Axios 是一个基于 Promise 的 HTTP 库,可以用在浏览器和 node.js 中。 GET方法 get请求第一种写法 //后端 Slf4j RestController RequestMapping("/demo") public class DemoController {RequestMapping("/getTest")// 被RequestParam标记的参数…...

智能EDA小白从0开始 —— DAY20 OrCAD
以下是对OrCAD和MATLAB两种EDA工具的深入解析,内容扩展至约2220字: OrCAD:电子设计自动化的强大工具 OrCAD,作为电子设计自动化(EDA)领域的佼佼者,为电子工程师们提供了一套全面的设计解决方案…...

C# WebApi 接口测试工具:WebApiTestClient应用技术详解
目录 一、引言 二、WebApiTestClient介绍 1、特性 2、应用场景 三、WebApiTestClient具体使用 1、WebApi项目引入组件 2、如何使用组件 1、修改Api.cshtml文件 2、配置读取注释的xml路径 3、测试接口 四、总结 一、引言 由于最近项目需要开发WebApi接口&…...

Qt_ymode自己实现
文章内容: 通过Qt实现Ymode协议的封装。通过传入的数据从里面一包一包拿数据。可以用作平时串口和网口的通信。也可以用来程序升级。 #include "ymodem.h"Ymodem::Ymodem() {m_data = nullptr; }Ymodem...

5.3章节python中字典:字典创建、元素访问、相关操作
1.字典的创建和删除 2.字典的访问和遍历 3.字典的相关操作 4.字典的生成式 一、字典的创建和删除 字典(dictionary)是一种用于存储键值对(key-value pairs)的数据结构。每个键(key)都映射到一个值…...

ECCV2024 Tracking 汇总
一、OneTrack: Demystifying the Conflict Between Detection and Tracking in End-to-End 3D Trackers paper: https://www.ecva.net/papers/eccv_2024/papers_ECCV/papers/01174.pdf 二、VETRA: A Dataset for Vehicle Tracking in Aerial Imagery paper&#…...

C语言知识点
命名规则: 字符组成:标识符只能由字母(A~Z,a~z)、数字(0~9)和下划线(_)组成。首字符要求:标识符的第一个字符必须是字母或下划线,不能是数字。长…...

ICMP协议以及ARP欺骗攻击
ping 命令使用的是 ICMP(Internet Control Message Protocol)协议,而不是 TCP 或 UDP 协议。因此,ping 命令并不使用特定的端口号。 ICMP 协议 ICMP 是一种网络层协议,主要用于在 IP 网络中传递控制消息。ping 命令利…...

qt5.12.12插件机制无法加载插件问题
环境:win11 vs2015 qt5.12.12 问题描述:确保插件代码正确的情况下,无法解析插件接口(即QPluginLoader类的instance(); 返回为空)。 问题现象:1、qt5.12.12的debug下无法解析;2、release下禁…...

机器学习面试笔试知识点-线性回归、逻辑回归(Logistics Regression)和支持向量机(SVM)
机器学习面试笔试知识点-线性回归、逻辑回归Logistics Regression和支持向量机SVM 一、线性回归1.线性回归的假设函数2.线性回归的损失函数(Loss Function)两者区别3.简述岭回归与Lasso回归以及使用场景4.什么场景下用L1、L2正则化5.什么是ElasticNet回归6.ElasticNet回归的使…...