链表的物理存储结构是用一组地址任意的存储单元存储数据的。不像顺序表占据连续的一段内存空间,而是将存储单元分散在内存的任意地址上。
链表结构中,每个数据元素记录都存放到链表的一个节点(node)中,而每个节点之间由指针将其连接在一起,形成了”链“的结构、
链表每个节点中,都必须有一个专门用来存放指针(地址)的域,用这个指针域来存放后继结点的地址,这样就达成了连接后继结点的目的。
一条链表通常有1个”表头“,是一个指针变量,用来存放第一个节点地址。此外,一条链表的最后一个节点的指针域要置空(NULL),表示该节点为链表的尾节点,因为它之没有后继结点了。
链表特征:
1)每个节点包括两部分:数据域和指针域。其中数据域用来存放数据元素本身的信息,指针域用来存放后继结点的地址。
2)链表逻辑上是连续的,而物理上不一定连续存储节点、
3)只要获得链表的头节点,就可以通过指针遍历整条链表。
实例:编写一个程序,要求:从终端输入一组整数(大于10个数),以0作为结束标志,将这一组整数存放在一个链表中,(结束标志0不包括在内),打印出该链表中的值。然后删除该链表中的第五个元素,打印出删除后的结果。最后在内存中释放掉该链表。
#include"stdio.h"
#include"stdlib.h"
typedef int ElemType;
typedef struct node{
ElemType data;
struct node *next;
}LNode,*LinkList;
LinkList GreatLinkList(int n) { //创建一个长度为n的链表
LinkList p,r,list=NULL;
ElemType e;
int i;
for(i=1;i<=n;i++) {
scanf("%d",&e); //输入结点的内容
p=(LinkList)malloc(sizeof(LNode));//为新建的结点开辟内存空间
p->data=e; //元素赋值
p->next=NULL;
if(!list)
list=p; //赋值链表头指针
else
r->next=p; //将结点连入链表
r=p;
}
return list; //返回链表头指针
}
void insertList(LinkList *list,LinkList q,ElemType e) {//向链表中插入结点 e
LinkList p;
p=(LinkList)malloc(sizeof(LNode));//为新建的结点开辟新的内存空间 ,生成一个新结点,由p指向它
p->data=e; //向该结点的数据域赋值e
if(!*list) {
*list=p; //list内容为NULL时,表示该链表为空,赋值链表头指针
p->next=NULL;
} //当链表为空时q没有意义,只能在头结点后面插入第一个元素
else {
p->next=q->next;//当链表不为空时,认为q指向的结点一定存在,将q指向的结点的next域的值赋给p指向结点的next域
q->next=p;
}
}
void delLink(LinkList *list,LinkList q) { //删除链表的某结点
LinkList r;
if(q==*list) { //如果删除第一个结点
*list=q->next;
free(q);
}
else { //删除其他结点
for(r=*list;r->next!=q;r=r->next)
if(r->next!=NULL) {
r->next=q->next;
free(q);
}
}
}
void destroyLinkList(LinkList *list) { //销毁一个链表
LinkList p,q;
p=*list;
while(p) { //循环释放掉每一个链表结点
q=p->next;
free(p);
p=q;
}
*list=NULL;//将*list的内容置为NULL,这样主函数中的链表list就为空,防止了list变为野指针,而且链表在内存中也完全被释放掉了。
}
int main() {
int e,i;
LinkList l,q;
q=l=GreatLinkList(l);//创建一个链表结点,q和l都指向该结点
scanf("%d",&e);
while(e) { //循环输入数据,同时插入新生成的结点
insertList(&l,q,e);
q=q->next;
scanf("%d",&e);
}
q=l;
printf("the content of the linklist\n");
while(q) { //输出链表中的内容
printf("%d",q->data);
q=q->next;
}
q=l;
printf("\nDelete the fifth element");
for(i=0;i<4;i++) { //将指针q指向链表的第五个元素
if(q==NULL) { //确保此时链表的长度大于等于5,否则将是非法操作
printf("the length of the linklist is smaller than 5!");
}
q=q->next;
}
delLink(&l,q); //找到链表中第五个元素,用q指向它,再删除q所指的结点
q=l;
while(q) { //打印出删除后的结果
printf("%d",q->data);
q=q->next;
}
destroyLinkList(&l); //销毁该链表
return 0;
}
创建链表注意:
(1)用malloc()函数在内存的动态存储区(堆内存)中开辟一块大小为sizeof(LNode)的空间,并将其地址赋给LinkList类型变量p,(LinkList为指向LNode变量的类型,LNode为前面定义的链表结点类型)。然后将数据e存入该结点的数据域data,指针域存放NULL。
(2)若指针变量list为空,说明本次生存的结点是第一个结点……
(3)若指针变量list不为空,说明本次生存的结点不是第一个结点,将p赋给r->next。此处的r是一个LinkList类型变量,永远指向原先链表的最后一个结点,也就是要插入结点的前一个结点。
(4)再将p赋值给r,目的是使r再次指向最后的结点,以便生成链表的下一个结点,即保证r永远指向原先链表的最后一个结点。
(5)重复(1)~(4)n次,生成n个结点的链表
(6)最后生成的链表的头指针list返回主调函数,通过list就可以访问到该链表的每一个结点。
删除链表注意:
从非空链表删除q所指的结点,考虑3种情况:
(1)q所指向的是链表的第一个结点
(2)q所指向的结点的前驱结点的指针已知
(3)q所指向的结点的前驱结点的指针未知
销毁链表注意
链表使用完建议销毁,因为链表本身会占用内存空间。若一个系统中使用很多链表,而使用完又不及时销毁,那么这些垃圾空间积累过多,最终导致内存的泄露甚至程序的崩溃。
————————————————————————
————————————————————————
程序运行时候,删除第5个元素,没有显示出预期结果。运行环境在DEV。