线性表
2.1 线性表的定义和特点
数据特性相同 有限序列
2.2 案例
一元多项式
稀疏多项式:在所有系数都不为零的情况下 需要多一倍的存储空间 但在一般情况下是省空间的
2.3 线性表的类型定义
2.4 线性表的顺序表示和实现
2.4.1 线性表的顺序存储表示
需要地址连续的存储单元
逻辑上相邻的数据元素在物理次序上也相邻
2.5.1 单链表的定义和表示
存储结构(物理位置)可以不连续。(非顺序映像/链式映像)
typedef struct LNode {
ElemType data; // 数据域
struct LNode *next; // 指针域
} LNode, *LinkList; // (同一结构体指针类型起了两个名称)
// LinkList是指向结构体LNode的指针类型
// 如果定义LinkList L 则L就是单链表的头指针
// 如果定义LNode *p 则p就是指向单链表中某个结点的指针
每个结点的指针域指向其直接后继结点。
单链表由表头指针唯一确定,所以可以用表头指针的名字为单链表命名。
| 头结点(不作为表长计入) | 头结点的指针域指向首元结点 |
|---|---|
| 首元结点 | 链表中存储第一个元素的结点 |
| 头指针 | 指向链表中第一个结点的指针 |
加入头结点的好处:
-
首元结点可以和其他结点同样处理。
-
判定空表:
L == NULL
头指针始终是指向头结点的非空指针。
单链表是顺序存取 意思是只能从头开始遍历
2.5.2 单链表基本操作的实现
-
初始化
Status IntList(LinkList &L) { L = new LNode; L -> next = NULL; // 头结点指针域置空 return OK; } -
取值
只能从头到尾遍历。
平均时间复杂度为O(n).
Status GetElem(LinkList L, int i, ElemType &e) { p = L-> next; j = 1; // 计数器 while(p && j < i) { p = p -> next; ++j; } if(!p || j > i) return ERROR; e = p -> data; return OK; } -
查找
从头到尾查找。
平均时间复杂度为O(n).
-
插入
Status ListInsert(LinkList &L, int i, ElemType e) { // 在带头结点的链表L中第i个位置插入值为e的结点 p = L; j = 0; while(p && j < i - 1) { p = p -> next; ++j; } if(!p || j > i - 1) return ERROR; s = new LNode; s -> data = e; s -> next = p -> next; p -> next = s; return OK; }注意指针域赋值的顺序。
要插入到第i个位置,要找到第i - 1个位置。
时间复杂度是O(n),因为要先遍历找到插入位置
-
删除
和插入一样,需要先找到待删除元素的前驱结点。
然后修改指针域,并释放待删结点的空间。
Status ListDelete(LinkList &L, int i) { p = L; j = 0; while(p -> next && j < i - 1) { p = p -> next; ++j; } if(!p || j > i - 1) return ERROR; q = p -> next; // 储存被删结点地址 以备释放 p -> next = q -> next; delete q; return OK; }时间复杂度是O(n)。
-
创建
(1) 前插法
每次生成的新结点插入头结点之后(也就是作为新的首元结点)。
void CreateList_H(LinkList &L, int n) { L = new LNode; L -> next = NULL; for(int i = 0; i < n; i++) { p = new LNode; cin >> p -> data; p -> next = L -> next; L -> next = p; // 注意顺序 } } 时间复杂度是O(n)。
(2) 后插法
为了便于每次将新结点插入表尾, 需要一个尾指针指向尾结点。
void CreateList_R(LinkList &L, int n) { L = new LNode; L -> next = NULL; r = L; for(int i = 0; i < n; i++) { p = new LNode; cin >> p -> data; p -> next = NULL; r -> next = p; r = p; } } 时间复杂度是O(n)。
2.5.3 循环链表
最后一个结点的指针域指向头结点
终止条件:p != L && p -> next != L
循环链表的合并:设立尾指针。将第一个表的尾指针指向第二个表的第一个结点,第二个表的尾指针指向第一个表的头结点,然后释放第二个表的头结点。时间复杂度是O(1)
2.5.4 双向链表
克服了单链表单向性的缺点,即既可以查找直接后继,也可以查找直接前驱
一个双链表结点有两个指针域。
d -> next -> prior = d -> prior -> next = d
插入
在第i个位置之前插入元素e
先找到第i - 1个结点的指针域,生成新结点使之数据域赋为e,然后对第i - 1个和第i个结点的指针域赋值(注意赋值顺序),实现新元素的插入。
s = new DulNode;
s -> data = e;
s -> prior = p -> prior;
p -> prior -> next = s;
s -> next = p;
p -> prior = s;
> 第3 4和5 6行代码可以换顺序 但是不能把5 6放在3 4前面 因为5 6改变了p的前驱
删除
删除第i个结点
找到第i - 1个结点的指针域,分别修改被删结点的前驱结点的后继指针和其后继结点的前驱指针
p -> prior -> next = p -> next;
p -> next -> prior = p -> prior;
delete p;
2.6 顺序表和链表的比较
2.6.1 空间
顺序表存储空间预先分配,不能改变;链表空间在使用时才生成,可以改变。
顺序表存储密度大。
2.6.2 时间
顺序表:随机存取 取值的时间复杂度为O(1) 插入/删除时需要大规模进行元素的位置移动
链表:顺序存取 插入删除操作本身的时间复杂度为O(1),但是需要遍历得到要插入/删除的位置
2.7 线性表的应用
2.7.1 线性表合并
2.7.2 有序表合并
-
顺序有序表
void MergeList_Sq(SqList LA, SqList LB, SqList &LC) { LC.length = LA.length + LB.length; LC.elem = new ElemType[LC.length]; // 给新表分配数组空间 pc = LC.elem; pa = LA.elem; pb = LB.elem; pa_last = LA.elem + LA.length - 1; pb_last = LB.elem + LB.length - 1; while(pa <= pa_last && pb <= pb_last) { // 两表都未到达表尾 if(*pa < *pb) *pc++ = *pa++; else *pc++ = *pb++; } while(pa <= pa_last) *pc++ = *pa++; // LB到达表尾 while(pb <= pb_last) *pc++ = *pb++; // LA到达表尾 }时间复杂度为O(m + n), 空间复杂度为O(m + n) 【m, n分别为两表的长度】
-
链表有序表
非递减链表A,B,合成一个非递减链表C
void MergeList(LinkList &LA, LinkList &LB, LinkList &LC) { pa = LA -> next; pb = LB -> next; LC = LA; // LA的头结点作为LC的头结点 pc = LC; while(pa && pb) { if(pa -> data <= pb -> data) { pc -> next = pa; // pa指的结点连到pc后面 pc = pa; pa = pa -> next; } else { pc -> next = pb; pc = pb; pb = pb -> next; } } pc -> next = pa ? pa : pb; // 非空表的剩余部分链接到pc所指结点之后 delete LB; // 释放LB头结点 }时间复杂度为O(m + n), 空间复杂度为O(1) 【因为不需要建立新的结点空间,只需要将原表结点间的连接关系解除再重新建立新的关系】
广义表的表示方法
一、头尾链表存储
广义表结点的分类
-
表结点:标志域、指示表头的指针域和指示表尾的指针域
-
原子结点:标志域和值域
一对确定的表头和表尾可唯一确定列表
typedef enum{ATOM, LIST} ElemTag;
typedef struct GLNode {
ElemTag tag;
union {
AtomType atom; // 原子结点的值域
struct {struct GLNode *hp, *tp;} ptr; // 表结点的指针域
};
} *GList;
二、扩展线性链表存储
与上述方法的不同点:原子结点也有三个域。
typedef enum{ATOM, LIST} ElemTag;
typedef struct GLNode {
ElemTag tag;
union { // 两种结点的联合部分
AtomType atom; // 原子结点的值域
struct GLNode *hp; // 表结点的表头指针
};
struct GLNode *tp; /// 指向下一个结点的指针
} *GList;
注意:广义表E(a, E)的深度是无穷。因为它自己是自己的一个元素。
浙公网安备 33010602011771号