简介
线性表,很好理解,就是一种排好队的组织方式
线性表的链式结构,听着很复杂,但我举一个最常见的实际例子来描述一下:经常去幼儿园接送孩子放学的家长应该都能注意到,每次都能在门口看到老师带着小朋友们,一个拉着另一个的衣服,依次从教室出来。而且每次他们的次序都是一样。 比如有个小男孩排在第5个,每次他都是在第5个,前面同样是那个小女孩,后面一直是另一个小男孩。
这么做的原因也很好理解:为了保障小朋友的安全,避免漏掉小朋友,所以给他们安排了出门的次序,事先规定好了,谁在谁的前面,谁在谁的后面。这样养成习惯后,如果有谁没有到位,他前面和后面的小朋友就会主动报告老师,某人不在。即使以后如果要外出到公园或博物馆等情况下,老师也可以很快地清点人数,万一有人走丢,也能在最快时间知道,及时去寻找。小朋友们始终按照次序排队做事,出意外的情况就可能会少很多。而且,真要有人丢失,小孩子反而是最认真负责的监督员。
这时再看看门外的这帮家长们,都挤在大门口,哪个分得清他们谁是谁呀。与小孩子们的井然有序形成了鲜明的对比。
简单的例子就表现出了链表的优势!
链表的结构
依旧是之前的例子,一个班的小朋友,一个跟着一个排着队,有一个打头,有一个收尾,当中的小朋友每一个都知道他前面一个是谁,他后面一个是谁,这样如同有一根线把他们串联起来了,就可以称为线性表。
线性表的链式存储结构的特点是用一组任意的存储单元存储线性表的数据元素,这组存储单元可以是连续的,也可以是不连续的。这就意味着,这些数据元素可以存在内存未被占用的任意位置。
链表的结构
链表的诞生很大程度是为了解决顺序表的一大缺陷——插入和删除时需要移动大量元素。
以前在顺序机构里,每个数据元素就只要存数据元素的信息就可以,现在在链式结构中,除了要存数据元素的信息外,还要存储它的后续元素的地址。
typedef struct Node
{
ElemType data;
struct Node *next;
} Node;
typedef struct Node *LinkList; /*定义 LinkList*/
链表的读取
获得链表第i个数据的算法思路:
1.声明一个结点p指向链表第一个结点,初始化j从1开始;
2.当j<i时,就遍历链表,让p的指针向后移动,不断指向下一结点,j累加1;
3.若到链表末尾p为空,则说明第i个元素不存在;
4.否则查找成功,返回结点p的数据。
GET ( LinkList L,int i,ElemType *e)
{
int j;
LinkList P; /*声明一结点p*/
p=L->next; /*让p指向链表L的第一个结点*/
j= 1; /*为计数器*/
while (p && j<i) /*p不为空或者计数器j还没有等于i时,循环继续*/
{
p = p->next; /*让p指向下一个结点*/
++j;
}
if (!p || j>i)
return false; /*第i个元素不存在*/
*e = p ->data;
}
链表的插入
单链表第i个数据插入结点的算法思路:
1.声明一结点p指向链表第一个结点,初始化j从1开始;
2.当j<i时,就遍历链表,让p的指针向后移动,不断指向下一结点,j累加1;
3.若到链表末尾p为空,则说明第i个元素不存在;
4.否则查找成功,在系统中生成一个空结点s;
5.将数据元素e赋值给s->data;
6.单链表的插入标准语句s->next = p->next; p->next=s;
7.返回成功。
Insert ( LinkList *L,int i,ElemType e )
{
int j;
LinkList p,s;
p= *L;
j=1;
while(p&&j<i) /*寻找第i个结点*/
{
p =p->next;
++j;
}
if(!p || j>i)
return false; /*第i个元素不存在*/
/*生成新的节点 */
s->data =e;
s->next=p->next; /*将p的后继结点赋值給s的后继*/
p->next =s; /*将s赋值给p的后继*/
return true;
}
链表的删除
单链表第i个数据删除结点的算法思路:
1.声明一结点p指向链表第一个结点,初始化j从1开始;
2.当j<i 时,就遍历链表,让p的指针向后移动,不断指向下一个结点,j累加1;
3.若到链表末尾p为空,则说明第i个元素不存在;
4.否则查找成功,将欲删除的结点p->next赋值给q;
5.单链表的删除标准语句p->next=q->next;
6.将q结点中的数据赋值给e,作为返回;
7.释放q结点;
8.返回成功。
Delete ( LinkList *L, int i, ElemType *e )
{
int j;
LinkList p, q;
p= *L;
j= 1;
while (p->next &&j< i) /*便利寻找第i个元素*/
{
p =p->next;
++j;
}
if(! (p->next) || j > i)
return false; /*第i个元素不存在*/
q = p->next;
p->next= q->next; /*将q的后继赋值给p的后继*/
*e = q->data; /*将q结点中的数据给e*/
return true;
}
总结
从整个算法来说,我们很容易推导出它们的时间复杂度都是O(n)。如果在我们不知道第i个元素的指针位置,单链表数据结构在插入和删除操作上,与线性表的顺序存储结构是没有太大优势的。但如果,我们希望从第i个位置,插入10个元素,对于顺序存储结构意味着,每一次插入都需要移动n-i个元素,每次都是O(n)。而单链表,我们只需要在第一次时, 找到第i个位置的指针,此时为O(n),接下来只是简单地通过赋值移动指针而已,时间复杂度都是O(1)。 显然,对于插入或删除数据越频繁的操作,单链表的效率优势就越是明显。
总的来说,线性表的顺序结构和链式结构都是比较基础两种数据结构,我们应该熟练掌握并加以应用。