数据结构之线性表
数据结构是程序设计的重要基础,它所讨论的内容和技术对从事软件项目的开发有重要作用。学习数据结构要达到的目标是学会从问题出发,分析和研究计算机加工的数据的特性,以便为应用所涉及的数据选择适当的逻辑结构、存储结构及其相应的操作方法,为提高利用计算机解决问题的效率服务。
数据结构是指数据元素的集合及元素间的相互关系和构造方法。元素之间的相互关系是数据的 逻辑结构 ,数据元素及元素之间关系的存储称为 存储结构(或物理结构) 。数据结构按照逻辑关系的不同分为 线性结构 和 非线性结构 两大类,其中,非线性结构又可分为树结构和图结构。
线性结构是一种基本的数据结构,主要用于对客观世界中具有单一前驱和后继的数据关系进行描述。线性结构的特点是数据元素之间呈现一种线性关系,即元素"一个接一个排列"。
线性表是最简单、最基本也是最常用的一种线性结构。常采用顺序存储和链式存储,主要的基本操作是插入、删除和查找等。
1、线性表定义
一个线性表是 n(n≥0)个元素的有限序列,通常表示为 (a~1~,a~2~,·...,a~n~)。非空线性表的特点如下:
(1) 存在唯一的一个称作 "第一个" 的元素。
(2) 存在唯一的一个称作 "最后一个" 的元素。
(3) 除第一个元素外,序列中的每个元素均只有一个直接前驱。
(4) 除最后一个元素外,序列中的每个元素均只有一个直接后继
2、线性表的存储结构
线性表的存储结构分为顺序存储和链式存储。
(1) 线性表的顺序存储
线性表的顺序存储是指用一组地址连续的存储单元依次存储线性表中的数据元素,从而使得逻辑上相邻的两个元素在物理位置上也相邻,如下图所示。在这种存储方式下,元素间的逻辑关系无须占用额外的空间来存储。
一般地,以 LOC(a~1~)表示线性表中第一个元素的存储位置,在顺序存储结构中,第 i 个元素 a~i~ 的存储位置为
LOC(a~i~)=LOC(a~1~) +(i-1) × L
其中,L 是表中每个数据元素所占空间的字节数。根据该计算关系,可随机存取表中的任一个元素。
线性表采用顺序存储结构的优点是可以随机存取表中的元素,缺点是插入和删除操作需要移动元素。在插入前要移动元素以挪出空的存储单元,然后再插入元素;删除时同样需要移动元素,以填充被删除的元素空出来的存储单元。
在表长为 n 的线性表中插入新元素时,共有 n+1 个插入位置,在位置 1(元素 a~1~ 所在位置)插入新元素,表中原有的n 个元素都需要移动,在位置 n+1 (元素 a~n~ 所在位置之后)插入新元素时不需要移动任何元素。因此,等概率下(即新元素在 n+1 个位置插入的概率相同时)插入一个新元素需要移动的元素个数期望值 E~insert~ 为
T i n s e r t = Σ i = 1 n + 1 P i × ( n − i + 1 ) = 1 n + 1 Σ i = 1 n + 1 ( n − i + 1 ) = n 2 T_{insert} = {\huge\Sigma}^{n+1}{i=1}P_i×(n-i+1) = \frac1{n+1} {\huge\Sigma}^{n+1}{i=1}(n-i+1) = \frac n2 Tinsert=Σi=1n+1Pi×(n−i+1)=n+11Σi=1n+1(n−i+1)=2n
其中,P~i~ 表示在表中的位置 i 插入新元素的概率。
在表长为 n 的线性表中删除元素时,共有 n 个可删除的元素,删除元素 a~i~ 时需要移动 n-1 个元素,删除元素 a~n~ 时不需要移动元素,因此,在等概率下删除元素时需要移动的元素个数期望值 E~delete~ 为
T d e l e t e = Σ i = 1 n q i × ( n − i ) = 1 n Σ i = 1 n ( n − i ) = n − 1 2 T_{delete} = {\huge\Sigma}^n_{i=1}q_i×(n-i) = \frac1n {\huge\Sigma}^n_{i=1}(n-i) = \frac {n-1}2 Tdelete=Σi=1nqi×(n−i)=n1Σi=1n(n−i)=2n−1
其中,q~i~ 表示删除第 i 个元素(即 a~i~) 的概率。
(2)线性表的链式存储
线性表的链式存储是用通过指针链接起来的结点来存储数据元素,基本的结点结构如下所示:
数据域 | 指针域 |
---|
其中,数据域用于存储数据元素的值,指针域则存储当前元素的直接前驱或直接后继的位置信息,指针域中的信息称为指针(或链)。
存储各数据元素的结点的地址并不要求是连续的,因此存储数据元素的同时必须存储元素之间的逻辑关系。另外,结点空间只有在需要的时候才申请,无须事先分配。
结点之间通过指针域构成一个链表,若结点中只有一个指针域,则称为线性链表(或单链表),如下图所示。
设线性表中的元素是整型,则单链表结点类型的定义为:
typedef struct node f
int data; // 结点的数据域,此处假设为整型
struct node *next; // 结点的指针域
}INODE,*LinkList;
在链式存储结构中,只需要一个指针(称为头指针,如上图中的 head) 指向第一个结点就可以顺序地访问到表中的任意一个元素。
在链式存储结构下进行插入和删除,其实质都是对相关指针的修改。在单链表中,若在 p所指结点后插入新元素结点 (s 所指结点,已经生成),如下图所示,其基本步骤如下。
(1) s->next = p->next;
(2) p->next = s;
即先将 p 所指结点的后继结点指针赋给 s 所指结点的指针域,然后将 p 所指结点的指针域修改为指向 s 所指结点。
同理,在单链表中删除 p 所指结点的后继结点时(如下图所示),步骤如下。
(1) q = p->next;
(2) p->next = p->next->next;
(3) free(g);
即先令临时指针 q 指向待删除的结点,然后修改 p 所指结点的指针域为指向 p 所指结点的后继的后继结点,从而将元素 b 所在的结点从链表中删除,最后释放 q 所指结点的空间。
在实际应用中,为了简化对链表状态的判定和处理,特别引入一个不存储数据元素的结点,称为头结点,将其作为链表的第一个结点并令头指针指向该结点。
下面给出单链表上查找、插入和删除运算的实现过程。
【函数】单链表的查找运算。
java
LinkList Find_List(LinkList L,int k) //L 为带头结点单链表的头指针
/* 在表中查找第k 个元素,若找到,返回该元素结点的指针;否则,返回空指针 NULL */
{
LinkList p; int i;
i= 1; p = L->next; // 初始时,令p指向第一个元素结点,i 为计数器
while (p &&i<k) { //顺指针链向后查找,直到p 指向第 k 个元素结点或p 为空指针
p = p->next; i++;
}
if(p && i== k) return p; //存在第 k 个元素且指针 p 指向该元素结点
return NULL; //第 k 个元素不存在,返回空指针*/
}/* Find_List */
【函数】单链表的插入运算
java
int Insert_List(LinkList L,int k,int newElem)/*L 为带头结点单链表的头指针*/
/*将元素 newElem 插入表中的第 k 个元素之前,若成功则返回0,否则返回-1*/
/*该插入操作等同于将元素 newElem 插入在第 k-1 个元素之后*/
{
LinkList p,s; /*p、s 为临时指针*/
if(k==1)p=L; /*元素 newElem 要插入到第 1个元素之前*/
else p = Find_List(L,k-1); /*查找表中的第 k-1 个元素并令 p 指向该元素结点*/
if(!p) return -1; /*表中不存在第 k-1 个元素,不满足运算要求*/
s=(NODE *)malloc(sizeof(NODE)); /创建新元素的结点空间*/
if(!s) return -1;
s->data = newElem;
s->next=p->next; p->next= s; /*将元素 newElem 插入第 k-1 个元素之后*/
return 0;
)/* Insert List */
【函数】单链表的删除运算。
java
int Delete_List(LinkList L,int k)/*L 为带头结点单链表的头指针*/
/*删除表中的第 k 个元素结点,若成功则返回 0,否则返回-1*/
/*删除第 k 个元素相当于令第 k-1 个元素结点的指针域指向第 k+1 个元素所在结点*/
{
LinkList p,q; /*p、q 为临时指针*/
if(k== 1)p=L; /*删除的是第一个元素结点*/
else p = Find_List(L,k-1); /*查找表中的第 k-1个元素并令 p 指向该元素结点*/
if(!p || !p->next) return -1; /*表中不存在第 k 个元素*/
q =p->next; /*令q 指向第k个元素结点*/
p->next = q->next; free(q); /*删除结点*/
return 0;
)/* Delete_List */
当线性表采用链表作为存储结构时,不能对数据元素进行随机访问,但是具有插入和删除操作不需要移动元素的优点。
根据结点中指针域的设置方式,还有其他几种链表结构。
1)双向链表。每个结点包含两个指针,分别指出当前元素的直接前驱和直接后继。其特点是可以从表中任意的结点出发,从两个方向上遍历链表。
2)循环链表。在单向链表(或双向链表)的基础上令表尾结点的指针指向链表的第一个结点,构成循环链表。其特点是可以从表中任意结点开始遍历整个链表。
3)静态链表。借助数组来描述线性表的链式存储结构,用数组元素的下标表示元素所在结点的指针。
若双向链表中结点的 front 和 next 指针域分别指示当前结点的直接前驱和直接后继,则在双向链表中插入结点*s 时指针的变化情况如下图所示,其操作过程可表示为:
(1)s->front = p->front;
(2)p-> front -> next = s; //或者表示为s -> front -> next =s;
(3)s-> next =p;
(4)p-> front =s;
在双向链表中删除结点时指针的变化情况如下图所示,其操作过程可表示为:
(1) p-> front -> next =p->next;
(2) p-> next -> front = p -> front; free§;