一、链表的定义
链表是一种数据结构,其元素个数不受限定,可以通过添加进行改变。在多节点通讯中有大量的应用。
链表的基础是结构体,在结构体中不能包含与本身类型相同的结构,但可以包含有与之相同类型结构的指针。链表中的每一项都包含在何处找到下一项的信息。
链表中会有一个头指针变量,这个指针变量保存一个地址,通过这个地址来找到这个链表。头指针节点指向第一个节点。(每个节点包含数据部分和指针部分)最后一个节点的指针指向必须为空null。
1、链表的基本使用
- 链表需要包含一些基本的头文件,涉及到内存的操作和字符串的操作。
#include "stdlib.h" //提供malloc()和free()
#include "string.h" //提供strcpy()等
malloc函数:void *malloc(unsigned int size);
free函数:void free(void *p);
- 创建节点
创建节点就是需要创建结构体,结构体名称就是类似float一样的变量而已
struct Node
{
int a; //数据域
struct Node* next; //指针域(指向节点的指针)
};
- 定义全局链表头尾指针
struct Node* head= NULL;
struct Node* end = NULL;
- 创建链表,实现在尾部添加一个数据
void AddListTill(int a )
{
//创建一个节点
struct Node* temp=(struct Node*)malloc(sizeof(struct Node)); //此处注意强制类型转换,创建一个节点并且申请一个节点的内存
//节点数据进行赋值
temp->a=a;
temp->next=NULL;
//连接分两种情况1.一个节点都没有2.已经有节点了,添加到尾巴上
if(NULL==head)
{
head=temp;//head指针就直接指向temp
// end=temp;
}
else
{
end->next=temp;//已经有节点了,就把原先的尾节点赋值到新建的节点上,把temp的地址存在了end->next的指针中
}
end=temp; //尾结点应该始终指向最后一个
}
- 遍历链表,也就是查链表
void ScanList()
{
struct Node *temp =head; //定义一个临时变量来指向头
while (temp !=NULL)
{
printf("%d\n",temp->a);
temp = temp->next; //temp指向下一个的地址 即实现++操作
}
}
- 查询指定的节点,一个个进行查找
struct Node* FindNode(int a )
{
struct Node *temp =head;
while(temp !=NULL)
{
if(a == temp->a)
{
return temp;
}
temp = temp->next;
}
//没找到
return NULL;
}
- 清空链表
void FreeList()
{
//一个一个NULL
struct Node *temp =head; //定义一个临时变量来指向头
while (temp !=NULL)
{
// printf("%d\n",temp->a);
struct Node* pt =temp;
temp = temp->next; //temp指向下一个的地址 即实现++操作
free(pt); //释放当前
}
//头尾清空 不然下次的头就接着0x10
head =NULL;
end =NULL;
}
tips:temp=temp->next;temp是指针变量,能赋值的就是地址,temp->next也是一个指针,相当于原先存在temp的next指向的地址赋值给了temp作为现在temp的内存地址。
- 在指定位置插入节点
void AddListRand(int index,int a)
{
if (NULL==head)
{
printf("链表没有节点\n");
return;
}
struct Node* pt =FindNode(index);
if(NULL==pt) //没有此节点
{
printf("没有指定节点\n");
return;
}
//有此节点
//创建临时节点,申请内存
struct Node* temp =(struct Node *)malloc(sizeof(struct Node));
//节点成员进行赋值
temp->a=a;
temp->next=NULL;
//连接到链表上 1.找到的节点在尾部 2.找到的节点在中间
if (pt == end)
{
//尾巴的下一个指向新插入的节点
end->next=temp;
//新的尾巴
end=temp;//最后的结尾指针就是temp本身了
}else
{
// 先连后面 (先将要插入的节点指针指向原来找到节点的下一个),相当于原先pt节点后面要新增节点temp
temp->next=pt->next;//首先把原先pt指向的下一个指针赋值给temp
//后连前面
pt->next=temp;//pt现在的next就要连接到temp上了
}
}
tips:对于temp->next=pt->next,pt=temp,pt->next=temp
temp->next=pt->next,把pt->next所指向的下一个节点的内存地址赋值给temp->next指针。
pt=temp,把temp指针地址赋值给pt,也就是说pt现在指向的就是原先temp的地址。
pt->next=temp,把temp的地址赋值给pt->next,也就是说pt->next指针链接到temp地址。
- 尾删除
void DeleteListTail()
{
if (NULL == end)
{
printf("链表为空,无需删除\n");
return;
}
//链表不为空
//链表有一个节点
if (head==end)
{
free(head);
head=NULL;
end=NULL;
}
else
{
//找到尾巴前一个节点
struct Node* temp =head;
while (temp->next!=end)
{
temp = temp->next;
}
//找到了,删尾巴
//释放尾巴
free(end);
//尾巴迁移
end=temp;
//尾巴指针为NULL
end->next=NULL;
}
}
- 删除指定节点
void DeleteListRand(int a)
{
//链表判断 是不是没有东西
if(NULL==head)
{
printf("链表没东西\n");
return;
}
//链表有东西,找这个节点
struct Node* temp =FindNode(a);
if(NULL==temp)
{
printf("查无此点\n");
return;
}
//找到了,且只有一个节点
if(head==end)
{
free(head);
head=NULL;
end=NULL;
}
else if(head->next==end) //有两个节点
{
//看是删除头还是删除尾
if(end==temp)
{ DeleteListTail(); }
else if(temp==head)
{ DeleteListHead(); }
}
else//多个节点
{
//看是删除头还是删除尾
if(end==temp)
DeleteListTail();
else if(temp==head)
DeleteListHead();
else //删除中间某个节点
{ //找要删除temp前一个,遍历
struct Node*pt =head;
while(pt->next!=temp)
{
pt=pt->next;
}
//找到了
//让前一个直接连接后一个 跳过指定的即可
pt->next=temp->next;
free(temp);
}
}
}
二、双向链表
双向链表在实际应用中还是比较广泛的,尤其是在数据的存储上。
双向链表中包含以下3部分信息:
- 指针域:用于指向当前节点的直接前驱节点;
- 数据域:用于存储数据元素;
- 指针域:用于指向当前节点的直接后继节点;
typedef struct line{
struct line * prior; //指向直接前趋
int data;
struct line * next; //指向直接后继
}line;
- 双向链表的创建
同单链表相比,双链表仅是各节点多了一个用于指向直接前驱的指针域。因此,我们可以在单链表的基础轻松实现对双链表的创建。
需要注意的是,与单链表不同,双链表创建过程中,每创建一个新节点,都要与其前驱节点建立两次联系,分别是:
将新节点的 prior 指针指向直接前驱节点; 将直接前驱节点的 next 指针指向新节点;
line* initLine(line * head){
head=(line*)malloc(sizeof(line));//创建链表第一个结点(首元结点)
head->prior=NULL;
head->next=NULL;
head->data=1;
line * list=head;
for (int i=2; i<=3; i++) {
//创建并初始化一个新结点
line * body=(line*)malloc(sizeof(line));
body->prior=NULL;
body->next=NULL;
body->data=i;
list->next=body;//直接前趋结点的next指针指向新结点
body->prior=list;//新结点指向直接前趋结点
list=list->next;
}
return head;
}
- 在表头添加节点
添加节点temp
重点是
temp->next = head;
head->prior = temp;
head=temp; - 在尾部插入节点
添加节点temp
temp->pre = end;
end->next = temp;
end = temp;
- 插入到某一位置前面
先要找到要插入位置的前一个结点body,插入节点temp
body->next->pre = temp;
temp->next = body ->next;
body->next = temp;
temp ->pre = body;
- 删除节点
删除节点temp
temp->pre->next = temp->next;
temp->next->pre=temp->pre;
理解时可以这么理解temp->pre指向的是前一个节点的地址,因此temp->pre->next 就是指前一个节点的next指针,temp->next就是需要删除节点指向的下一个节点。下一语句也类似这种意思,这样就跳过了节点temp。