数据结构

链表的专题研究:
链表是一种重要的,动态的,数据存储结构
.
数组的特点是元素个数固定,不适合相对元素个数不固定的时候.虽然它用起来比较简单直观.链表看上去比较复杂,但是它却克服了数组的这一缺点,在某些时候用链表是很有效果的
.
这里要深入理解一个问题,那就是:链表的结构.它是一个由结构体类型的多个结点组成的,每一个结构体里都含有一个指向另一个结构体的指针
.
链表有一个头指针指向这个链表的首地址,用来表示这个链表的入口访问地址.还有一个表尾它的指针部分被置空
.
由于链表的这种特殊结构,使得它可以在内存中不必要连续存放.比较灵活
.
举个例子来仔细说明
:
可以设计这样一个结构体类型
:
struct student
{int num;
float score;
struct student *next;
};
这个例子里的num,score是用户要用的数据.而这里面必须包括的一项是指针变量.否则就不能构成链表结构.再来仔细看一下这个指针变量:它是结构体struct student类型的指针变量.用来指向这个结构体变量的起始地址.有了数据和指针就构成了结点和链表如下所示
:

num
score
next

简单链表
:
#define NULL 0
struct student
{long num;            
首先定义结构体,它是整个链表的基础
.
float score;
struct student *next;
};
main()
{struct student a,b,c,*head,*p;//
定义a,b,c*head,*p为结构体类型变量,指针变量,分配空间
.
a.        num=99101;a.score=89.5;
b.        num=99103;a.score=90;        a,b,c
分别是三个结点.给结点中的变量赋值
.
c.        num=99107;b.score=85;
head=&a;//
把第一个结点的地址给头指针
”.
a.        next=&b;
b.        next=&c;         
分别把后一个结点的地址给前一个结点的指针变量.这样就形成了链
.
c.        next=NULL;
p=head;
do
{printf(“%ld%5.1f/n”,p->num,p->score);//->:
为指向运算符,p->num相当于(*p).num,它表示指针p指向的内容
.
p=p->next;//
这一句很关键,要深入理解.我们说p它对一个结构体指针,它可以指向一个结构体,当然它同样也可以指向结构体里的成员.在这里p->next也就是(*p).next.这样就好理解一点,也就是说p->next它就是后一个结点的入口地址.可以说指针p很重要,如果说链表是动态的,那就这个动即由p在动.它一会指向结构体,一会指向结构体内的成员.如果有软件工程里的隐喻来说那就是 就像我们的双腿一样,我们要想行走,那么要先迈一只腿,这里就是指向结构体.在迈出一只的基础上,我们在迈出另一只,这里就是指向结构体里的成员.当然这里面也有一个很重要的导盲犬,它在传递这下一步要走在哪的地址.这里就是指结构体指针
next.
}while(p!=NULL);//
p指向空的时候,最后一个结点已经输出.这时应该结束程序了
.
}
要声明一下的是:上例是一个静态链表,它的结点a,b,c都是在程序中先定义的.不是临时开辟的.这样的话,就失去了作为链表最有价值的地方,所以链表很少这样使用.正真使用的链表是动态的链表
.
下面我们来研究一下最有价值的动态链表
:
动态的开辟,动态的释放,是一个比较复杂的过程.而这个过程又是研究动态链表的必用过程,所以C语言编译系统提供了它的有关库函数,这样就简单方便多了.我们来学习一下这几个常用的库函数
.
Malloc
函数:此函数的原型为:void *mall(unsigned int size);从这个原型我们可以看到它是一个指针类型的函数.调用它之后,返回的是一个指针,即地址.其作用就是在内存区开辟一块长度为size的连续空间.提到这个连续空间,我们要注意一下的是,这里的连续空间是指一个变量所用的空间,希望不要和链表的不连续空间混为一谈
.
Calloc
函数:此函数的原型为:void *calloc(unsigned n,unsigned size);当然它也是一个指针类型的函数.它与malloc的不同仅在于它是在内存中开辟N个长度为size的连续空间.它可以为一个数组为配空间,其中N就是数组元素的个数
.
Free
函数:此函数的原型为:void free(void *p);从这个原型我们要以看到它并不是一个指针类型的,而它的形参却是指针,从而就不难明白它是什么作用了.即释放P所指向的内存区
.
好了,有了以上的细致深入的学习,下面到了最精彩的地方了.让我们再来深入细致的研究一下
:
建立动态链表
:
明白一个概念:什么叫动态链表?所谓的动态链表是指在程序运行过程中从无到有的建立起一个链表,即一个一个的开辟结点和输入结点的数据,并建立前后相链的关系
.
从这个概念中我们可以知道要建立一个动态链表要做三件事:开辟结点,输入数据,建立链接
.
:写一个函数建立一个有3名学生数据的单向链表
.
分析:首先我要写的是一个函数,而不是一个完整的程序
.
其次要明确这个函数的功能是建立一个链表
.
最后要清楚建立链表要做的三件事情
.
下面我们来分析一个具体算法
:
首先要定义三个结构体指针变量:head,p1,p2.接下来用malloc来开辟一个结点空间,并使p1,p2指向这个结点的起始地址.再从键盘输入数据.这里我们约定输入的学号不为0,0就要结束建立链表.如果这时给该结点输入的学号不为0,则这个结点应链入.此时由于该结点是链表的第一个结点,所以应该把head指向第一个结点的起始地址,:head=p1.然后再开辟一个结点,使p1指向它,接着输入该结点的数据,同理如果学号不为0,则应链入.此时N!=1,则应使p1指向的结点地址给前一个结构体指针,这样才能使前后相链.:p2->next=p1.接着使p2=p1,因为只有这样才能不断的开辟下一个结点.此时再开辟一个结点,使p1指向它.输入数据,如果学号不为0,则链入.再将p1指向的地址给前一个结构体指针,:p2->next=p1.接着使p2=p1.如此重复以上步骤,可以不停的开辟多个结点.要想停止开辟,只要输入学号为0即可.不过此时要有一点要注意的:即然有了输入,也就是说这个结点已经开辟了,但是学号为0只是一个结束条件,并不是真正的学生号,所以它不应该被链入链表中.所以应该让该结点的前一个结点的结构体指针置空.:p2->next=NULL.此时结束了整个链表的建立
.
清楚了算法下面可以用代码来实现
:
#define NULL 0
#define LEN sizeof(struct student)
struct student
{long num;
float score;
struct student *next;
};
int n;//
全局变量

struct student *creat(void)//
这是我们建立的函数
{struct student *head;   
分别定义了三个结构体类型的指针,head是所谓的头指针”,p1指向
struct student *p1,*p2;   
建立的结点,p2指向最后一个结点.
n=0;//
它表示节点的个数

p1=p2=(struct student*)malloc(LEN);//malloc
带回的是一个无类型的指针,所以要用强制类型转换(struct student*)把它转换成结构体类型.
scanf(“%ld,%f”,&p1->num,&p1->score);
head=NULL;
while(p1->num!=0)
{n=n+1;
if(n==1)head=p1;
else p2->next=p1;
p2=p1;
p1=(struct student*)malloc(LEN);
scanf(“%ld,%f”,&p1->num,&p1->score);
}
p2->next=NULL;
return(head);//
返回链表的起始地址
.
}
学习至此已经对链表比较熟悉了,它的结构也比较清晰.可以在此基础上作一些相对灵活的操作了
.
输出链表
:
总体思想:首先知道第一个结点地址,然后设一个指针P先指向第一个结点,输出P所指结点,再使P后移
.
:编写一个输出链表的函数
.
Void print(struct student *head)
{struct student *p;
printf(“/nNow,These%d records are :/n”,n);
p=head;
if(head!=NULL)
do
{printf(“%ld %5.1f/n”,p->num,p->score);
p=p->next;
}while(p!=NULL);
}
说句高兴的话,其实一切都那么简单,在你明白它以后.呵呵
!
对链表的删除操作
:
首先明确一点:删除一个结点并不是真正从内存中把该结点沫掉,而是它还在,只是与当前链表脱离关系了
.
思想:找到要删除的结点,让该结点的前一个结点的结构体指针指向该结点的后一个结点地址.但是这里有一个要注意的地方:即要删的结点是第一个结点,或是无此结点的处理
.
代码如下
:
struct student *del(struct student *head,long num)
{struct student *p1,*p2;
if(head==NULL){printf(“/nlist null!/n”);return(head);}
p1=head;
while(num!=p1->num&&p1->next!=NULL)
{p2=p1;p1=p1->next;}
if(num==p1->num)
{if(p1==head)head=p1->next;
else p2->next=p1->next;
printf(“delete:%ld/n”,num);
n=n-1;
}
else printf(“%ld not been found!/n”,num);
return(head);
}
到此也就没什么好多解释了
.
对链表的插入操作
:
思想:找到要插入的位置,完成插入操作.在这里要注意一个地方就是:要插入的位置在第一个结点之前,或在尾结点之后的操作
.
函数代码如下
:
struct student *insert(struct student *head,struct student *stud)//
给了两个指针,后一个是要插入的地址
.
{struct student *p0,*p1,*p2;
p1=head;
po=stud;
if(head==NULL)
{head=p0;p0->next=NULL;}//
在空链表的基础上增加一个结点

else
{while((p0->num>p1->num)&&(p1->next!=NULL))
{p2=p1;
p1=p1->next;}   
要插入的号比当前号大,要把当前指针后移.
if(p0->num<p1->num)
{if(head==p1)head=p0;//
要插入的结点在链表第一个结点前

else p2->next=p0;
p0->next=p1;}   
正常插入情况在队中
else
{p1->next=p0;p0->next=NULL;}}//
要插入的结点在队尾结点后
n=n+1;//
结点数累加
return(head);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值