数据结构记录-链表
1、单链表
1、单链表的组成
最基本的单链表组成如下:
typedef struct Link
{
char elem; /*数据域*/
struct Link *next; /*指针域*/
}link;/*节点名,每个阶段都是一个Link结构体*/
为什么这样的就是链表呢,需要从这个结构体内部组成来看,struct Link next; 定义了一个指针变量 next,它的类型是 struct Link,即指向 struct Link 结构体的指针,所以它可以执行一个这样的结构体,所以可以指向下一个结构体,只要我们每次都指一下就会这样一直循环不断下去,这样就形成一个链表。
2、单链表实现
创建一个最简单的单链表如下所示:
link *initLink()
{
/*创建无头结点*/
// link *p = NULL;
// link *temp = (link*)malloc(sizeof(link));
// temp->elem = 1;
// temp->next = NULL;
// p = temp;
// for(int i = 2; i<5; i++)
// {
// link *a = (link*)malloc(sizeof(link));
// a->elem = i;
// a->next = NULL;
// temp->next = a;
// temp = temp->next; /*这样循环创建后驱*/
// }
/*创建有头结点*/
link *p = (link*)malloc(sizeof(link));
link *temp = p; /*声明一个指针指向头结点*/
for(int i = 1; i<5; i++)
{
link *a = (link*)malloc(sizeof(link));
a->elem = i;
a->next = NULL;
temp->next = a;
temp = temp->next; /*这样循环创建后驱*/
}
return p;
}
最关键的代码在这:
temp->next = a;
temp = temp->next;
从循环来看,第一次循环,数据域赋值为1,temp的第一个next指向这个新申请的a,之后temp更新到a这里,之后继续创建数据域为2的,在执行相同操作,这样一直到4结束。
但是这样链表的第一个元素,数据域是空的,因为没有地方给他赋值,图示上是这样的
3、单链表的基本操作
插入元素
插入如下图所示,就是先移动到他前面一个节点,之后修改next指针的指向。
/*
插入元素:
1、将新节点的next指针指向插入位置后的节点
2、将插入位置前节点的next指针指向插入节点
[param] elem 新数据
[param] add 插入位置
*/
link *insertElem(link *p, int elem, int add)
{
link *temp = p;
for(int i = 1; i<add; i++) /*将temp指针指向要插入的前一个节点*/
{
temp = temp->next;
if(temp == NULL)
{
printf("插入位置无效\n");
return p;
}
}
link *c = (link*)malloc(sizeof(link));
c->elem = elem;
c->next = temp->next; /*创建一个新节点,将c的next指向temp的next,再把temp的next指向c*/
temp->next = c;
return p;
}
删除元素
和插入类似,找到,之后指向下一个的下一个。
/*
删除元素:
1、将节点从链表中摘除
2、手动释放节点
[param] elem 要删除元素的值
*/
link *delElem(link *p, int add)
{
link *temp = p;
for(int i=1;i<add;i++) /*移动到要删除的上一个节点*/
{
temp = temp->next;
if(temp->next == NULL)
{
printf("删除位置无效\n");
return p;
}
}
link *del = temp->next;
temp->next = temp->next->next; /*指向下一个节点是下一个的下一个*/
free(del);
return p;
}
寻找元素
int selectElem(link *p, int elem)
{
link *t = p;
int i=1;
while (t->next)
{
t=t->next;
if(t->elem == elem)
return i;
i++;
}
return -1;
}
link *amendElem(link *p, int add, int newElem)
{
link *temp = p;
temp = temp->next;
for(int i=1; i<add;i++)
{
temp = temp->next;
}
temp->elem = newElem;
return p;
}
修改元素
link *amendElem(link *p, int add, int newElem)
{
link *temp = p;
temp = temp->next;
for(int i=1; i<add;i++)
{
temp = temp->next;
}
temp->elem = newElem;
return p;
}
2、顺序表和链表优缺点总结
之前总结了顺序表:顺序表示例
这两种储存结构的差异在于顺序表申请的是一大块完整的空间,而链表申请的是不一定是完整的,是有什么时候需要就申请空间,因此从空间利用率来看,顺序表的空间利用率要比链表更高一点。
从时间复杂度来看,从以下两类来看:
- 1、涉及访问元素的操作,元素的插入、删除和移动操作极少,适合使用顺序表。这是因为,顺序表中存储的元素可以使用数组下标直接访问,无需遍历整个表,因此使用顺序表访问元素的时间复杂度为
O(1)
;而在链表中访问数据元素,需要从表头依次遍历,直到找到指定节点,花费的时间复杂度为O(n)
; - 2、涉及元素的插入、删除和移动,访问元素的需求很少,适合使用链表。链表中数据元素之间的逻辑关系靠的是节点之间的指针,当需要在链表中某处插入或删除节点时,只需改变相应节点的指针指向即可,无需大量移动元素,因此链表中插入、删除或移动数据所耗费的时间复杂度为
O(1)
;而顺序表中,插入、删除和移动数据可能会牵涉到大量元素的整体移动, 因此时间复杂度至少为O(n)
;
3、单链表反转
常用的实现方案有 4 种,这里分别将它们称为迭代反转法、递归反转法、就地逆置法和头插法。递归反转法更适用于反转不带头节点的链表;其它 3 种方法既能反转不带头节点的链表,也能反转带头节点的链表。
1、迭代反转
迭代反转链表的思路如下所示:
代码如下:
link *iteration_reverse(link *head)
{
if(head == NULL || head->next == NULL){
printf("link error \n");
return head;
}
else{
link *beg = NULL;
link *mid = head;
link *end = head->next;
while(1)
{
mid->next = beg;
if(end == NULL)
break;
beg = mid;
mid = end;
end = end->next;
}
/*修改head头指针的指向*/
head = mid;
return head;
}
}
2、递归反转
流程如下:(这个没怎么看懂,要debug看一下)
源代码如下:
link *recursive_reverse(link *head)
{
if(head == NULL || head->next == NULL)
return head;
else{
link *new_head = recursive_reverse(head->next);
head->next->next = head;
head->next = NULL;
return new_head;
}
}
3、头插法反转
原理图如下所示:
代码如下:
link *head_reverse(link *head)
{
link *new_head = NULL;
link *temp = NULL;
if(head == NULL || head->next == NULL)
return head;
while (head != NULL)
{
temp = head;
head = head->next; //将temp从head中摘除
temp->next = new_head; /*temp插入到new_head的头部*/
new_head = temp;
}
return new_head;
}
4、就地逆置法反转
原理如下图所示:
代码如下
link *local_reverse(link *head)
{
link *beg = NULL;
link *end = NULL;
if(head == NULL || head->next == NULL)
return head;
beg = head;
end = head->next; /*暂存一下临时的节点*/
while (end != NULL)
{
beg->next = end->next; /*移除end*/
/*将end移动到表头*/
end->next = end;
head = end;
/*end指向beg的后一个节点*/
end = beg->next;
}
return head;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· DeepSeek在M芯片Mac上本地化部署
· 葡萄城 AI 搜索升级:DeepSeek 加持,客户体验更智能