存取方式:顺序存取
与链式存储有关的术语
- 结点:数据元素的存储映像。由数据域和指针域两部分组成
- 链表:n个节点由指针链组成一个链表。(它是线性表的链式存储映像,称为线性表的链式存储结构)
单链表、双链表、循环链表:
- 结点只有一个指针域的链表,称为单链表或线性链表
- 结点有两个指针域的链表,称为双链表
- 首尾相接的链表称为循环链表
头指针、头结点和首结点:
头指针:是指向链表中第一个结点的指针
首元结点:是指链表中存储第一个数据元素a1的结点
头结点:是在链表的首元结点之前附设的一个结点
- 在链表中设置头结点的好处:
- 便于首元结点的处理:首元结点的地址保存在头结点的指针域中,所以在链表的第一个位置上操作和其他位置一致,无需进行特殊处理
-
便于空表非空表的统一处理:无论是链表是否为空,头指针都是指向头结点的非空指针,因此空表和非空表的处理也就统一
单链表:
- 单链表的存储结构
单链表的定义:
typedef struct Lnode{ //声明结点的类型和指向结点的指针类型
ElemType data; //结点的数据域
struct Lnode *next; //结点的指针域
}Lnode,*LinkList; //LinList为指向结构体Lnode的指针类型
举个列子:存储学生学号,姓名,成绩的单链表结点类型定义如下所示
单链表的基本操作:
单链表的初始化:
【算法步骤】:
- 生成新结点作头结点,用头指针L指向头结点
将头结点的指针域置空
Status InitList_L(LinkList &L){
L=new LNode; //或 L=(LinkList) malloc (sizeof(LNode))
L->next=NULL;
return OK;
}
【补充算法1】——判断链表是否为空
空表:链表中无元素,称为空链表(头指针和头结点仍然存在)
【算法思路】 判断头结点指针是否为空
Status LinkList(LinkList L){ //若为空表,则返回1,否则返回0
if(L->next) //非空
return 0;
else
return 1;
}
【补充算法2】——单链表的销毁:链表销毁后不存在
【算法思路】从头指针开始,依次释放所有结点
Status DestroyList(LinkList &L){ //销毁单链表
Lnode *p; //或LinkList p;
while(L){
p=L;
L=L->next;
delete p;
}
return OK;
}
【补充算法3】——清空链表
链表仍存在,但链表中无元素,成为空链表(头指针和头结点仍然存在)
【算法思路】依次释放所有结点,并将头结点指针域设置为空
//清空单链表L
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;
}
【补充算法4】——求单链表的表长
【算法思路】从首元结点开始,依次计数所有结点
//计算单链表的表长
Int LinkList_L(LinkList L){ //返回L中数据元素的个数
LinkList p;
p=L->next //p指向第一个结点
i=0;
while(p){ //遍历单链表,统计节点数
i++;
p=p->next;
}
}
取值——去单链表中第i个元素的内容
【算法步骤】
-
从第一个结点(L->next)顺链扫描,用指针p指向当前扫描到的结点,p初值p=L->next
-
j做计数器,累计当前扫描过的结点数,j初值为1
-
当p指向扫描到的下一个结点时,计数器j加1
-
当j==i,p所指向的结点就是要找到的第i个结点
代码实现:
Status GetElem_L(LinkList L int i,ElemType &e){ //获取线性表L中的某个数据元素的内容,通过变量e返回
p=L->next;
j=1; //初始化
while(p&&j<i){ //向后扫描,直到p指向第i个元素或p为空
p=p->next;
++j;
}
if(!p||j>i) return ERROR;//第i个元素不存在
e=p->data; //取第i个元素
return OK;
}//GetElemn_L
按值查找——根据指定数据获取该数据所在的位置(地址)
【算法步骤】
- 从第一个结点起,依次和e相比较
- 如果找到一个其值与e相等的数据元素,则返回其在链表中的“位置”或者地址
- 如果查遍整个链表都没有找到其值和e相等的元素,则返回0或“NULL”
// 按值查找——根据指定数据获取该数据所在的位置(地址)
Lnode *LocateElem_L(LinkList L,Elemtype e){
//在线性表L中查找值为e的数据元素
//找到,则返回L中值为e的数据元素的地址,查找失败返回NULL
p=L->next;
while(p&&p->data!=e){
p=p->next;
}
return p;
}
按值查找——根据指定数据获取该数据所在的位置序号
【算法描述】
//按值查找——根据指定数据获取该数据所在的位置序号
int LcoateElem_L(LinkList L,Elemtype e){
//返回L中值为e的数据元素的位置序号,查找失败返回0
p=L->next;
j=1;
while(p&&p->data!=e){
p=p->next;
j++;
}
if(p) return j;
else return 0;
}
插入——在第i个结点前插入值为e的新结点
【算法步骤】
- 首先找到ai-1的存储位置p
- 生成一个数据域为e 的新结点s
- 插入新结点:新结点指针域指向结点ai,结合ai-1的指针域指向新结点
思考:步骤一和步骤二能互换么?先执行步骤二,后执行步骤一可以么?
答:不可以!会丢失掉ai的地址
代码实现:
//在L中第i个元素之前插入数据元素e
Status LinkInsert_L(LinkList &L,int i,ElemType e){
p=L;
j=0;
while(p&&j<i-1){
p=p->next;
++j;//寻找第i-1个结点,p指向i-1结点
if(!p||j>i-1) return ERROR;//i大于表长+1或者小于1,插入位置非法
s=new LNode;
s->data=e;//生成新结点s,将结点s的数据域置为e
s->next=p->next; //将结点s插入L中
p->next=s;
}
} //LinkInsert_L
删除——删除第i个结点
【算法步骤】
- 首先找ai-1的存储位置p,保存要删除的a的值
- 令p->next 指向ai+1
- 释放ai结点的空间
代码实现:
// 删除——删除第i个结点
Status ListDelete_L(LinkList &L,int i,ElemType &e){
p=L;
j=0;
while(p->next &&j<i-1){
p=p->next; //寻找第i个结点,并令p指向其前驱
++j;
}
if(!(p->next)||j>i-1) return ERROR;//删除位置不合理
q=p->next; //临时保存被删结点地址以备释放空间
p->next=q->next;//改变删除结点前驱结点的指针域
e=q->data; //保存删除结点的数据域
delete q; //释放删除结点的空间
return OK;
}//ListDelete_L
单链表的建立:
-
头插法:——元素插入在链表头部也叫前插法
- 从一个空表开始,重复读入数据
- 生成新结点,将读入数据存放到新结点的数据域中
- 从最后一个结点开始,依次将各结点插入到链表的前端
代码实现:
//头插法
void CreateList_H(LinkList &L,int n){
L=new LNode;
L->next=NULL;//先建立一个带头结点的单链表
for(int i=n;i>0;i--){
p=new LNode;//生成新结点p=(LNode*) malloc(sizeof(LNode));
cin>>p->data;//输入元素值scanf(&p->data);
p->next=L->next; //插入到表头
L->next=p;
}
}//CreateList_H
-
尾插法——元素插在链表尾部(后插法)