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

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” 了吗?如果看懂了 ,这个也应该没问题的。因为他们类似的,都起到一个指针传递的作用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值