单链表的基本操作
链表基本操作注意的两个点:
> 1: 插入删除操作时,一定要找到直接前驱,以防止断链!
> 2: 单链表操作,往往伴随 while§ 或 while(p!=NULL)
创建单链表的两种方式
头插法
- 头插法返回头指针
/* 头插法(每次新结点都插入在头结点后,第一个数据结点之前),形成逆序的单链表 */
//注意: 定义指针变量,为了安全起见和习惯,一般需要初始化为NULL
#include <stdio.h>
#include <stdlib.h> //malloc函数的依赖
typedef int ElemType; //重命名int为ElemType
//结点定义
typedef struct LNode
{
ElemType data; //数据域
struct LNode *next; //指针域
} LNode, *LinkList; //结构体的一个普通变量(用于数据结点)和一个指针变量(用于头结点)
//返回头指针方式创建链表(头插法): 传入指向链表头结点的头指针作为形参
LinkList Creat_list(LinkList head)
{
head = (LinkList)malloc(sizeof(LNode)); //为头指针(头结点)开辟内存空间,由于malloc返回的是空指针类型,故前面才需进行强转
head->next = NULL; //没有数据结点,故初始化头结点的指针域为空
LNode *node = NULL; //定义工作指针,即用于辅助的指针变量,且安全起见初始化为NULL
int count = 0; //初始化已创建的数据结点的个数为0
printf("(head insert)Please input the number of Node: ");
scanf("%d", &count); //输入需要创建的数据结点的个数
//开始构造链表
for (int i = 0; i < count; i++)
{
node = (LNode *)malloc(sizeof(LNode)); // 为新结点开启一个结点的内存空间
node->data = i; //数据域的赋值
//指针域的赋值,即新结点 插入 前驱结点头结点的后面(从右往左)
node->next = head->next;
head->next = node;
}
//返回指向头结点的指针,即头指针
return head;
}
//----------------------------
//test,打印链表
void main()
{
LinkList head = NULL;
LinkList linkList = Creat_list(head);
LNode *p = linkList->next; //指向第一个数据结点
while (p != NULL)
{
printf("%d ", p->data);
p = p->next;
}
}
- 头插法返回二重指针
#include <stdio.h>
#include <stdlib.h> //malloc函数的依赖
typedef int ElemType; //重命名int为ElemType
//结点定义
typedef struct LNode
{
ElemType data; //数据域
struct LNode *next; //指针域
} LNode, *LinkList; //结构体的一个普通变量(用于数据结点)和一个指针变量(用于头结点)
//双重指针方式创建链表(头插法): 所以内部使用*L等于原先的头指针进行操作
void CreateList(LNode **L)
{
*L = (LinkList)malloc(sizeof(LNode)); //为头指针(头结点)开辟内存空间,由于malloc返回的是空指针类型,故前面才需进行强转
(*L)->next = NULL; //没有数据结点,故初始化头结点的指针域为空
LNode *node = NULL; //定义工作指针,即用于辅助的指针变量,且安全起见初始化为NULL
int count = 0; //初始化已创建的数据结点的个数为0
printf("(head insert)Please input the number of Node: ");
scanf("%d", &count); //输入需要创建的数据结点的个数
//开始构造链表
for (int i = 0; i < count; i++)
{
node = (LNode *)malloc(sizeof(LNode)); // 为新结点开启一个结点的内存空间
node->data = i; //数据域的赋值
//指针域的赋值,即新结点 插入 前驱结点头结点的后面(从右往左)
node->next = (*L)->next;
(*L)->next = node;
}
}
//--------------------------------------------
//输出函数
void print_list(LNode *node)
{
do
{
node = node->next;
if (node == NULL)
{
break;
}
printf("%d", node->data);
} while (node != NULL);
}
//test
int main()
{
LNode *node = NULL;
CreateList(&node); //传入指针的地址
print_list(node);
printf("\n");
return 0;
}
尾插法
- 尾插法返回头指针
/*尾插法(每次新结点都插入在尾结点后)
(为了提高查找尾结点的效率,需新定义一个尾指针专门指向尾结点,且始终指向尾结点)
(尾结点是带有数据的数据结点,而头结点是不带数据的)*/
#include <stdio.h>
#include <stdlib.h> //malloc函数的依赖
typedef int ElemType; //重命名int为ElemType
//结点定义
typedef struct LNode
{
ElemType data; //数据域
struct LNode *next; //指针域
} LNode, *LinkList; //结构体的一个普通变量和一个指针变量(用于数据结点)和一个指针变量(用于头结点/尾结点)
//创建链表(尾插法): 传入指向链表头结点的头指针作为形参
LinkList Creat_list(LinkList head)
{
head = (LinkList)malloc(sizeof(LinkList)); //为头指针(头结点)开辟内存空间
head->next = NULL; //没有数据结点,故初始化头结点的指针域为空
LNode *end = NULL; // 定义尾指针变量并初始化为NULL
end = head; //没有数据结点,故尾指针初始化指向头结点
LNode *node = NULL; //定义工作指针,即用于辅助的指针变量,初始化为NULL
int count = 0; //初始化已创建的数据结点的个数为0
printf("(tail insert)Please input the number of Node: ");
scanf("%d", &count);
//开始构造链表
for (int i = 0; i < count; i++)
{
node = (LNode *)malloc(sizeof(LNode)); // 为新结点开启一个结点的内存空间
node->data = i; //数据域的赋值
//指针域的赋值,即新结点 插入 尾结点的后面
end->next = node; //新结点连接在尾结点的后面
end = node; //新结点成为新的尾结点
}
end->next = NULL; //链表构造结束,令尾指针指向的尾结点的指针域为NULL
//返回指向头结点的指针,即头指针
return head;
}
//----------------------------------
//test,打印链表
void main()
{
LinkList head = NULL;
LinkList linkList = Creat_list(head);
LNode *p = linkList->next; //指向第一个数据结点
while (p != NULL)
{
printf("%d ", p->data);
p = p->next;
}
}
- 尾插法返回二重指针
#include <stdio.h>
#include <stdlib.h> //malloc函数的依赖
typedef int ElemType; //重命名int为ElemType
//结点定义
typedef struct LNode
{
ElemType data; //数据域
struct LNode *next; //指针域
} LNode, *LinkList; //结构体的一个普通变量和一个指针变量(用于数据结点)和一个指针变量(用于头结点/尾结点)
//创建链表(尾插法): 传入指向链表头结点的头指针作为形参
void Creat_list(LNode **head)
{
*head = (LinkList)malloc(sizeof(LinkList)); //为头指针(头结点)开辟内存空间
(*head)->next = NULL; //没有数据结点,故初始化头结点的指针域为空
LNode *end = NULL; // 定义尾指针变量并初始化为NULL
end = *head; //没有数据结点,故尾指针初始化指向头结点
LNode *node = NULL; //定义工作指针,即用于辅助的指针变量,初始化为NULL
int count = 0; //初始化已创建的数据结点的个数为0
printf("(tail insert)Please input the number of Node: ");
scanf("%d", &count);
//开始构造链表
for (int i = 0; i < count; i++)
{
node = (LNode *)malloc(sizeof(LNode)); // 为新结点开启一个结点的内存空间
node->data = i; //数据域的赋值
//指针域的赋值,即新结点 插入 尾结点的后面
end->next = node; //新结点连接在尾结点的后面
end = node; //新结点成为新的尾结点
}
end->next = NULL; //链表构造结束,令尾指针指向的尾结点的指针域为NULL
//返回指向头结点的指针,即头指针
}
//-----------------------------------
//test,打印链表
void main()
{
LinkList head = NULL;
Creat_list(&head);
LNode *p = head->next; //指向第一个数据结点
while (p != NULL)
{
printf("%d ", p->data);
p = p->next;
}
}
选取原则: “头逆尾顺!”。(头插法形成逆序,尾插法形成顺序)
单链表查找
- 按值查找
/*按值查找,查找是否有结点值等于key的结点
若有,返回首次找到的结点
没有,则返回NULL
注:查找值可以从第一个数据结点开始,但如果涉及增减就需要从头节点开始*/
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
typedef int ElemType; //重命名int为ElemType
//结点定义
typedef struct LNode
{
ElemType data; //数据域
struct LNode *next; //指针域
} LNode, *LinkList; //结构体的一个普通变量(用于数据结点)和一个指针变量(用于头结点)
//查找(函数返回的类型是结点的指针变量类型)
LNode *Locate_Node(LinkList head, int key)
{
LNode *p = NULL; //定义工作指针,即用于辅助的指针变量,且一般初始化为NULL
p = head->next; //工作指针指向第一个数据结点
while (p != NULL && p->data != key) //当有数据结点且结点值与查找的值不等时,指针继续后移
{
p = p->next;
}
if (p->data == key) //找到首个相等值的结点,返回这个结点
{
return p;
}
else //查找失败
{
printf("The Node is not in the linkList!");
return NULL;
}
}
- 按序号查找
/*按序号查找,取单链表中的第i个元素,其中第一个数据结点作为1
注:查找值可以从第一个数据结点开始,但如果涉及增减就需要从头节点开始*/
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
typedef int ElemType; //重命名int为ElemType
//结点定义
typedef struct LNode
{
ElemType data; //数据域
struct LNode *next; //指针域
} LNode, *LinkList; //结构体的一个普通变量(用于数据结点)和一个指针变量(用于头结点)
//查找(查找的数据保存在e中)
bool GetElem_L(LinkList head, int i, ElemType *e)
{
LNode *p; //定义工作指针,即用于辅助的指针变量,且一般初始化为NULL
p = head->next; //工作指针指向第一个数据结点
int j = 1; //j代表位置的序号,初始化第一个数据结点的位置为1
if (!p || j > i) //没有数据结点或查找的位置序号小于1
{
return false;
}
//开始往后查找(当有数据结点+查找的位置大于1)
while (p && j < i)
{
p = p->next;
++j;
}
*e = p->data; //e用于保存查找的值
return true;
}
//创建链表(头插法): 传入指向链表头结点的头指针作为形参
LinkList Creat_list(LinkList head)
{
head = (LinkList)malloc(sizeof(LNode)); //为头指针(头结点)开辟内存空间,由于malloc返回的是空指针类型,故前面才需进行强转
head->next = NULL; //没有数据结点,故初始化头结点的指针域为空
LNode *node = NULL; //定义工作指针,即用于辅助的指针变量,且安全起见初始化为NULL
int count = 0; //初始化已创建的数据结点的个数为0
printf("(head insert)Please input the number of Node: ");
scanf("%d", &count); //输入需要创建的数据结点的个数
//开始构造链表
for (int i = 0; i < count; i++)
{
node = (LNode *)malloc(sizeof(LNode)); // 为新结点开启一个结点的内存空间
node->data = i; //数据域的赋值
//指针域的赋值,即新结点 插入 前驱结点头结点的后面(从右往左)
node->next = head->next;
head->next = node;
}
//返回指向头结点的指针,即头指针
return head;
}
//-------------------------------------
//test,打印链表
void main()
{
LinkList head = NULL;
LinkList linkList = Creat_list(head);
printf("linkList created successfully!\n");
//LNode *p = linkList->next; //指向第一个数据结点
/* while (p != NULL)
{
printf("%d ", p->data);
p = p->next;
} */
int e, i;
printf("please input the number's order: ");
scanf("%d", &i);
bool result = GetElem_L(linkList, i, &e);
printf("bool result is: %d\n", result);
printf("e: %d", e);
}
单链表删除
- 按值删除
/*单链表按值删除值为key的第一个数据结点
目的: 首先要查找值为key的结点是否存在,若存在则删除,否则返回NULL
删除操作需要: 成对指针,一个指明删除元素的位置,一个指明删除元素的直接前驱
实现思路: 由于要删除第一个值相同的结点,故需要找到其前驱结点来修改其指针指向,修改好后再释放待删除结点,故需要设置一个工作指针始终指向移动中的指针的前驱结点
注:查找值可以从第一个数据结点开始,但如果涉及增减就需要从头节点开始*/
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h> //malloc函数的依赖
typedef int ElemType; //重命名int为ElemType
//结点定义
typedef struct LNode
{
ElemType data; //数据域
struct LNode *next; //指针域
} LNode, *LinkList; //结构体的一个普通变量(用于数据结点)和一个指针变量(用于头结点)
//删除第一个值相同的数据结点(e保存删除的结点数值)
void Delete_LinkList(LinkList head, int key, ElemType *e)
{
LNode *p = NULL; //定义工作指针,用于始终指向移动指针指向的数据结点的前驱结点
p = head; //故初始化先指向头结点
LNode *q = NULL; //定义移动指针,用于指针后移
q = head->next; //故初始化先指向第一个数据结点
while (q != NULL && q->data != key)
{
p = q; //更新p的位置,确保其始终指向待删除结点的前驱结点
q = q->next; //当数据结点结点值与查找的值不等时,移动指针继续后移
}
if (q != NULL && q->data == key)
{
//找到了,修改其前驱结点的指针指向,保存数据后释放结点
p->next = q->next;
*e = q->data;
free(q);
}
else
{
printf("no exist\n");
}
}
//创建链表(尾插法): 传入指向链表头结点的头指针作为形参
LinkList Creat_list(LinkList head)
{
head = (LinkList)malloc(sizeof(LinkList)); //为头指针(头结点)开辟内存空间
head->next = NULL; //没有数据结点,故初始化头结点的指针域为空
LNode *end = NULL; //定义尾指针变量并初始化为NULL
end = head; //没有数据结点,故尾指针初始化指向头结点
LNode *node = NULL; //定义工作指针,即用于辅助的指针变量,初始化为NULL
int count = 0; //初始化已创建的数据结点的个数为0
printf("(tail insert)Please input the number of Node: ");
scanf("%d", &count);
//开始构造链表
for (int i = 0; i < count; i++)
{
node = (LNode *)malloc(sizeof(LNode)); // 为新结点开启一个结点的内存空间,结点需要开辟内存空间才能使用
node->data = i + 1; //数据域的赋值
//指针域的赋值,即新结点 插入 尾结点的后面
end->next = node; //新结点连接在尾结点的后面
end = node; //新结点成为新的尾结点
}
end->next = NULL; //链表构造结束,令尾指针指向的尾结点的指针域为NULL
//返回指向头结点的指针,即头指针
return head;
}
//输出函数
void print_list(LNode *node)
{
do
{
node = node->next;
if (node == NULL)
{
break;
}
printf("%d ", node->data);
} while (node != NULL);
printf("\n");
}
//-------------------------------------------------------
//test,打印链表
void main()
{
LinkList head = NULL;
LinkList linkList = Creat_list(head);
print_list(linkList);
printf("linkList created successfully!\n");
//测试删除
int key; //i用于指定第几个数据结点
int e = 0; //e用于保存删除的数据值,初始化为0
printf("please input the value you want to delete: ");
scanf("%d", &key);
Delete_LinkList(linkList, key, &e);
if (e != 0)
{
printf("del value is: %d\n", e);
}
print_list(linkList);
}
- 按序号删除
/*单链表按序号删除第i个数据结点
删除操作需要: 成对指针,一个指明删除元素的位置,一个指明删除元素的直接前驱
思路: 删除第i个数据结点,需找到第i个结点的地址,而该地址存储在其前驱结点的next域中
故: 需先找到第i个结点的前驱结点p,则p->next就是第i个结点q,然后令p->next指向q->next,最后再释放q的空间
注:查找值可以从第一个数据结点开始,但如果涉及增减就需要从头节点开始*/
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h> //malloc函数的依赖
typedef int ElemType; //重命名int为ElemType
//结点定义
typedef struct LNode
{
ElemType data; //数据域
struct LNode *next; //指针域
} LNode, *LinkList; //结构体的一个普通变量(用于数据结点)和一个指针变量(用于头结点)
//删除第i个数据结点(删除结点数据保存在e中)
bool ListDelete_L(LinkList head, int i, ElemType *e)
{
LNode *p = NULL; //定义工作指针,即用于移动的指针变量,一般初始化为NULL
p = head; //工作指针指向头结点,因为万一删除第一个点时需要找前驱节点头结点,故只能从头结点开始
LNode *q = NULL; //定义另外一个工作指针,用于代表待删除结点,一般初始化为NULL
int j = 0; //j代表位置的序号,初始化头结点位置为0
//容错判断(没有数据结点或查找的位置序号小于1)
if (!p || j > i - 1)
{
return false; //删除位置不合理
}
//有数据结点的情况下寻找第i-1个数据结点
while (p->next && j < i - 1)
{
p = p->next;
++j;
}
//找到了第i-1个数据结点(默认输入都是删除已存在的点,故没有附加条件判断)
q = p->next; //工作指针q指向待删除的第i个结点
p->next = q->next; //调整指针连接
*e = q->data; //保存删除结点的数据
free(q); //释放第i个结点空间
return true;
}
//创建链表(尾插法): 传入指向链表头结点的头指针作为形参
LinkList Creat_list(LinkList head)
{
head = (LinkList)malloc(sizeof(LinkList)); //为头指针(头结点)开辟内存空间
head->next = NULL; //没有数据结点,故初始化头结点的指针域为空
LNode *end = NULL; //定义尾指针变量并初始化为NULL
end = head; //没有数据结点,故尾指针初始化指向头结点
LNode *node = NULL; //定义工作指针,即用于辅助的指针变量,初始化为NULL
int count = 0; //初始化已创建的数据结点的个数为0
printf("(tail insert)Please input the number of Node: ");
scanf("%d", &count);
//开始构造链表
for (int i = 0; i < count; i++)
{
node = (LNode *)malloc(sizeof(LNode)); // 为新结点开启一个结点的内存空间
node->data = i + 1; //数据域的赋值
//指针域的赋值,即新结点 插入 尾结点的后面
end->next = node; //新结点连接在尾结点的后面
end = node; //新结点成为新的尾结点
}
end->next = NULL; //链表构造结束,令尾指针指向的尾结点的指针域为NULL
//返回指向头结点的指针,即头指针
return head;
}
//输出函数
void print_list(LNode *node)
{
do
{
node = node->next;
if (node == NULL)
{
break;
}
printf("%d ", node->data);
} while (node != NULL);
printf("\n");
}
//-------------------------------
//test,打印链表
void main()
{
LinkList head = NULL;
LinkList linkList = Creat_list(head);
print_list(linkList);
printf("linkList created successfully!\n");
//测试删除
int i, e; //i用于指定第几个数据结点,e用于保存删除的数据值
printf("please the order you want to delete: ");
scanf("%d", &i);
bool result = ListDelete_L(linkList, i, &e);
printf("result: %d\n", result);
printf("del value is: %d\n", e);
print_list(linkList);
}
- 单链表插入
/* 单链表的插入: 将值为e的结点插入到表的第i个数据结点的位置上
思路: 执行插入操作,首先必须找到插入位置的前驱结点,然后创建新结点并赋值,再将新结点插在其后面
注:查找值可以从第一个数据结点开始,但如果涉及增减就需要从头节点开始*/
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h> //malloc函数的依赖
typedef int ElemType; //重命名int为ElemType
//结点定义
typedef struct LNode
{
ElemType data; //数据域
struct LNode *next; //指针域
} LNode, *LinkList; //结构体的一个普通变量(用于数据结点)和一个指针变量(用于头结点)
//插入结点
bool ListInsert_L(LinkList head, int i, ElemType e)
{
LNode *p = NULL; //定义工作指针,即用于辅助的指针变量,且初始化为NULL
p = head; //工作指针指向头结点,而不是指向第一个数据结点开始,是因为可能新结点插入到第1个数据结点的位置的话,那么需要找前驱结点,也就是头结点
int j = 0; //此时的头指针位置标记为序号0
//容错判断
if (!p || j > i - 1)
{
return false;
}
//寻找插入的第i个结点的前驱: 即寻找第i-1个结点
while (p && j < i - 1)
{
p = p->next;
++j;
}
//找到了第i-1个结点
LNode *s = NULL; //1:创建新结点
s = (LNode *)malloc(sizeof(LNode)); //2:给新结点分配空间并赋值
s->data = e;
//3:新结点插入到第i-1个结点的后面
s->next = p->next;
p->next = s;
return true;
}
//创建链表(头插法): 传入指向链表头结点的头指针作为形参
LinkList Creat_list(LinkList head)
{
head = (LinkList)malloc(sizeof(LNode)); //为头指针(头结点)开辟内存空间,由于malloc返回的是空指针类型,故前面才需进行强转
head->next = NULL; //没有数据结点,故初始化头结点的指针域为空
LNode *node = NULL; //定义工作指针,即用于辅助的指针变量,且安全起见初始化为NULL
int count = 0; //初始化已创建的数据结点的个数为0
printf("(head insert)Please input the number of Node: ");
scanf("%d", &count); //输入需要创建的数据结点的个数
//开始构造链表
for (int i = 0; i < count; i++)
{
node = (LNode *)malloc(sizeof(LNode)); // 为新结点开启一个结点的内存空间
node->data = i; //数据域的赋值
//指针域的赋值,即新结点 插入 前驱结点头结点的后面(从右往左)
node->next = head->next;
head->next = node;
}
//返回指向头结点的指针,即头指针
return head;
}
//-----------------------------------
//test
void main()
{
LinkList head = NULL;
LinkList linkList = Creat_list(head);
printf("linkList created successfully!\n");
//测试插入结点元素
int i, e;
printf("please input the number you want to insert: ");
scanf("%d", &e);
printf("please input the position: ");
scanf("%d", &i);
bool result = ListInsert_L(linkList, i, e);
//测试输出结果
printf("result is:%d\n", result);
LNode *p = linkList->next; //指向第一个数据结点
while (p != NULL)
{
printf("%d ", p->data);
p = p->next;
}
}
输出所有元素
/*打印链表的所有数据结点的值*/
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h> //malloc函数的依赖
typedef int ElemType; //重命名int为ElemType
//结点定义
typedef struct LNode
{
ElemType data; //数据域
struct LNode *next; //指针域
} LNode, *LinkList; //结构体的一个普通变量(用于数据结点)和一个指针变量(用于头结点)
//打印数据结点值【链表由于不知道表长度,故多采用while循环】
void printL(LinkList head)
{
LNode *p = NULL; //工作指针,辅助作用
p = head->next; //工作指针指向第一个数据结点
while (p != NULL)
{
printf("%d ", p->data); //打印结点数据
p = p->next; //指针后移
}
}
释放单链表
/*释放单链表*/
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h> //malloc函数的依赖
typedef int ElemType; //重命名int为ElemType
//结点定义
typedef struct LNode
{
ElemType data; //数据域
struct LNode *next; //指针域
} LNode, *LinkList; //结构体的一个普通变量(用于数据结点)和一个指针变量(用于头结点)
//创建链表(尾插法): 传入指向链表头结点的头指针作为形参
LinkList Creat_list(LinkList head)
{
head = (LinkList)malloc(sizeof(LinkList)); //为头指针(头结点)开辟内存空间
head->next = NULL; //没有数据结点,故初始化头结点的指针域为空
LNode *end = NULL; // 定义尾指针变量并初始化为NULL
end = head; //没有数据结点,故尾指针初始化指向头结点
LNode *node = NULL; //定义工作指针,即用于辅助的指针变量,初始化为NULL
int count = 0; //初始化已创建的数据结点的个数为0
printf("(tail insert)Please input the number of Node: ");
scanf("%d", &count);
//开始构造链表
for (int i = 0; i < count; i++)
{
node = (LNode *)malloc(sizeof(LNode)); // 为新结点开启一个结点的内存空间
node->data = i; //数据域的赋值
//指针域的赋值,即新结点 插入 尾结点的后面
end->next = node; //新结点连接在尾结点的后面
end = node; //新结点成为新的尾结点
}
end->next = NULL; //链表构造结束,令尾指针指向的尾结点的指针域为NULL
//返回指向头结点的指针,即头指针
return head;
}
//释放单链表
/*
输入:head原链表的头节点
功能:释放链表空间
*/
void FreeList(LinkList head)
{
LNode *p = head->next; //遍历指针p指向第一个数据结点
LNode *temp;
//一直遍历,一边将各个结点释放
while (p != NULL)
{
temp = p->next; //因为释放q后,就断链了,故需提前用另一个指针保存下一个数据结点的位置
free(p);
p = temp; //遍历指针继续移动
}
//最后释放头结点
free(head);
printf("The linkList has been freed!");
}
//-----------------------------------------------
//打印数据结点值【链表由于不知道表长度,故多采用while循环】
void printL(LinkList head)
{
LNode *p = NULL; //工作指针,辅助作用
p = head->next; //工作指针指向第一个数据结点
while (p != NULL)
{
printf("%d ", p->data); //打印结点数据
p = p->next; //指针后移
}
}
//test
void main()
{
//创建链表
LinkList head = NULL;
LinkList linkList = Creat_list(head);
printf("linkList created successfully!following: \n");
printL(linkList);
//释放链表
printf("\nafter:\n");
FreeList(linkList);
}
单链表的应用示例
有序单链表合并
- 合并两个升序链表为升序
/*合并两个按升序排列的分别以La_head和Lb_head为头结点
将其合并成升序排列的单链表,并返回新链表的头指针,要求使用原表的结点数据空间,
核心思路: 谁小拿出来,利用尾插法构建新链表,“头逆尾顺,故选择尾插法”*/
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h> //malloc函数的依赖
typedef int ElemType; //重命名int为ElemType
//结点定义
typedef struct LNode
{
ElemType data; //数据域
struct LNode *next; //指针域
} LNode, *LinkList; //结构体的一个普通变量(用于数据结点)和一个指针变量(用于头结点)
//合并两个升序单链表为升序
LNode *Merge_LinkList(LinkList La_head, LinkList Lb_head)
{
//初始化c
LNode *Lc_head, *pa, *pb, *pc, *ptr;
Lc_head = La_head; //以原先La链表的头结点作为新链的头结点
pc = Lc_head; //新链表的头指针指向其头结点
pa = La_head->next; //指向La链表的第一个数据结点
pb = Lb_head->next; //指向Lb链表的第一个数据结点
//构造新链(当两天链均不为空时)
while (pa != NULL && pb != NULL)
{
if (pa->data < pb->data) //当La的数据结点值小于Lb链的数据结点值,将较小值La的数据结点链接到新链中
{
pc->next = pa;
pc = pa; //更新新链表的指针位置,确保其始终指向最后一个数据结点
pa = pa->next;
}
if (pa->data > pb->data) //当La的数据结点值大于Lb链的数据结点值,将较小值Lb的数据结点链接到新链中
{
pc->next = pb;
pc = pb; //更新新链表的指针位置,确保其始终指向最后一个数据结点
pb = pb->next;
}
if (pa->data == pb->data) //值相同,取下a链表的结点,释放b链表的结点
{
//取下a链表的结点链接到LC中
pc->next = pa;
pc = pa; //更新新链表的指针位置,确保其始终指向最后一个数据结点
pa = pa->next;
//删除b链表结点(成对指针)
ptr = pb;
pb = pb->next;
free(ptr);
}
}
//将剩余的结点链直接合并到LC链表
if (pa != NULL) //如果La链还有剩的数据结点
{
pc->next = pa;
}
else //Lb链还有剩的数据结点
{
pc->next = pb;
}
//释放Lb的头结点,La头结点由于充当Lc头结点,故不用释放
free(Lb_head);
//返回新链表的头结点
return Lc_head;
}
//创建链表(尾插法): 传入指向链表头结点的头指针作为形参
LinkList Creat_list(LinkList head)
{
head = (LinkList)malloc(sizeof(LinkList)); //为头指针(头结点)开辟内存空间
head->next = NULL; //没有数据结点,故初始化头结点的指针域为空
LNode *end = NULL; // 定义尾指针变量并初始化为NULL
end = head; //没有数据结点,故尾指针初始化指向头结点
LNode *node = NULL; //定义工作指针,即用于辅助的指针变量,初始化为NULL
int count = 0; //初始化已创建的数据结点的个数为0
printf("(tail insert)Please input the number of Node: ");
scanf("%d", &count);
//开始构造链表
for (int i = 0; i < count; i++)
{
node = (LNode *)malloc(sizeof(LNode)); // 为新结点开启一个结点的内存空间
int value = 0;
printf("%d data:", i + 1);
scanf("%d", &value);
node->data = value; //数据域的赋值
//指针域的赋值,即新结点 插入 尾结点的后面
end->next = node; //新结点连接在尾结点的后面
end = node; //新结点成为新的尾结点
}
end->next = NULL; //链表构造结束,令尾指针指向的尾结点的指针域为NULL
//返回指向头结点的指针,即头指针
return head;
}
//---------------------------------
//输出函数
void print_list(LNode *node)
{
do
{
node = node->next;
if (node == NULL)
{
break;
}
printf("%d ", node->data);
} while (node != NULL);
printf("\n");
}
//test,打印链表
void main()
{
LinkList head_a = NULL, head_b = NULL;
LinkList linkList_a = Creat_list(head_a);
LinkList linkList_b = Creat_list(head_b);
printf("This is A:");
print_list(linkList_a);
printf("This is B:");
print_list(linkList_b);
printf("this is result:");
//测试合并
LinkList Lc = Merge_LinkList(linkList_a, linkList_b);
print_list(Lc);
}
- 合并两个升序链表为降序
/*合并两个按升序排列的分别以La_head和Lb_head为头结点
将其合并成降序排列的单链表,并返回新链表的头指针,要求使用原表的结点数据空间,
核心思路: 谁小拿出来,利用头插法构建新链表,"头逆尾顺",故选择头插法”*/
/*----------------------------------------------------------------*/
/*易错点:
直接以pa/pb来做判断条件,pa/pb判断后会移动,会导致各个if比较的结点数据当前同个结点的数据,故需要保存当前比较的位置信息
改进: 需要提取保存当前轮次比较的结点数据,再用其做判断条件,各个if判断的结点数据才是同个数据*/
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h> //malloc函数的依赖
typedef int ElemType; //重命名int为ElemType
//结点定义
typedef struct LNode
{
ElemType data; //数据域
struct LNode *next; //指针域
} LNode, *LinkList; //结构体的一个普通变量(用于数据结点)和一个指针变量(用于头结点)
//合并两个升序链表为降序
LNode *Merge_LinkList(LinkList La_head, LinkList Lb_head)
{
//初始化
LNode *Lc_head, *pa, *pb, *pc, *ptr;
pa = La_head->next; //指向La链表的第一个数据结点
pb = Lb_head->next; //指向Lb链表的第一个数据结点
Lc_head = La_head; //以原先La链表的头结点作为新链的头结点
Lc_head->next = NULL; //由于是头插法,新链的头结点的指针域要设置为NULL
LNode *tmp; //存储下一个位置信息的中间指针
LNode *current_pa, *current_pb; //存储每一轮次比较的两个结点,并用作判断条件
//构造新链(当两天链均不为空时)
while (pa != NULL && pb != NULL)
{ //因为如果直接以pa/pb来做判断条件,pa/pb判断后会移动,会导致各个if比较的结点数据当前同个结点的数据,故需要保存当前比较的位置信息
current_pa = pa; //保存当前一轮比较的la结点位置
current_pb = pb; //保存当前一轮比较的lb结点位置
if (current_pa->data < current_pb->data) //当La的数据结点值小于Lb链的数据结点值,将较小值La的数据结点链接到新链中
{
//因为pa->next被操作了,会断链,故需提前存储下一个位置
tmp = pa->next;
//头插法插入La结点
pa->next = Lc_head->next;
Lc_head->next = pa;
//La结点后移
pa = tmp;
}
if (current_pa->data > current_pb->data) //当La的数据结点值大于Lb链的数据结点值,将较小值Lb的数据结点链接到新链中
{
//由于pb->next被操作了,会断链,故需提前存储下一个位置
tmp = pb->next;
//头插法插入Lb结点
pb->next = Lc_head->next;
Lc_head->next = pb;
//Lb结点后移
pb = tmp;
}
if (current_pa->data == current_pb->data) //值相同,取下a链表的结点,释放b链表的结点
{
//因为pa->next被操作了,会断链,故需提前存储下一个位置,而pb->next没有被操作,故不需要保存
tmp = pa->next;
//取下a链表的结点以头插法链接到LC中
pa->next = Lc_head->next;
Lc_head->next = pa;
pa = tmp; //移动la结点指针
//删除b链表结点(成对指针)
ptr = pb;
pb = pb->next; //移动lb结点指针
free(ptr);
}
}
//如果La链还有剩的数据结点,逐个结点以头插法链接到Lc中
while (pa != NULL)
{
//printf("pa_data: %d,pb_data: %d", pa->data, pb->data);
//保留当前位置
tmp = pa->next;
//逐个结点链接
pa->next = Lc_head->next;
Lc_head->next = pa;
//指针所指结点后移
pa = tmp;
}
//Lb链还有剩的数据结点,逐个结点以头插法链接到Lc中
while (pb != NULL)
{
//保留当前位置
tmp = pb->next;
//逐个结点链接
pb->next = Lc_head->next;
Lc_head->next = pb;
//指针所指结点后移
pb = tmp;
}
//释放Lb的头结点,La头结点由于充当Lc头结点,故不用释放
free(Lb_head);
//返回新链表的头结点
return Lc_head;
}
//创建链表(尾插法,用来创建顺序的链表): 传入指向链表头结点的头指针作为形参
LinkList Creat_list(LinkList head)
{
head = (LinkList)malloc(sizeof(LinkList)); //为头指针(头结点)开辟内存空间
head->next = NULL; //没有数据结点,故初始化头结点的指针域为空
LNode *end = NULL; // 定义尾指针变量并初始化为NULL
end = head; //没有数据结点,故尾指针初始化指向头结点
LNode *node = NULL; //定义工作指针,即用于辅助的指针变量,初始化为NULL
int count = 0; //初始化已创建的数据结点的个数为0
printf("(tail insert)Please input the number of Node: ");
scanf("%d", &count);
//开始构造链表
for (int i = 0; i < count; i++)
{
node = (LNode *)malloc(sizeof(LNode)); // 为新结点开启一个结点的内存空间
int value = 0;
printf("%d data:", i + 1);
scanf("%d", &value);
node->data = value; //数据域的赋值
//指针域的赋值,即新结点 插入 尾结点的后面
end->next = node; //新结点连接在尾结点的后面
end = node; //新结点成为新的尾结点
}
end->next = NULL; //链表构造结束,令尾指针指向的尾结点的指针域为NULL
//返回指向头结点的指针,即头指针
return head;
}
//-----------------------------------
//输出函数
void print_list(LNode *node)
{
do
{
node = node->next;
if (node == NULL)
{
break;
}
printf("%d ", node->data);
} while (node != NULL);
printf("\n");
}
//test,打印链表
void main()
{
LinkList head_a = NULL, head_b = NULL;
LinkList linkList_a = Creat_list(head_a);
LinkList linkList_b = Creat_list(head_b);
printf("This is A:");
print_list(linkList_a);
printf("This is B:");
print_list(linkList_b);
printf("this is result:");
//测试合并
LinkList Lc = Merge_LinkList(linkList_a, linkList_b);
print_list(Lc);
}
单链表逆置
逆转线性单链表(就地逆序排列原先链表)
就地逆序,采用头插法,以此借助于原有结点的存储空间采用头插法建立新的单链表!
/*逆转线性单链表(就地逆序排列原先链表)
就地逆序,采用头插法,以此借助于原有结点的存储空间采用头插法建立新的单链表*/
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h> //malloc函数的依赖
typedef int ElemType; //重命名int为ElemType
//结点定义
typedef struct LNode
{
ElemType data; //数据域
struct LNode *next; //指针域
} LNode, *LinkList; //结构体的一个普通变量(用于数据结点)和一个指针变量(用于头结点)
//逆转单链表(头插法)
LNode *revert_list(LinkList head)
{
if (head->next == NULL)
{
//原链表为空,则直接返回头结点
return head;
}
LNode *p = NULL; //定义工作指针,用于指示要插入的结点
p = head->next; //指向第一个数据结点
head->next = NULL; //初始化逆转后的链表的头结点的指针域为空
LNode *tmp = NULL; //定义工作指针,用于指示当前插入的数据结点的下一个数据结点位置
while (p)
{
tmp = p->next; //指向当前插入的数据结点的下一个数据结点位置
//头插法插入逆转后的头结点后面
p->next = head->next;
head->next = p;
//移动到下一个要插入的结点
p = tmp; //移向下一个结点
}
//返回逆转后的链表的头结点
return head;
}
//创建链表(尾插法): 传入指向链表头结点的头指针作为形参
LinkList Creat_list(LinkList head)
{
head = (LinkList)malloc(sizeof(LinkList)); //为头指针(头结点)开辟内存空间
head->next = NULL; //没有数据结点,故初始化头结点的指针域为空
LNode *end = NULL; // 定义尾指针变量并初始化为NULL
end = head; //没有数据结点,故尾指针初始化指向头结点
LNode *node = NULL; //定义工作指针,即用于辅助的指针变量,初始化为NULL
int count = 0; //初始化已创建的数据结点的个数为0
printf("(tail insert)Please input the number of Node: ");
scanf("%d", &count);
//开始构造链表
for (int i = 0; i < count; i++)
{
node = (LNode *)malloc(sizeof(LNode)); // 为新结点开启一个结点的内存空间
node->data = i + 1; //数据域的赋值
//指针域的赋值,即新结点 插入 尾结点的后面
end->next = node; //新结点连接在尾结点的后面
end = node; //新结点成为新的尾结点
}
end->next = NULL; //链表构造结束,令尾指针指向的尾结点的指针域为NULL
//返回指向头结点的指针,即头指针
return head;
}
//--------------------------------
//输出函数
void print_list(LNode *node)
{
do
{
node = node->next;
if (node == NULL)
{
break;
}
printf("%d ", node->data);
} while (node != NULL);
printf("\n");
}
//test,打印链表
void main()
{
LinkList head = NULL, head_b = NULL;
LinkList linkList = Creat_list(head);
printf("before\n");
print_list(linkList);
//测试合并
LinkList L = revert_list(linkList);
printf("after\n");
print_list(L);
}
删除链表中重复值
- 实现方式一
/*删除单链表中所有值重复的结点,使得所有结点的值都不相同
思路: 数据比较操作+删除操作
数据比较操作: 需要固定住一个结点,与其后的所有结点比较完后,固定结点再移动到下一个结点
删除操作需要: 成对指针,一个指明删除元素的位置,一个指明删除元素的直接前驱*/
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h> //malloc函数的依赖
typedef int ElemType; //重命名int为ElemType
//结点定义
typedef struct LNode
{
ElemType data; //数据域
struct LNode *next; //指针域
} LNode, *LinkList; //结构体的一个普通变量(用于数据结点)和一个指针变量(用于头结点)
//删除以head为头结点的单链表中所有值相同的结点
void Delete_Node_value(LinkList head)
{
LNode *p = NULL;
p = head->next; //p用于指示当前比较的元素值
//数据删除操作涉及到的成对指针: q和ptr
LNode *q = NULL; //q用于指示待删除元素的直接前驱
LNode *ptr = NULL; //ptr用于指示待删除元素位置
//检查链表中所有结点(当有数据结点时)
while (p != NULL)
{
//成对指针初始化位置
q = p; //开始时待删除元素的前驱结点就是当前比较的元素结点
ptr = p->next; //开始时为指向当前比较的结点p的下一个数据结点
//检查p的所有后继结点ptr
while (ptr != NULL)
{
if (ptr->data == p->data)
{
//找到相同值,删除操作
q->next = ptr->next; //调整待删除元素的前驱结点的指针指向
free(ptr); //释放待删除的结点的空间
ptr = q->next; //重新赋值指针的指向
}
else
{
//找不到,成对指针继续后移
q = ptr;
ptr = ptr->next;
}
}
//比较下一个值
p = p->next;
}
}
//创建链表(尾插法): 传入指向链表头结点的头指针作为形参
LinkList Creat_list(LinkList head)
{
head = (LinkList)malloc(sizeof(LinkList)); //为头指针(头结点)开辟内存空间
head->next = NULL; //没有数据结点,故初始化头结点的指针域为空
LNode *end = NULL; // 定义尾指针变量并初始化为NULL
end = head; //没有数据结点,故尾指针初始化指向头结点
LNode *node = NULL; //定义工作指针,即用于辅助的指针变量,初始化为NULL
int count = 0; //初始化已创建的数据结点的个数为0
printf("(tail insert)Please input the number of Node: ");
scanf("%d", &count);
//开始构造链表
for (int i = 0; i < count; i++)
{
node = (LNode *)malloc(sizeof(LNode)); // 为新结点开启一个结点的内存空间
int value = 0;
printf("%d data:", i + 1);
scanf("%d", &value);
node->data = value; //数据域的赋值
//指针域的赋值,即新结点 插入 尾结点的后面
end->next = node; //新结点连接在尾结点的后面
end = node; //新结点成为新的尾结点
}
end->next = NULL; //链表构造结束,令尾指针指向的尾结点的指针域为NULL
//返回指向头结点的指针,即头指针
return head;
}
//------------------------------------------
//输出函数
void print_list(LNode *node)
{
do
{
node = node->next;
if (node == NULL)
{
break;
}
printf("%d ", node->data);
} while (node != NULL);
printf("\n");
}
//test,打印链表
void main()
{
LinkList head = NULL;
LinkList linkList = Creat_list(head);
printf("before\n");
print_list(linkList);
//测试
Delete_Node_value(linkList);
printf("after\n");
print_list(linkList);
}
- 实现方式二
/*已知有一普通单链表,设计算法,将链表中里面的重复的数据结点删除*/
/*----------------------------------------------------------------*/
/*注意:
在删除操作容易犯的错误:即用一个新的指针q指向了被删除结点,然后释放了这个指针q所指的结点,然后执行了Ptr=ptr->next。
错误的原因在于: 释放前ptr和q指向同个结点,释放了q所指结点,ptr的next指向就没了,故执行ptr=ptr->next会错误
总结:核心就是ptr=ptr->next这句错了,因为前面ptr已经释放了,没有next了*/
/*------------------------*/
/*正确做法:
因为在修改前驱结点的指针指向时刚好保存了ptr->next信息,故指针移动需要执行的是ptr=pre->next才能正确移动*/
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h> //malloc函数的依赖
typedef int ElemType; //重命名int为ElemType
//结点定义
typedef struct LNode
{
ElemType data; //数据域
struct LNode *next; //指针域
} LNode, *LinkList; //结构体的一个普通变量(用于数据结点)和一个指针变量(用于头结点)
LinkList Delete(LinkList head)
{
LNode *p = NULL; //初始化外层循环遍历指针
p = head->next; //初始化指向第一个数据结点
LNode *pre = NULL; //用于删除操作指向删除元素的前驱结点
LNode *ptr = NULL; //用于内层循环遍历的工作指针
//(固定一个不动,故需二重循环,外层固定元素,内层循环与固定元素的每个元素进行比较)
//外层循环固定结点
while (p != NULL)
{
pre = p; //初始化指向前驱结点的位置为固定的元素
ptr = p->next; //初始化从内存遍历指针从外层固定的元素结点的下一个结点开始
//内存循环检查p的所有后继结点进行比较和删除
while (ptr != NULL)
{
//找到了重复的结点,删除元素(成对指针删除套路)
if (ptr->data == p->data)
{
pre->next = ptr->next; //改变删除元素的前驱结点的指针连接
free(ptr); //释放待删除的结点空间
ptr = pre->next; //内层循环遍历指针继续移动
}
else
{
//元素不相等,继续移动
pre = ptr; //更新前驱结点的位置,确保pre指针始终指向前驱结点
ptr = ptr->next; //内层循环遍历指针继续移动
}
}
//一个固定的数据结点遍历完,轮到固定下一个数据结点
p = p->next;
}
//返回头结点
return head;
}
//创建链表(尾插法): 传入指向链表头结点的头指针作为形参
LinkList Creat_list(LinkList head)
{
head = (LinkList)malloc(sizeof(LinkList)); //为头指针(头结点)开辟内存空间
head->next = NULL; //没有数据结点,故初始化头结点的指针域为空
LNode *end = NULL; // 定义尾指针变量并初始化为NULL
end = head; //没有数据结点,故尾指针初始化指向头结点
LNode *node = NULL; //定义工作指针,即用于辅助的指针变量,初始化为NULL
int count = 0; //初始化已创建的数据结点的个数为0
printf("(tail insert)Please input the number of Node: ");
scanf("%d", &count);
//开始构造链表
for (int i = 0; i < count; i++)
{
node = (LNode *)malloc(sizeof(LNode)); // 为新结点开启一个结点的内存空间
int value = 0;
printf("%d data:", i + 1);
scanf("%d", &value);
node->data = value; //数据域的赋值
//指针域的赋值,即新结点 插入 尾结点的后面
end->next = node; //新结点连接在尾结点的后面
end = node; //新结点成为新的尾结点
}
end->next = NULL; //链表构造结束,令尾指针指向的尾结点的指针域为NULL
//返回指向头结点的指针,即头指针
return head;
}
//-----------------------------------
//输出函数
void print_list(LNode *node)
{
do
{
node = node->next;
if (node == NULL)
{
break;
}
printf("%d ", node->data);
} while (node != NULL);
printf("\n");
}
//test,打印链表
void main()
{
LinkList head_a = NULL;
LinkList linkList_a = Creat_list(head_a);
printf("This is A:");
print_list(linkList_a);
//测试合并
LinkList Lc = Delete(linkList_a);
print_list(Lc);
}
有选择性删除结点
已知线性表中的元素以递增有序排列,以单链表作为存储结构.
设计算法,删除表中所有值大于mink和小于maxk的元素(若表中存在这样的元素)同时释放被删除结点的空间
注意: 删除时,不能断链,故用一个指针始终指向待删除的结点的前驱结点*/
/*已知线性表中的元素以递增有序排列,以单链表作为存储结构.
设计算法,删除表中所有值大于mink和小于maxk的元素(若表中存在这样的元素)
同时释放被删除结点的空间
注意: 删除时,不能断链,故用一个指针始终指向待删除的结点的前驱结点*/
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h> //malloc函数的依赖
typedef int ElemType; //重命名int为ElemType
//结点定义
typedef struct LNode
{
ElemType data; //数据域
struct LNode *next; //指针域
} LNode, *LinkList; //结构体的一个普通变量(用于数据结点)和一个指针变量(用于头结点)
//删除单链表中大于mink小于maxk之间的数据元素
bool DeleteMiddleElem(LinkList head, int mink, int maxk)
{
//容错条件
if (mink > maxk)
{
return false;
}
LNode *p = head; //初始化指向头结点,p用于元素遍历
LNode *prev = p; //prev用于始终指向p的前驱结点,故初始化指向头结点
p = p->next; //p指向第一个数据结点
LNode *q = NULL; //确定删除位置的指针
//while遍历循环中一个设置判断条件
while (p && p->data < maxk)
{ //if中再设置一个判断条件
if (p->data < mink)
{
//小于mink,不是待删除的元素
prev = p; //保存前驱结点位置
p = p->next; //遍历结点后移
}
else
{
//大于mink(成对指针法,删除对应结点)
prev = p->next; //改变前驱结点的指针指向
q = p; //q指出待删除的位置
free(q); //释放待删除结点的存储空间
p = p->next; //遍历指针后移
}
}
return true;
}
有选择性移动结点
已知有一普通单链表,设计算法将链表中数据域值最小的结点移动到链表的最前面!! 注:不能申请新的链结点
/*已知有一普通单链表,设计算法将链表中数据域值最小的结点移动到链表的最前面
注:不能申请新的链结点*/
/*注: 易错点:
容易在更新最小值的过程中忽略了最小值的前驱结点位置也需要更新,故才需要在遍历指针遍历的过程中保存其前驱指针的位置,
这样才可以更新最小值的前驱结点的位置*/
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h> //malloc函数的依赖
typedef int ElemType; //重命名int为ElemType
//结点定义
typedef struct LNode
{
ElemType data; //数据域
struct LNode *next; //指针域
} LNode, *LinkList; //结构体的一个普通变量(用于数据结点)和一个指针变量(用于头结点)
//移动最小的结点到第一个数据结点位置
LinkList Move(LinkList head)
{
//赋值一个初始的最小值及最小结点,如第一个数据结点值,遍历结点值与已知的最小值比较,遍历结束便是链表最小值
LNode *minNode = head->next; //初始化最小结点的位置为第一个数据结点
LNode *p = NULL; //工作指针
p = minNode->next; //初始化遍历指针指向第二个数据结点开始,以便于与已知的最小值比较
LNode *premin = NULL; //初始化最终指向最小结点的前驱结点的位置
LNode *pre = head->next; //始终指向遍历指针的前驱指针
//1.找到最小的数据结点及其前驱结点(从第二个数据结点开始比较)
while (p != NULL)
{
//1. 遍历找到最小值结点及其前驱结点
//比较是否有更小的值(与初始化的最小值进行比较)
if (p->data < minNode->data)
{
//更新最小值的前驱结点的位置
premin = pre;
//更新最小结点的位置
minNode = p;
}
//否则,更新指针的位置
pre = p; //更新遍历指针的前驱结点的位置
p = p->next; //遍历指针继续移动
}
//while遍历结束后,找到了最终的最小值以及最小值的前驱结点
//更改最小值的前驱结点的指针连接
premin->next = minNode->next;
//利用头插法更改最小值结点的指针连接
minNode->next = head->next;
head->next = minNode;
//返回头结点
return head;
}
//创建链表(尾插法): 传入指向链表头结点的头指针作为形参
LinkList Creat_list(LinkList head)
{
head = (LinkList)malloc(sizeof(LinkList)); //为头指针(头结点)开辟内存空间
head->next = NULL; //没有数据结点,故初始化头结点的指针域为空
LNode *end = NULL; // 定义尾指针变量并初始化为NULL
end = head; //没有数据结点,故尾指针初始化指向头结点
LNode *node = NULL; //定义工作指针,即用于辅助的指针变量,初始化为NULL
int count = 0; //初始化已创建的数据结点的个数为0
printf("(tail insert)Please input the number of Node: ");
scanf("%d", &count);
//开始构造链表
for (int i = 0; i < count; i++)
{
node = (LNode *)malloc(sizeof(LNode)); // 为新结点开启一个结点的内存空间
int value = 0;
printf("%d data:", i + 1);
scanf("%d", &value);
node->data = value; //数据域的赋值
//指针域的赋值,即新结点 插入 尾结点的后面
end->next = node; //新结点连接在尾结点的后面
end = node; //新结点成为新的尾结点
}
end->next = NULL; //链表构造结束,令尾指针指向的尾结点的指针域为NULL
//返回指向头结点的指针,即头指针
return head;
}
//---------------------------------
//输出函数
void print_list(LNode *node)
{
do
{
node = node->next;
if (node == NULL)
{
break;
}
printf("%d ", node->data);
} while (node != NULL);
printf("\n");
}
//test,打印链表
void main()
{
LinkList head_a = NULL;
LinkList linkList_a = Creat_list(head_a);
printf("This is A:");
print_list(linkList_a);
//测试合并
LinkList Lc = Move(linkList_a);
print_list(Lc);
}
链表一分为二
设有一个带头结点的单链表hc,其结点值序列为(a1,b1,a2,b2,…,an,bn)(n>=1,且a,b成对出现)
设计算法,将hc拆分成两个带头结点的单链表ha和hb,
其中ha(a1,a2,…an),hb的结点值序列为(bn,bn-1,…,b1)
要求ha利用原hc的头结点,算法的时间复杂度为O(1)
/*设有一个带头结点的单链表hc,其结点值序列为(a1,b1,a2,b2,...,an,bn)(n>=1,且a,b成对出现)
设计算法,将hc拆分成两个带头结点的单链表ha和hb,
其中ha(a1,a2,..an),hb的结点值序列为(bn,bn-1,...,b1)
要求ha利用原hc的头结点,算法的时间复杂度为O(1)*/
/*---------------------------------------------------------------*/
/*思路: 根据"头逆尾顺"的原则,生成ha使用尾插法(其实只需保留原来的结点即可,生成hb使用头插法,对应了单链表的逆置)
并且ha的位置序号对应1,3,5..等奇数,而hb的位置序号为2,4,6..等偶数,故需增加一个位置变量代表位置*/
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h> //malloc函数的依赖
typedef int ElemType; //重命名int为ElemType
//结点定义
typedef struct LNode
{
ElemType data; //数据域
struct LNode *next; //指针域
} LNode, *LinkList; //结构体的一个普通变量(用于数据结点)和一个指针变量(用于头结点)
//算法实现
void split(LinkList hc, LinkList ha, LinkList hb)
{
//1:ha使用原hc的头结点,且ha只需保留原位置即可
//2.ha使用尾插法,为了方便尾插,需要头指针和尾指针
LNode *p = NULL; //定义遍历指针
p = hc->next; //指向hc第一个数据结点
int counter = 1; //初始化第一个数据结点的位置序号为1
LNode *tail = NULL;
hb->next = NULL; //初始化hb头插法的头结点的指针域为空
tail = ha; //初始化ha的尾插法的尾指针指向头结点
while (p != NULL)
{
if (counter % 2 != 0)
{
//奇数位置,对应生成Ha序列,可以使用尾插法,或者保留不动(这里示例采用尾插法)
//尾插法套路
tail->next = p; //插入到尾结点后
tail = p; //更新尾指针位置
//遍历指针后移
p = p->next;
}
else
{
//偶数位置,先改变其前驱结点的指针连接,再使用头插法将结点插入hb序列
//由于p插入修改了p->next,会导致丢失LC下个结点位置,故需用另一个指针提前保存位置
LNode *pnext = p->next;
//头插法套路
p->next = hb->next;
hb->next = p;
//遍历指针后移(利用前面保存的位置信息)
p = pnext;
}
//更新数据结点的位置序号
counter++;
}
//尾插法最后的数据结点作为尾结点指针域赋值为NULL
tail->next = NULL;
}
//创建链表(尾插法,用来创建顺序的链表): 传入指向链表头结点的头指针作为形参
LinkList Creat_list(LinkList head)
{
head = (LinkList)malloc(sizeof(LinkList)); //为头指针(头结点)开辟内存空间
head->next = NULL; //没有数据结点,故初始化头结点的指针域为空
LNode *end = NULL; // 定义尾指针变量并初始化为NULL
end = head; //没有数据结点,故尾指针初始化指向头结点
LNode *node = NULL; //定义工作指针,即用于辅助的指针变量,初始化为NULL
int count = 0; //初始化已创建的数据结点的个数为0
printf("(tail insert)Please input the number of Node: ");
scanf("%d", &count);
//开始构造链表
for (int i = 0; i < count; i++)
{
node = (LNode *)malloc(sizeof(LNode)); // 为新结点开启一个结点的内存空间
int value = 0;
printf("%d data:", i + 1);
scanf("%d", &value);
node->data = value; //数据域的赋值
//指针域的赋值,即新结点 插入 尾结点的后面
end->next = node; //新结点连接在尾结点的后面
end = node; //新结点成为新的尾结点
}
end->next = NULL; //链表构造结束,令尾指针指向的尾结点的指针域为NULL
//返回指向头结点的指针,即头指针
return head;
}
//-------------------------------------
//输出函数
void print_list(LNode *node)
{
do
{
node = node->next;
if (node == NULL)
{
break;
}
printf("%d ", node->data);
} while (node != NULL);
printf("\n");
}
//test,打印链表
void main()
{
LinkList head_c = NULL, head_a = NULL, head_b = NULL;
head_b = (LNode *)malloc(sizeof(LNode));
LinkList linkList_c = Creat_list(head_c);
printf("This is hc:");
print_list(linkList_c);
//测试
split(linkList_c, linkList_c, head_b);
printf("this is ha:");
print_list(linkList_c);
printf("this is hb:");
print_list(head_b);
}
链表间相交/公共结点判断
给定两个不含有环的单链表的头指针分别为head1和head2,
设计算法判断两个单链表是否相交(存储的数据相同),如果相交就返回第一个交点,
要求算法的时间复杂度为:O(length1+length2),其中length1和length2分别为各自链表长度
分析:
相交返回第一个结点,要求时间复杂度为O(length1+length2),而如果固定一个挨个比较的话,时间复杂度是O(length1*length2)
按照其时间复杂度分析,说明两个单链表存在公共后缀(后端对齐)【注: 就地逆序两两对应比较虽然可以满足时间复杂度,但逆序改变了表结构,不可取】
按照具有公共后缀,返回第一个公共结点的方法。
由于两个链表长度不一定一样,因此先在较长链表上遍历k个结点,再同步遍历两个链表,保证两个链表同时到达最后一个结点,这样也就保证了能够同时到达第一个公共结点!
/*给定两个不含有环的单链表的头指针分别为head1和head2,
设计算法判断两个单链表是否相交(存储的数据相同),如果相交就返回第一个交点,
要求算法的时间复杂度为:O(length1+length2),其中length1和length2分别为各自链表长度*/
/*------------------------------------------------*/
/*相交返回第一个结点,要求时间复杂度为O(length1+length2),而如果固定一个挨个比较的话,时间复杂度是O(length1*length2)
按照其时间复杂度分析,说明两个单链表存在公共后缀(后端对齐)【注: 就地逆序两两对应比较虽然可以满足时间复杂度,但逆序改变了表结构,不可取】
按照具有公共后缀,返回第一个公共结点的方法。
由于两个链表长度不一定一样,因此先在较长链表上遍历k个结点,再同步遍历两个链表,
保证两个链表同时到达最后一个结点,这样也就保证了能够同时到达第一个公共结点*/
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h> //malloc函数的依赖
typedef int ElemType; //重命名int为ElemType
//结点定义
typedef struct LNode
{
ElemType data; //数据域
struct LNode *next; //指针域
} LNode, *LinkList; //结构体的一个普通变量(用于数据结点)和一个指针变量(用于头结点)
//获取链表长度
int getLength(LinkList L)
{
//获取链表长度
return 0;
}
//算法实现
LinkList SearchFirst(LinkList L1, LinkList L2)
{
//获取两个链表长度
int len1 = getLength(L1);
int len2 = getLength(L2);
//声明两个指针分别用于指向较长和较短链表的第一个数据结点
LNode *longlist, *shortlist;
int dist; //差距
//如果L1链比较长
if (len1 > len2)
{
longlist = L1->next;
shortlist = L2->next;
//差的长度(用来确定在长的链上需要先遍历多少个结点)
dist = len1 - len2;
}
else
{ //L2链比较长或一样长
longlist = L2->next;
shortlist = L1->next;
//差的长度(用来确定在长的链上需要先遍历多少个结点)
dist = len2 - len1;
}
//先在长的链上遍历dist个数据结点才能对齐(谁长让谁先走)
while (dist--)
{
longlist = longlist->next;
}
//两链一样长了,开始两两对应位置比较,寻找公共结点
while (longlist != NULL)
{
//找到了公共结点
if (longlist == shortlist)
{
return longlist;
} //否则,两个对应位置指针继续在链上移动
else
{
longlist = longlist->next;
shortlist = shortlist->next;
}
}
//没有公共结点
return NULL;
}
其它链表应用示例
题一
设有一个循环双链表,其中有一结点为p
编写函数,实现p与右边的一个结点进行交换
/*设有一个循环双链表,其中有一结点为p
编写函数,实现p与右边的一个结点进行交换*/
/*----------------------------------------------------------------*/
/*画图结合,指针修改防止断链原则: 间接指针指向的结点的指针先修改*/
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h> //malloc函数的依赖
typedef int ElemType; //重命名int为ElemType
//结点定义
typedef struct DNode
{
ElemType data; //数据域
struct DNode *right; //向后的指针
struct DNode *left; //向前的指针
} DNode, *Dlinklist; //结构体的一个普通变量(用于数据结点)和一个指针变量(用于头结点)
//算法实现
void swap(DNode *p)
{
DNode *q = NULL;
q = p->right; //q指向p的右边结点
//容错判断
if (q == NULL)
{
return;
}
else
{ //否则,交换结点位置(画图结合,修改原则:间接指针指向的结点的指针先修改)
p->right = q->right;
q->right->left = p;
p->left->right = q;
q->left = p->left;
p->left = q;
q->right = p;
}
}
题二
已知L为单链表的头指针,表中共有m(m>3)个结点,
从表中第i个结点(1<=i<m)到第m个结点构成以一个部分循环单链表。
设计算法: 将这部分循环单链表中所有结点的顺序完全倒置
/*已知L为单链表的头指针,表中共有m(m>3)个结点,
从表中第i个结点(1<=i<m)到第m个结点构成以一个部分循环单链表。
设计算法: 将这部分循环单链表中所有结点的顺序完全倒置*/
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h> //malloc函数的依赖
typedef int ElemType; //重命名int为ElemType
//结点定义
typedef struct LNode
{
ElemType data; //数据域
struct LNode *next; //指针域
} LNode, *Linklist; //结构体的一个普通变量(用于数据结点)和一个指针变量(用于头结点)
//在第i个结点和第m个结点之间形成一个循环单链表
void geta(Linklist L, int i, int m)
{
int count = 1; //初始化第一个数据结点的位置序号为1
Linklist r = NULL, p = NULL; //r指针用于指向第i个结点,p指针是遍历指针,并初始化为NULL
p = L->next; //遍历指针p初始化指向第一个数据结点
//寻找第i个结点(且1<=i<m)
while (count < m && p != NULL)
{
//找到第i个结点
if (count == i)
{
r = p; //r指针指向第i个结点
//break;
}
p = p->next; //遍历指针移动
count++; //位置序号随着遍历指针的移动而自增
}
//循环结束后,p指向了第m个结点
if (p == NULL)
{
return;
}
else
{
p->next = r; //m结点的指针域设置为i结点,从而形成了从i到m局部的循环单链表
}
/*--------------------------------------------------------*/
/*形成了局部的循环单链表之后,接下来是实现局部单链表的逆置,即使用头插法实现
此题的头插法实现思路: 由于要将第i到m的这个局部循环单链表实现头插法
先找到了第i个结点的前驱结点,然后从第i+1个位置开始直到m位置的结点,
逐个使用头插法连接到i结点的前驱结点后面和i结点的之前。
故注意:第i个结点不用头插法,因为其后面的点插完后,i结点自动变成了尾结点*/
/*--------------------------------------------------------*/
count = 1; //仍然是起始的第一个数据结点位置序号标记为1
LNode *prev = NULL;
prev = L; //初始化指向前驱结点的位置为头指针
p = L->next; //初始化遍历指针指向第一个数据结点
//寻找第i个结点的前驱结点
while (count < i)
{
//更新前驱结点的指针,确保其始终指向遍历结点的前驱结点
prev = p;
//更新遍历指针的位置
p = p->next;
//位置序号遍历自增
count++;
}
//上面的循环指向结束后,p指向了第i个位置,故prev指向了第i个结点的前驱结点位置
r = prev; //r指针指向第i个结点的前驱结点
prev = p; //prev指针指向第i个结点
p = p->next; //p指针指向了第i+1个结点,且p作为这个局部链的遍历指针
//由于头插法最后i结点成为了尾结点,prev指向了第i个结点,方便循环条件的设置。因为当p遍历指针移动到了i结点,就证明到了尾结点,而尾结点不需要头插法
while (p != prev)
{ //由于使用头插法操作了p->next,导致了后面结点的位置丢失,故插入前需要用指针提前保存位置,方便后面相应指针的移动
prev->next = p->next; //利用prev的next指针域是空的,用其来保存位置信息
//头插法套路(插入到i结点的前驱结点后且i结点之前)
p->next = r->next;
r->next = p;
//遍历指针继续移动(利用前面已经保存的位置信息)
p = prev->next;
}
}
题三
设计算法: 用于判断带头结点的循环双链表是否对称
/*设计算法: 用于判断带头结点的循环双链表是否对称*/
/*--------------------------------------------------------*/
/*由于是双向链表,本算法从两头扫描循环双链表,以判断链表是否对称*/
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h> //malloc函数的依赖
typedef int ElemType; //重命名int为ElemType
//结点定义
typedef struct DNode
{
ElemType data; //数据域
struct DNode *right; //向后的指针
struct DNode *left; //向前的指针
} DNode, *Dlinklist; //结构体的一个普通变量(用于数据结点)和一个指针变量(用于头结点)
//算法实现(symmetry:对称)
bool Symmetry(Dlinklist L)
{
//定义指向两头的数据结点的工作指针(双指针)
DNode *p = L->right; //左端,指向第一个数据结点
DNode *q = L->left; //右端,指向最后一个数据结点,即头结点的前驱结点
//从两头开始汇拢
while (p != q && q->right != p) //判断条件p!=q是判断不能没有数据结点,判断条件q->right!=p是汇拢结束的边界条件
{
if (p->data == q->data)
{
//所指结点值相同则继续移动
p = p->right;
q = q->left;
}
else
{
//出现了有结点值不同,则不是对称的,返回false
return false;
}
//能执行到最后,则是对称的,返回true
return true;
}
}
题四
设有一带头结点的循环单链表,其结点值均为正值
设计算法:反复找出单链表中结点值最小的结点并输出,然后删除该结点,
反复如此,直到单链表为空为止,再删除表头结点
- 实现方式一
/*设有一带头结点的循环单链表,其结点值均为正值
设计算法:反复找出单链表中结点值最小的结点并输出,然后删除该结点,
反复如此,直到单链表为空为止,再删除表头结点*/
//----------------------------------------------------------------
//算法实现,外层循环固定住一个,内层循环比较剩余的元素
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h> //malloc函数的依赖
typedef int ElemType; //重命名int为ElemType
//结点定义
typedef struct LNode
{
ElemType data; //数据域
struct LNode *next; //指针域
} LNode, *Linklist; //结构体的一个普通变量(用于数据结点)和一个指针变量(用于头结点)
//算法实现
void del_min(Linklist L)
{
//注意:循环单链表的判空条件
while (L->next != L) //当链表不为空
{
//结点删除后每层外循环开始的数据结点不同
//故相关定义需要定义在第一个循环里面
LNode *p = L->next; //遍历的工作指针初始化指向第一个数据结点,当进入下次循环时,指向下一个数据结点
LNode *pre = L; //初始化指向遍历指针的前驱结点的指针指向头结点
int min = p->data; //初始化链表的最小值为第一个数据结点的值
LNode *temp; //临时的指针
while (p->next != L) //注意循环单链表的判空条件
{
//有更小的值
if (p->next->data <= min)
{
//更新最小值前驱结点的指针
pre = p; //由于遍历的指针遍历使用的是p->next,故这里p对应其前驱结点
//更新最小值
min = p->next->data;
}
//内层循环遍历指针继续移动
p = p->next;
}
//一轮内层循环过后,得到当前链表中的最小值及最小值的前驱结点
printf("min: %d\n", min); //打印本轮次的最小值
//通过最小值前驱结点删除最小值结点
temp = pre->next; //指向目前待删除的最小值的结点
pre->next = pre->next->next; //改变最小值前驱结点的指针连接
free(temp); //释放结点空间
}
//表为空时,释放头结点
free(L);
}
//创建链表(尾插法): 传入指向链表头结点的头指针作为形参
Linklist Creat_list(Linklist head)
{
head = (Linklist)malloc(sizeof(Linklist)); //为头指针(头结点)开辟内存空间
head->next = NULL; //没有数据结点,故初始化头结点的指针域为空
LNode *end = NULL; // 定义尾指针变量并初始化为NULL
end = head; //没有数据结点,故尾指针初始化指向头结点
LNode *node = NULL; //定义工作指针,即用于辅助的指针变量,初始化为NULL
int count = 0; //初始化已创建的数据结点的个数为0
printf("(tail insert)Please input the number of Node: ");
scanf("%d", &count);
//开始构造链表
for (int i = 0; i < count; i++)
{
node = (LNode *)malloc(sizeof(LNode)); // 为新结点开启一个结点的内存空间
int value = 0;
printf("%d data:", i + 1);
scanf("%d", &value);
node->data = value; //数据域的赋值
//指针域的赋值,即新结点 插入 尾结点的后面
end->next = node; //新结点连接在尾结点的后面
end = node; //新结点成为新的尾结点
}
end->next = head; //单向循环链表,尾结点指针指向头节点
//返回指向头结点的指针,即头指针
return head;
}
//----------------------------------
//输出循环单链表函数
void print_list(LNode *head)
{
LNode *node = head;
do
{
node = node->next;
if (node == head)
{
break;
}
printf("%d ", node->data);
} while (node != head);
printf("\n");
}
//test,打印链表
void main()
{
Linklist head_a = NULL;
Linklist linkList_a = Creat_list(head_a);
printf("This is A:");
print_list(linkList_a);
//测试
printf("result\n");
del_min(linkList_a);
print_list(linkList_a);
}
- 实现方式二:
/*设有一带头结点的循环单链表,其结点值均为正值
设计算法:反复找出单链表中结点值最小的结点并输出,然后删除该结点,
反复如此,直到单链表为空为止,再删除表头结点*/
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h> //malloc函数的依赖
typedef int ElemType; //重命名int为ElemType
//结点定义
typedef struct LNode
{
ElemType data; //数据域
struct LNode *next; //指针域
} LNode, *Linklist; //结构体的一个普通变量(用于数据结点)和一个指针变量(用于头结点)
//算法实现
void del_min(Linklist L)
{
//注意:循环单链表的判空条件
while (L->next != L) //当链表不为空
{
LNode *p = L->next; //指向第一个数据结点,作为内层循环的遍历指针的初始位置
LNode *prev = NULL; //用于指向遍历指针的前驱结点
/*之前的错误点,模仿前面的只是执行了一次删除最小值,故初始化minPrev指针方向无所谓
但本题需要执行多次删除,假如原先初始化为minPrev为NULL,
但考虑特殊情况,第一个为最小值,那么下面的p->data<=min则不会执行
则minPrev指针也就不会改变,那么在执行最下面的删除元素minPrev->next是因为minPrev是NULL就会出错,
如果没有这种特殊情况还好,因为p->data<=min会执行,所以minPrev也会改动,故也会正常的多次删除,
但考虑了这种特殊情况,初始化值minPrev值就要注意,不能任意,就只能初始化为第一个为最小值的结点的前驱结点,即头结点*/
//LNode *minPrev = NULL;
LNode *minPrev = L; //用于指向最小值的前驱结点,假如第一个为最小值,则前驱结点为头结点
int min = p->data; //初始化最小值为第一个数据结点的数据值
LNode *q = NULL; //用于指向待删除的结点
prev = L->next; //初始化指向内层循环遍历指针的前驱结点的位置
//(内层循环)寻找最小值结点(需要初始化最小值),并删除结点(记录前驱结点)
//从固定结点p后开始查找更新最小值
p = p->next; //更改p指向第二个数据结点作为起始点开始遍历
while (p != L) //注意:循环单链表的判空条件
{
//有更小的值
if (p->data <= min)
{
//更新最小值前驱结点的指针
minPrev = prev;
//更新最小值
min = p->data;
}
//更新指向遍历指针前驱结点的位置,确保始终指向其前驱结点
prev = p;
//内层循环遍历指针继续移动
p = p->next;
}
//一轮内层循环过后,得到当前的最小值及最小值的前驱结点
printf("min: %d\n", min); //打印本轮次的最小值
//通过最小值前驱结点删除最小值结点
q = minPrev->next; //指向目前待删除的最小值的结点
minPrev->next = minPrev->next->next; //改变最小值前驱结点的指针连接
free(q); //释放结点空间
}
//表为空时,释放头结点
free(L);
}
//创建链表(尾插法): 传入指向链表头结点的头指针作为形参
Linklist Creat_list(Linklist head)
{
head = (Linklist)malloc(sizeof(Linklist)); //为头指针(头结点)开辟内存空间
head->next = NULL; //没有数据结点,故初始化头结点的指针域为空
LNode *end = NULL; // 定义尾指针变量并初始化为NULL
end = head; //没有数据结点,故尾指针初始化指向头结点
LNode *node = NULL; //定义工作指针,即用于辅助的指针变量,初始化为NULL
int count = 0; //初始化已创建的数据结点的个数为0
printf("(tail insert)Please input the number of Node: ");
scanf("%d", &count);
//开始构造链表
for (int i = 0; i < count; i++)
{
node = (LNode *)malloc(sizeof(LNode)); // 为新结点开启一个结点的内存空间
int value = 0;
printf("%d data:", i + 1);
scanf("%d", &value);
node->data = value; //数据域的赋值
//指针域的赋值,即新结点 插入 尾结点的后面
end->next = node; //新结点连接在尾结点的后面
end = node; //新结点成为新的尾结点
}
end->next = head; //单向循环链表,尾结点指针指向头节点
//返回指向头结点的指针,即头指针
return head;
}
//----------------------------
//输出循环单链表函数
void print_list(LNode *head)
{
LNode *node = head;
do
{
node = node->next;
if (node == head)
{
break;
}
printf("%d ", node->data);
} while (node != head);
printf("\n");
}
//test,打印链表
void main()
{
Linklist head_a = NULL;
Linklist linkList_a = Creat_list(head_a);
printf("This is A:");
print_list(linkList_a);
//测试
printf("result\n");
del_min(linkList_a);
print_list(linkList_a);
}
欢迎评论点赞和关注个人公众号,感谢!!