线性表是一种比较简单且常用的数据结构。简单来说,线性表(List)是零个或多个数据元素的有限序列。首先,线性表的元素的个数是有限的;其次,线性表中各个元素的位置有先后顺序。线性表的主要操作有:初始化、判断一个线性表是否为空、清除线性表、获取线性表元素、删除线性表元素、插入元素到线性表、获取线性表长度等操作。同时线性表根据元素存储方式,可以大致分为顺序存储和链式存储着两种结构。
线性表的顺序存储结构是指用一段连续的存储空间依次存储线性表的元素。我们可以考虑用一维数组来实现线性存储,数据结构定义如下:
#define MAXSIZE 20 //链表最大存储空间大小
//顺序链表定义
typedef int ElemType;
typedef struct{
ElemType data[MAXSIZE];//链表元素
int length;//链表时间长度
}SqList;
其相应的插入和删除函数定义如下:
#define OK 1
#define ERROR 0
typedef int Status;//返回状态
/*
* 获取顺序链表元素
* 成功返回1,并且元素赋值给e
* 失败返回0
*/
Status getElem(SqList L,int i,ElemType *e){
if(i < 1 || i > L.length || L.length == 0)
return ERROR;
*e = L.data[i-1];
return OK;
}
/* *
* 在位置i插入一个元素到链表中
* 成功返回1,失败返回0
* */
Status insertList(SqList *L,int i,ElemType e){
if(L->length == MAXSIZE)
return ERROR;//元素已满,不能再插入
if(i < 1 || i > L->length+1)
return ERROR;//插入元素位置不正确
int k;
for(k = L->length-1;k >= i-1;k--)
{
L->data[k+1] = L->data[k];
}
L->data[i-1] = e;
L->length++;
return OK;
}
/* *
* 删除指定位置i的元素
* 删除成功返回1,并把删除元素赋值给e
* 删除失败返回0
* */
Status deleteList(SqList *L,int i,ElemType *e){
if(L->length == 0)
return ERROR;//链表为空,删除失败
if(i < 1 || i > L->length)
return ERROR;//删除的元素不存在
int k;
*e = L->data[i-1];
for(k = i; k <= L->length - 1; k++)
{
L->data[k-1] = L->data[k];
}
L->length--;
return OK;
}
可以看到,线性表的顺序存储结构具有较好的查找性能,但其插入和删除元素将会有大量元素的移动。同时,在建立顺序存储线性表时需要确定线性表的大小,这样有可能造成空间的浪费。为此,线性表的链式存储结构用来解决顺序存储中的不足,提供较好的插入和删除性能。链式存储的线性表结构主要是由一系列的节点Node组成,这些节点由两部分组成:一部分是指向存储的数据元素,另一部分是指向下一个节点的指针。这样只需知道链表的头节点就可以一直遍历下去,直到链表的尾端。链式存储的线性表数据结构如下所示:
//链式存储线性表定义
typedef int ElemType;
typedef struct Node{
ElemType data;//链表元素
struct Node *next;//指向下一个链表节点
}Node;
typedef struct Node* LinkList;//指向链表的指针
其相应的初始化、插入和删除函数如下所示:
#define OK 1
#define ERROR 0
typedef int Status;//返回状态
/* *
* 初始化线性链表,创建n个节点的链表
* 采用尾插入
* */
void initList(LinkList *L,int n){
LinkList p,q;
int i;
*L = (LinkList)malloc(sizeof(Node));//注意参数为*L,这样才能把修改后的地址传递出去
p = *L;
for(i = 0; i <= n-1; i++)
{
q = (LinkList)malloc(sizeof(Node));
q->data = rand()%100+1;
p->next = q;
p = q;
}
q->next = NULL;
}
/* *
* 获取链表元素
* 成功返回1,并返回元素
* 失败返回0
* */
Status getElem(LinkList L,int i,ElemType *e){
int j = 1;
LinkList p,q;
p = L->next;
//遍历到第i个元素
while(p && j<i){
p = p->next;
j++;
}
if(!p || j > i )
return ERROR;//该位置没有元素,返回错误
*e = p->data;
return OK;
}
/* *
* 插入元素到链表中第i个节点之后
* 成功返回1
* 失败返回0
* */
Status insertList(LinkList L,int i,ElemType e){
//插入元素到链表头节点之后
if(i == 0){
LinkList m = (LinkList)malloc(sizeof(Node));
m->data = e;
m->next = L->next;
L->next = m;
return OK;
}
int j = 1;
LinkList p,s;
p = L->next;
while(p && j < i)
{
p = p->next;
j++;
}
if(!p || j > i)
return ERROR;//插入的位置不正确
s = (LinkList)malloc(sizeof(Node));
s->data = e;
s->next = p->next;
p->next = s;
return OK;
}
/* *
* 删除链表中第i个节点之后的元素
* 成功返回1
* 失败返回0
* */
Status deleteList(LinkList L,int i,ElemType *e){
//删除头节点后的第一个元素
if(i == 0){
LinkList m = L->next;
if(m){
L->next = m->next;
*e = m->data;
free(m);
return OK;
}else
return ERROR;
}
int j = 1;
LinkList p,q;
p = L->next;
while(p && j < i)
{
p = p->next;
j++;
}
if(!(p->next) || j > i)
return ERROR;//删除的元素不存在
q = p->next;
p->next = q->next;
*e = q->data;
free(q);
return OK;
}
链式存储线性表的优点是插入和删除所需要的代价较小,且事先可以不用知道所需存储空间大小,适用于经常插入和删除元素的场景。以上两种线性表存储结构有各自的优缺点,根据应用场景可以选择相应的存储结构。