1 线性表的定义
- 线性表:零个或多个数据元素的有限序列。
- 若将线性表记为(a1...ai−1,ai,ai+1,...an),则称ai−1为ai的直接前驱元素,ai−1为ai−1的直接后继元素。
- 线性表元素的个数n定义为线性表的长度,当n=0时,称为空表。
- 在较复杂的线性表中,一个数据元素可以由若干个数据项组成,比如人员信息表等。
2 线性表的抽象数据类型
operation:
InitList(*L): 初始化操作,建立一个空的线性表L。
ListEmpty(L): 若线性表为空,返回true,否则返回false。
ClearList(*L): 将线性表清空。
GetElem(L, i, *e): 将线性表L中第i个位置的元素值返回给e。
LocateElem(L, e): 在线性表中查找与定值e相等的元素,如查找成 功,返回该元素在表中序号,否则返回0表示失败。
ListInsert(*L, i, e): 在线性表L中的第i个位置插入新元素e。
ListDelete(*L, i, *e): 删除线性表L中的第i个位置,并用e返回其值。
ListLength(L): 返回线性表L的元素个数。
/*实现两个线性表A和B的并集操作*/
void unionL(list *La, list Lb)
{
int La_len, Lb_len, i;
ElemType e;
La_len = ListLength(*La);
Lb_len = ListLength(*Lb);
for(i = 1; i <= Lb_len; i++)
{
GetElem(La, i, &e);
if(!LocateElem(*La, e))
ListInsert(La, ++La_len, e);
}
}
3 线性表的顺序存储结构
顺序存储结构:用一段地址连续的存储单元依次存储线性表的数据元素。
顺序存储结构的结构代码:
# define MAXSIZE 20
typedef int ElemType;
typedef struct
{
ElemType data[MAXSIZE];
int length;
}SqList;
顺序存储结构元素地址:LOC(ai) = LOC(a1) + (i-1)*c,其存取的时间性能为O(1)。
3.1 顺序存储结构的插入与删除
插入操作:
Status ListInsert(SqList *L, int i, ElemType e)
{
int k;
if(L->length == Maxsize) /*顺序线性表已满*/
return ERROR;
if(i<1 || i>L->length + 1)
return ERROR;
if(i <= L->length)
{
for(k = L->length; k >= i; k--)
{
L->data[k] = L->data[k - 1];
}
}
L->data[i-1] = e;
L->length++;
return OK;
}
删除操作:
Status ListDelete(SqList *L, int i, ElemType *e)
{
int k;
if(L->length == 0)
return ERROR;
if(i<1 || i > L->length)
return ERROR;
*e = L->data[i-1];
if(i < L->length)
{
for(k = i; k < L->length; k++)
L->data[k-1] = L->data[k];
}
L->length--;
return OK;
}
顺序存储结构的时间复杂度都是O(n)。
3.2 顺序存储结构的优缺点
优点:
1. 无需为表示表中元素之间的逻辑关系额外增加存储空间。
2. 可实现快速存取表中任意位置的元素。
缺点:
1. 插入和删除操作需要移动大量元素,不适用于需要频繁插入和删除。
2. 难以确定存储空间容量。
3. 造成存储空间“碎片”。
4 线性表的链式存储结构
1 链式存储结构定义
为了表示每个元素与其直接后继元素之间的逻辑关系,对数据元素ai来说,除了存储本身信息之外,还需存储指示其直接后继元素的存储位置的信息,我们将存储数据元素信息的域称为数据域,把存储直接后继位置的域称为指针域,指针域中存储的信息称为指针或链,把这两部分信息组成数据元素ai的存储映像称为结点。
n个节点链结成一个链表,即为线性表(a1,a2……an)的链式存储结构,因为每个节点只包含一个指针域,故称为单链表。
链表中第一个节点的位置称为头指针,常用头指针冠以链表的名字,是链表的必要元素。单链表中最后一个节点指针为空,用NULL表示。
可在单链表的第一个节点前附设一个头结点,头结点的数据域可不存储任何信息,头结点指针域存储指向第一节点的头指针,不一定必须。
2 链式存储结构代码描述
/*单链表存储结构*/
typedef struct Node
{
ElemType data;
struct Node *next;
} Node;
typedef struct Node *LinkList;
节点ai的数据域用p->data表示,指针域用p->next表示。
3 单链表的读取
/*用e返回L中第i个数据元素的值*/
Status GetElem(LinkList L, int i, ElemType *e)
{
int j;
LinkList p; /* 生成一指针p */
p = L->next; /* 让p指向链表L的第一个节点 */
j = 1; /* j为计数器 */
while(p && j < i)
{
p = p->next;
j ++;
}
if(!p)
return ERROR; /*第i个节点不存在*/
*e = p->data;
return OK;
}
单链表查找的时间复杂度为O(n)。
4 单链表的插入与删除
Status ListInsert(LinkList *L, int i, ElemType e)
{
int j;
LinkList p, s;
p = *L; /*p指向链表头结点*/
j = 1;
while(p && j < i) /*寻找第i-1个节点*/
{
p = p->next;
j++;
}
if(!p)
return ERROR;
s = (LinkList) malloc (sizeof(Node));/*生成新节点(c标准函数)*/
s->data = e;
s->next = p->next;
p->next = s;
return OK;
}
第一次插入的时间复杂度是O(n),后面都是O(1)。
5 单链表的整表创建与删除
/*随机产生n个元素的值,建立带表头节点的单链线性表L(尾插法)*/
void CreateListTail(LinkList *L, int n)
{
LinkList p, r;
int i;
srand(time(0));
*L = (LinkList)malloc(sizeof(Node));
r = *L;
for(i = 0; i<n; i++)
{
p = (Node*) malloc (sizeof(Node)); /*生成新结点*/
p->data = rand()%100 + 1;
r->next = p; /*表尾终端结点的指针指向新结点*/
r = p; /*新结点定义为表尾终端结点*/
}
r->next = NULL; /*当前链表结束*/
}
/*将单链表重置为空表*/
Status ClearList(LinkList L)
{
LinkList p, q;
p = (*L)->next;
while(p)
{
q = p->next;
free(p);
p = q;
}
(*L)->next = NULL;
return OK;
}
6 单链表结构与顺序存储结构的优缺点
(1) 存储分配方式
顺序存储用一段连续的存储单元依次存储线性表的数据元素;单链表用一组任意的存储单元存放线性表元素。
(2) 时间性能
查找:顺序存储结构O(1);单链表O(n)。
插入和删除:顺序存储为O(n);单链表在找出某位置的指针后,仅为O(1)。
(3) 空间性能
顺序存储需要预分配存储空间,大了浪费,小了溢出。
单链表不需分配,元素个数不受限制。
5 静态链表
用数组描述的链表叫做静态链表。(主要用于没有指针的语言如Basic等)
6 循环链表
将单链表中终端结点的指针端由空指针改为指向头结点,这种头尾相接的单链表称为单循环列表。
/*将两个循环链表合并为一个表,尾指针分别为rearA,rearB,则*/
p = rearA->next;
/*将B表的第一个结点赋值给rearA->next*/
rearA->next = rearB->next->next;
q = rearB->next;
rearB->next = p;
free(q);
7 双向链表
双向链表是在单链表的每个结点中,再设置一个指向其前驱结点的指针域。
/*线性表的双向链表存储结构*/
typedef struct DulNode
{
ElemType data;
struct DulNode *prior; /*直接前驱指针*/
struct DulNode *next; /*直接后驱指针*/
} DulNode, *DuLinkList;
/*将存储元素e的结点s插入到p和p->next之间*/
s->prior = p;
s->next = p->next;
p->next->prior = s;
p->next = s;
/*删除结点p*/
p->prior->next = p->next;
p->next->prior = p->prior;
free(p);
参考:
程杰. 大话数据结构[M]. 清华大学出版社, 2011.