线性表List
1. 概念
由0个或多个数据元素组成的有序序列。当线性表的元素个数是0是,称之为空表。
注意 :**(1) 线性表是个序列,有先来后到;
(2) 若元素存在对个,第一个元素无前驱,第二个元素无后继,其他元素都有且只有一个前驱和后继;
(3) 线性表是有限的。
其中ai-1领先于ai,ai领先于ai+1,称ai-1是ai的直接前驱元素,ai+1是ai的直接后继元素。
2. 线性表的操作
(1) InitList(*L)
:初始化操作,建立一个空的线性表。
(2) ListEmpty(L)
:判断线性表是否为空表,若线性表为空,返回true,否则返回false。
(3) ClearList(*L)
:将线性表清空。
(4) GetElem(L, i, *e)
:将线性表L中的第i个位置元素值返回给e。
(5) LocateElem(L, e)
: 在线性表L中查找与给定值e相等的元素,如果查找成功,返回该元素在表中序号表示成功;否则,返回0表示失败。
(6) ListInsert(*L, i, e)
:在线性表L中第i个位置插入新的元素e。
(7) ListDelete(*L, i, e)
: 删除线性表L中第i个位置元素,并用e返回其值。
(8) ListLength(L)
: 返回线性表L的元素个数。
3. 线性表的存储结构
- 线性表的顺序存储结构
线性表的顺序存储结构,指的是用一段地址连续的存储单元依次存储线性表的数据元素。
线性表(a1,a2,……,an)的顺序存储如下:
注意: 数组的长度是存放线性表的存储空间的总长度,一般初始化后不变。而线性表的当前长度是线性表中元素的个数,是会变化的。
#define MaxSize 20 //数值根据需要可改变
typedef int ElemType; //定义整形常量ElemType
typedef struct
{
ElemType data[MaxSize]; //数据类型为ElemType的长度为MaxSize的data数组
int length; //线性表当前长度
}SqList; //顺序表类型,类似于class中的类名,新定义的一个数据类型的名称
- 地址计算方法
(1) 数组从0开始计算,而线性表是从1开始的。
(2) 假设线性表中元素占用的是c个存储单元(字节),那么线性表中第i+1个数据元素和第i个数据元素的存储位置的关系是(LOC表示获得存储位置的函数):
LOC(ai+1) = LOC(ai)+ c
所以对于第i个数据元素ai的存储位置可以由a1推算得出:
LOC(ai)= LOC(a1) + (i - 1) * c
- 线性表顺序存储结构的优缺点
- 优点:
(1) 无需为表中元素之间的逻辑关系而增加额外的存储空间;
(2) 可以快速地存取表中任意位置的元素。 - 缺点:
(1) 插入和删除操作需要移动大量元素;
(2) 当线性表长度变化较大时,难以确定存储空间的容量;
(3) 容易造成存储空间的“碎片”。
- 线性表的链式存储结构
线性表的链式存储结构的特点是用一组任意的存储单元存储线性表的数据元素,这组存储单元可以存在内存中未被占用的任意位置。
例如:使用链表存储 {1,2,3},数据的物理存储状态如下图所示:
可以看到,逻辑上相邻的两个元素在物理位置不一定也相邻。
- 结点(Node)
在链式存储结构中,除了需要存储数据元素的信息之外,还要存储它的后继元素的存储地址(指针)。
- 数据域:把存储数据元素信息的域称之为数据域。
- 指针域:把存储直接后继位置的域称之为指针域。指针域中存储的信息称之为指针域链。 - 单链表
链表的每个结点中只包含一个指针域。
头指针:链表中的第一个结点的存储位置。具有标识作用,是链表的必要元素,不为空。
头结点:为了操作的统一和方便而设立的,放在第一个元素的结点之前,其数据域一般无意义。头结点不是链表的必要元素。
以C为例,自定义一个单链表结构:
typedef struct Node
{
ElemType data; //数据域
struct Node* Next; //指针域
}Node;
typedef struct Node* LinkList;
假设p是指向线性表第i个元素的指针,则该结点ai的数据域可以用p->data的值是一个数据元素,结点ai的指针域可以用p->next来表示。
- 单链表的操作
- 插入
s->next = p->next;
p->next = s;
- 删除
p->next = p->next->next;
或者
q = p->next;
p->next = q->next;
- 建立单链表
- 头插法
从一个空表开始,生成新结点,读取数据存放到新结点的数据域中,然后将新结点插入到当前链表的表头上,直到结束为止。
- 头插法
/* 头插法建立单链表示例 */
void CreatListHead(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 CreatListHead(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 = (LinkList)malloc(sizeof(Node)); // 生成新结点
p->data = rand() % 100 + 1;
r->next = p;
r = p;
}
r->next = NULL;
}
尾插法的读入顺序和生成顺序相同。
- 双向链表
双链表就是同时具有前驱指针和后继指针的链表。
以C为例,自定义一个双链表结构:
typedef struct DualNode
{
ElemType data;
struct DualNode *prior; // 前驱结点
struct DualNode *next; // 后继结点
} DualNode, *DuLinkList;
- 双链表的操作
- 插入
s->next = p->next;
p->next->prior = s;
p->next = s;
s->prior = p;
- 删除
p->next = s->next;
s->next->prior = p;
- 循环链表
将单链表中终端结点的指针端由空指针改为指向头结点,就使整个单链表形成一个环,这种头尾相接的单链表成为循环单链表,简称循环链表。
判断是否为空表的条件:表尾结点的 next
是否是等于头指针。
4. 链表结构和顺序存储结构的优缺点
- 存储分配方式
- 顺序存储结构用一段连续的存储单元依次存储线性表的数据元素。
- 链表采用链式存储结构,用一组任意的存储单元存放线性表元素。
- 时间性能
- 查找
顺序存储结构O(1);链表O(n) - 插入和删除
顺序存储结构O(n);链表O(1)
- 空间性能
- 顺序存储结构需要预分配存储空间,分大了,容易造成空间浪费,分小了,容易造成溢出。
- 链表结构不需要分配存储空间,只要有就可以分配,元素个数也不受限制。
若线性表需要频繁查找,很少进行插入和删除操作,宜采用顺序存储结构。
若需要频繁插入和删除时,宜采用链表结构。