数据结构——链表的基本操作目录标题
一.链表的基本概念
1.链表的概念:
- 链表是一种逻辑顺序是连续的但物理存储单元上非连续、非顺序的存储结构。
2.链表的实现方法:
- 链表是通过以结构体为节点,然后将所有节点通过结构体的指针域指向下一个节点连接起来(连接的实现),将数据存储到数据域中(存储的实现)。
typedef struct Node//结点结构体
{
int val;//数据域(数据域可以多组数据,多种类型)
struct Node *next;//指针域(用于指向下一结点)
}Node, *Linklist;//重定义:定义struct Node 类型为 Node, struct Node * 类型为 Linklist
3.链表的分类:
- 含头结点的链表
- 不含头结点的链表
- 静态链表:不能动态增添结点,但对其操作和数组类似,比较便捷。(通过结构体数组实现)
内存分配:分配一整片连续的内存空间,各个结点集中安置。 - 双向链表:结构体内含两个指针域,一个指向前一结点,另一个指向下一结点
双向链表的优点:对结点的删除等操作比单链表便捷,不用独立指针记录前一结点
双向链表结构体创建:
typedef struct Node
{
int val;//数据域
struct Node *next;//指向后一结点
struct Node *prev;//指向前一结点
}*Linklist, Node;
4.链表的优缺点(同数组相比):
- 优点:不需要预先知道数据的个数,删除添加只需要改变附近节点的指针域
- 缺点:创建使用麻烦
5.说明:
- 头指针为与链表节点类型相同的指针
- 一般情况情况下,头指针所指向的第一个节点不存数据(此结点称为头结点),这样对在后面进行添加删除排序有很大的便利
- 最后一个节点指针域存NULL。方便在遍历链表时提示遍历完成
但一般只对单链表(含头节点)和双向链表使用较多,所以只对它们进行讲解
二.链表的建立
1.含头结点:对于含头结点链表的所有操作头指针一直不变,一直指向头结点
头结点的创建
Linklist CrheadNode()//创建头结点
{
Linklist headNode = (Linklist)malloc(sizeof(Node));
headNode->next = NULL;
return headNode;
}
新结点的创建
Linklist CrnewNode(int val)//创建新结点
{
Linklist P = (Linklist)malloc(sizeof(Node));
P->val = val;
return P;
}
将新结点插到第一结点前
思路:直接让新结点指向第一节点(即继承头结点的指向),再让头结点指向新结点
void AddNodehead(Linklist headNode, Linklist P)//将新结点插到第一结点结点前
{
P->next = headNode->next;
headNode->next = P;
}
将新节点插到尾之后
void AddNodetail(Linklist headNode, Linklist P)//将新节点插到尾之后
{
while(headNode->next)
{
headNode = headNode->next;
}
P->next = NULL;
headNode->next = P;
}
头插法创建链表(重复将新结点插到第一结点前过程即可)
void CrLinsthead(Linklist headNode, int n)//头插法创建链表,n为链表长度
{
int val;
for(int i = 0; i < n; i++)
{
scanf("%d", &val);
AddNodehead(headNode,CrnewNode(val));//将新结点插在头结点与第一结点之间
}
}
尾插法创建链表(重复将新结点插到尾之过程即可)
void CrListtail(Linklist headNode, int n)//尾插法创建链表
{
int val;
for(int i = 0; i < n; i++)
{
scanf("%d", &val);
AddNodetail(headNode, CrnewNode(val));
}
}
寻找结点(寻找第几结点前一结点,为了方便后续操作)
Linklist SeekNode(Linklist headNode, int index)//寻找第几结点前一结点,为了方便后续操作
{
if(!headNode->next || index <= 0)
return NULL;
while(--index)
{
if(headNode->next == NULL)
return NULL;
headNode = headNode->next;
}
return headNode;
}
在第几结点位置增添结点,超出链表范围则不进行增添操作
void AddIndexnode(Linklist headNode, int index)//增添结点
{
int val;
headNode = SeekNode(headNode,index);
if(!headNode)
return;
printf("请输入你要插入的值:");
scanf("%d", &val);
Linklist P = CrnewNode(val);
P->next = headNode->next;
headNode->next = P;
}
将第几结点修改为val ,超出链表范围则不进行增添操作
void AtlerNode(Linklist headNode, int index)//将第几结点修改为val
{
int val;
headNode = SeekNode(headNode,index);
if(!headNode)//判断是否超范围
return;
headNode = headNode->next;//找到结点
if(!headNode)//判断是否超范围(避免给NULL指针赋值)
return;
printf("请输入你修改的值:");//修改结点
scanf("%d", &val);
headNode->val = val;
}
删除第几结点
void DeleteNode(Linklist headNode, int index)//删除第几结点
{
headNode = SeekNode(headNode,index);
if(!headNode || !headNode->next)
return;
Linklist p = headNode->next;
headNode->next = headNode->next->next;
free(p);
}
查找第几结点并返回 (若查找失败则返回-1)
int SearchNode(Linklist headNode, int index)//查找第几结点并返回
{
headNode = SeekNode(headNode,index);
if(!headNode)
return -1;
headNode = headNode->next;
if(!headNode)//判断是否超范围(避免给NULL指针赋值)
return -1;
return headNode->val;
}
链表逆置
void Listreverse (Linklist headNode)//链表逆置 逆置思想:将头结点提出来,后续将链表排在头结点后,将其依次插到头结点与第一节点之间
{
Linklist p, s;
p = headNode->next;
headNode->next = NULL;//让头节点指向空
while (p)//若节点为空,则停止逆置
{
s = p;
p = p->next;
s->next = headNode->next;//先指向头结点的指向
headNode->next = s;//让头结点指向自己(插入头结点和后续节点之间)
}
}
返回倒数第几结点(快慢指针法)(若检索失败返回-1)
int Seeknodetail (Linklist headNode, int index)//快慢指针法 ,返回倒数第几节点的值
{
Linklist p_1, p_2;
int i = 0;
p_1 = headNode;
p_2 = headNode;
while (p_1 != NULL)
{
p_1 = p_1->next;
if (i < index)
{
i++;
continue;
}
p_2 = p_2->next;
}
if(!p_1)
return -1;
return p_2->val ;
}
输出链表
void PrintList(Linklist headNode)//输出链表
{
headNode = headNode->next;
while(headNode)
{
printf("%d ", headNode->val);
headNode = headNode->next;
}
}
完整代码
#include<stdio.h>
#include<stdlib.h>
typedef struct Node//结点结构体
{
int val;//数据域(数据域可以多组数据,多种类型)
struct Node *next;//指针域(用于指向下一结点)
}Node, *Linklist;
Linklist CrheadNode()//创建头结点
{
Linklist headNode = (Linklist)malloc(sizeof(Node));
headNode->next = NULL;
return headNode;
}
Linklist CrnewNode(int val)//创建新结点
{
Linklist P = (Linklist)malloc(sizeof(Node));
P->val = val;
return P;
}
void AddNodehead(Linklist headNode, Linklist P)//将新结点插到第一结点结点前
{
P->next = headNode->next;
headNode->next = P;
}
void AddNodetail(Linklist headNode, Linklist P)//将新节点插到尾之后
{
while(headNode->next)
{
headNode = headNode->next;
}
P->next = NULL;
headNode->next = P;
}
void CrLinsthead(Linklist headNode, int n)//头插法创建链表
{
int val;
for(int i = 0; i < n; i++)
{
scanf("%d", &val);
AddNodehead(headNode,CrnewNode(val));
}
}
void CrListtail(Linklist headNode, int n)//尾插法创建链表
{
int val;
for(int i = 0; i < n; i++)
{
scanf("%d", &val);
AddNodetail(headNode, CrnewNode(val));
}
}
Linklist SeekNode(Linklist headNode, int index)//寻找第几结点前一结点,为了方便后续操作
{
if(!headNode->next || index <= 0)
return NULL;
while(--index)
{
if(headNode->next == NULL)
return NULL;
headNode = headNode->next;
}
return headNode;
}
void AddIndexnode(Linklist headNode, int index)//增添结点
{
int val;
headNode = SeekNode(headNode,index);
if(!headNode)
return;
printf("请输入你要插入的值:");
scanf("%d", &val);
Linklist P = CrnewNode(val);
P->next = headNode->next;
headNode->next = P;
}
void AtlerNode(Linklist headNode, int index)//将第几结点修改为val
{
int val;
headNode = SeekNode(headNode,index);
if(!headNode)
return;
headNode = headNode->next;
if(!headNode)
return;
printf("请输入你修改的值:");
scanf("%d", &val);
headNode->val = val;
}
void DeleteNode(Linklist headNode, int index)//删除第几结点
{
headNode = SeekNode(headNode,index);
if(!headNode || !headNode->next)
return;
Linklist p = headNode->next;
headNode->next = headNode->next->next;
free(p);
}
int SearchNode(Linklist headNode, int index)//查找第几结点并返回
{
headNode = SeekNode(headNode,index);
if(!headNode)
return -1;
headNode = headNode->next;
if(!headNode)//判断是否超范围(避免给NULL指针赋值)
return -1;
return headNode->val;
}
void PrintList(Linklist headNode)//输出链表
{
headNode = headNode->next;
while(headNode)
{
printf("%d ", headNode->val);
headNode = headNode->next;
}
}
int main(void)
{
int n, m;
scanf("%d", &n);
Linklist headNode = CrheadNode();//创建头结点
//CrLinsthead (headNode, n);//头插法创建链表
CrListtail (headNode, n);//尾插法创建链表
scanf("%d", &m);
//AddIndexnode(headNode, m);//增添结点
//AtlerNode(headNode, m);//修改结点
//DeleteNode(headNode, m);//删除结点
//Listreverse(headNode)//链表逆置
//printf("%d\n", SearchNode(headNode, m));//查找结点
//printf("%d\n", Seeknodetail(headNode, m));//返回倒数第几结点
//PrintList(headNode);//输出链表
return 0;
}
2.双链表:双链表和单链表操作基本相似,只是在查找结点时不必查找想要查找到的结点的前一位,因为你可直接通过指向前一结点的指针找到前一结点,所以对于双链表的增添和删除过程要比单链表简单一些
双向链表结构体创建:
typedef struct Node
{
int val;//数据域
struct Node *next;//指向后一结点
struct Node *prev;//指向前一结点
}*Linklist, Node;
寻找结点(直接寻找想要结点)
Linklist SeekNode(Linklist headNode, int index)//寻找第几结点
{
if(!headNode->next || index <= 0)//若链表为NULL,返回NULL
return NULL;
while(index--)//与单链表的区别,index--与--index的区别导致偏移一位
{
if(headNode->next == NULL)
return NULL;
headNode = headNode->next;
}
return headNode;
}
将新结点插到第一结点前
void AddNodehead(Linklist headNode, Linklist P)//将新结点插到第一结点结点前
{
P->next = headNode->next;
P->prev = headNode;
if(headNode->next != NULL)/*与单链表的区别,在头插时判断链表是否为
空,不为空则需让第一结点的前指针指向当前结点*/
headNode->next->prev = P;
headNode->next = P;
}
完整代码:双链表操作和单链表基本相似,只是在每次操作时需要同时对前指针操作,使之指向前一节点
#include<stdio.h>
#include<stdlib.h>
typedef struct Node
{
int val;
struct Node *next;
struct Node *prev;
}*Linklist, Node;
Linklist CrheadNode()//创建头结点
{
Linklist headNode = (Linklist)malloc(sizeof(Node));
headNode->next = NULL;
headNode->prev = NULL;
return headNode;
}
Linklist CrnewNode(int val)//创建新结点
{
Linklist P = (Linklist)malloc(sizeof(Node));
P->val = val;
return P;
}
void AddNodehead(Linklist headNode, Linklist P)//将新结点插到第一结点结点前
{
P->next = headNode->next;
P->prev = headNode;
if(headNode->next != NULL)
headNode->next->prev = P;
headNode->next = P;
}
void AddNodetail(Linklist headNode, Linklist P)//将新节点插到尾之后
{
while(headNode->next)
{
headNode = headNode->next;
}
P->next = NULL;
P->prev = headNode;
headNode->next = P;
}
void CrLinsthead(Linklist headNode, int n)//头插法创建链表
{
int val;
for(int i = 0; i < n; i++)
{
scanf("%d", &val);
AddNodehead(headNode,CrnewNode(val));
}
}
void CrListtail(Linklist headNode, int n)//尾插法创建链表
{
int val;
for(int i = 0; i < n; i++)
{
scanf("%d", &val);
AddNodetail(headNode, CrnewNode(val));
}
}
Linklist SeekNode(Linklist headNode, int index)//寻找第几结点(规定结点从1算起)
{
if(!headNode->next || index <= 0)
return NULL;
while(index--)
{
if(headNode->next == NULL)
return NULL;
headNode = headNode->next;
}
return headNode;
}
void AddIndexnode(Linklist headNode, int index)//增添结点 (结点前插)
{
int val;
headNode = SeekNode(headNode,index);
if(!headNode)
return;
printf("请输入你要插入的值:");
scanf("%d", &val);
Linklist P = CrnewNode(val);
P->next = headNode;
P->prev = headNode->prev;
headNode->prev->next = P;
headNode->prev = P;
}
void AtlerNode(Linklist headNode, int index)//将第几结点修改为val
{
int val;
headNode = SeekNode(headNode,index);
if(!headNode)
return;
printf("请输入你修改的值:");
scanf("%d", &val);
headNode->val = val;
}
void DeleteNode(Linklist headNode, int index)//删除第几结点
{
headNode = SeekNode(headNode,index);
if(!headNode)
return;
headNode->prev->next = headNode->next;
if(headNode->next != NULL)
headNode->next->prev = headNode->prev;
}
int SearchNode(Linklist headNode, int index)//查找第几结点并返回
{
headNode = SeekNode(headNode,index);
if(!headNode)
return -1;
return headNode->val;
}
void PrintList(Linklist headNode)//输出链表
{
headNode = headNode->next;
while(headNode)
{
printf("%d ", headNode->val);
headNode = headNode->next;
}
}
int main(void)
{
int n, m;
scanf("%d", &n);
Linklist headNode = CrheadNode();//创建头结点
//CrLinsthead (headNode, n);//头插法创建链表
CrListtail (headNode, n);//尾插法创建链表
scanf("%d", &m);
//AddIndexnode(headNode, m);//增添结点
//AtlerNode(headNode, m);//修改结点
//DeleteNode(headNode, m);//删除结点
printf("%d\n", SearchNode(headNode, m));//查找结点
PrintList(headNode);//输出链表
return 0;
}
简单应用
1.升序合并(力扣21)
struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){
if(!l1)//若其中一个为空,直接返回另一链表
return l2;
if(!l2)
return l1;
struct ListNode *headNode = (struct ListNode*)malloc(sizeof(struct ListNode));//创建头结点
struct ListNode *p//记录头结点
p = headNode;
while(l1 && l2)//将两链表中较小的结点加在p后,更新p的指向
{
if(l1->val < l2->val)
{
p->next = l1;
l1 = l1->next;
p = p->next;
}
else
{
p->next = l2;
l2 = l2->next;
p = p->next;
}
}
if(!l1)//将为遍历完的链表加在p后
p->next = l2;
if(!l2)
p->next = l1;
return headNode->next;//返回链表
}