系列文章目录
前言
在学习链表这一数据结构时,我深刻体会到了其在算法与数据结构领域的重要性。从链表的头插法到按位查找、按值查找,再到插入元素和删除操作,每一个操作都展现了链表在实际应用中的灵活性和高效性。
一、链表的结构是什么?
二、链表的头插法和尾插法(创建链表)
对于头插法和尾插法,我理解为在链表操作中的两种常见方式,分别适用于不同的场景。头插法可以快速插入元素并更新头指针,而尾插法则更适合在链表末尾添加元素。
头插法:
代码如下:
#include <stdio.h>
#include <stdlib.h>
typedef int Elemtype;
typedef struct LNode { //因为有结构体指针,此处名称不能省略
Elemtype data;
struct LNode* next;//指向下一个结点
}LNode,*LinkList;
//LNode*是结构体指针,和LinkList完全等价的
//L->next,意思是存储了下一个结点的地址,L的右边指针域有下一个结点的地址,第一个结点存了第二个结点的地址....
void list_head_insert(LinkList& L) {//因为L是头结点,该函数接下来会对L进行malloc空间申请,会改变L,所以要&
LinkList s;//用来指向申请的新结点
Elemtype x;//存放第一个结点的数据
scanf("%d", &x);
L = (LinkList)malloc(sizeof(LNode));//申请头结点空间,链表的结构体类型是LNode,即sizeof(LNode)
L->next = NULL;//因为插入数据一直在头结点的右边,导致第一个结点的指针域成为尾部,要设为NULL
while (x != 9999) {
s = (LinkList)malloc(sizeof(LNode));//申请一个新空间给s,强制类型转换
s->data = x;//上面scanf读取到的值存放在新的s的data中
//L是头结点
//s是指向整个方块的指针,s->data是方块的左边数值,s->next是方块的右边指针域
s->next = L->next;
//因为L->next已经指向了前面的结点,即L->next=s'(s'是上一个结点),相当于s->next=s',为步骤①
L->next = s;//L的next存储新结点的地址
scanf("%d", &x);//继续读取下一个结点的数值
}
}
void print_list(LinkList L) {
L = L->next;//因为L->next已经指向下个结点,此处表示头指针向第一个结点偏转
while (L != NULL) {
printf("%3d", L->data);
L = L->next;//指针继续向下个结点偏转
}
printf("\n");
}
int main() {
LinkList L;//定义链表头(里面没有数据),是结构体指针类型
list_head_insert(L);//输入数据可以为 3 4 5 6 7 9999,头插法新建链表
print_list(L);//链表打印
return 0;
}
图片如下:
尾插法:
代码如下:
#include <stdio.h>
#include <stdlib.h>
typedef int Elemtype;
typedef struct LNode {
Elemtype data;
struct LNode* next;
}LNode, * LinkList;
void list_tail_insert(LinkList& L) {
LinkList s;//定义申请的新结点
Elemtype x;
scanf("%d", &x);
L = (LinkList)malloc(sizeof(LNode));//申请头结点空间,结点都是LNode*,sizeof为链表类型LNode
LinkList r = L;//r代表链表表尾结点,初始化指向链表头结点尾部,因为链表还没连上,只有一个L,所以r=L
while (x != 9999) {
s = (LinkList)malloc(sizeof(LNode));
s->data = x;
r->next = s;//r是前一个结点,连接下一个结点操作
r = s;//r继续向下一个结点偏转,本来是r=s->next,但s的指针域还没存结点的地址
scanf("%d", &x);//继续读取下一个结点数值
}
r->next = NULL;//最后的尾部记得要赋上空
}
void print_list(LinkList L) {
L = L->next;//因为L->next已经指向下个结点,此处表示头指针向第一个结点偏转
while (L != NULL) {
printf("%3d", L->data);
L = L->next;//先打印再继续偏转
}
printf("\n");
}
int main() {
LinkList L;//链表头,是结构体指针类型
list_tail_insert(L);//尾插法输入数据可以为 3 4 5 6 7 9999
print_list(L);//链表打印
return 0;
}
图片如下:
结果差异:
①头插法使数值反方法排列
②尾插法是相反的效果
三、链表的按位查找
需要如何通过遍历链表来找到目标节点
代码如下:
#include <stdio.h>
#include <stdlib.h>
typedef int Elemtype;
typedef struct LNode {
Elemtype data;
struct LNode* next;
}LNode, * LinkList;
void list_tail_insert(LinkList& L) {
LinkList s;
Elemtype x;
scanf("%d", &x);
L = (LinkList)malloc(sizeof(LNode));
LinkList r = L;
while (x != 9999) {
s = (LinkList)malloc(sizeof(LNode));
s->data = x;
r->next = s;
r = s;
scanf("%d", &x);
}
r->next = NULL;
}
void print_list(LinkList L) {
L = L->next;
while (L != NULL) {
printf("%3d", L->data);
L = L->next;//先打印再继续偏转
}
printf("\n");
}
LinkList GetElem(LinkList L, int i) {
int j = 1;//用来遍历链表,链表的第一个结点位置是1
LinkList r = L->next;//r代表链表表尾结点,初始化指向链表头结点尾部,因为链表已经连上,所以r = L->next
if (i == 0) {
return L;//如果要查找位置0的结点,就是头结点(位置为0)
}
if (i < 1) {
return NULL;//查找位置<1,返回NULL
}
while (r != NULL && j < i) {//r偏转不为空,且j遍历小于查找位置
r = r->next;//继续偏转
j++;
}
return r;//返回找到的指针,如果r为NULL了,j还没到i,跳出循环返回NULL,表示要查找的位置超出链表长度了
}
int main() {
LinkList L;
LinkList search;//用来储存查找的结点
list_tail_insert(L);//尾插法创建链表
print_list(L);
search = GetElem(L, 2);//按照位置查找第二个位置的数值,L头结点位置为0
if (search != NULL) {
printf("成功找到数值,该位置的数值为%d\n", search->data);
}
return 0;
}
图片如下:
四、链表的按值查找
代码如下:
#include <stdio.h>
#include <stdlib.h>
typedef int Elemtype;
typedef struct LNode {
Elemtype data;
struct LNode* next;
}LNode, * LinkList;
void list_tail_insert(LinkList& L) {
LinkList s;
Elemtype x;
scanf("%d", &x);
L = (LinkList)malloc(sizeof(LNode));
LinkList r = L;
while (x != 9999) {
s = (LinkList)malloc(sizeof(LNode));
s->data = x;
r->next = s;
r = s;
scanf("%d", &x);
}
r->next = NULL;
}
void print_list(LinkList L) {
L = L->next;
while (L != NULL) {
printf("%3d", L->data);
L = L->next;
}
printf("\n");
}
LinkList LocateElem(LinkList L, Elemtype e) {
LinkList r = L->next;//定义一个指针进行偏转
while (r != NULL && r->data != e) {//指针不能为空,且还没找到e
r = r->next;
}
return r;
}
int main() {
LinkList L;
LinkList search;
list_tail_insert(L);
print_list(L);
search = LocateElem(L, 6);//查找数值为6
if (search != NULL) {
printf("成功找到了数值为%d", search->data);
}
return 0;
}
图片如下:
五、往第i个位置插入元素
需要考虑边界情况和指针操作,以保证链表的正确性。
代码如下:
#include <stdio.h>
#include <stdlib.h>
typedef int Elemtype;
typedef struct LNode {
Elemtype data;
struct LNode* next;
}LNode, * LinkList;
void list_head_insert(LinkList& L) {
LinkList s;
Elemtype x;
scanf("%d", &x);
L = (LinkList)malloc(sizeof(LNode));
L->next = NULL;
while (x != 9999) {
s = (LinkList)malloc(sizeof(LNode));
s->data = x;
s->next = L->next;
L->next = s;
scanf("%d", &x);
}
}
void print_list(LNode* L) {
L = L->next;
while (L != NULL) {
printf("%3d", L->data);
L = L->next;
}
printf("\n");
}
LinkList GetElem(LinkList L, int i) {
int j = 1;//用来遍历链表,链表的第一个结点位置是1
LinkList r = L->next;//r代表链表表尾结点,初始化指向链表头结点尾部,因为链表已经连上,所以r = L->next
if (i == 0) {
return L;//如果要查找位置0的结点,就是头结点(位置为0)
}
if (i < 1) {
return NULL;//查找位置<1,返回NULL
}
while (r && j < i) {//r偏转不为空,且j遍历小于查找位置
r = r->next;
j++;
}
return r;
}
bool ListFrontInsert(LinkList L, int i, Elemtype e) {//因为L是头结点,创建链表的时候已经对L申请了空间,不需改变L,不需要&
LinkList p = GetElem(L, i - 1);//先定义并找到插入位置前一个结点的位置,按位查找
if (p == NULL) {
return false;//查找位置异常
}
LinkList s;//定义新插入的结点
s = (LinkList)malloc(sizeof(LNode));//申请空间,L在创建链表已经申请空间
s->data = e;//先为插入结点赋值
s->next = p->next;//为图中①
p->next = s;//为图中②
//适用与表头、中间和表尾
//如果是在表尾插结点,也行得通
/*s->next = NULL;
p->next = s;*/
return true;//记得返回true
}
int main() {
LinkList L;
list_head_insert(L);//头插法创建链表
print_list(L);
bool ret;
ret = ListFrontInsert(L, 2, 99);//链表L,插入的位置,插入的数值
if (ret) {
printf("插入成功");
print_list(L);
}
return 0;
}
图片如下:
六、链表的删除操作
需要确保删除节点后正确地维护链表的连接关系,避免出现内存泄漏或指针错误。
代码如下:
#include <stdio.h>
#include <stdlib.h>
typedef int Elemtype;
typedef struct LNode {
Elemtype data;
struct LNode* next;
}LNode, * LinkList;
void list_tail_insert(LinkList& L) {
LinkList s;
Elemtype x;
scanf("%d", &x);
L = (LinkList)malloc(sizeof(LNode));
LinkList r = L;
while (x != 9999) {
s = (LinkList)malloc(sizeof(LNode));
s->data = x;
r->next = s;
r = s;
scanf("%d", &x);
}
r->next = NULL;
}
void print_list(LinkList L) {
L = L->next;
while (L != NULL) {
printf("%3d", L->data);
L = L->next;
}
printf("\n");
}
LinkList GetElem(LinkList L, int i) {
int j = 1;
LinkList r = L->next;
if (i == 0) {
return L;
}
if (i < 1) {
return NULL;
}
while (r && j < i) {
r = r->next;
j++;
}
return r;
}
bool ListDelete(LinkList L, int i) {//bool类型方便进行以下特殊情况判断
LinkList p = GetElem(L, i - 1);//先找到删除结点的前一个结点
if (p == NULL) {
return false;//先判断
}
LinkList q = p->next;//储存要删除的结点,并赋上地址
p->next = q->next;//进行断链
free(q);
return true;
}
int main() {
LinkList L;
list_tail_insert(L);
print_list(L);
bool ret;
ret=ListDelete(L, 4);//删除第4个结点
if (ret != NULL) {
printf("删除成功\n");
print_list(L);
}
return 0;
}
图片如下:
总结
通过这些学习,我更深入地理解了链表这一数据结构的设计原理和应用场景,也意识到在实际编程中灵活运用链表可以提高程序的效率和可维护性。
如果在以上内容中存在错误或不足之处,还请各位读者批评指正,让我们共同进步!