线性表顺序存储结构
有关线性表的讨论分为以下几点:
- 线性表的逻辑结构
- 存储结构
- 相关运算
定义
由n(n>=0)个数据特性相同的元素构成的有限序列称为线性表。
特别地,当n=0时,称该线性表为空表。
对于非空的线性表和线性结构,其特点是:
- 存在唯一的一个被称作“第一个”的数据元素;
- 存在唯一的一个被称作“最后一个”中的数据元素;
- 除第一个之外,结构中的每个数据元素均只有一个前驱;
- 除最后一个之外,结构中的每个元素均只有一个后继;
- 线性表有限
判断是否为线型结构?
- 公司的组织架构?
- 班级同学之间的情谊?
- 班级里的点名册?
答案:
1.错,树结构,一对多的关系
2.错,图结构,多对多的关系
3.对,线性结构,按照学号排成线性关系
线性表的抽象数据类型
ADT 线性表(List)
Data
线型表的数据对象集合为{a1,a2,…,an},每个元素的类型均为DataType。
Operation
InitList(*L):初始化操作,加你一个空的线性表L。
ListEmpty(L):判断线性表是否为空表,若线性表为空,返回true,否则返回false.
ClearList(*L):将线性表清空。
GetElem(L,i,*e):将线性表L中的第i个位置元素值返回给e。
LocateElem(L,e):在线性表L中查找与给定值e相等的元素,如果查找成功,返回该元素在表中序号标识成功;否则返回0表示失败。
ListInsert(*L,i,e):在线性表中第i个位置插入新元素e。
ListDelete(*L,i,*e):删除线性表L中第i个位置元素,并返回e的值。
ListLength(L):返回线性表中L的元素个数。
endADT
线性表有两种物理存储结构:顺序存储结构和链式存储结构。
线性表的顺序存储结构
指的是用一段地址连续的存储单元一次存储线性表的数据元素。
表示顺序表存储的结构代码:
#define MAXSIZE 20
typedef int ElemType;
typedef struct
{
ElemType data[MAXSIZE];
int length;
}SqList;
顺序存储结构的三个属性:
- 存储空间的起始位置,数组data,它的存储位置就是线性表存储空间的存储位置。
- 线性表的最大存储容量:数组的长度MAXSIZE/
- 线性表的当前长度:length
地址计算方法
假设ElemType占用的是c个存储单元(字节),那么元素表中第i+1个元素和第i个元素的位置关系为:
== Loc(ai+1)=Loc(ai)+c ==
Loc(ai)=Loc(a1)+(i-1)*c
查找操作(GetElem)
typedef int Status;
Status GetElem(SqList L,int i,ElemType *e)
{
if(L.length==0||i<1||i>L.length){
return 0;
}
*e=L.data[i-1];
return 1;
}
插入操作(ListInsert)
思路:
- 如果插入位置不合理,抛出异常;
- 如果线性表长度大于等于数组长度,则抛出异常或动态增加数组容量;
- 从最后一个元素开始向前遍历到第i个位置,分别将它们都向后移动一个位置;
- 将要插入的元素填入位置i;
- 线性表长度+1
typedef int Status;
Status ListInsert (SqList *L,int i,ElemType e)
{
int k;
if(L->length==MAXSIZE)//线性表已满
{
return 0;
}
if(i<1||i>L->length+1)//i不在范围
{
return 0;
}
if(i<=L->length){//若插入数据不在表尾
for(k=L->length-1;k>=i-1;k--)//将插入位置后的元素向后移位
{
L->data[k+1]=L->data[k];
}
}
L->data[i+1]=e;//插入新元素 , 下标从1开始
L->length++;
return 1;
}
删除操作:
#define ERROR 0
#define OK 1
typedef int Status;
Status ListDelete(SqList *L,int i)
{
int j;
if (i<1||i>L.length)//删除元素位置不符
{
return ERROR;
}
for(j=1;j<=L.length;j++)//遍历线性表
{
L.elem[j-1]=L.elem[j];//将删除后的元素位置向前移位
--L.length;//线性表长度-1
return OK;
}
}
综上:
在存储、读取数据时,不管是哪个位置,时间复杂度都是O(1),
而在插入或删除时,时间复杂度都是O(n)。
这就说明,线性表适合元素个数较稳定,不经常插入和删除元素,更多的操作是存取数据的应用。
顺序存储结构优点:
- 无须为表示表中元素之间的逻辑关系而增加额外的存储空间。
- 可以快速地存取表中任意位置的元素
== 缺点==:
- 插入和删除操作需移动大量元素
- 当线性表长度变化较大时,难以确定存储空间的容量。
顺序表的链式存储结构
特点:用一组任意的存储单元存储线性表的数据元素,这组存储单元可以存在内存中未被占用的任意位置。
除了要存储数据元素信息外,还要存储它的后继元素的存储地址(指针)。
链表的每个结点中只包含一个指针域,叫做单链表。
链表中第一个节点的存储位置叫做头指针,最后一个结点指针为空(NULL)
头结点的数据域不存储任何信息。
头指针与头结点的异同
-
头指针是指链表指向第一个结点的指针,若链表头结点,则是指向头结点的指针。
-
具有标识作用,常作为链表的名字(指针变量的名字)。
-
无论链表是否为空,头指针均不为空。
-
头指针是链表的必要元素。
-
头结点放在第一个元素的结点之前,其数据域一般无意义(但也可以用来存放链表的长度)。
-
头结点不是链表的必要元素。
用结构指针来描述单链表:
typedef struct Node
{
ElemType data;//数据域
struct Node* Next;//指针域
}Node;
typedef struct Node* LinkList;
结点由存放数据的数据域和存放后继结点地址的指针域组成。
假设p是指向线性表第i个元素的指针,则p->data第i个元素的数据。p->next指第i+1个元素,也就是指向ai+1。
单链表的读取(GetElem)
思路:
- 声明 一个结点p指向链表第一个结点,初始化j从1开始;
- 当j<i时,遍历链表。让p的指针向后移动,不断指向下一结点,j+1;
- 若到链表尾为空,则说明元素不存在,查找失败。
- 否则查找成功,返回结点p的数据。
//GetElem.c
由于单链表的结构中没有定义表长,因此不知道要循环多少次,因此也就不用for来控制循环。
单链表的插入
假设插入的单链表元素为s,两个标准语句
-
s->next=p->next;
-
p->next=s;
思路: -
声明一结点p指向链表头结点,初始化j从1开始;
-
当j<1时,就遍历链表,让p的指针向后移动,不断指向下一节点,j+1;
-
若到链表末尾p为空,则说明第i个元素不存在;
-
否则查找成功,在系统中声称一个空结点s;
-
将数据元素e赋值给s->data;
-
插入两个标准语句;
-
返回成功
//ListInsert
单链表的删除
- p->next=p->next->next;
- q=p->next ; p->next=q->next;
//ListDelete
单链表删除插入总结
- 单链表无论是插入还是删除,都是先遍历第i个元素,然后再实现插入和删除操作。
- 时间复杂度都为O(n)。
- 单链表中无法预知元素i的位置,每次操作需遍历链表。存在的意义在于:若在第i个位置插入连续的10个元素,对于顺序存储结构,每一次插入需移动n-i个位置,所以每次都是O(n)。对于单链表,只需要在第一次时,找到第i个位置的指针,此时为O(n),接下来时间复杂度都为O(1)。
- 对于插入或删除数据越频繁的操作,单链表的效率优势越明显。