线性表(linear list)是最常用且最简单的一种数据类型。
一个线性表是n个数据元素的有限序列。
至于每个数据元素的具体含义,在不同情况下各不相同。
在稍复杂的线性表中,一个数据元素可以由若干个数据项(item)构成。
在这种情况下,常把数据元素称为记录(record),含有大量纪录的线性表又称文件(file)
线性表中的数据元素可以是各种各样的,但同一线性表中的元素必定具有相同特性,即属于同一数据对象,相邻数据元素之间存在着序偶关系。
序偶关系:有序、成对
若将线性表记为
( a 1 , ⋯   , a i − 1 , a i , a i + 1 , ⋯   , a n ) (a_1, \cdots, a_{i - 1}, a_i, a_{i + 1}, \cdots, a_n) (a1,⋯,ai−1,ai,ai+1,⋯,an)
则表中 a i − 1 a_{i - 1} ai−1领先于 a i a_i ai, a i a_i ai领先于 a i + 1 a_{i + 1} ai+1,称 a i − 1 a_{i - 1} ai−1是 a i a_i ai的直接前驱元素, a i + 1 a_{i + 1} ai+1是 a i a_i ai的直接后继元素
当 i = 1 , 2 , ⋯   , n − 1 i = 1, 2, \cdots ,n - 1 i=1,2,⋯,n−1 时, a i a_i ai有且只有一个直接后继,
当 i = 2 , 3 , ⋯   , n i = 2, 3, \cdots ,n i=2,3,⋯,n 时, a i a_i ai有且只有一个直接前驱。
线性表中第一个元素称为表头元素,最后一个元素称为表尾元素
只有一个首结点和尾结点
除首尾结点外,其他结点只有一个直接前驱和一个直接后继
线性表中元素的个数 n ( n ≥ 0 ) n(n\ge0) n(n≥0)定义为线性表的长度, n = 0 n = 0 n=0时称为空表。
在非空表中的每一个数据元素都有一个确定的位置
如 a 1 a_1 a1是第一个元素, a n a_n an是最后一个元素
a i a_i ai是第 i i i个元素,称 i i i为数据元素 a i a_i ai在线性表中的位序
线性表的顺序表示指的是用一组地址连续的存储单元一次存储线性表的数据结构
数组可以静态分配(大小固定)
也可以动态分配(利用动态分配语句)
动态分配并不是链式存储,同样还是属于顺序存储结构,只是分配的空间大小可以在运行时决定
假设线性表的每个元素需占用 l l l个存储单元,并以所占的第一个单元的存储地址作为数据元素的存储位置。
则线性表中第 i + 1 i + 1 i+1个数据元素的存储位置 L O C ( a i + 1 ) LOC(a_{i + 1}) LOC(ai+1)和第 i i i个数据元素的存储位置 L O C ( a i ) LOC(a_i) LOC(ai)之间满足下列关系:
L O C ( a i + 1 ) = L O C ( a i ) + l LOC(a_{i + 1}) = LOC(a_i) + l LOC(ai+1)=LOC(ai)+l
一般来说,线性表的第 i i i个数据元素 a i a _ i ai的存储位置为
L O C ( a i ) = L O C ( a 1 ) + ( i − 1 ) × l LOC(a_i) = LOC(a_1) + (i - 1) \times l LOC(ai)=LOC(a1)+(i−1)×l
式中 L O C ( a 1 ) LOC(a_1) LOC(a1)是线性表的第一个数据元素 a 1 a_1 a1的存储位置,通常称做线性表的起始位置或基位置。
只要确定了线性表的起始位置,线性表中任一数据元素都可随机存取,所以线性表的顺序存储结构是一种随机存取的存储结构
利用数据元素的存储位置表示线性表中相邻数据元素之间的前后关系,即线性表的逻辑结构与存储结构一致
在访问线性表时,可以快速地计算出任何一个数据元素的存储地址。因此可以粗略地认为,访问每个元素所花时间相等
这种存取元素的方法被称为随机存储法
查找,插入,删除算法的平均时间复杂度为 O ( n ) O(n) O(n)
顺序表的空间复杂度 S ( n ) = O ( 1 ) S(n) = O(1) S(n)=O(1)(没有占用辅助空间)
存储密度大(结点本身所占存储量/结点结构所占存储量)
可以随机存取表中任一元素
在插入、删除某一元素时,需要移动大量元素
浪费存储空间,对存储空间要求高,会产生存储空间的“碎片”
属于静态存储形式,数据元素的个数不能自由扩充
为了克服这一缺点,我们可以采用链式存储结构
它不要求逻辑上相邻的元素在物理位置上也相邻,因此它没有顺序存储结构所具有的弱点,但同时也失去了顺序表可随机存取得优点
线性表的链式存储结构的特点是
用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的)
结点在存储器中的位置是任意的,即逻辑上相邻的数据元素在物理上不一定相邻
访问时只能通过头指针进入链表,并通过每个结点的指针域向后扫描其余结点,所以寻找第一个结点和最后一个结点所花费的时间不等
这种存取元素的方法被称为顺序存取法
一部分存储数据元素信息,另一部分存储直接后继存储位置。
存储数据元素信息的域称为数据域
存储直接后继存储位置的域称为指针域
指针域中存储的信息称做指针或链。
n n n个结点 ( a i ( 1 ≤ i ≤ n ) 的 储 存 映 像 ) (a_i(1\le i \le n)的储存映像) (ai(1≤i≤n)的储存映像)链接称一个链表,即为线性表的链式存储结构
( a 1 , a 2 , ⋯ a n ) (a_1, a_2, \cdots a_n) (a1,a2,⋯an)
又由于此链表的每个结点中只包含一个指针域
故又称 线性链表 或 单链表。
有时我们在单链表的第一个结点之前附设一个结点,称之为头结点。
头结点的数据域可以不存储任何信息,也可以存储如线性表的长度等附加信息
头结点的指针域存储指向第一个节点的指针(即第一个元素结点的存储位置)
处理操作起来方便(统一代码,减少特判,相当于哨兵)
无论链表是否为空,其头指针是指向头结点的非空指针,因此空表和非空表的处理也就统一了
不管带不带头结点,头指针始终指向链表的第一个结点
而头结点是带头结点链表中的第一个结点,节点内通常不存储信息,它是为了方便做的一种处理
表中最后一个结点的指针域指向头结点,整个链表形成一个环
从表中任一结点出发均可找到表中其他结点。
为了克服单链表这种单向性的缺点,可以利用双向链表
在双向链表的结点中有两个指针域,其一指向直接后继,另一指向直接前驱
数据元素的个数可以自由扩充
插入、删除等操作不必移动数据,只需修改链接指针,修改效率较高
存储密度小
存取效率不高,必须采用顺序存取,即存取数据元素时,只能按链表的顺序进行访问(顺藤摸瓜)
typedef struct PNode{
float coef;//系数
int expn; //指数
struct PNode *next; //指针域
}PNode, *Polynomial;
生成一个新结点*s;
输入多项式当前项的系数和指数赋给新结点*s的数据域;
设置一前驱指针pre,用于指向待找到的第一个大于输入项指数的结点的前驱,pre初值指向头结点;
指针q初始化,指向首元结点;
循链向下逐个比较链表中当前结点与输入项指数,找到第一个大于输入项指数的结点*q;
将输入项结点s插入到结点q之前。
void CreatePolyn(Polynomial &P,int n) {
//输入m项的系数和指数,建立表示多项式的有序链表P
P = new PNode;
P->next = NULL; //先建立一个带头结点的单链表
for(i = 1; i <= n; ++i) { //依次输入n个非零项
s=new PNode; //生成新结点
scanf("%d %d", &(s->coef), &(s->expn)); //输入系数和指数
pre = P; //pre用于保存q的前驱,初值为头结点
q = P->next; //q初始化,指向首元结点
while(q && q->expn < s->expn) { //找到第一个大于输入项指数的项*q
pre = q;
q = q->next;
}
s->next = q; //将输入项s插入到q和其前驱结点pre之间
pre->next = s;
}
}
有下列3种情况:
当p1->expn等于p2->expn时,则将两个结点中的系数相加,若和不为零,则修改p1所指结点的系数值,同时删除p2所指结点,若和为零,则删除p1和p2所指结点;
当p1->expn小于p2->expn时,则应摘取p1所指结点插入到“和多项式”链表中去;
当p1->expn大于p2->expn时,则应摘取p2所指结点插入到“和多项式”链表中去。