导图
线性表定义
线性表是由n个数据元素组成的有限序列,其中n≥0。线性表中的每个数据元素都有一个唯一的前驱元素和一个唯一的后继元素,除了第一个元素没有前驱元素,最后一个元素没有后继元素。线性表可以为空表,即n=0。线性表中的数据元素可以是任意类型的,如整数、字符、字符串、结构体等。线性表通常用一维数组来实现,也可以用链表、栈、队列等数据结构来实现。
顺序表
1、概念
用一组地址连续的存储单元依次存储线性表的数据元素
特点:元素在内存中的存储是连续的,元素之间的顺序与其在表中的顺序一致。
顺序表的最大特点是随机存储,只要确定了线性表的起始存储位置,就可以做到将每一项元素随机存储。与一维数组一样,都是随机存储。
2、存储结构
访问顺序表中的元素
- 使用下标arry[i]
- 使用指针p,p+i
3、构建顺序表
顺序表的两大必要元素:基地址、存储长度。
typedef int ElemType; //ElemType的类型根据实际情况而定,这里假定为int
#define maxsize 100 //顺序表可能达到的最大长度
typedef struct
{
ElemType *elem; //存储空间的基地址
int length; //存储长度
}SqList;
注意:length表示顺序表中存储数据的个数。因为数组的下标是从0开始的,而位置序号是从1开始的。所以数据元素a1、a2…an在数组的位置依次为elem[0]、elem[1]…elem[n-1]。
4、顺序表的初始化
算法步骤:
- 为顺序表L动态分配应该预定义大小的数组空间,使elem指向这段空间的基地址。
- 将表的当前长度设置为0。
Status InitList(SqList* L){
//构造一个空的线性表L
L -> elem = (ElemType *)malloc(MAXSIZE*sizeof(ElemType)); //动态分配空间、避免空间的浪费
if(!L -> elem){
return ERROR; //无地址分配、分配失败,退出程序
}
L -> length = 0;
return OK;
}
5、顺序表的取值
算法步骤:
- 判断指定的位置序号为 i 是否合理(1<<i<<L.length),若
Status GetElem(SqList L,int i,ElemType &e)
{
if(i < 1 || i > L.length)
return Error;
e = L,elem[i-1];
return Ok;
}
6、顺序表的插入
算法步骤:
- 判断插入位置 i 是否合法(i 值的合法范围是1<< i << n+1),若不合法返回error。
- 判断顺序表的存储空间是否已满,若满则返回error。
- 将第n个至第 i 个位置的元素依次向后移动一个位置,空出第 i 个位置(i = n+1时无需移动 -> 尾插)。
- 将要插入的元素e放入第 i 个位置。
- 表长加1。
/*
删除操作
初始条件:顺序表L已存在
操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减1
*/
Status ListDelete(SqList *L, int i, ElemType *e){
int k;
if(L->length == 0){ //线性表为空
return ERROR;
}
if(i < 1 || i > L->length){ //删除位置不正确
return ERROR;
}
*e = L -> elem[i-1];
if(i < L->length){ //如果删除位置不在最后位置
for(k = i;k < L->length;k++){
L->elem[k-1] = L->elem[k];
}
}
L->length--; //长度减1
return OK;
}
7、顺序表的删除
算法步骤:
- 判断删除位置 i 是否合法(i 值的合法范围是1<< i << n),若位置不合法则返回error。
- 将第i+1个至第n个元素依次向前移动一个位置(i=n时无需移动)
- 表长减一。
/*
删除操作
初始条件:顺序表L已存在
操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减1
*/
Status ListDelete(SqList *L, int i, ElemType *e){
int k;
if(L->length == 0){ //线性表为空
return ERROR;
}
if(i < 1 || i > L->length){ //删除位置不正确
return ERROR;
}
*e = L -> elem[i-1];
if(i < L->length){ //如果删除位置不在最后位置
for(k = i;k < L->length;k++){
L->elem[k-1] = L->elem[k];
}
}
L->length--; //长度减1
return OK;
}
链表
用一组任意的存储单元存储线性表的数据元素
一、单链表
整个链表的存取必须从头指针开始进行,头指针指示链表中的第一个节点(首元节点)的存储位置。
同时,由于最后一个数据元素没有直接后继,则单链表中最后一个节点的指针为空。
根据上述图片,1的后继节点为2,1的指针域存储的是2的地址,同理,2是1的前驱节点。
1、单链表的存储结构
//------单链表的存储结构------
typedef struct LNode
{
ElemType data; //节点的数据域
struct LNode *next; //节点的指针域
}LNode,*LinkList; //LinkList 是指向结构体 LNode 的指针类型
为了提高程序的可读性。再次对同一结构体指针类型起了两个名称,LinkList与LNode * ,两者的本质是等价的。通常习惯上用LinkList定义单链表,强调定义的是某个单链表的头指针;用LNode 定义指向点链表中任意节点的指针变量。
2、单链表的构建
算法步骤:
- 生成新节点作为头节点,用头指针L指向头节点。
- 头节点的指针域置空。
Status InitList(LinkList &L)
{
L = new LNode;
L->next = NULL:
return Ok;
}
3、单链表的取值
算法步骤:
- 用指针p指向首元结点,用 j 做计数器初始值赋值为1。
- 从首元结点开始依次顺着链域next向下访问,只要指向当前结点的指针不为空(NULL),并且没有达到序号为 i 的节点,则循环执行:
- p指向下一个节点;
- 计数器 j 相应加 1。
- 退出循环时,如果指针 p 为空,或者计数器 j 大于 i ,说明指定的序号 i 值不合法(i 大于表长 n 或者 i 小于等于 0),取值失败返回Error;否则取值成功,此时j = i时,p所指的结点就是要找的结点就是要找的第 i 个结点,用参数e保存当前结点的数据域,返回Ok。
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;
}
4、单链表的查找
算法步骤:
- 用指针 p 指向首元结点。
- 从首元结点开始依次顺着链域next向下访问,只要指向当前结点的指针不为空(NULL),并且所指节点的数据域不等于给定值 e,则循环执行以下操作:p指向下一个节点。
- 返回 p。若查找成功,p此时指向结点的地址值,若查找失败,则 p 的值为 NULL。
LNode *LocateElem(LinkList L,ElemType e)
{
p=L->next;
whlie(P&&p->data!=e)
p==->next;
return p;
}
5、单链表的插入
算法步骤:
- 查找结点ai-1并由指针 p 指向该结点。
- 生成一个新结点*s。
- 将新结点*s的数据域置位e。
- 将新结点*s的指针域指向ai。
- 将结点*p的指针域指向新结点 *s。
Status ListIsert(LinkList &L,Int I,ElemType e)
{
p=L;i=0;
while(p && (j<j-1))
{
p=p->next;
j++;
}
if(!p || j>j-1)
return Error;
s=new LNode;
s->data=e;
s->next=p->nexcy;
p->next=s;
return Ok;
}
6、单链表的删除
算法步骤:
- 查找结点 ai-1 并由指针 p 指向该结点。
- 临时保存待删除结点 ai 的地址在 q 中,以备释放。
- 将结点 *p 的指针域指向 ai 的直接后继结点。
- 释放结点 ai 的空间。
Status ListDelete(LinkList &L,int i)
{
p=L;i=0;
while((p->next) && (j<i-1))
{
p=p->next;
j++;
}
if(!(p->next) || (j>i-1))
retuen Error;
q=p->next;
p->next=q->next;
delete q;
return Ok;
}
7、创建单链表
前插法
算法步骤:
- 创建一个只由头结点的空链表。
- 根据待创建链表包括的元素个数 n,循环 n 次执行以下操作:
- 生成一个新结点 *p;
- 输入元素赋值给新结点 *p 的数据域;
- 将新结点 *p 插入到头结点中。
void CreateList_H(LinkList &L,int n)
{
L=new LNode;
L->next=NULL;
for(i=0;i<n;i++)
{
p=new LNode;
cin>>p->data;
p->next=L->next;
L-enxt=P;
}
}
后插法
算法步骤:
- 创建一个只由头结点的空链表。
- 尾指针 r 初始化,指向头结点。
- 根据创建链表包括的元素个数 n,循环 n 次执行以下操作:
- 生成一个新结点 *p;
- 输入元素值赋给新结点 *p 的数据域;
- 将新结点 *p 插入尾结点 *r之后;
- 尾指针 r 指向新的尾节点 *p。
void CreateList_R(LinkList &L,int n)
{
L=new LNode;
L->next=NULL;
r=L;
for(i=0;i<n;i++)
{
p=new LNode;
cin>>p->data;
p->next=NULL;
r->next=p;
r=p;
}
}
二、循环链表
循环链表其特点是表中最后一个结点的指针域指向头结点,整个链表形成一个环。
空循环链表:
非空循环链表:
三、双向链表
在双向链表的结点中有两个指针域,一个指向直接后继,一个指向直接前驱。
1、 双向链表的存储结构
typedef struct DuLNode
{
ElemType data;
struct DuLNode *prior;
struct DoLNode *next;
}DuLNode,*DuLinkList;
2、双向链表的插入
Status ListIsert_DuL(DuLinList &L,int i,ElemType e)
{
if(!(P=GetElem_DuL(L,i)))
return Error;
s=new DuLNode;
s->data=e;
s->prior=p->prior;
p->prior->next=s;
s->next=p;
p->prior=s;
return Ok;
}
3、双向链表的删除
Status ListDelete_DuL(DuLinList &L,int i,ElemType e)
{
if(!(P=GetElem_DuL(L,i)))
return Error;
p->prior-next=p->next;
p->nextprior=p-prior;
deleye p;
return Ok;
}