您的位置:首页 > 其它

链表基本操作及其过程详细叙述

2014-09-07 10:15 495 查看
链表概述:链表是一种常见的数据结构。数组可以存放数据,但是使用数组时要先指定数组中包含元素的个数,即数组长度。但是如果向这个数组中加入的元素个数超过了数组的大小时,便不能将内容完全保存。例如在定义一个班级的人数时,如果小班是30人,普通班是50人,且定义班级人数时使用数组,那么要定义数组的个数为最大,也就是最少为50个元素,否则不满足最大时的情况。这种方式非常浪费空间。而链表,其存储元素的个数不受限定的,当进行添加元素的时候存储的个数就会随之改变。

如图1所示为链表结构的示意图



在链表中有一个头指针变量,图中head表示的就是头指针,这个指针变量保存一个地址。从图中的箭头可以看到该地址为一个变量的地址,也就是说头指针指向一个变量。这个变量称为元素,在链表中每一个元素包括两个部分:数据部分和指针部分。数据部分用来存放元素所包含的数据,而指针部分用来指向下一个元素。最后一个元素指针指向NULL,表示指向的地址为空。从图1可以看到,head头结点指向第一个元素,第一个元素中的指针又指向第二个元素,第二个元素的指针又指向第三个元素的地址,第三个元素的指针就指向为空。

根据对链表的描述,可以想象到链表就像一个铁链,一环扣一环。然后通过头指针寻找链表中的元素,这就好比在一个幼儿园中,老师拉着第一个小朋友的手,第一个小朋友又拉着第二个小朋友的手,这样下去在幼儿园中的小朋友就连成了一条线。最后一个小朋友没有拉着任何人,他的手是空着的,他就好像是链表中链尾,而老师就是头指针,通过老师就可以找到这个队伍中的任何一个小朋友。

注意:在链表这种结构中,必须利用指针才能实现。因此链表中的结点应该包含一个指针变量来保存下一个结点的地址。

三个函数:

void *malloc(unsigned int size);

该函数的功能是在内存中动态地分配一块size大小的内存空间。malloc函数会返回一个指针,该指针指向分配的内存空间,如果出现错误则返回NULL。

void *calloc(unsigned int n,unsigned int size);

该函数的功能是在内存中动态分配n个长度为size的连续内存空间数组。calloc函数会返回一个指针,该指针指向动态分配的连续内存空间地址。当分配空间错误时,返回NULL。

void free(void *ptr);

该函数的功能是使用由指针ptr指向的内存区,使部分内存区能被其他变量使用(简单来说就是释放不需要的内存空间)。ptr是最近一次调用calloc或malloc函数时返回的值,free函数无返回值。



链表的插入操作:链表的插入操作可以在链表的头指针位置进行(图3),也可以在某个结点的位置进行,或者可以像创建结构时在链表的后面添加结点(图2)。这三种插入操作的思想都是一样的。





链表头插入结点的过程就比如手拉手的小朋友连成一条线,这时又来了一个小朋友他要站在老师和一个小朋友的中间,那么老师就要放开原来的小朋友,拉住新加入的小朋友。这个新加入的小朋友就拉住原来的那个小朋友。这样,这条连成的线还是连在一起。

链表的删除操作:当希望删除链表中的结点时,应该怎么办?还是通过前文中小朋友手拉手的比喻进行理解。例如队伍中的一个小朋友离开队伍了,并且这个队伍不会断开的方法只需他两边的小朋友将手拉起来就可以了。

例如在一个链表中删除其中的一点:


通过图4可以发现要删除一个结点,首先要找到这个结点的位置,例如图中的NO2结点。然后将NO1结点的指针指向NO3结点,最后将NO2结点的内存空间释放掉,这样就完成了结点的删除操作。

实例:

#include <stdio.h>
#include <stdlib.h>

struct student			//学生结构体
{
	char name[20];		//姓名
	int number;		//学号	
	struct student * next;	//指向下一个结点的指针
};

int count;			//全局变量表示链表长度

/*
	create函数的功能是创建链表,在create的外部可以看到一个整型的全局变量count,这个变量
的作用是表示链表中结点的数量。在create函数中,首先定义需要用到的指针变量,head用来表示头
指针,end用来指向原来的尾结点,new指向新创建的结点。
	使用malloc函数分配内存,先用end和new两个指针都指向第一个分配的内存。然后显示提示信息,
先输入一个学生的姓名,再输入学生的学号。使用while进行判断,如果学号为0,则不执行循环语句。
	在while循环中,count++自加操作表示链表中结点的增加。然后要判断新加入的结点是否是第一次
加入的结点,如果是第一次加入结点则执行if语句块中的代码,否则执行else语句块中的代码。
	在if语句块中,因为第一次加入结点时其中没有结点,所以新结点即为首结点也为最后一个结点,
并且要将新加入的结点的指针指向NULL,即为head指向。else语句实现的是链表中已经有结点存在时的
操作。首先将新结点new的指针指向NULL,然后将原来最后一个结点的指针指向新结点,最后将end指针
指向最后一个结点。
	这样一个结点创建完之后,要再进行分配内存,然后向其中输入数据,通过while语句再次判断输入
的数据是否符合结点的要求。当结点不符合要求时,执行下面的代码,调用free函数将不符合要求的结点
空间进行释放。
*/
struct student * create()
{
	struct student * head = NULL;	//初始化链表头指针为空
	struct student * end,* new;
	count = 0;			//初始化链表长度
	end = new = (struct student *)malloc(sizeof(struct student));
	printf("please first enter name,then nember\n");
	scanf("%s",new->name);
	scanf("%d",&new->number);
	while(0 != new->number)
	{
		count++;
		if(1 == count)
		{
			new->next = head;	//使得指向为空
			end = new;		//跟踪新加入的结点
			head = new;		//头指针指向首结点
		}
		else
		{
			new->next = NULL;	//新结点的指针为空
			end->next = new;	//原来的尾结点指向新结点
			end = new;		//end指向新结点
		}
		new = (struct student *)malloc(sizeof(struct student));//再次分配结点内存空间
		scanf("%s",new->name);
		scanf("%d",&new->number);
	}
	free(new);	//释放没用到的空间
	return head;
}

/*
	print函数是用来将链表中的数据进行输出。在函数的参数中,head表示一个链表的头结点。
在函数中,定义一个临时的指针temp用来进行循环操作。定义一个整型变量表示链表中的结点序号。
然后将临时指针temp指针变量保存首结点的地址。
	使用while语句将所有的结点中保存的数据都显示输出。其中每输出一个结点的内容后,就移动
	temp指针变量指向下一个结点的地址。当最后一个结点时,所拥有的指针指向NULL,此时循环结束。
*/
void print(struct student * head)
{
	struct student *temp;	//循环所用的临时指针
	int index = 1;		//表示链表中结点的序号
	
	printf("---the list has %d members:---\n\n",count);
	temp = head;	//指针得到首结点的地址

	while(NULL != temp)
	{
		printf("the NO%d member is:\n",index);          //输出结点序号
		printf("the name is:%s\n",temp->name);		//输出姓名
		printf("the number is:%d\n\n",temp->number);    //输出学号
		temp = temp->next;	//移动临时指针到下一个结点
		index++;			
	}
}

/*
	insert函数为链表头插入操作函数,在代码中,为要插入的新结点分配内存,然后向新结点中输入数据。这样
一个结点就创建完成了,接下来就是将这个结果插入到链表中。首先将新结点的指针指向原来的首结点,保存首结
点的地址。然后将头指针指向新结点,这样就完成了结点的连接操作,最后增加链表的结点数量。
*/
struct student * insert(struct student * head)
{
	struct student * new;	//指向新分配的空间
	printf("---insert member at first---\n");
	new = (struct student *)malloc(sizeof(struct student));//分配内存空间,并返回指向该内存空间的指针

	scanf("%s",new->name);
	scanf("%d",&new->number);

	new->next = head;	//新结点指针指向原来的首结点
	head = new;		//头指针指向新结点
	count++;		//增加链表结点数量
	return head;
}

/*
	为delete函数传递两个参数,head表示链表的头指针,index表示要删除结点在链表中的位置。
定义整型变量i用来控制循环的次数,然后定义两个指针,分别用来表示要删除的结点和这个结点
之前的结点。
	输出一行提示信息表示要进行删除操作,之后利用for语句进行循环操作找到要删除的结点,
使用temp保存要删除结点的地址,pre保存前一个结点的地址。找到要删除的结点后,连接删除结点
两边的结点,并使用free函数将temp指向的内存空间进行释放。
*/
void delete(struct student * head,int index)//head表示头结点,index表示要删除的结点下标
{
	int i;			//控制循环变量
	struct student * temp;	//临时指针
	struct student * pre;	//表示要删除结点前的结点
	temp = head;		//得到头结点
	pre = temp;

	printf("---delete NO%d member---\n\n\n",index);
	for(i=1;i<index;i++)	//for循环使得temp指向要删除的结点
	{
		pre = temp;
		temp = temp->next;
	}
	pre->next = temp->next;	//连接删除结点两边的结点
	free(temp);		//释放掉要删除结点的内存空间
	count--;		//减少链表中的元素个数
}

/*
	在main函数中,先定义一个头结点指针head,然后调用create函数创建链表,并将链表的头结点
返回给head指针变量。利用得到的头结点head作为print函数的参数。
	在main函数中分别添加代码执行插入和删除操作。
*/
int main()
{
	struct student * head;	//定义头结点
	head = create();	//创建结点
	print(head);		//输出链表
	head = insert(head);	//插入结点
	print(head);		//输出链表
	delete(head,2);		//删除第二个结点
	print(head);		//输出链表

	return 0;
}


参考文献:[C语言从入门到精通].王娣等



[C语言从入门到精通].王娣等PDF文档下载地址:

http://download.csdn.net/detail/dezhihuang/7876745
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: