线性表分为上下两篇来学习,(上)部分主要针对顺序存储结构与链式存储结构各自的概念、算法实现以及时间与空间复杂度的比较。
线性表是最基本、最简单、也是最常用的一种数据结构。它是
由0个或多个元素组成的有限序列。
满足如下条件:
- 元素有先后之分。
- 第一个元素无前驱,最后一个元素无后继。其余元素都只有一个前驱和后继。数据之间一一对应。
- 线性表是有限的。
说完了基本概念,可以引出线性表的两种重要分类:
顺序存储结构和
链式存储结构。
1.线性表的顺序存储结构
顺序存储结构是将线性表中的数据元素依次存入一组地址连续的存储单元中,以最经常的操作插入和删除为例说明顺序结构的算法。
1.1 插入算法
插入算法需要考虑插入的结点,之后将结点后的元素分别向后移动一个位置。其算法及代码描述如下。(只考虑算法,未考虑代码完整性)
void Insert(int i,int n,int x) //i:插入位置 n:长度 x:插入值
{
if(n==0) return; //表长不为0
if(i<1 || i>length) return;//插入位置小于1或者大于表长length,返回
for(int j=length;j>=i+1;j--)
{
data[j+1]=data[j]; //每个元素后移
}
data[i+1]=x;
length++; //表长加一
}
而插入算法的复杂度是由插入位置决定的,由于for循环的存在,插入的结点越靠前,复杂度越高,反之越低。因此最大的复杂度为O(n+4),最小为O(1+4),即在表尾插入。运用第一篇博客中复杂度分析的计算方法,插入算法的复杂度为O(n)。
1.2 删除算法
删除与插入类似,其结构与代码如下。
void Delete(int i,int l,int x) //i:删除位置 l:长度 x:插入值
{
if(l==0) return; //表长不为0
if(i<1 || i>length) return;//删除位置小于1或者大于表长length,返回
for(int j=i+1;j<=length;j++)
{
data[i-1]=data[i]; //每个元素后移
}
length--; //表长减一
}
与插入算法类似,删除算法的复杂度也是O(n)。
2.链式存储结构
研究了上述顺序存储结构的种种弊端后,引出另一种灵活的存储结构——链式存储结构。
在使用链式存储结构时,表示每个元素时,
除了存储数据元素ai后,还要存储指向下一个元素ai+1的指针
(一个元素需要两个储存空间才能完整表示)。因为这种独特设定,在物理空间中就不需要两个元素处于相邻位置了,只要有指针信息,就能找到对应的元素,因此它的特点与结构如下。
上图也称作链表。链表的操作较为复杂,接下来演示链表的功能。
2.1插入算法
链式存储结构的优越性体现在插入与删除等操作中,插入数据的算法如下所示
对应的实现代码如下:
struct node
{ int d;
struct node *next;
};
//head 循环链表头指针
//x 循环链表中的元素(在此元素之前插入,T为元素类型)
//b 需要插入的新元素(T为元素类型)
#include <stdlib.h>
void ins_linked_CList(struct node *head, T x, T b)
{ struct node *p, *q;
p=malloc(sizeof(struct node)); //申请一个新结点
p->d=b; //置新结点的数据域
q=head;
while ((q->next!=head)&&(((q->next)->d)!=x))
q=q->next; //寻找包含元素x的前一个结点q
p->next=q->next; q->next=p; //新结点p插入到结点q之后,即上图第三步
return;
}
上述程序中,如果不考虑查找的过程,只考虑插入步骤,算法的复杂度近似为O(1)
2.2删除算法
删除算法与插入算法类似,下图展示了相应的流程和代码。
struct node
{
int d;
struct node *next;
};
//head 线性链表头指针
//x 需要删除的元素(T为元素类型)
//函数返回一个标志。0表示链表中无此元素;1表示原表中有此元素,且已被删除
#include <stdlib.h>
int delete_linked_List(struct node **head, T x)
{ struct node *p, *q;
if (*head==NULL) return(0); //链表为空,无删除的元素
if (((*head)->d)==x) //删除第一个结点
{ p=(*head)->next; free(*head); *head=p; return(1); }
q=*head;
while ((q->next!=NULL)&&(((q->next)->d)!=x))
q=q->next; //寻找包含元素x的前一个结点q
if (q->next==NULL) return(0); //链表中无删除的元素
p=q->next; q->next=p->next; //删除q的下一个结点p
free(p); //释放结点p的存储空间
return(1);
}
与插入算法类似,时间复杂度为O(1)
以上就是顺序表和链表最经典的插入与删除算法,可以很好地比较时间性能与空间性能,总结如下。
时间性能比较:
查找:
顺序表:O(1),只要有下标就可查询。
链表:O(n),要查找特定的数据域里的值,只能通过第一个后继指针指向下一个元素,再通过下一个元素的后继指针继续向后查找。
删除:
顺序表:O(n),移动的平均时间为表长的一半。
链表:O(1),在计算出某位置的指针后,插入与删除时间仅为O(1)
空间性能比较:
顺序表:需要预分配存储空间,大了造成浪费,小了会溢出。
链表:根据需求自适应请求存储空间,不受元素个数的限制。
得到一个经验性的结论:
在需要进行
频繁查找
的时候,宜用顺序存储结构。如游戏中的用户信息,只需要创建一次,更多的是登陆时的查询操作,应当采用顺序存储结构。
在需要进行
频繁添加与删除
时,宜用链式结构。如游戏中的装备列表,在升级时需要不断进行添加与删除,应用到链式结构。
本篇主要讲的是顺序结构与链式结构的比较,由于链式存储结构的知识点较多,下篇会继续扩展链式存储结构。