链表的物理存储结构是用一组地址任意的存储单元存储数据的。不像顺序表占据连续的一段内存空间,而是将存储单元分散在内存的任意地址上。

 

链表结构中,每个数据元素记录都存放到链表的一个节点(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。

posted on 2017-05-12 15:18  王小东大将军  阅读(331)  评论(0编辑  收藏  举报