链表的物理存储结构是用一组地址任意的存储单元存储数据的。不像顺序表占据连续的一段内存空间,而是将存储单元分散在内存的任意地址上。
链表结构中,每个数据元素记录都存放到链表的一个节点(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。