提示:本文仅做简单介绍,部分图来自网络,侵删
一、线性表的类型定义
线性表linear_list
是 n
个数据的元素的有限序列,例如:
(1, 2, 3, 4, ……,n)
(A, B, C, D, ……, Z)
(a1, a2, a3, ……, an)
数据元素在不同情况下有不同含义,在复杂的线性表中一个数据元素可以有若干 数据项item
组成,常把数据元素称为 记录record
,含大量记录的线性表称为文件file
。
以上述线性表为例,a(i-1)
是ai
的 直接前驱元素,a(i +1)
是 ai
的直接后继元素。
线性表中元素个数 n (n >= 0)
定义为线性表的长度,当 n = 0
时称为空表,对于非空表中,每个元素的位置都是相对确定的,即有序排列。
抽象数据类型线性表的定义如下:
// 伪代码
ADT list {
// 数据对象
D = {a1, a2, a3, ……, an};
//数据关系
R1 = {<a(i-1), a(i)>};
// 基本操作
InitList(&L) {
// 构造一个空的线性表L
}
DestroyList(&L) {
// 销毁线性表L
}
ClearList(&L) {
// 将L重置为空表
}
ListEmpty(L){
// 若L为空表,则返回TRUE,否则返回FALSE
}
ListLength(L) {
// 返回L表的长度
}
GetElem(L, i, &e) {
// 用e返回L中的第i个元素的值
}
LocateElem(L, e, compare()) {
// 返回L中第一个e满足compare()的数据元素的位序,e不存在则返回0
}
PriorElem(L, cur_e, &pre_e) {
// 用pre_e返回cur_e的直接前驱元素
}
NextElem(L, cur_e, &next_e) {
// 用next_e 返回 cur_e的直接后继元素
}
ListInsert(&L, i, e) {
// 在L第i个位置前插入元素e,L长度加1
}
ListDelete(&L, i, &e) {
// 删除L的第i个位置的数据元素,并用e返回删除元素的值,L长度减1
}
ListTraverse(L, visit()) {
// 对L 的每个数据元素调用visit()函数,如果某个元素调用visit()失败,则操作失败
}
}
union求并集A = AUB 算法时间复杂度O(LA_length * LB_length)
void ListUnion(LA, LB){
int LA_length = LA.ListLength(LA);
int LB_length = LB.ListLength(LB);
for(int i = 0; i < LB_length; i ++) {
GetElem(LB, i, e);
if (!LocateElem(LA, e, equal)) {
ListInsert(LA, ++LA_length, e);
}
}
return;
}
merge合并非递减有序线性表 算法时间复杂度O(LA_length + LB_length)
List ListMerge(LA, LB) {
InitList(LC);
int i = 0, j = 0, k = 0;
LA_length = ListLength(LA);
LB_length = ListLength(LB);
while (i < LA_length && j < LB_length) {
GetElem(LA, i, ai);
GetElem(LB, i, bi);
if(ai < bi) {
ListInsert(LC, k, ai);
k ++;
i ++;
}
else {
ListInsert(LC, k, bi);
k ++;
j ++;
}
}
while (i < LA_length) {
GetElem(LA, i, ai);
ListInsert(LC, k, ai);
}
while (j < LB_length) {
GetElem(LB, i, bi);
ListInsert(LC, k, bi);
}
return LC;
}
线性表分为顺序表 和 链表,下面分别介绍
二、线性表的顺序表示和实现
顺序表:使用一组地址连续的存储空间依次存储线性表的数据元素,元素的逻辑位置与物理位置一致。
假设,每个元素占用 SIZE
个存储单元,则可计算表中任意元素位置
LOC(ai) = LOC(a1) + (i - 1) * SIZE
// ----- 线性表的动态分配顺序存储结构 -----
#define LIST_INIT_SIZE 100
#define LIST_INCREMENT 10
typedef struct {
ElemType *elem;
int length;
int listSize; // listSize = sizeof(ElemType)
}SqList;
// ----- 初始化 -----
Status InitList(SqList &l) {
L.elem = (ElemType *)malloc(LIST_INIT_SIZE * sizeof(ElemType));
if (! L.elem) {
exit(OVERFLOW);
}
L.length = 0;
L.listSIze = LIST_INIT_SIZE ;
return OK;
}
插入数据元素
Status ListInsert(SqList &L, int i , ElemType e) {
if(i < 1 && i >L.length + 1) { return ERROR; }
if(L.length() >= L.listSize) {
//空间不够
newBase = (ElemType *)realloc(L.elem , (L.initSize + LIST_INCREMENT) * sizeof(ElemType));
if(! newBase) { exit(OVERFLOW); }
L.elem = newBase;
L.initSize += LIST_INCREMENT;
}
SqList * q = &L.elem[i - 1];
for(SqList * p = &L.elem[L.length -1]; p >= q; p -- ) { *(p + 1) = *p; } // 元素后移
*q = e;
L.length ++;
return OK;
}
// O(n / 2)
删除数据元素
Status ListDelete(SqList &L, int i , &e) {
if(i < 1 && i >L.length + 1) { return ERROR; }
SqList *p = &L.elem[i - 1]; // p指向e的位置
e = *p;
SqList *q = &L.elem[L.length - 1];
for(++ p; p <= q; ++ p) { *(p - 1) = *p; } // 元素前移
L.length --;
return OK;
}
// O((n - 1) / 2)
查找数据元素
int LocateElem(SqList L, int e, Status (*compare)(ElemType ElemType)) {
int i = 1;
p = L.elem;
while(i <= L.length && !(* compare)(*p ++, e)) { i ++; }
if(i <= L.length) { return i; }
else { return 0; }
}
三、线性表的链式表示和实现
链表:使用一组一组任意的存储空间存储线性表的数据元素,这意味着数据元素之间的存储地址可以是不连续的,也可以是连续的。
链表的组成数据元素称为节点node
,它包含两个域:存储数据元素的称为数据域,存储直接前驱或直接后驱的称为指针域,通常在第一个节点之前附设一个头节点。单向链表示例:
typedef struct ListNode{
ElemType data;
struct ListNode* next;
}LNode *LinkList;
3.1 线性链表
访问数据元素
Status GetElem(ListNode *head, int i, ElemType &e) {
ListNode *p = head->next;
int j = 0;
while(j < i && p != NULL) { p = p->next; j ++; }
if(j > i || !p) { return ERROR; }
e = p ->data;
return OK;
}
插入数据元素
Status ListInsert(ListNode *head, int i,ElemType e)
{
// ListNode *dummyNode = new ListNode(-1);
// dummyNode->next = head;
ListNode * curr = head;
int j = 1;
while(curr && j < i - 1) {
curr = curr->next;
j ++;
}
if(!curr || j > i - 1) { return ERROR; }
ListNode *newNode = new LIstNode(0);
newNode->data = e;
newNode->next = curr->next;
curr->next = newNode;
return OK;
}
删除数据元素
Status ListDelete(ListNode *head, int i, ElemType *e) {
ListNode *curr = head;
int j = 0;
while(j < i -1 && curr->next) {
curr = curr->next;
j ++;
}
if(!curr->next || j > i - 1) { return ERROR; }
ListNode *delNode = curr->next;
curr->next = delNode->next;
e = delNode->data;
delete delNode;
return OK;
}
合并非递减单向链表
LIstNode *mergeList(LIstNode *LA, LIstNode *LB) {
LIstNode *LC;
// LIstNode *pa = LA->next;
// LIstNode *pb = LB->next;
LIstNode *pc = LC->next;
while(LA && LB) {
if(LA->data <= LB->data) {
pc ->next = LA;
LA = LA->next;
pc = pc->next;
}
else {
pc->next = LB;
LB = LB->next;
pc = pc->next;
}
}
pc->next = pa ? pa : pb;
return PC;
}
3.2 循环链表
循环链表circular linked list
的特点是最后一个节点的指针域指向它的头节点。
循环结束条件curr->next = head
3.3 双向链表
双向链表double linked list
struct DulNode {
ElemType data;
DulNode *prior;
DulNode *next;
DulNode(ElemType data = 0, DulNode *prior = nullptr, DulNode *next = nullptr);
DulNode(ElemType data, DulNode* p, DulNode* n) : m_data(data), m_p(p), m_n(n) { }
}
增删改查略
四、总结
线性表中关于顺序表和链表的优缺点:
顺序表(array):访问数据简单,增删数据复杂,适用于查询频繁,很少增删,对存储空间要求不大的情景;
链表(list):数据增删方便,访问耗时,适用于数据量小,频繁增删的情景;