2.1线性表的定义和特点
定义:
由n(n≥0)个数据特性相同的元素构成的有限序列,称为线性表。线性表中元素的个数n(n≥0)定义为线性表的长度,当n=0时称之为空表。
特点:
(1)存在唯一的一个被称作“第一个”的数据元素;
(2)存在唯一的一个被称作“最后一个”的数据元素;
(3)除第一个元素之外,结构中的每个数据元素均只有一个前驱;
(4)除最后一个元素之外,结构中的每个数据元素均只有一个后继。
2.2线性表的顺序存储
线性表的顺序表示:
指的是用一组地址连续的存储单元依次存储线性表的数据元素,这种表示也称作线性表的顺序存储结构或顺序映像。通常,称这种存储结构的线性表为顺序表(Seguential List)。
它与数组类似,可用一维数组表示。它们都用变量表示顺序表长度属性,但数组长度不可动态定义。
特点:
逻辑上相邻的数据元素,其物理位置也是相邻的。
顺序存储结构是占用一片连续的存储空间。
*地址连续,依次存放,随机存取,类型相同*
参数传递:
函数调用时传送给形参表的实参必须与形参三个一致(类型,个数,顺序)。
方式:①传值(参数为整型、实型、字符型)②传地址
参数为指针变量、引用类型、数组名。
基本操作:
2.3线性表的链式存储
2.3.1单链表
1.定义:
由存储数据和其直接后继存储位置两部分信息组成数据元素a的存储映像,称为节点(node)。
节点包括两个域:其中存储数据元素信息的域称为数据域;存储直接后继存储位置的域称为指针域。指针域中存储的信息称作指针或链。
n个节点的存储映像]链接成一个链表,即为线性表:(a1, a2,… ,an) 的链式存储结构。又由于此链表的每个节点中包含一个指针域]故又称线性链表或单链表。
2.特点:
用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的),其为顺序存储。
3.相关说明:
(1)首元节点是指链表中存储第一个数据元素a1的节点。
(2)头节点(不计入链表长度值)是在首元节点之前附设的一个节点,其指针域指向首元节点。头节点的数据域可以不存储任何信息,也可存储与数据元素类型相同的其他附加信息。例如,当数据元素为整型时,头节点的数据域中可存放该线性表的长度。
*链表增加头节点的作用:便于首元节点的处理;便于空表和非空表的统一处理*
(3)头指针是指向链表中第一个节点的指针,若链表设有头节点,则头指针所指节点为线性表的头节点;若链表不设头节点,则头指针所指节点为该线性表的首元节点。
4.基本操作:
2.3.2循环链表
循环链表(Circular Linked List)是另一种形式的链式存储结构。
特点:是表中最后一个节点的指针域指向头节点,整个链表形成一个环。由此,从表中任一节点出发均可找到表中其他节点,图示为单链的循环链表。
类似地,还可以有多重链的循环链表。
与单链表差别仅在于:当链表遍历时,判别当前指针p是否指向表尾节点的终止条件不同。在单链表中,判别条件为p!=NULL或p->next!=NULL;而循环单链表的判别条件为p!=L或p->next!=L
2.3.3双向链表
双向链表的节点中有两个指针域,一个指向直接后继,另一个指向直接前驱。
在单链表中,查找直接后继的执行时间为0(1),而查找直接前驱的执行时间为O(n)、为克服单链表这种单向性的缺点,可利用双向链表(Double Linked List)。双向链表不可随机存取,按位查找、按值查找操作只能用遍历的方式实现。
typedef struct DNode{
ElemType data;
struct DNode *prior, *next;
}DNode, *DLinklist;
bool InitDLinkList(DLinklist &L){
L = (DNode *)malloc(sizeof(DNode)); //分配一个头结点
if(L == NULL){ //内存不足,分配失败
return false;
}
L->prior = NULL; //头结点的prior永远指向NULL
L->next = NULL; //头结点之后暂时还没有结点
return true;
}
void testDLinkList(){
//初始化双链表
InitDLinkList(L);
......
}
typedef struct DNode{
ElemType data;
struct DNode *prior, *next;
}DNode, *DLinklist;
// 在p结点之后插入s结点
bool InsertNextDNode(DNode *p, DNode *s){
if(p == NULL || s == NULL){ //非法参数
return false;
}
s->next = p->next;
if(p->next != NULL){ //如果p结点有后继结点
p->next->prior = s;
}
s->prior = p;
p->next = s;
return true;
}
typedef struct DNode{
ElemType data;
struct DNode *prior, *next;
}DNode, *DLinklist;
// 删除p结点的后继结点
bool DeletenextDNode(DNode *p){
if(p == NULL || s == NULL){ //非法参数
return false;
}
DNode *p = p->next; //找到p的后继结点q
if(q == NULL){
return false; //p没有后继
}
if(q->next != NULL){ //q结点不是最后一个结点
q->next->prior = p;
}
free(q); //释放结点空间
return true;
}
2.4.顺序存储和链式存储的比较
2.4.1空间性能的比较
(1)存储空间的分配
顺序表的存储空间必须预先分配,元素个数有一定限制,易造成存储空间浪费或空间溢出现象;而链表不需要为其预先分配空间,只要内存空间允许,链表中的元素个数就没有限制。
基于此,当线性表的长度变化较大,难以预估存储规模时,宜采用链表作为存储结构。
(2)存储密度的大小
存储密度是指数据元素本身所占用的存储量和整个节点结构所占用的存储量之比,即:
*存储密度 = 数据元素本身占用的存储量 / 节点结构占用的存储量*
存储密度越大,存储空间的利用率就越高。顺序表的存储密度为1,而链表的存储密度小于1。如果每个元素数据域占据的空间较小,则指针的结构性开销就占用了整个节点的大部分空间,这样存储密度较小。例如,若单链表的节点数据均为整数,指针所占用的空间和整型量所占用的相同,则单链表的存储密度为0.5。因此,如果不考虑顺序表中的空闲区,则顺序表的存储空间利用率为100%,而单链表的存储空间利用率仅为50%。
基于此,当线性表的长度变化不大,易于事先确定其大小时,为了节约存储空间,宜采用顺序表作为存储结构。
2.4.1时间性能的比较
(1)存取元素的效率
顺序表是由数组实现的,它是一种随机存取结构,指定任意一个位置序号i,都可以在O(1)
时间内直接存取该位置上的元素,即取值操作的效率高;而链表是一种顺序存取结构,按位置
访问链表中第i个元素时,只能从表头开始依次向后遍历链表,直到找到第i论个位置上的元素,时
间复杂度为O(n),即取值操作的效率低。
基于此,若线性表的主要操作是和元素位置紧密相关的一类取值操作,很少做插入或删紧时,宜采用顺序表作为存储结构。
(2)插入和删除操作的效率
对于链表,在确定插入或删除的位置后,插入或删除操作无须移动数据,只需要修改指针,时间复杂度为0(1)。而对于顺序表,进行插人或删除时,平均要移动表中近一半的节点.时间复杂度为0(n)。尤其是当每个节点的信息量较大时,移动节点的时间开销就相当可观。
基于此,对于频繁进行插入或删除操作的线性表,宜采用链表作为存储结构。
2.5线性表的应用
多项式的加法