线性表(List):零个或多个数据元素的有限序列。
ADT 线性表(List)
Data
数据对象为数据元素的集合,每个元素类型为DataType(用户自定义类型)。
数据元素之间的关系是一对一的关系。
Operation
InitList(*L):初始化操作,建立一个空的线性表L
ListEmpty(L):判断线性表是否为空,是返回TRUE,否返回FALSE
ClearList(*L):将线性表清空
GetElem(L, i, *e):将线性表L中第i个位置的值返回给e
LocateElem(L, e):中L中查找与e相等的元素,查找成功,返回元素的序号,失败返回0
ListInsert(*L, i, e):在L中第i个位置之前插入元素e
ListDelete(*L, i, *e):删除第i个位置元素,用e返回其值
ListLength(L): 返回L的元素个数
endADT
线性表按照存储结构可分为:线性表的顺序存储结构、线性表的链式存储结构
线性表的顺序存储结构可以通过数组来实现:
#define MAXSIZE 100 //线性表存储数据元素的空间值
typedef int ElemType; //数据元素的类型
typedef struct{
ElemType data[MAXSIZE]; //存储数据元素的数组
//另一种形式:ElemType *elem; 指向数组首地址,空间通过用户自定义分配
int length; //当前已有数据元素个数
}SqList;
用数组存储顺序表意味着要分配固定长度的数组空间
顺序表的随机存取结构:可以对每个位置进行存入和取出
顺序表的一些常用操作:
//取得L中位置i上的数据元素值,用e返回
//L的存储数据元素是从0开始的,故第i个位置上的元素就是数组下标i-1位置上的元素
Status GetElem(SqList L, int i, ElemType *e)
{
if (L.length == 0 || i<1 || i>L.length) //数据元素位置1<=i<=L.length(L的元素个数),且L中元素个数不能没有
return FALSE;
*e = L.data[i-1];
return OK;
}
//在L中第i个位置之前插入数据元素e
Status ListInsert(SqList *L, int i, ElemType e)
{
int j;
if (L->length == MAXSIZE) //线性表L当前长度已满
return FALSE;
if (i<1 || i>L->length+1) //i的位置应该为:1<=i<=L->length+1
return FALSE;
if (i <= L->length)
for (j = L->length-1; j >= i-1; j--)
L->data[j+1] = L->data[j];
L->data[i-1] = e;
L->length++;
return OK;
}
//在L中删除第i个位置的数据元素,并用e返回其删除位置的值
Status ListDelete(SqList *L, int i, ElemType *e)
{
int j;
if (L->length == 0) //线性表为空,没有数据元素删除
return ERROR;
if (i < 1 || i > L->length) //i的删除位置为:1<=i<=L->length
return ERROR;
*e = L->data[i-1];
if (i < L->length) //i不是最后一个位置的时候,i后元素都得前移
for (j = i; j <= L->length-1; j++)
L->data[j-1] = L->data[j];
L->length--;
return OK;
}
线性表的链式存储结构:
单链表:每个结点都有一个数据域和一个指针域。其中第一个结点的存储位置叫做头指针,最后一个结点的指针域不指向结点,为空(NULL 或 ^)。有时,会在第一个结点前附设一个结点,称为头结点。
常用头指针为链表的名字,头指针无论什么时候都不为空,可能指向随机地址。
单链表的存储结构:
typedef struct Node{
ElemType data; //数据域
struct Node *next; //指针域,存储下个结点的地址
}Node;
typedef struct Node *LinkList; //定义链表,头指针
链表的常用操作:
//取链表的元素时,1<=i<=ListLength(i)
Status GetElem(LinkList L, int i, ElemType *e)
{
Node *p;
p = L->next; //指向首元结点
int j = 1;
while (j < i && p) //循环结束,使p指向第i个位置
{
j++;
p = p->next;
}
if (j > i || !p) //i的位置不合法或者位置i为空(i的值超出链表长度,结点i不存在)
return ERROR;
*e = p->data;
return OK;
}
Status ListInsert(LinkList *L, int i, ElemType e)
{
int j;
Node *p, *s;
p = *L; //指向头结点
j = 1;
while (j < i && p) //j<i且没有到达末尾就遍历链表,直至找到第i-1个元素
{
p = p->next;
j++;
}
if (!p || j>i) //提前到达末尾或者i的位置不合法
return ERROR;
s = (Node *)malloc(sizeof(Node));
s->data = e;
s->next = p->next;
p->next = s;
return OK;
}
Status ListDelete(LinkList *L, int i, ElemType *e)
{
int j;
Node *p, *s;
p = *L;
j = 1;
while (j < i && p->next) //找到第i-1个元素,并判断其后是否为空
{
p = p->next;
j++;
}
if (!(p->next) || j>i) //提前为空或者i的位置不合法
return ERROR;
s = p->next;
p->next = s->next;
*e = s->data;
free(s);
return OK;
}
其操作核心在于循环部分
链表的整表创建可以用头插法和尾插法。整表的删除通过两个指针循环操作。
void CreateListHead(LinkList *L, int n)
{
Node *p;
int i;
*L = (LinkList)malloc(sizeof(Node));
(*L)->next = NULL; //建立一个带有头结点的空链表
srand(time(0));
for (i = 0; i < n; i++)
{
p = (Node *)malloc(sizeof(Node));
p->data = rand()%100+1;
p->next = (*L)->next;
(*L)->next = p;
}
}
void CreateListTail(LinkList *L, int n)
{
Node *p, *s; //p为了生成结点,s为了指向末尾结点
int i;
*L = (LinkList)malloc(sizeof(Node));
s = *L;
srand(time(0));
for (i = 0; i < n; i++)
{
p = (Node *)malloc(sizeof(Node));
p->data = rand()%100+1;
s->next = p;
s = p;
}
s->next = NULL;
}
Status ClearList(LinkList *L)
{
Node *p, *q;
p = (*L)->next; //p指向首元结点
while (p)
{
q = p->next;
free(p);
p = q;
}
(*L)->next = NULL;
return OK;
}
除了顺序表和链表外,还有静态链表、循环链表、双向链表等。
静态链表:通过存储数组下标来描述逻辑结构。其中未被使用的数组元素称为备用链表。其中下标为0的元素就存放备用链表(未被使用过)的第一个结点的下标,而最后一个元素就存放第一个有数值的元素的下标,没有就存0(相当于头结点)。其中数据元素存放下一个数据元素的下标,最后一个有数据的元素存放下标0
静态链表的存储结构:
#define MAXSIZE 100
typedef struct{
ElemType data;
int cur;
}Component, StaticLinkList[MAXSIZE];
循环链表:将单链表中的终端结点的指针域改为指向头结点,形成头尾相接。 循环链表的终端结点可以用尾指针rear来指向,这样查找头结点和终端结点的时间复杂度都是O(1)。
双向链表:在单链表的基础上,再设置一个指向前驱结点的指针域。另外,双向链表也可以是循环表。
//双向链表的存储结构
typedef struct DulNode{
ElemType data;
struct DulNode *prior;
struct DulNode *next;
}DulNode, *DulLinkList;
线性表的无序合并:通过遍历表LB中元素,查找LA是否存在相同元素,不存在则插入。
线性表的有序合并:前提两个线性表都是有序的。通过两个指针分别指向表头,不断指向后继元素,按照指定次序比较,只要到达一个线性表末尾就停止,将另一个线性表的剩余元素都插入。顺序表和链表的有序合并不同,顺序表需要开辟新的空间,链表不需要。