1、定义:由零个或多个数据元素组成的有限序列,若元素存在多个,则第一个元素无前驱,而最后一个元素无后继,其他元素都有且只有一个前驱和后继。
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): 删除线性表L中第i个位置元素,并用e返回其值。
ListLength(L): 返回线性表L的元素个数。
线性表有两种物理存储结构:顺序存储结构和链式存储结构
2、顺序存储结构指用一段地址连续的存储单元依次存储线性表的数据元素
#define MAXSIZE 20
typedef int ElemType;
typedef struct
{
ElemType data[MAXSIZE];
int length; // 线性表当前长度
} SqList;
(1) 顺序存储结构封装三个属性
存储空间的起始位置,数组data,它的存储位置就是线性表存储空间的存储位置;
线性表的最大存储容量:数组的长度MaxSize;
线性表的当前长度:length
(2) 插入操作
如果插入位置不合理,抛出异常;
如果线性表长度大于等于数组长度,则抛出异常或动态增加数组容量;
从最后一个元素开始向前遍历到第i个位置,分别将它们都向后移动一个位置;
将要插入元素填入位置i处;
线性表长+1
(3) 删除操作
如果删除位置不合理,抛出异常;
取出删除元素;
从删除元素位置开始遍历到最后一个元素位置,分别将它们都向前移动一个位置;
表长-1
(4) 优缺点
无须为表示表中元素之间的逻辑关系而增加额外的存储空间;快速地存取表中任意位置的元素
插入和删除操作需要移动大量元素;当线性表长度变化较大时,难以确定存储空间的容量;容易造成存储空间的碎片
3、线性表的链式存储结构是用一组任意的存储单元存储线性表的数据元素,这组存储单元可以存在内存中未被占用的任意位置。把存储数据元素信息的域称为数据域,把存储直接后继位置的域称为指针域。指针域中存储的信息称为指针或链。这两部分信息组成数据元素称为存储映像,称为结点Node。
(1) 头指针:头指针是指链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针;头指针具有标识作用,常用头指针冠以链表的名字(指针变量的名字);无论链表是否为空,头指针均不为空;头指针是链表的必要元素
(2) 头结点:放在第一个元素的结点之前,其数据域一般无意义(但也可以用来存放链表的长度);有了头结点,对在第一元素结点前插入结点和删除第一结点起操作与其它结点的操作就统一;头结点不一定是链表的必要要素
(3) 单链表:结点由存放数据元素的数据域和存放后继结点地址的指针域组成。
typedef struct Node
{
ElemType data; // 数据域
struct Node* Next; // 指针域
} Node;
typedef struct Node* LinkList;
获取链表第i个数据的算法思路:声明一个结点p指向链表第一个结点,初始化j从1开始;当j小于i时,就遍历链表,让p的指针向后移动,不断指向下结点,j+1;若到链表末尾p为空,则说明第i个元素不存在;否则查找成功,返回结点p的数据
Status GetElem( LinkList L, int i, ElemType *e )
{
int j;
LinkList p;
p = L->next;
j = 1;
while( p && j<i )
{
p = p->next;
++j;
}
if( !p || j>i ) return ERROR;
*e = p->data;
return OK;
}
第i个数据插入结点的算法思路:s->next = p->next; p->next = s
第i个数据删除操作:声明结点p指向链表第一个结点,初始化j=1;当j小于1时,就遍历链表,让p的指针向后移动,不断指向下一个结点,j累加1;若到链表末尾p为空,则说明第i个元素不存在;否则查找成功,将欲删除结点p->next赋值给q;单链表的删除标准语句p->next = q->next;将q结点中的数据赋值给e,作为返回;释放q结点。
单链表的整表创建:声明一结点p和计数器变量i;初始化空链表L;让L的头结点的指针指向NULL,即建立一个带头结点的单链表;循环实现后继结点的赋值和插入。
头插法:把新加进的元素放在表头后的第一个位置,先让新节点的next指向头节点之后,然后让表头的next指向新结点
void CreateListHead(LinkList *L, int n)
{
LinkList p;
int i;
srand(time(0)); // 初始化随机数种子
*L = (LinkList)malloc(sizeof(Node));
(*L)->next = NULL;
for( i=0; i < n; i++ )
{
p = (LinkList)malloc(sizeof(Node)); // 生成新结点
p->data = rand()%100+1;
p->next = (*L)->next;
(*L)->next = p;
}
}
尾插法: 把新结点都插入到最后
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;
}
单链表整表删除:声明结点p和q;将第一个结点赋值给p,下一结点赋值给q;循环执行释放p和将q赋值给p的操作;
Status ClearList(LinkList *L)
{
LinkList p, q;
p = (*L)->next;
while(p)
{
q = p->next;
free(p);
p = q;
}
(*L)->next = NULL;
return OK;
}
(4) 单链表结构与顺序存储结构优缺点
存储分配方式:顺序存储结构用一段连续的存储单元依次存储线性表的数据元素;单链表采用链式存储结构,用一组任意的存储单元存放线性表的元素。
时间性能:查找:顺序存储结构O(1),单链表O(n);插入和删除:顺序存储结构需要平均移动表长一半的元素,时间为O(n),单链表在计算出某位置的指针后,插入和删除时间仅为O(1)
空间性能:顺序存储结构需要预分配存储空间,分大了,容易造成空间浪费,分小了,容易发生溢出;单链表不需要分配存储空间,只要有就可以分配,元素个数也不受限制。
(5) 静态链表
#define MAXSIZE 1000
typedef struct
{
ElemType data; // 数据
int cur; // 游标(Cursor)
} Component, StaticLinkList[MAXSIZE];
对静态链表进行初始化相当于初始化数组:
Status InitList(StaticLinkList space)
{
int i;
for( i=0; i < MAXSIZE-1; i++ ) space[i].cur = i + 1;
space[MAXSIZE-1].cur = 0;
return OK;
}
对数组的第一个和最后一个元素做特殊处理,他们的data不存放数据;把未使用的数组元素称为备用链表;数组的第一个元素,即下标为0的那个元素的cur就存放备用链表的第一个结点的下标;数组的最后一个元素,即下标为MAXSIZE-1的cur则存放第一个有数值的元素的下标,相当于单链表中的头结点作用。