线性表顺序表示和实现
线性表的定义及特点
在稍复杂的线性表中一个数据元素可以由若干个数据项组成。在这种情况下,常把数据元素成为记录,含有大量记录的线性表又称为文件。
线性表的逻辑特征是:
1、 在非空的线性表,有且仅有一个开始结点a1,他没有直接前驱,而仅有一个直接后继a2;
2、有且仅有一个终端节点an,他没有直接后继,而仅有一个直接前驱an-1;
3、其余的内部节点ai(2<=i<=n-1)都有且仅有一个直接前驱ai-1和一个直接后继ai+1
抽象数据类型线性表的定义
ADT List {
数据对象:
𝐷={ 𝑎_𝑖 |𝑎_𝑖∈𝐸𝑙𝑒𝑚𝑆𝑒𝑡, 𝑖=1,2,…,𝑛, 𝑛≥0 }
数据关系:
𝑅={ <𝑎_(𝑖−1),𝑎_𝑖>|𝑎_(𝑖−1),𝑎_𝑖∈𝐷, 𝑖=2,…,𝑛
(序偶关系)
基本操作:
结构初始化操作
结构销毁操作
引用型操作
加工型操作
}ADT List
顺序表基本操作
- 结构初始化操作
InitList( &L )
操作结果:构造一个空的线性表L - 结构销毁操作
DestroyList( &L )
初始条件:线性表 L 已存在
操作结果:销毁线性表L - 引用型操作
1、ListEmpty( L ) :判断线性表是否为空
初始条件:线性表 L 已存在
操作结果:若L为空表,则返回TRUE,否则FALSE
2、ListLength( L ):求线性表长度
初始条件:线性表 L 已存在
操作结果:返回L中元素个数。
3、PriorElem( L, cur_e, &pre_e ):求数据元素cur_e的前驱
初始条件:线性表 L 已存在
操作结果:若cur_e是L的元素,但不是第一个,则用pre_e 返回它的前驱,否则操作失败,pre_e无定义
4、NextElem( L, cur_e, &next_e ):求数据元素cur_e的后继
初始条件:线性表 L 已存在
操作结果:若cur_e是L的元素,但不是最后一个,则用next_e返回它的后继,否则操作失败,next_e无定义。
5、GetElem( L, i, &e ):求线性表第i位置的值
初始条件:线性表 L 已存在且 1≤ i ≤ ListLength (L)
操作结果:用e 返回L中第 i 个元素的值
6、LocateElem( L, e, compare( ) ):定位函数
初始条件:线性表 L 已存在,e为给定值,compare( )是元素判定函数
操作结果:返回L中第1个与e满足关系compare( )的元素的位序。若这样的元素不存在,则返回值为0。 - 加工型操作
1、ClearList( &L ):线性表置空
初始条件:线性表 L 已存在
操作结果:将L重置为空表
2、PutElem( &L, i, e ):改变数据元素的值
初始条件:线性表 L 已存在且 1≤ i ≤ListLength (L)
操作结果:L中第i个元素赋值为e的值
线性表的顺序表示和实现
线性表的顺序表示又称为顺序存储结构或顺序映像
顺序存储定义:
把逻辑上相邻的数据元素存储在物理上相邻的存储单元中的存储结构。
线性表顺序存储结构占用一片连续的存储空间。知道某个元素的存储位置就可以计算其他元素的存储位置
顺序表的特点:
1、地址连续
2、依次存放
3、随机存取
4、类型相同
顺序表的特点与一维数组一致,所以用一维数组表示顺序表,但是由于线性表长可变(删除),而数组长度不可动态定义,所以我们用一变量表示顺序表的长度属性
顺序表的C语言描述(结构模板):
#define LIST_INIT_SIZE 100 // 线性表存储空间的初始分配量
#define LISTINCREMENT 10 // 线性表存储空间的分配增量
typedef struct {
ElemType *elem; // 存储空间基址
int length; // 当前长度
int listsize; // 当前分配的存储容量
// (以sizeof(ElemType)为单位)
} SqList; // 俗称 顺序表
C语言的内存动态分配
传地址方式 --引用类型作参数
上图可以理解为 i和j 共用一片空间,对i进行操作就相当于对j进行操作
引用类型做形参的三点说明
操作算法中用到的预定义常量和类型:
线性表的基本操作在顺序表中的实现:
InitList(&L) // 结构初始化
Status InitList_Sq( 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;
} // InitList_Sq
算法时间复杂度:O(1)
LocateElem(L, e, compare()) // 查找
int LocateElem_Sq(SqList L, ElemType e, Status (*compare)(ElemType, ElemType)) {
// 在顺序表中查询第一个满足判定条件的数据元素,
// 若存在,则返回它的位序,否则返回 0
i = 1; // i 的初值为第 1 元素的位序
p = L.elem; // p 的初值为第 1 元素的存储位置
while (i <= L.length && !(*compare)(*p++, e)) ++i;
if (i <= L.length) return i;
else return 0;
} // LocateElem_Sq
算法时间复杂度:O(ListLength(L))
ListInsert(&L, i, e) // 插入元素
Status ListInsert_Sq(SqList &L, int i, ElemType e) {
// 在顺序表L的第 i 个元素之前插入新的元素e, i 的合法范围为 1≤i≤L.length+1
……
if(i<1||i>L.length+1)return ERROR;
if(L.length ==MAXSIZE)return ERROR;
q = &(L.elem[i-1]); // q 指示插入位置
for (p = &(L.elem[L.length-1]); p >= q; --p)
*(p+1) = *p; // 插入位置及之后的元素右移
*q = e; // 插入e
++L.length; // 表长增1
return OK;
} // ListInsert_Sq
算法时间复杂度:O(n)
ListDelete(&L, i) // 删除元素
Status ListDelete_Sq(SqList &L, int i, ElemType &e) {
if ((i < 1) || (i > L.length)) return ERROR; // 删除位置不合法
p = &(L.elem[i-1]); // p 为被删除元素的位置
e = *p; // 被删除元素的值赋给 e
q = L.elem+L.length-1; // 表尾元素的位置
for (++p; p <= q; ++p) *(p-1) = *p; // 被删除元素之后的元素左移
--L.length; // 表长减1
return OK;
} // ListDelete_Sq
时间复杂度为O(n)
线性表的顺序存储结构的特点:
存储方式自然;
随机存取元素;
插入、删除操作效率较低;
存储区域连续且独占使用。
线性表的链式表示和实现
与链式存储有关的术语:
1、结点:数据元素的存储映像。有数据域和指针域两部分组成
2、链表:n个结点由指针链组成一个链表。
3、头指针:是指向链表中第一个结点的指针
4、首元结点结点:是指链表中存储第一个数据元素a1的结点
5、头结点:是在链表的首元结点之前附设的一个结点
链表(链式存储结构)的特点
单链表的定义和表示
单链表是由表头唯一确定,因此单链表可以用头指针的名字来命名,若头指针名是L,则把链表成为表L
单链表的存储结构
单链表的C语言表示:
Typedef struct LNode {
ElemType data; // 数据域
struct LNode *next; // 指针域
} LNode, *LinkList;
LinkList L; // L 为单链表的头指针
定义
单链表的基本操作:
- 单链表的初始化
Status InitList(LinkList &L){
L = (LinkList)malloc(sizeof(LNode)) //L=new LNode
L->next = NULL;
return OK;
}
2.判断链表是否为空
int ListEmpty(LinkList L){
if(L->next)
return 0;
else
return 1;
}
- 单链表的销毁(链表销毁后不存在)
思路:从头指针开始,一次释放所有结点
Status DestroyList_L(LinkList &L){
Lnode *p; //或者 LinkList p;
while(L){
p=L;
L = L->next;
delete p;}
return OK;
}
- 清空链表(链表仍存在,单链表中无元素,成为空链表,头指针和头结点仍然在)
思路:一次释放所有结点,并将头结点指针域设置为空
Status ClearList(LinkList &L){
Lnode *p,*q;// 或LinkList p,q;
p = L->next;
while(p){
q = p->next;
delete p;
p = q;}
L->next = NULL;
return OK;
}
- 取值——取单链表中第i个元素的内容
Status GetElem_L(LinkList L, int i, ElemType &e) {
// L是带头结点的链表的头指针,以 e 返回第 i 个元素
p = L->next; j = 1; // p指向第一个结点,j为计数器
while (p && j<i) { p = p->next; ++j; } // 顺指针向后查找,直到 p 指向第 i 个元素或 p 为空
if ( !p || j>i )
return ERROR; // 第 i 个元素不存在
e = p->data; // 取得第 i 个元素
return OK;
} // GetElem_L
算法时间复杂度为:O(ListLength(L))
- ListInsert(&L, i, e),在第i个结点前插入新结点
Status ListInsert_L(LinkList &L, int i, ElemType e) {
// L 为带头结点的单链表的头指针,本算法在链表中第i 个结点之前插入新的元素 e
p = L; j = 0;
while (p && j < i-1)
{ p = p->next; ++j; } // 寻找第 i-1 个结点
if (!p || j > i-1)
return ERROR; // i 大于表长或者小于1
s = (LinkList) malloc ( sizeof (LNode)); // 生成新结点
s->data = e;
s->next = p->next;
p->next = s; // 插入
return OK;
} // LinstInsert_L
- ListDelete (&L, i, &e),删除第i个结点
Status ListDelete_L(LinkList L, int i, ElemType &e) {
// 删除以 L 为头指针(带头结点)的单链表中第 i 个结点
p = L; j = 0;
while (p->next && j < i-1) { p = p->next; ++j; }
// 寻找第 i 个结点,并令 p 指向其前趋
if (!(p->next) || j > i-1)
return ERROR; // 删除位置不合理
q = p->next; p->next = q->next; // 删除并释放结点
e = q->data; free(q);
return OK;
} // ListDelete_L
顺序表和链表的比较