数据结构与算法--线性表系列(链式存储结构的线性表)

hello,上篇文章我们讲到了顺序存储结构的线性表,这篇我们来一道学习链式存储结构的线性表。

顺序存储结构不足的解决办法:
顺序存储结构的插入、删除之所以那么麻烦。是因为,他们是在一段有序的地址内存存放,彼此
之间是相邻的。所以,当一个元素删除或者添加新元素,都会影响相邻的元素。
我们可以动动脑筋,来解决这个弊端。
所有元素在内存中随便存放,只要让元素知道它的下一个元素的位置就好。这样,我们只需知道第一个元素的位置,就可以找到第二个,找到第三个。。。。

线性表链式存储结构定义
线性表的链式存储结构的特点是用一组任意的存储单元存储线性表的数据元素,这组数据单元可是连续的,也可以是不连续的。这就意味着,这些数据元素可以
存放在内存中未被占用的任何地方。

以前在顺序存储结构中,每个数据元素只存数据元素信息就可以了。现在链式存储,不只存放数据元素信息,还要存储它的后继元素的存储地址。我们把存放数据元素的域,称为
数据域,存放后继元素地址的域称为指针域。指针域中存放的信息,我们称为指针。这两部分信息组成数据元素的存储映像,称为结点。

n个结点链结成一个链表,即为线性表的链式存储结构。因为链表中的每个结点中只包含一个指针域,所以叫做单链表。
头指针:我们把链表的第一个结点的存储位置叫做头指针。线性链表的最后一个结点的指针为NULL 。

头结点:为了方便对链表的操作,我们可以在第一个结点之前创建一个结点,成为头结点。头结点的数据域可以不存信息,也可以存放表长等附加信息。指针域存放第一个结点的地址。

头结点与头指针的区别:

头指针:

Firstly,头指针是指向链表第一个结点位置的指针。若链表有头结点,则头指针指向头结点。

Secondly,头指针具有标识性,常常冠以链表的名字。

Thirdly,无论链表是否为空,头指针不为空。头指针是必不可少的元素。

头结点:

Firstly,头结点是为了操作的统一与方便而设立的,放在第一个元素的结点之前。其数据域一般无意义(可以存放表长等一些附加信息),指针域存放第一个元素的地址。

Secondly,头结点的设立,使得向第一个结点之前插入元素,删除第一个结点,等操作变得与其他元素结点操作一致。

Thirdly,头结点可有可无,不是必须元素。

线性表链式存储结构代码描述

typedef struck Node

{

    ElemType Data;

    struck Node * Node;

}Node;

typedef struck Node  *LinkList /*定义LinkList*/

 

单链表的读取

获取单链表第i个数据的算法思路

Firstly,创建一个结点P,计数器变量j.

Secondly,使结点p指向第一个结点,j初始为1。

Thirdly,使p不断指向下个结点,j累加1.

Fourthly,若遍历表尾,p为空,则i位置元素不存在。

Fifthly,将p赋值给e。

Status GetElem(LinkList L,int I,ElemType *e)

{

                 LinkList  p;/*声明一个结点p*/

                  int j;

                   p=L->next;/*p为第一个元素*/

                   j=1;

if(p&&j<i)

{

               p=p->next;/*p指向下一个元素*/

              ++j;

}

   if(!p||j>i)

return Error;/*第i个元素不存在*/

*e=p->data;/*取第i个数据*/

return OK;

}

 

大家通过算法可以发现,单链表查找的时间复杂度为O【n】。有人会觉得,这还不如顺序存储结构呢。世上任何一件事儿,都是两面性的。我们要明白这个道理,存在即合理。让我们一道看看单链表的其他一些操作吧,看看会不会从中发现一些单链表的优点。

单链表的插入与删除

单链表第i个数据插入结点的算法思路:
Firstly,创建一个结点p,一个计数器变量j.

secondly,使p指向第一个结点,j初始为1.

Thirdly,当p->next不为空,j<i时,循环p指向下个结点,j累加1.

Fourthly,若到链表末尾p为空,则说明第i个元素不存在。

Fifthly,否则查找成功,在系统中生成一个空结点S。

Sixthly,将数据元素e赋给s->data.

Seventhly,单链表的插入标准语句s->next=p->next;p->next=s

Status ListInsert(LinkList *L,int I,ElemType e)

{

         LinkList p,s;

           p=(*L);

           int j=1;

          if(p&&j<i)

       p=p->next;

       ++j;

}

  if(!p||j>i)

return Error;

s=(LinkList)malloc(sizeof(Node));/*生成新结点(C标准函数)*/

s->data=e;

s->next=p->next;

p->next=s;

return OK;

}

 

单链表的删除

单链表删除的算法思路:

Firstly,声明一结点p,指向链表的第一个结点,声明一个计数器变量j,初始值为1

Secondly,当j<i时,就遍历链表,让p的指针向后移动,不断指向下一个结点,j累加1;

Thirdly,若到链表末尾p为空,则说明第i个元素不存在;

Fourthly,否则查找成功,将欲删除的结点p->next赋值给q;

Fifthly,单链表的删除标准语句p->next=q->next;

Sixthly,将q结点的数据赋值给e.

Seventhly,释放q结点。

Status ListDelete(LinkList *L,int I,ElemType *e)

{

     LinkList p,q;

      p=(*L);

     int j=1;

if(p->next&&j<1)

{

       p=p->next;

        ++j;

}

if(!(p->next)||j>i)

return Error;

q=p->next;

p->next=q->next;

*e=q->data;

free(q);

return ok;

}

我们仔细分析一下上面的算法,可以发现。当我们不知道要删除或要插入的i位置时,单链表的时间复杂度与顺序结构一样都为O【n】.但是当找到i的位置,进行插入删除操作时,它的时间复杂度为O【1】.我们可以设想一下,假如要在i位置插入10个数据。使用单链表,查找i的位置的时间复杂度为O【n】,但是插入操作是O【1】.显然,对于插入或删除数据越频繁,单链表的效率越明显。

 

单链表的整表创建

回忆一下顺序结构的创建,实际上就是创建了一个固定大小的数组,给相应位置赋值。但是,单链表就不是这么回事儿了。创建单链表的过程,就是动态生成链表的过程。即从”空表”的初始状态起,依次建立各元素结点,并逐个插入链表。

单链表整表创建的算法思路:

Firstly,声明一个结点P和计数器变量i;

Secondly,初始化一空链表L;

Thirdly,让L的头结点指针指向NULL,即建立了一个带头结点的空单链表。

Fourthly,循环:

生成一个新结点赋值给P;

随机生成一个数字赋值给P的数据域p->data;

将p插入到头结点与前一新结点之间。

void CreateListHead(LinkList *L,int n)

{

   LinkList p;

  int I;

srand(time(0));/*初始化随机数种子*/

*L=(LinkList)malloc(sizeof(Node));

(*L)->next=NULL;/*先建立一个带头结点的单链表*/

for(i=0;i<n)

{

   p=(LinkList)malloc(sizeof(Node));/*生成新结点*/

p->data=rand()%100+1;/*随机生成100以内的数字*/

p->next=(*L)->next;

(*L)->next=p;/*插入到表头*/

}

}

上面的算法,也称为头插法。将插入元素放入表头。那么,我们一般情况下,习惯先来后到,那么让我们一起来看看下面的尾插法吧。

void CreateListTail(LinkList *L,int n)

{

        LinkList p,r;

        int I;

        srand(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;/*表示当前链表结束*/

}

 

单链表的整表删除

单链表整表删除的算法思路:
Firstly,声明一个结点p和q.

Secondly,将第一个结点赋值给p.

Thirdly,循环:
将下一结点赋值给q

释放p

将q赋值给p

Status ClearList(LinkList *L)

{

          LinkList p,q;

        p=(*L)->next;      /*p指向第一个结点*/

     while(p)

{

      q=p->next;

      free(p);

    p=q;

 

}

   (*L)->next=NULL;/*头结点指针域为空*/

return Ok;

}

好了,让我们来分析一下单链表与顺序存储结构优缺点

存储分配方式:
顺序存储结构采用一组连续的存储单元依次存储线性表的数据元素。

单链表采用链式存储结构,用一组任意的存储单元存放线性表的元素。

时间性能:

查找:

         顺序存储结构O(1)

        单链表O【n】

插入和删除:

        顺序存储结构需要平均移动表长一半的元素,时间为O[n]

       单链表在找到某位置的指针后,插入和删除时间仅为O【1】

空间性能:

顺序存储结构需要预分配存储空间,分大了,浪费。分小了,发生上溢。

单链表不需要分配存储空间,只要有就分配,元素个数也不受限。

 

以上是单链表的内容,接下来,让我一起学习静态链表吧。

posted @ 2014-06-04 15:00  VitoCorleone  阅读(666)  评论(0编辑  收藏  举报