复习数据结构线性表的基本操作
一、单链表的定义和表示
线性表链式存储结构的特点:
用一组任意的存储单元存储线性表的数据元素。
结点:链表中的每个数据元素的存储影响,它包括两个域,其中存储数据元
素信息的域称为数据域,存储后继结点地址的域称为指针域。
链表:由n个结点所连接成的表。链表也称为顺序存取的数据结构
二、单链表的基本操作
(1)单链表的存储结构
typedef struct LNode
{
ElemType data;//结点的数据域
struct LNode *next;//结点的指针域
}LNode,*LinkList;//LinkList为指向结点的指针
(2)单链表的初始化
Status InitList(LinkList &L)
{
L=(LNode*)malloc(sizeof(LNode));//生成一个头结点,用头指针L指向它
L->next=NULL;//空间点的指针域为空
return OK;
}
头结点的作用:
(1)便于对首元结点的操作(即头结点后面的那一个结点)
(2)便于空表和非空表的统一处理。即当增加了头结点后,无论该表是不是空表,头指针L都指向着一个不为空的结点。
(3)头结点中的数据域可以存放关于链表属性的值,例如表长。
(3)单链表的创建
1.前插法创建单链表
所谓前插法,就是将每一个新结点都插入到链表的最前端,也就是头结点的后继处,即原序列在前插法创建单链表中是倒序的。
如图所示,先来的节点接到了头结点的后面,依次重复,构成一个链表。
a.算法
①创建一个只有头结点的单链表
②根据创建链表的N个数,循环N次以下操作
· 生成一个新结点P
· 将元素值赋给P的数据域
· 将新结点连到头结点的后继,再将头结点连到新结点
b.代码实现
main()
{
...
InitList(L);
for(i = 1; !feof(fp) ; i++)
{
fscanf(fp,"%d",&num);//将文件中的值赋给num
printf("%d ",num);
CreateList_H(L,num);
}
}
void CreateList_H(LinkList &L,ElemType data)
{
LinkList P;
P=(LNode*)malloc(sizeof(LNode));
P->next=L->next;//将P节点先练到头结点的后继
L->next=P;//再将头结点连到结点P //两步的顺序不能颠倒
P->data=data;
}
c.算法分析
前插法的时间复杂度为O(n)
2.后插法创建单链表
所谓后插法,就是将每一个新结点都插入到链表的最尾端,也就是当前链表最后一个结点的后继处,后插法创建单链表中与原序列顺序相同。
如图所示,将新结点依次连接到原链表的最后一个结点的后面。由图可知,后插法需要一个辅助指针R。
a.算法
①创建一个只有头结点的单链表,创建一个辅助指针R。将R指向头结点。
②根据创建链表的N个数,循环N次以下操作
· 申请P结点的空间
· 将元素值赋给P的数据域
· 新结点P的指针域赋值为NULL
· 将R的指针域指向新结点
· 令R=P;
b.代码实现
main()
{
for(i = 1; !feof(fp) ; i++)
{
fscanf(fp,"%d",&num);
printf("%d ",num);
//CreateList_T
P=(LNode*)malloc(sizeof(LNode));
P->data=num;
P->next=NULL;
R->next=P;
R=P;
/*因为我在这里写的是文件,又因为后插法需要一个辅助指针且每
次循环都要改变辅助指针,所以我把后插法写到了main函数中,没
想出来怎样把后插法函数单独写出来*/
}
c.算法分析
后插法的时间复杂度为O(n)
(4)单链表的取值
a.算法
① 让P指向首元结点,用i做计时器
② 从首元结点依次向下一个访问,只要P不为空或还没有到达第n个结点,则:
· 让P指向下一个结点
· 计时器加一
③ 若P为空或i>n,则返回ERROR;否则返回OK
b.代码实现
ElemType GetElem(LinkList L,int n)
{
int i;
LinkList P;
P=L->next;
for(i=1;i<n&&P;i++)
P=P->next;
if(i<1||!P) return ERROR;
else return P->data;
}
c.算法分析
该算法与while语句的频度有关。 当1≤i≤n时,while频度为i-1,因为判定条件是i<n.当
i>n时,此时while语句频度为n,因为此时的循环结束判定调节为P。因此该算法的最坏平均复杂度为O(n)。
平均复杂度:
设每个位置取值概率相同,p=n/1
ASL=p*Σ(1~n)(i-1)=(n-1)/2
所以该算法的平均复杂度为O(n).
(4)单链表的查找
a.算法
① 使P指向首元结点
② 从首元结点依次向下一个访问,只要P不为空或还没有查找到要查找的值,则另P指向下一个结点
③ 若返回的P值不为空,则查找成功;否则查找失败。
b.代码实现
LNode *LocateElem(LinkList L,ElemType dt)
{
LinkList P;
P=L->next;
while(P&&P->data!=dt)// 疑问? : while(P->data!=dt && P)调试过不去
P=P->next;
return P;
}
c.算法分析
① 该算法的最差时间复杂度为O(n)
② 每个值被查找的概率为n/1,频度为i-1(i为查找成功时该值的位置)。
③ 平均时间复杂度:p=n/1
ASL=p*Σ(1~n)(i-1)=(n-1)/2
平均复杂度:该算法的平均时间复杂度为O(n).
(5)单链表的插入
a.算法
① 找到要插入的位置的前一个结点,并让P指向该结点
② 生成一个新的结点S
③ 结点的数据域放入插入值
④ S指向P的后继
⑤ 结点P 指向S
b.代码实现
Status ListInsert(LinkList &L,int n,ElemType num)
{
int i;
LinkList P,S;
P=L;
S=(LNode*)malloc(sizeof(LNode));
S->data=num;
for(i=0;P&&i<n-1;i++)
P=P->next;
if(!P||i>n-1) return ERROR;
S->next=P->next;
P->next=S;
return OK;
}
c.算法分析
时间主要花费在找插入位置前一个结点上,时间复杂度为O(n)
(5)单链表的插入
a.算法
① 找到要删除的结点的前驱,并用P指向他
② 用S指向P的后继
③ 用P指向S的后继
④ 释放S
b.代码实现
Status ListDelete(LinkList &L,int n)
{
int i;
LinkList P,S;
P=L;
for(i=0;(P->next)&&i<n-1;i++)
P=P->next;
if(!(P->next)||i>n-1) return ERROR;
S=P->next;
P->next=S->next;
free(S);
return OK;
}
c.算法分析
与插入算法相同,平均时间复杂度为O(n)
需要注意的是,删除算法中的循环条件**(P->next)&&i<n-1与插入算法中的循环条件P&&i<n-1**是有区别的,因为一个链表中能插入的位置有n+1个,如果插入算法的循环条件与删除算法的循环条件相同,那么循环只能进行n次,无法包含整个插入位置。删除同理。
“可见,线性表的链式存储结构只能顺序存取表中的元素,但链式表的存储在插入和删除操作时,不需需要移动大量的元素,操作过程较为简单,存储效率高,但相应的取值效率降低。”