引入
在前两篇文章中,分别介绍了顺序表与单链表的具体操作的代码实现,即顺序表的初始化、判断空满、顺序表的遍历输出、元素的追加、插入、删除、逆序与升序排序(冒泡排序),以及单链表的创建、判空、遍历输出、长度输出、升序排序(冒泡排序)、插入、删除。在顺序表与单链表的操作过程中,可以发现,线性表的不同存储结构对元素的具体操作方式,都是几乎相同的。然而,正如你在某一个地方,要考虑从这一个地方到下一个地方的时间效率和交通工具燃油或人工等选择的成本问题,这就需要你基于这样一段距离,综合考虑并选择出你这次出行的交通工具。在具体实现上述操作代码的背后的抉择也正是如此,当你需要线性表中的一个存储结构选择,该存储结构不仅便于你具体需求的操作,并且要能够增加代码效率与简洁性。因此,了解与认识顺序表与单链表的优缺点是很有必要的。
声明
以下代码的具体操作解释,详见之前所写——《关于数据结构(c语言)中,线性表中顺序存储结构的大部分操作的代码实现》与《关于数据结构(c语言)中,线性表中链式结构的大部分操作的代码实现》的具体文章,在此不作赘述。
一。顺序表
1.顺序表的重要操作的时间复杂度分析
a. 插入
在顺序表中的第i个位置之前插入一个元素且顺序表中的有效元素个数(cnt)为n时,其时间主要耗费在插入元素位置之后的元素移动上,插入某个元素,平均需移动顺序表的一半的元素,而准确的移动元素的个数则是n-i+1。因为顺序表在插入时需要使插入的元素从最后一个向后依此移动一个位置,故需要遍历顺序表,代码为:
for(int i=pSl->cnt-1;i>=pos-1;--i)
{
pSl->pBase[i+1]=pSl->pBase[i];
}
故顺序表的插入操作的时间复杂度为O(n).
bool insert_sl(struct SqList * pSl, int pos, int val)
{
if(is_full(pSl))
{
return false;
}
if(pos<1||pos>pSl->cnt+1)
{
return false;
}
for(int i=pSl->cnt-1;i>=pos-1;--i)
{
pSl->pBase[i+1]=pSl->pBase[i];
}
pSl->pBase[pos-1]=val;
pSl->cnt++;
return true;
}
b.删除
删除顺序表的第i个位置的元素,当顺序表中的有效元素个数(cnt)为n时,其时间主要耗费在删除i位置的元素后,之后的元素依此向前移动一个位置的操作上,删除某个元素,平均需要移动(n-1)/2个元素,而准确的移动元素的个数则是n-i。因为元素需要依此向前移动一个位置,代码为:
for(int i=pos;icnt;++i)
{
pSl->pBase[i-1]=pSl->pBase[i];
}
故顺序表的删除操作的时间复杂度为O(n).
bool delete_sl(struct SqList * pSl, int pos, int * pVal)
{
if(pos<1||pos>pSl->cnt)
{
return false;
}
if(is_empty(pSl))
{
return false;
}
* pVal=pSl->pBase[pos-1];
for(int i=pos;icnt;++i)
{
pSl->pBase[i-1]=pSl->pBase[i];
}
pSl->cnt--;
return true;
}
2.优点
(1)顺序表是随机存储结构,查找元素方便,可随机存取表中的任意一个元素。
(2)存储空间小。
3.缺点
(1)在插入删除时,需要移动大量的元素,在等概率的条件下,平均需要移动一半的元素,代码效率较低。
(2)要求存储空间连续。
(3)可能造成存储空间的闲置或溢出。
(4)顺序表的容量不易扩充。
二.单链表
1.顺序表的重要操作的时间复杂度分析
a.插入
在单链表中的第i个结点之前的插入一个元素,无需移动元素即可完成操作。因为在单链表中插入结点时需要先找到第i-1个结点,代码为:
while(NULL!=p&&ipNext;
++i;
}
故单链表的插入操作的时间复杂度仍为O(n).
bool insert_list(PNODE pHead, int pos, int val)
{
int i=0;
PNODE p=pHead;
while(NULL!=p&&ipNext;
++i;
}
if(i>pos-1||NULL==p)
{
return false;
}
PNODE pNew=(PNODE)malloc(sizeof(NODE));
if(NULL==pNew)
{
printf("新结点的动态内存分配失败!\n");
exit(-1);
}
pNew->data=val;
PNODE q=p->pNext;
p->pNext=pNew;
pNew->pNext=q;
return true;
}
b.删除
删除单链表中的第i个结点,无需移动元素即可实现。因为需要找到待删除的结点,代码为(与插入操作相同):
while(ipNext;
++i;
}
故单链表的删除操作的时间复杂度为O(n).
bool delete_list(PNODE pHead, int pos, int * pVal)
{
int i=0;
PNODE p=pHead;
while(ipNext;
++i;
}
if(i>pos-1||NULL==p)
{
return false;
}
PNODE q=p->pNext;
*pVal=q->data;
//删除p结点之后一个位置的结点
p->pNext=p->pNext->pNext;
free(q);
q=NULL;
}
2.优点
(1)存储空间动态分配,只要有内存空间,数据就不会溢出。
(2)便于实现插入与删除操作。
3.缺点
(1)存储空间不一定连续,内容分散,有时会导致调试不便。
(2)每一个结点(除头结点)都有数据域与指针域,增大存储空间的开销。
(3)单链表查找结点时,需要从头开始查找,增加查找时间。
三.综合比较
1.单链表的插入与删除操作优于顺序表的对应操作。
2.顺序表的查找操作优于单链表的对应操作,
3.线性表长度变化较大,难以估计其存储规模时,宜采用链表的存储结构。
4.线性表长度变化较为固定,易事先确定其存储规模,宜采用顺序的存储结构,节省存储空间。