链表的基础知识了解
什么是链表
链表是一种动态的进行存储分配的重要数据结构。
相较于数组存放数据必须事先定义固定数组长度,链表则更加灵活,可以随意随时删减增添 ,不会导致内存浪费。
链表的结构:
由头指针变量和结点构成,每一个结点包含两个部分(1)储存的数据(2)下一个结点的地址。头指针变量指向第一个结点,第一个结点指向第二个结点,直到最后一个结点,结点有包含下一个结点的地址,最后一个结点不再指向任何结点,因此它的地址部分为“NULL”,它称为“表尾”,链表到此结束。
由链表的结构可知,要存放地址,必须使用指针变量才能实现。而结点里包含两个部分,所以,可以包含若干成员的结构体变量用来建立链表便是再合适不过。如下,一个结构体节点
struct NodeLink
{
int data;//数据
struct NodeLink* p;//用以储存下一个结点的地址
};
/*因为要指向下一个结点,下一个结点也是结构体变量,所以这里的指针类型为结构体指针*/
当然,对于单个结点的设计,可以让它包含不止一种数据(只要有数据且有下个结点的地址就行),也就是说,链表里面的结点可以由不同的结构体变量通过指针指引串联成(灵活度真的太高啦),例如下面这种结构体也可以作为结点(两种数据+下个结点地址)
struct Student
{ int num;
float score;
struct Student*next;
}
为了简便,后文对于动态链表的函数编写操作中,结点都取相同的第一种结点。
头指针变量和多个结构体节点构成链表
struct NodeLink node1={10,NULL};
struct NodeLink node2={20,NULL};
struct NodeLink node3={30,NULL};
struct NodeLink node4={40,NULL};
struct NodeLink node5={50,NULL};
struct NodeLink*q=&node1;//头指针变量
node1.p=&node2;//结点1
node2.p=&node3;//结点2
node3.p=&node4;//结点3
node4.p=&node5;//结点4
链表的类型
静态链表
所有的结点都是在程序中定义的,不是临时开辟的,也不能用完后释放,这种链表称为“静态链表”。
下面是建立和输出一个简单静态链表的例子
#include<stdio.h>
struct NodeLink//声明结点的结构体类型
{
int data;
struct NodeLink* p;
};
void free1()
{
struct NodeLink node1={10,NULL};//定义结构体变量并为数据部分赋初值
struct NodeLink node2={20,NULL};
struct NodeLink node3={30,NULL};
struct NodeLink node4={40,NULL};
struct NodeLink node5={50,NULL};
struct NodeLink*q=&node1;//头指针变量指向第一个结点
node1.p=&node2;//依次通过指针指向的地址串联起来
node2.p=&node3;
node3.p=&node4;
node4.p=&node5;
while(q!=NULL)//判断是否到达链尾的地址域
{
printf("%d",q->data);//输出数据
q=q->p;//指针后移一位
}
}
int main()
{
free1();
return 0;
}
动态链表
结点不是提前定义好的,可以临时开辟新结点和删除链表里的结点,可以用完后释放。
建立动态链表是指在程序执行过程中从无到有地建立起一个链表,即一个一个地开辟新结点和输入各结点数据,并建立起前后相链的关系。(因为是动态的从无到有建立,所以要用到动态内存分配的malloc函数和free函数)
下面是建立一个动态链表的例子
先写出建立链表的函数,再写一个main函数调用它
#include<stdio.h >
#include<stdlib.h>//定义节点类型
struct LinkNode
{
int data;
struct LinkNode* next;
};struct LinkNode* Init_Linklist(void)
{
struct LinkNode* header = malloc(sizeof(struct LinkNode));//开辟一个新单元,创建一个结点,
if (header == NULL)
{
return NULL;
}
header->data = -1;//无效数据,后面的输入中即是以-1结束
header->next = NULL;//头指针变量
struct LinkNode* till= header;//建立一个辅助指针变量
int val = -1;
while (1)
{
scanf("%d", &val);//读取数据
if (val == -1)//读到-1输入结束,链表结点全部创建完成
{
break;
}
//先创建新节点
struct LinkNode* newnode = malloc(sizeof(struct LinkNode));
newnode->data = val;
newnode->next = NULL;
till->next = newnode;//新结点插入操作
till = newnode;//辅助指针变量后移,帮助不断向后读取数据,创建结点
}
return header;
int main()
{
struct LinkNode *chain0;
chain0=Init_Linklist( );//函数返回链表头指针变量即链表地址
return 0;
}
对链表进行各项操作的函数
遍历输出链表
(其核心就是利用链表在上一个结点中有下个结点的地址进行辅助指针变量逐个后移输出数据)
void Foreach_Linklist(struct LinkNode* header)
{
if (header == NULL)
return;
struct LinkNode* pcurrent = header->next;//辅助指针变量
while (pcurrent != NULL)
{
printf("%d", pcurrent->data);
pcurrent = pcurrent->next;//后移
}
}
链表中在值为oldval的后面插入一个新的数据newval
(其核心就是建立新结点插到链表中,oldval的地址域指向新结点,新结点中有原来oldval后一个结点的地址)
void InsertByValue_Linklist(struct LinkNode* header, int oldval, int newval)
{
if (header == NULL)
{
return;
}
struct LinkNode* pCurrent = header->next;
struct LinkNode* prev = header;
while (pCurrent != NULL)
{
if (pCurrent->data == oldval)
{
struct LinkNode* newnode = malloc(sizeof(struct LinkNode));
if (newnode == NULL)
return;
newnode->data = newval;
newnode->next = pCurrent->next;
pCurrent->next = newnode;
break;
}
prev = pCurrent;
pCurrent = pCurrent->next;
}
if (pCurrent == NULL)
{
struct LinkNode* newnode = malloc(sizeof(struct LinkNode));
if (newnode == NULL)
return;
newnode->data = newval;
prev->next = newnode;
newnode->next = NULL;
}
}
删除值为val的结点
(核心就是,free结点后,把val那个地方的结点前面的结点指针指向val那个的结点后面的结点,这样一连,诶,就OK啦)
void RemoveByValue_Linklist(struct LinkNode* header, int delValue)
{
if (header == NULL)
{
return;
}
//辅助指针变量
struct LinkNode* prev= header;
struct LinkNode* pCurrent=header->next;
while (pCurrent != NULL)
{
if (pCurrent->data == delValue)
{
break;
}
prev = pCurrent;
pCurrent = pCurrent->next;
}
if (pCurrent==NULL)
{
return;
}
//重新建立待删除节点的前驱和后继的关系;
prev->next = pCurrent->next;
free(pCurrent);
pCurrent=NULL;
}
清空链表
(核心是除了头指针变量,后面的结点全都释放(free)掉)
void Clear_linklist(struct LinkNode* header)
{
if (NULL == header)
{
return;
}
struct LinkNode* pCurrent = header->next;
while (pCurrent != NULL)
{
struct LinkNode* pnext = pCurrent->next;
//释放当前节点内存
free(pCurrent);
//指针后移一位
pCurrent = pnext;}
销毁链表
(核心是包含头指针变量,全都free,完全找不到这个链表了)
void Destory_Linklist(struct LinkNode* header)
{
if (NULL == header)
{
return;
}
struct LinkNode* pCurrent = header;
while (pCurrent != NULL)
{
struct LinkNode* pnext = pCurrent->next;
//释放当前节点内存
free(pCurrent);
//指针后移一位
pCurrent = pnext;
上面的链表全为单向链表,即知道了某个结点,可以逐次向后遍历得到后面的结点,但却不能知道这个结点之前的结点。(因为每个结点的指针只储存了下个结点的地址嘛)。
除了单向链表之外,还有环形链表和双向链表。此外还有队列,树,栈,图等数据结构。有关这些,待笔者深入学习过后再做探讨。
总结:
回顾前面所说的链表相较于数组要更加灵活相信现在已经能够体会到了。链表像是把一系列数据串起来,数组是把一系列数据固定在一起。
再比如,对于要在一组数据中添加某个数据的过程,链表只需关注插入位置的前后结点即可,而数组则需要让那个插入位置之后的所有数据都有改动,高下立见。
但是,也不一定所有情况都是链表比数组好,像有些小量数据比较简单的操作就是输入输出,那么数组连续的内存可以让知道首地址就通过不断的地址+1后移进行操作,这里就是数组较好。所以用链表还是用数组视情况而定吧。
笔者拙见,如有错误,请多指正。