大家好,本文是我关于链表的学习内容。
单链表
一:概述
但链表是一种物理存储结构上非连续,非顺序的存储结构,数据的逻辑顺序是通过链表中指针链接次序实现的。链表与数组的本质区别是,链表由数据域和指针域两部分组成,这样的结构使其既能存储数据,又能比较灵活的对数据进行插入删除。
二:图解
三:代码实现
1.定义
typedef struct LNode {
int data;
struct LNode* next; //指针指向下一个结点,指针的类型为结点类型
} *LinkNode; //声明*LinkNode为结构体指针类型
先声明LinkNode为结构体类型,在使用该类型是,将其对应的变量定义为指针即可。该结构体中存放着int类型的数据和指针域。
2.初始化
void InistLinkNode(LinkNode& L) { //初始化
L = (LNode*)malloc(sizeof(LNode)); //分配头节点
L->next = NULL;
}
我们首先要建立一个空的单链表,然后给它分配头节点。
3.头插法
void InsertLinkNode(LinkNode& L) { //头插法
LNode* s;
int x, Len;
printf("请输入你要插入的元素个数:");
scanf("%d", &Len);
printf("请输入你要插入的元素:\n");
for (int j = 0; j < Len; j++) {
s = (LNode*)malloc(sizeof(LNode)); //每插入一个元素之前,都要给它分配结点空间
scanf_s("%d", &x); //用户自行输入要插入的结点元素
s->data = x;
s->next = L->next;
L->next = s;
}
}
头插法是将新元素插入到头节点之后,就是真正的第一个结点。
4.尾插法
void TailInsertLinkNode(LinkNode& L) { //尾插法
LNode* s, * r;
int x, Len;
r = L; //r表示尾指针
printf("请输入你要插入的元素个数:");
scanf("%d", &Len);
printf("请输入你要插入的元素:\n");
for (int j = 0; j < Len; j++) {
s = (LNode*)malloc(sizeof(LNode));
scanf_s("%d", &x);
s->data = x;
r->next = s;
r = s; //s为当前的表尾指针,将它的值赋给r,使r永远指向表尾
}
printf("\n");
r->next = NULL;
}
尾插是将元素插入链表的末尾,以便按照顺序打印,一般尾插分为两种情况
(1)链表初始无结点,开辟结点后直接插入即可。
(2)链表初始有结点,先找尾,再将开辟的结点插入尾部。
5.删除第i个元素
void DelLinkNode(LinkNode& L) { //删除第i个元素
int x, j = 0, e;
printf("请输入你要删除的元素位序:\n");
scanf("%d", &x);
LNode* p = L;
while (p != NULL && j < x - 1) {
p = p->next;
j++;
}
if (p == NULL) {
printf("不存在我们要删除的元素");
}
if (p->next == NULL) {
printf("不存在我们要删除的元素");
}
LNode* q = p->next;
e = q->data;
p->next = q->next;
free(q); //为了内存合理利用,将删除的结点释放内存
}
删除某元素之前,首先要保证这个元素是非NULL,其次还要保证它前面的结点也是非NULL,因为只有这样,才能实现后续元素和前面子表的链接。
6.在第i个位置插入
void InLinkNode(LinkNode& L) { //在第i个位置插入
printf("请输入要插入的元素和位序:(元素和位序之间用逗号隔开)\n");
int x, j = 0, e;
scanf("%d,%d", &e, &x);
LNode* s = L, * r = (LNode*)malloc(sizeof(LNode));
while (j < x - 1 && s != NULL) {
j++;
s = s->next;
}
r->data = e;
r->next = s->next;
s->next = r;
}
上面代码的顺序不能改变,即让原来相连的两个结点的第一个指向新节点,新结点再指向原来的第二个结点,这个元素就插入其中了。
7.打印输出单链表
void PrintLinkNode(LinkNode& L) { //打印输出单链表
LNode* s = L->next;
printf("单链表元素如下:\n");
while (s != NULL) {
printf("%d", s->data);
s = s->next;
}
printf("\n");
}
将经过上述操作后的链表显示在屏幕上。
8.求单链表结点的个数
void LenLinkNode(LinkNode& L) { //求线性表长度
LNode* s = L->next;
int n = 0;
while (s != NULL) {
n++;
s = s->next;
}
printf("单链表长度为%d", n);
printf("\n");
}
指针每向后移动一次,n加1,从而计算出线性表的长度。
9.查找元素的位序
void SearchLinkNode(LinkNode& L) { //查找元素的位序
int x, j = 1;
LNode* p = L->next;
printf("请输入你要查找的元素:\n");
scanf("%d", &x);
while (p != NULL && p->data != x) {
p = p->next;
j++;
}
if (p == NULL) {
printf("你查找的元素不存在");
}
else {
printf("你要查找的元素%d的位序是%d\n", x, j);
}
}
利用while循环,如果遍历链表时找到数据相同的结点,就返回它的位序值。
10.结点值的修改
void change_node(LinkNode& L) //修改结点的值
{
LNode* cru = L->next;
int x, y;
printf("请输入要修改的数据x:");
scanf("%d", &x);
printf("请输入要修改后的数据y:");
scanf("%d", &y);
while (cru != NULL)
{
if (cru->data == x)
{
cru->data = y;
break;
}
else {
cru = cru->next;
}
}
if (cru == NULL) {
fprintf(stdout, "要修改的数据不存在,请重新修改\n"); //stdout标准输出流
}
else
{
fprintf(stdout, "修改成功\n");
}
}
首先查找到这个结点,再将其值替换。
整个单链表操作的完整代码如下:
#define _CRT_SECURE_NO_WARNINGS 1 //有了这个宏定义,下面的代码就能用sanf()代替scanf_s()
#include <stdio.h>
#include <stdlib.h> //malloc函数所在的头文件
typedef struct LNode {
int data;
struct LNode* next; //指针指向下一个结点,指针的类型为结点类型
} *LinkNode; //声明*LinkNode为结构体指针类型
void InistLinkNode(LinkNode& L) { //初始化
L = (LNode*)malloc(sizeof(LNode)); //分配头节点
L->next = NULL;
}
void InsertLinkNode(LinkNode& L) { //头插法
LNode* s;
int x, Len;
printf("请输入你要插入的元素个数:");
scanf("%d", &Len);
printf("请输入你要插入的元素:\n");
for (int j = 0; j < Len; j++) {
s = (LNode*)malloc(sizeof(LNode)); //每插入一个元素之前,都要给它分配结点空间
scanf_s("%d", &x); //用户自行输入要插入的结点元素
s->data = x;
s->next = L->next;
L->next = s;
}
}
void TailInsertLinkNode(LinkNode& L) { //尾插法
LNode* s, * r;
int x, Len;
r = L; //r表示尾指针
printf("请输入你要插入的元素个数:");
scanf("%d", &Len);
printf("请输入你要插入的元素:\n");
for (int j = 0; j < Len; j++) {
s = (LNode*)malloc(sizeof(LNode));
scanf_s("%d", &x);
s->data = x;
r->next = s;
r = s; //s为当前的表尾指针,将它的值赋给r,使r永远指向表尾
}
printf("\n");
r->next = NULL;
}
void PrintLinkNode(LinkNode& L) { //打印输出单链表
LNode* s = L->next;
printf("单链表元素如下:\n");
while (s != NULL) {
printf("%d", s->data);
s = s->next;
}
printf("\n");
}
void LenLinkNode(LinkNode& L) { //求线性表长度
LNode* s = L->next;
int n = 0;
while (s != NULL) {
n++;
s = s->next;
}
printf("单链表长度为%d", n);
printf("\n");
}
void GetElemLinkNode(LinkNode& L) { //按照位序查找元素
printf("输出你要找的元素位序:\n");
int i, j = 0;
LNode* s = L;
scanf("%d", &i);
while (j < i && s != NULL) {
j++;
s = s->next;
}
if (s == NULL) {
printf("不存在我们要查找的元素");
}
else {
printf("元素位序为%d的元素是%d", i, s->data);
}
printf("\n");
}
void DelLinkNode(LinkNode& L) { //删除第i个元素
int x, j = 0, e;
printf("请输入你要删除的元素位序:\n");
scanf("%d", &x);
LNode* p = L;
while (p != NULL && j < x - 1) {
p = p->next;
j++;
}
if (p == NULL) {
printf("不存在我们要删除的元素");
}
if (p->next == NULL) {
printf("不存在我们要删除的元素");
}
LNode* q = p->next;
e = q->data;
p->next = q->next;
free(q); //为了内存合理利用,将删除的结点释放内存
}
void InLinkNode(LinkNode& L) { //在第i个位置插入
printf("请输入要插入的元素和位序:(元素和位序之间用逗号隔开)\n");
int x, j = 0, e;
scanf("%d,%d", &e, &x);
LNode* s = L, * r = (LNode*)malloc(sizeof(LNode));
while (j < x - 1 && s != NULL) {
j++;
s = s->next;
}
r->data = e;
r->next = s->next;
s->next = r;
}
void SearchLinkNode(LinkNode& L) { //查找元素的位序
int x, j = 1;
LNode* p = L->next;
printf("请输入你要查找的元素:\n");
scanf("%d", &x);
while (p != NULL && p->data != x) {
p = p->next;
j++;
}
if (p == NULL) {
printf("你查找的元素不存在");
}
else {
printf("你要查找的元素%d的位序是%d\n", x, j);
}
}
void change_node(LinkNode& L) //修改结点的值
{
LNode* cru = L->next;
int x, y;
printf("请输入要修改的数据x:");
scanf("%d", &x);
printf("请输入要修改后的数据y:");
scanf("%d", &y);
while (cru != NULL)
{
if (cru->data == x)
{
cru->data = y;
break;
}
else {
cru = cru->next;
}
}
if (cru == NULL) {
fprintf(stdout, "要修改的数据不存在,请重新修改\n"); //stdout标准输出流
}
else
{
fprintf(stdout, "修改成功\n");
}
}
int main() {
LinkNode L;
InistLinkNode(L);
TailInsertLinkNode(L);
PrintLinkNode(L);
LenLinkNode(L);
GetElemLinkNode(L);
InLinkNode(L);
PrintLinkNode(L);
DelLinkNode(L);
PrintLinkNode(L);
SearchLinkNode(L);
change_node(L);
PrintLinkNode(L);
return 0;
}
运行结果(示例):
建议此代码在vs2022上运行,因为我在dev c++运行经常报错。
双链表
一.概念
在单链表上能轻松到达下一个结点,但返回上一个结点很难,这种单向的关系就使数据在处理时不够灵活,而双链表就解决了这个问题。在单链表的基础上,双链表是每一个结点的指针既指向下一个结点,也同时指向上一个结点,所以,每个结点都被两个结点所指。既可以从头遍历到尾,也可以从尾遍历到头,但是它占的内存空间更大一些。
二.图解
三.代码实现
1.创建
//创建一个双向链表的节点
Node *CreatNode(Node *head) {
head = (Node *)malloc(sizeof(Node)); //申请一个链表节点的空间
if (head == NULL) {
printf("malloc error!\r\n");
return NULL;
}
head->pre = NULL; //指向前一个节点的指针
head->next = NULL; //指向后一个节点的指针
head->data = rand() % MAX; //随机数 0~100
return head;
}
//创建一串个数为length的双向链表
Node *CreatList(Node *head) {
int length;
printf("请输入链表长度:");
scanf("%d", &length);
if (length == 1) {
return ( head = CreatNode(head));
} else {
head = CreatNode(head);//先创建一个链表节点作为链表头
Node *list = head; //定义一个链表指针指向该链表头
for (int i = 1; i < length; i++)
/*创建并初始化一个新结点*/
{
Node *body = NULL;
body = CreatNode(body);//再初始化一个链表节点
/*直接前趋结点的next指针指向新结点*/
list->next = body; //链表指针的前一个(指针)与新节点连
/*新结点指向直接前趋结点*/
body->pre = list; //新节点的后一个(指针)与上一个节点相连
/*把body指针给list返回*/
list = list->next;
}
}
/*加上以下两句就是双向循环链表*/
// list->next=head;
// head->prior=list;
return head;//返回该链表的链表头
}
每创建一个新结点,都要与其前驱节点建立两次联系:
将新节点的prior指针指向直接前驱节点;
将直接前驱节点的next指针指向新节点。
2.插入
//在第add位置的前面插入data节点
Node *InsertListHead(Node *head) {
//新建数据域为data的结点
int add, data;
printf("请输入要插入的位置和数据(用逗号隔开):");
scanf("%d,%d", &add, &data);
Node *temp = (Node *)malloc(sizeof(Node));
if (temp == NULL) {
printf("malloc error!\r\n");
return NULL;
} else {
temp->data = data;
temp->pre = NULL;
temp->next = NULL;
}
//插入到链表头,要特殊考虑
if (add == 1) {
temp->next = head;
head->pre = temp;
head = temp;
} else {
Node *body = head;
//找到要插入位置的前一个结点
for (int i = 1; i < add - 1; i++) {
body = body->next;
}
//判断条件为真,说明插入位置为链表尾
if (body->next == NULL) {
body->next = temp;
temp->pre = body;
} else {
body->next->pre = temp;
temp->next = body->next;
body->next = temp;
temp->pre = body;
}
}
return head;
}
//在第add位置的后面插入data节点
Node *InsertListEnd(Node *head) {
int i = 1;
int add, data;
printf("请输入要插入的位置和数据(用逗号隔开):");
scanf("%d,%d", &add, &data);
//新建数据域为data的结点
Node *temp = (Node *)malloc(sizeof(Node));
temp->data = data;
temp->pre = NULL;
temp->next = NULL;
Node *body = head;
while ((body->next) && (i < add + 1)) {
body = body->next;
i++;
}
//判断条件为真,说明插入位置为链表尾
if (body->next == NULL) {
body->next = temp;
temp->pre = body;
temp->next = NULL;
} else {
temp->next = body->pre->next;
temp->pre = body->pre;
body->next->pre = temp;
body->pre->next = temp;
}
return head;
}
若添加至表头,假设表头结点为head,则temp->next=head;head->prior=temp.
添加至表尾,则找到双链表中最后一个结点,让新结点与最后一个结点建立双层逻辑关系。
3.删除
//删除数据是data的节点
Node *DeleteList(Node *head) {
Node *temp = head;
int data;
printf("请输入要删除的元素:");
scanf("%d", &data);
//遍历链表
while (temp) {
//判断当前结点中数据域和data是否相等,若相等,摘除该结点
if (temp->data == data) {
//判断是否是头结点
if (temp->pre == NULL) {
head = temp->next;
temp->next = NULL;
free(temp);
return head;
}
//判断是否是尾节点
else if (temp->next == NULL) {
temp->pre->next = NULL;
free(temp);
return head;
} else {
temp->pre->next = temp->next;
temp->next->pre = temp->pre;
free(temp);
return head;
}
}
temp = temp->next;
}
printf("Can not find %d!\r\n", data);
return head;
}
遍历链表,找到要删除的结点,然后将该结点从表中摘除即可。
4.双链表的更改,查找,打印就不再细说了,大家可以在后面的完整代码里找到,代码中都附有解释。
四:双链表增删改查完整代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MAX 100 //随机数的范围
typedef struct Node {
struct Node *pre;
int data;
struct Node *next;
} Node;
Node *CreatNode(Node *head);//创建一个双向链表的节点
Node *CreatList(Node *head);//创建一串双向链表
void PrintList(Node *head); //输出链表的功能函数
Node *InsertListHead(Node *head);//在第add位置的前面插入data节点
Node *InsertListEnd(Node *head);//在第add位置的后面插入data节点
Node *DeleteList(Node *head); //删除数据是data的节点
Node *ModifyList(Node *p); //更新函数,其中,add 表示更改结点在双链表中的位置,newElem 为新数据的值
int FindList(Node *head);//head为原双链表,elem表示被查找元素
//创建一个双向链表的节点
Node *CreatNode(Node *head) {
head = (Node *)malloc(sizeof(Node)); //申请一个链表节点的空间
if (head == NULL) {
printf("malloc error!\r\n");
return NULL;
}
head->pre = NULL; //指向前一个节点的指针
head->next = NULL; //指向后一个节点的指针
head->data = rand() % MAX; //随机数 0~100
return head;
}
//创建一串个数为length的双向链表
Node *CreatList(Node *head) {
int length;
printf("请输入链表长度:");
scanf("%d", &length);
if (length == 1) {
return ( head = CreatNode(head));
} else {
head = CreatNode(head);//先创建一个链表节点作为链表头
Node *list = head; //定义一个链表指针指向该链表头
for (int i = 1; i < length; i++)
/*创建并初始化一个新结点*/
{
Node *body = NULL;
body = CreatNode(body);//再初始化一个链表节点
/*直接前趋结点的next指针指向新结点*/
list->next = body; //链表指针的前一个(指针)与新节点连
/*新结点指向直接前趋结点*/
body->pre = list; //新节点的后一个(指针)与上一个节点相连
/*把body指针给list返回*/
list = list->next;
}
}
/*加上以下两句就是双向循环链表*/
// list->next=head;
// head->prior=list;
return head;//返回该链表的链表头
}
//输出链表
void PrintList(Node *head) {
Node *temp = head;
while (temp) {
//如果该节点无后继节点,说明此节点是链表的最后一个节点
if (temp->next == NULL) {
printf("%d\n", temp->data);
} else {
printf("%d ", temp->data);
}
temp = temp->next;
}
}
//在第add位置的前面插入data节点
Node *InsertListHead(Node *head) {
//新建数据域为data的结点
int add, data;
printf("请输入要插入的位置和数据(用逗号隔开):");
scanf("%d,%d", &add, &data);
Node *temp = (Node *)malloc(sizeof(Node));
if (temp == NULL) {
printf("malloc error!\r\n");
return NULL;
} else {
temp->data = data;
temp->pre = NULL;
temp->next = NULL;
}
//插入到链表头,要特殊考虑
if (add == 1) {
temp->next = head;
head->pre = temp;
head = temp;
} else {
Node *body = head;
//找到要插入位置的前一个结点
for (int i = 1; i < add - 1; i++) {
body = body->next;
}
//判断条件为真,说明插入位置为链表尾
if (body->next == NULL) {
body->next = temp;
temp->pre = body;
} else {
body->next->pre = temp;
temp->next = body->next;
body->next = temp;
temp->pre = body;
}
}
return head;
}
//在第add位置的后面插入data节点
Node *InsertListEnd(Node *head) {
int i = 1;
int add, data;
printf("请输入要插入的位置和数据(用逗号隔开):");
scanf("%d,%d", &add, &data);
//新建数据域为data的结点
Node *temp = (Node *)malloc(sizeof(Node));
temp->data = data;
temp->pre = NULL;
temp->next = NULL;
Node *body = head;
while ((body->next) && (i < add + 1)) {
body = body->next;
i++;
}
//判断条件为真,说明插入位置为链表尾
if (body->next == NULL) {
body->next = temp;
temp->pre = body;
temp->next = NULL;
} else {
temp->next = body->pre->next;
temp->pre = body->pre;
body->next->pre = temp;
body->pre->next = temp;
}
return head;
}
//删除数据是data的节点
Node *DeleteList(Node *head) {
Node *temp = head;
int data;
printf("请输入要删除的元素:");
scanf("%d", &data);
//遍历链表
while (temp) {
//判断当前结点中数据域和data是否相等,若相等,摘除该结点
if (temp->data == data) {
//判断是否是头结点
if (temp->pre == NULL) {
head = temp->next;
temp->next = NULL;
free(temp);
return head;
}
//判断是否是尾节点
else if (temp->next == NULL) {
temp->pre->next = NULL;
free(temp);
return head;
} else {
temp->pre->next = temp->next;
temp->next->pre = temp->pre;
free(temp);
return head;
}
}
temp = temp->next;
}
printf("Can not find %d!\r\n", data);
return head;
}
//更新函数,其中,add 表示更改结点在双链表中的位置,newElem 为新数据的值
Node *ModifyList(Node *p) {
Node *temp = p;
int add, newElem;
printf("结点位序和新数据值(用逗号隔开):");
scanf("%d,%d", &add, &newElem);
//遍历到被删除结点
for (int i = 1; i < add; i++) {
temp = temp->next;
}
temp->data = newElem;
return p;
}
//head为原双链表,elem表示被查找元素
int FindList(Node *head) {
//新建一个指针t,初始化为头指针 head
Node *temp = head;
int i = 1;
int elem;
printf("输入要查找的元素:");
scanf("%d", &elem);
while (temp) {
if (temp->data == elem) {
return i;
}
i++;
temp = temp->next;
}
printf("该元素的位序是%d\n", i);
//程序执行至此处,表示查找失败
return -1;
}
int main() {
Node *head = NULL;
//创建双链表
head = CreatList(head);
printf("新创建双链表为\n");
PrintList(head);
//插入
head = InsertListHead(head);
printf("在表中该位置前插入元素后为:\n");
PrintList(head);
head = InsertListEnd(head);
printf("在表中该位置后插入元素后为:\n");
PrintList(head);
//删除
head = DeleteList(head);
printf("表中删除元素后为: \n" );
PrintList(head);
printf("元素的位置是\n%d\n", FindList(head));
//修改
head = ModifyList(head);
// printf("表中第i个节点中的数据改为存储a\n");
PrintList(head);
return 0;
}
运行结果(示例):
有不当的地方,还请大佬不吝赐教,拜拜