【数据结构】顺序表及链表详细分析(全)

前言

线性表、栈、队列、串和数组都属于线性结构。 线性结构的基本特点是除第一个元素无直接前驱,最后一个元素无直接后继之外,其他每个数据元素都有一个前驱和后继。

线性表定义

它们的数据元素虽然不同,但同一线性表中的元素必定具有相同的特
性, 即属千同一数据对象,相邻数据元素之间存在着序偶关系。
诸如此类由n (n>=0)个数据特性相同的元素构成的有限序列称为线性表。
线性表中元素的个数n (n>=0)定义为线性表的长度,n=0时称为空表。

对于非空的线性表或线性结构, 其特点是:
(1) 存在唯一的一个被称作 “第一个" 的数据元素;
(2)存在唯一的一个被称作 “最后一个" 的数据元素;
(3)除第一个之外, 结构中的每个数据元素均只有一个前驱;
(4)除最后一个之外,结构中的每个数据元素均只有一个后继。

线性表顺序结构

顺序表定义

线性表的顺序表示指的是用一组地址连续的存储单元依次存储线性表的数据元素, 这种表示也称作线性表的顺序存储结构或顺序映像。通常, 称这种存储结构的线性表为顺序表(Sequential List)。其特点是,逻辑上相邻的数据元素, 其物理次序也是相邻的。

总之只要确定了存储线性表的起始位置, 线性表中任一数据元素都可随机存取, 所以线性表的顺序存储结构是一种随机存取的存储结构。

顺序表的存储结构:

#define  MAXSIZE 100   //顺序表中可能到达的最大的长度
typedef  struct 
{
ElemType *elem;   //存储空间的基地址
int length;  //当前长度
}SqList;  //顺序表的结构类型为SqList

上述定义后,可以通过变量定义语句 SqList L;,将L定义为SqList类型的变量,用变量引用。

顺序表基本操作

1.顺序表的初始化操作就是构造一个空的顺序表。

Status InitList(SqList &L) //构造一个空的顺序表
{ L.elem= new ElemType[MAXSIZE]; //为顺序表分配一个大小为MAXSIZE的数组空间
 if (!L.elem) exit (OVERFLOW);//存储分配失败退出
 L.length=0; //空表长度为0
 return OK;
 }

动态分配线性表的存储区域可以更有效地利用系统的资源,当不需要该线性表时,可以使 销毁操作及时释放占用的存储空间

2.顺序表的取值

Status GetElem(SqList  L,int i,ElemType  &e) 
{
if {i<1|i>L.length)  return ERROR;  //判断i值是否合理,若不合理, 返回 ERROR
e=L.elem[i一 1]; //elem[i-1] 单元存储第 i 个数据元素
return OK;
}

3.顺序表查找

int LocateELem(SqList L,ElemType e) 
 {//在顺序表i中查找值为e的数据元素, 返回其序号
 for(i=0;i< L.length;i++) 
 if(L.elem[i)==e) return i+l; //查找成功,  返回序号 i+l
 return 0;//查找失败,  返回 0
 }

4.顺序表的插入

Status Listinsert(SqList &L,int i ,ElemType e) 
 {//在顺序表 L 中第i个位置之前插入新的元素 e, i值合法范围是1<=i<=L.length+l 
 if((i<l)|| (i>L.length+l)) return ERROR; //i值不合法
 if(L.length==MAXSIZE) return ERROR; //当前存储空间已满
 for(j=L. length-1; j>=i-1; j--) 
 	L.elem[j+l]=L.elem[j];//插入位置及之后的元素后移
 L.elem[i-l]=e;//将新元素e放入第l个位置
 ++L.length; 
 return OK;
}

5.顺序表的删除

Status ListDelete(SqList &L,int i) 
 {//在顺序表L中删除第J个元素,J值的合法范围是 1<=i<=L. length 
 if((i<1) || (i>L.length)) return  ERROR; //值不合法
 for (j=i; j <=L. length-1; j ++) 
	 L.elem[j-1]=L.elem[j]; //被删除元素之后的元素前移
 --L.length; //表长减1
 return OK; 
 }

顺序表总结

1.顺序表可以随机存取表中任一元素,其存储位置可用一个简单、直观的公式来表示。然而,从另一方面来看,这个特点也造成了这种存储结构的缺点:在做插入或删除操作时,需移动大量元素。 另外由于数组有长度相对固定的静态特性, 当表中数据元素个数较多且变化较大时,操作过程相对复杂,必然导致存储空间的浪费。 所有这些问题,都可以通过线性表的另一种表示方法链式存储结构来解决。
2.代码演练的时候,链表表长是否会变化以及元素如何移动的思考

线性表链式结构

链表的定义

线性表链式存储结构的特点是:用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的)。 因此,为了表示每个数据元素ai, 与其直接后继数据元素ai+1,之间的逻辑关系,对数据元素a,来说,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置)。 这两部分信息组成数据元素ai的存储映像,称为结点(node)。它包括两个域:其中存储数据元素信息的域称为数据域;存储直接后继存储位置的域称为指针域。指针域中存储的信息称作指针或链。n个结点(ai+1(1<=i<=n) 的存储映像)链结成一个链表,又由于此链表的每个结点中只包含一个指针域,故又称线性链表或单链表。

根据链表结点所含指针个数、指针指向和指针连接方式,可将链表分为单链表、循环链表、双向链表、二叉链表、十字链表、邻接表、邻接多重表等。其中单链表、循环链表和双向链表用于实现线性表的链式存储结构,其他形式多用于实现树和图等非线性结构.

单链表定义

整个链表的存取必须从指针开始进行,头指针指示链表中第一个结点(即第一个数据元素的存储映像,也称首元结点)的存储位置。同时,由于最后一个数据元素没有直接后继,则单链表中最后一个结点的指针为空(NULL)。

单链表的存储结构:

typedef struct LNode
{
 ElemType data;  //结点的数据域
 struct LNode *next; //结点的指针域
 }LNode,*LinkList; //LinkList 为指向结构体 LNode 的指针类型

LinkList 与 LNode* , 两者本质上是等价的。通常习 惯上用 LinkList 定义单链表, 强调定义的是某个单链表的头指针; 用 LNode 定义指向单链表中任意结点的指针变 量。例如,若定义 LinkList L,则L为单链表的头指针,若定义 LNodep, 则p为指向单链表中某个结点的指针,用p 代表该结点。当然也可以使用定义 LinkList p, 这种定义形式完全等价于 LNodep。

增加头结点的单链表的逻辑状态
区分首元结点,头结点,头指针:
1.首元结点是指链表中存储第一个数据元素a1 的结点,即结点"ZHAO" 。
2.头结点是在首元结点之前附设的一个结点,其指针域指向首元结点。头结点的数据域可以不存储任何信息,也可存储与数据元素类型相同的其他附加信息,附加信息可以是长度
3.头指针是指向链表中第一个结点的指针,若链表设有头结点,则头指针所指结点为线性表的头结点;若链表不设头结点,则头指针所指结点为该线性表的首元结点

单链表基本操作

1.初始化操作

Status InitList(LinkList  &L)//构造一个空的单链表L
 L=new LNode; //生成新结点作为头结点, 用头指针L指向头结点
 L->next=NULL; //头结点的指针域置空
 return OK; 
 }

2.取值

Status GetElem(LinkList L,int i,ElemType &e) 
 {/ /在带头结点的单链表L中根据序号i,获取元素的值,用e返回L中第i个数据元素的 
 p=L->next;j=1;  //初始化,p指向首元结点,计数器j初值赋为1
 while(p&&j<i)  //顺链域向后扫描,直到p为空或p指向第i个元素 
 p=p->next;  / /p指向下一个结点
 ++j;  //计数器j相应加1
 if(!p||j>i) return ERROR;  //值不合法 i>n或i<=0
 e=p->data;  //取第i个结点的数据域
 return OK;
 }

3.查找

LNode *LocateELem(LinkList L, Elemtype e) 
{//在带头结点的单链表L中查找值为e的元素 
p=L->next;  //初始化,p指向首元结点
while(p&& p->data!=e) //顺链域向后扫描,直到p为空或p所指结点的数据域等于e
p= p ->next; //p指向下一个结点
return  p;  //查找成功返回值为e的结点地址p, 查找失败p为NULL
 }

4.插入

Status  Listinsert(LinkList  &L,int i,ElemType  e) 
{//在带头结点的单链表L中第i个位置插入值为e的新结点 
p=L; j=0; 
while(p &&  (j<i-1)) 
{p=p->next;++j;} //查找第i-1个结点,p指向该结 
if (!p || j>i- 1)  return ERROR; //i>n+l或者i<1
s = new  LNode; //生成新结点* s
s->data = e; //将结点* s的数据域置为e
s->next =p->next; //*s 的指针域指向结点a
p->next=a;//将*p的指针域指向*s
return ok;
}

5.删除

Status  ListDelete(LinkList  &L,int i) 
{//在带头结点单链表L中,删除第i个元素
p=L; j=O;
while ((p->next)  && (j<i-1)) //查找第i-1个结点,p指向该结点
p=p->next; ++j ;} 
if(!(p->next)||(j>i-1))  return ERROR;  //当i>n或i<1时,删除位置不合理
q=p->next;//临时保存被删结点的地址以备释放
p->next=q->next;  //改变删除结点前驱结点的指针域
delete q; //释放删除结点的空间
return OK; 
}

区别:

删除算法中的循环条件(p->next&&j<i-1)和插入算法中的循环条件(p&&(j<i-1))是有所区别的。因为插入操作中合法的插入位置有n+1个,而删除操作中合法的删除位置只有n个,如果使用与插入操作相同的循环条件,则会出现引用空指针的情况,使删除操作失败。

创建单链表

前插法

前插法是通过将新结点逐个插入链表的头部(头结点之后)来创建链表,每次申请一个新结点,读入相应的数据元素值,然后将新结点插入到头结点之后。

void CreateList_H(LinkList &L,int n) 
{//逆位序输入n个元素的值,建立带表头结点的单链表L
L=new LNode; 
L->next=NULL; //先建立一个带头结点的空链表
for(i=O;i<n;++i) 
	{
	p=new LNode; //生成新结点*p
	cin>>p->data; //输入元素值赋给新结点*p的数据域
	p->next=L->next;L->next=p; //将新结点*p插人到头结点之后
	}
}

后插法

后插法是通过将新结点逐个插入到链表的尾部来创建链表。同前插法一样,每次申请一个新结点,读入相应的数据元素值。不同的是,为了使新结点能够插入到表尾,需要增加一个尾指针r指向链表的尾结点。

void CreateList_R(LinkList &L,int n) 
{//正位序输人n个元素的值,建立带表头结点的单链表L
L=new LNode; 
L->next=NULL;//先建立一个带头结点的空链表
r=L; //尾指针r指向头结点
for(i=O;i<n;++i)
	{
	p=new LNode; //生成新结点
	cin>>p->data; //输人元素值赋给新结点*p的数据域
 	p->next=NULL; r->next=p; //将新结点*p插人尾结点*r之后
 	r=p; //r指向新的尾结点*p
 	}
}

循环链表

循环链表是另一种形式的链式存储结构。其特点是表中最后一个结点的指针域指向头结点,整个链表形成一个环。
单链表与循环链表区别:

循环单链表的操作和单链表基本一致,差别仅在于:当链表遍历时,判别当前指针p是否指向表尾结点的终止条件不同。在单链表中,判别条件为p!=NULL或p->next!=NULL,而循环单链表的判别条件为p!=L或p->next!=L。

双向链表

在双向链表的结点中有两个指针域,一个指向直接后继, 另一个指向直接前驱,基本操作和线性链表的操作大同小异,但插入和删除不同
双向链表的存储结构:

typedef struct DuLNode 
{ElemType data; //数据域
struct DuLNode *prior; //直接前驱
struct DuLNode *next; //直接后继
}DuLNode,*DuLinkList; 

双向链表插入

插入结点时需要修改四个指针

Status Listinsert_DuL(DuLinkList &L,int i,ElemType e) 
{//在带头结点的双向链表L中第i个位置之前插入元素e
if(!(p=GetElem_DuL (L, i)))//在L中确定第i个元素的位置指针p 
return ERROR; //p为 NULL 时,第i个元素不存在
s=new DuLNode;//生成新结点*s 
s->data=e; //将结点*s数据域置为e
s->prior=p->prior; //将结点*s插入L中,
p->prior->next=s; 
s->next=p;
p->prior=s; 
return OK; 
}

双向链表删除

删除结点时需要修改两个指针

Status ListDelete_DuL(DuLinkList  &L,int i) 
{//删除带头结点的双向链表1中的第1个元素
if (!(p=GetElem_DuL (L, i))) //在L 中确定第i个元素的位置指针p
return ERROR; //p为 NULL 时,第i个元素不存在
p->prior->next=p->next; //修改被删结点的前驱结点的后继指针
p->next->prior=p->prior; //修改被删结点的后继结点的前驱指针
return OK; 
}

顺序表和链表比较

空间性能比较:

1.存储空间的分配
2.存储密度的大小

时间性能比较:

1.存取元素的效率
2.插入删除的效率

一键三连不迷路,有问题可补充交流

  • 7
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农研究僧

你的鼓励将是我创作的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值