您的位置:首页 > 理论基础 > 数据结构算法

《大话数据结构》--学习笔记9 ***重点***

2012-05-15 11:48 281 查看

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数据。

函数一:int rand(void);

从srand (seed)中指定的seed开始,返回一个[seed, RAND_MAX(0x7fff))间的随机整数。

函数二:void srand(unsigned seed);

参数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” 了吗?如果看懂了 ,这个也应该没问题的。因为他们类似的,都起到一个指针传递的作用。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: