3.9 单链表的整表创建
单链表和顺序存储结构的区别:
1.单链表不像顺序存储结构这么集中,它可以很散,是一种动态结构;
2.对每个链表来说,它所占用空间的大小和位置是不需要预先分配划定的,可以根据系统的情况和实际的需求即时生成。
所建立单链表的过程就是一个动态生成链表的过程。即从“空表”的初始化状态起,依次建立各元素结点,并逐个插入链表。
单链表整表创建的算法思路:
1.声明一个结点p和计数器变量i;
2.初始化一空链表L;
3.让L的头结点的指针指向NULL,即建立一个带头结点的单链表;
4.循环:
& 生成一新结点赋值给p;
& 随机生成一数字赋值给p的数据域p->data;
& 将p插入到头结点与前一新结点之间;
实现单链表整表创建代码算法如下:
/*随机产生n个元素的值,建立带表头结点的单链线性表L(头插法)*/
void CreateListHead (LinkList *L, int n )
{
LinkList p;
int i;
strand (time(0)); /*初始化随机数种子*/
*L = ( LinkList ) malloc (sizeof(Node));
(*L)->next =NULL; /*先建立一个带头结点的单链表*/
for (i=0; i<n; i++)
{
p = (LinkList ) malloc (sizeof(Node)); /*生成新结点*/
p->data = rand () %100 +1; /*随机生成100以内的数字*/
p->next = (*L)->next; /*即NULL结尾*/
(*L)->next =p; /*插入到表头*/
}
}
这段算法代码里,我们其实用的是插队的办法,就是始终让新结点在第一的位置。我们可以把这种算法简称为头插法。如图:
可事实上,我们还是可以不这样干,为什么不把新结点都放最后呢?这才是排队的正常思维,所谓先来后到。
我们把每次新结点都插在终端结点的后面,这种算法称之为尾插法。
实现尾插法代码算法如下:
/*随机产生n个元素的值,建立带表头结点的单链线性表L(尾插法)*/
void CreateListTail (LinkList *L, int n )
{
LinkList p,r;
int i;
strand (time(0)); /*初始化随机数种子*/
*L = ( LinkList ) malloc (sizeof(Node)); /*为整个线性表*/
r =* L; /*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; /*表示当前链表结束*/
}
注意L与r的关系,L是指整个单链表,而r是指向尾结点的变量,r会随着循环的不断变化结点,而L则是随着循环增长为一个多结点的链表。
这里需要解析一下,r->next=p 是将刚才的表尾端结点r的指针指向新结点p; (难点:) r =p就不是很好理解了,是什么意思?看图:
它的意思是,就是本来r是在ai-1元素的结点,可现在它已经不是最后的结点了。(其实r就是一个尾节点的标志,当r没有在尾时,必须把它移到尾)
循环结束,那么应该让这个链表指针域置空,因此有了“r->next=NULL”以便以后遍历时可以确认其尾部。
个人思考附加:
附加一:
头插法和尾插法的区别:
头插法:头指针装NULL,每次从后面添加元素,NULL尾都要往后挪一个地方;
尾插法:头指针没有装NULL,每次从后面添加元素,添加完后,再在尾指针装NULL;
附加二:
srand函数是随机数发生器的初始化函数。
原型:void srand(unsigned seed);
用法:它需要提供一个种子,这个种子会对应一个随机数,如果使用相同的种子后面的rand()函数会出现一样的随机数。如: srand(1); 直接使用1来初始化种子。不过为了防止随机数每次重复常常使用系统时间来初始化,即使用 time函数来获得系统时间,它的返回值为从 00:00:00 GMT, January 1, 1970 到现在所持续的秒数,然后将time_t型数据转化为(unsigned)型再传给srand函数,即: srand((unsigned) time(&t)); 还有一个经常用法,不需要定义time_t型t变量,即: srand((unsigned) time(NULL)); 直接传入一个空指针,因为你的程序中往往并不需要经过参数获得的t数据。
函数一:intrand(void);
从srand(seed)中指定的seed开始,返回一个[seed,RAND_MAX(0x7fff))间的随机整数。
函数二:voidsrand(unsignedseed);
参数seed是rand()的种子,用来初始化rand()的起始值。
可以认为rand()在每次被调用的时候,它会查看:
1)如果用户在此之前调用过srand(seed),给seed指定了一个值,那么它会自动调用
srand(seed)一次来初始化它的起始值。
2)如果用户在此之前没有调用过srand(seed),它会自动调用srand(1)一次。
根据上面的第一点我们可以得出:
1)如果希望rand()在每次程序运行时产生的值都不一样,必须给srand(seed)中的seed一个变值,这个变值必须在每次程序运行时都不一样(比如到目前为止流逝的时间)。
2)否则,如果给seed指定的是一个定值,那么每次程序运行时rand()产生的值都会一样,虽然这个值会是[seed,RAND_MAX(0x7fff))之间的一个随机取得的值。
3)如果在调用rand()之前没有调用过srand(seed),效果将和调用了srand(1)再调用rand()一样(1也是一个定值)。
rand () %100 即随机生成100以内的数;
3.10 单链表的整表删除
当我们不打算使用这个单链表时,我们需要把它销毁,其实也就是在内存中将它释放掉,以便留出空间给其他程序员或软件使用。
单链表的整表删除的算法思路如下:
1.声明一结点p和q;
2.将第一个结点赋值给p;
3.循环:
& 将下一结点赋值给q;
& 释放p;
&将q赋值给p;
实现单链表的整表删除代码算法如下:
/*初始化条件:顺序线性表L已存在,操作结果:将L重置为空表*/
Status ClearList (LinkList *L)
{
ListList p ,q;
p = (*L)->next; /*p指向第一个结点*/
while (p) /*没到表尾*/
{
q = p->next; /*一个个释放*/
free (p);
p = q ;
}
(*L)->next =NULL; /*头结点指针域为空*/
return OK;
}
这段代码里,常见的错误就是由同学会觉得q变量没有存在的必要。在循环体内直接写free(p);p=p->next ;即可,可这样带来什么问题?
要知道p是一个结点,它除了有数据域,还有指针域。你在做free(p)时,其实时在对它整体结点进行删除和内存释放工作。
怎么说呢?你看懂《大话数据结构》--学习笔记9 ***重点***尾插法中的那句:“r=p” 了吗?如果看懂了 ,这个也应该没问题的。因为他们类似的,都起到一个指针传递的作用。