目录
前言
回顾一下,顺序储存结构的创建,其实就是数组的初始化,即声明一个类型和大小的数组并赋值的过程。而单链表和顺序储存结构就不一样,它不像顺序储存结构一样那么集中,它可以很散,类似于一种动态结构。对于每个链表来说,它所占用的空间的大小和位置是不需要预先分配划定的,可以根据系统的情况和实际的需求即时生成。
所以,创建单链表的过程就是一个动态生成链表的过程。
一、单链表的整表创建
算法思路如下:
- 声明一指针p和计数器变量i;
- 初始化一个空链表a;
- 让a的头结点的指针指向NULL,即建立一个带头结点的单链表。
- 循环:
- 生成一新结点赋值给p;
- 随机生成一数字赋值给p的数据域p.data;
- 将p插入到头结点与前一新结点之间。
实现代码如下:
void createlist(linklist *a,int n)
{
linklist p;
int i;
srand(time(0)); //初始化随机数种子
*a=(linklist)malloc(sizeof(node));
(*a).next=NULL; //先建立一个带头结点的单链表
for(i=0,i<n,i++)
{
p=(linklist)malloc(sizeof(node)); //生成新结点
p.data=rand()%100+1; //随机生成100以内的数字
p.next=(*a).next;
(*a).next=p; //插入到表头
}
}
在这段代码里,我们用的是插队的办法,就是始终让新结点在第一的位置。称之为头插法。
可事实上,这不符合我们的正常思维。正所谓先来后到,我们可以把新结点都插在终端结点的后面,这种算法称之为尾插法。
实现代码算法如下:
void createlisttail(linklist *a,int n)
{
linklist p,r;
int i;
srand(time(0)); //初始化随机种子
*a=(linklist)malloc(sizeof(node)); //a为整个线性表
r=*a; //r为指向尾部的结点
for(i=0;i<n;i++)
{
p=(node*)malloc(sizeof(node)); //生成新结点
p.data=rand()%100+1; //随机生成100以内的数字
r.next=p; //表尾终端结点指向新指针
r=p; //将当前定义的新结点定义为表尾的终端结点
}
r.next=NULL; //表示当前链表结束
}
这里需要我们注意a与r的关系,a是指整个单链表,而r是指向尾指针的变量,r会随着循环不断的变化结点,而a则是随着循环增长为一个多节点的链表。
可能会有很多同学不理解r=p的含义,这里其实就是一个新结点被创造出来了,原来的r结点就不是尾结点了,尾结点其实是新创造出来的p。所以将p赋值给r,则r又是最终的尾结点了。
循环结束后,那么应该让这个结点的指针域置空,因此有了“r.next=NULL”,以便于以后遍历时可以确认其是尾部。
二、单链表的整表删除
当我们不打算使用这个单链表时,我们需要把它销毁,其实也就是在内存中将它放掉,以便于留出空间给其他程序或软件使用。
算法思路如下:
- 声明两指针p和q;
- 将第一个结点赋值给p;
- 循环
- 将下一结点赋值给q;
- 释放p;
- 将q赋值给p;
实现代码如下:
status clearlist(linklist *a)
{
linklist p,q;
p=(*a).next; //p指向第一个指针
while(p) //没到表尾
{
q=p.next;
free(p);
p=q;
}
(*a).next=NULL; //头结点指针域为空
return ok;
}
这里有一个很多同学都容易犯的错误,就是觉得q变量没有存在的必要。直接写free(p);p=p.next即可。但是,变量q的最大作用就在于它使得下一个结点是谁得到了记录,以便于当前结点释放后,把下一结点拿回来补充。