链表
线性表的链式表示与实现
链式存储结构:结点在存储器中的位置是任意的,即逻辑上相邻的数据元素在物理上不一定相邻。
线性表的链式表示又称为非顺序映像。
如何实现?使用指针。
各结点由两个域组成:
数据域:存储元素数值数据
指针域:存储直接后继结点的存储位置
与链式存储有关的术语
01.结点:数据元素的存储映像,由数据域和指针域两部分组成
02.链表:n个结点由指针链成一个链表,是线性表的链式存储映像,称为线性表的链式存储结构
03.单链表、双链表、循环链表:
-结点只有一个指针域的链表,称为单链表或线性链表
-有两个指针域的链表,称为双链表
-首位相接的链表称为循环链表
04.头指针、头节点、首元结点:
-头指针:指向链表中第一个结点的指针
-首元结点:链表中存储第一个元素的结点
-头结点:在首元结点之前设的一个结点;数据域内放表的信息,指针域指向首元结点
如何表示空表?
有头结点时,当头结点的指针域为空表示空表
设置头结点有何用?
01.便于首元结点的处理:首元结点的地址保存在头结点的指针域里,所以链表第一个位置上的操作与其他位置相同,无须特殊处理
02.便于处理空表和非空表:无论链表是否为空,头指针都是指向头结点的非空指针。
03.头结点的数据域内装的是什么?
可以为空,也可与存放线性表的附加信息
链表的特点
01 结点在存储器中的位置是任意的,即逻辑上相邻的数据元素在物理上不一定相邻
02 访问时只能通过头指针进入链表,并通过每个结点的指针域向后扫描其余结点,所以寻找第一个结点和最后一个结点花费时间不同
这种存取元素的方法称为顺序存取法
链表的优缺点
优点:
-数据元素的个数可以自由扩充
-插入、删除等操作不必移动数据,只需修改链接指针,修改效率高
缺点:
-存储密度小
-存取效率不高,必须采用顺序存取,即存取匀速时只能按照链表的顺序进行访问。
单链表的定义和实现
单链表由表头唯一确定,所以单链表可以用头指针的名字命名。
若头指针名为L,则链表称为表L。
单链表的存储结构定义
typedef struct Lnode
{
ElemType data;
struct LNode *next;
}LNode,*LinkList;
//LinkList为Lnode类型的指针
注意区分指针变量和结点变量两个不同的概念
指针变量p:表示结点地址
结点变量*p:表示一个结点
初始化(构造一个空表)
(1)生成新结点作为头结点,用头指针L指向头结点
(2)头结点的指针域置空
Status InitList_L(LinkList&L){
L=new LNode
L->next=NULL
return OK
}
销毁
Status DestroyList_L(Linklist&L)
{
LinkList p;
while(L){
p=L;
L=L->next;
delete p;
}
return OK;
}
清空
Status ClearList(LinkList &L){
//将表L重置为空表
LinkList p,q;
p=L->next //p指向第一个结点
while(p){
q=p->next;
delete p;
p=q;
}
L->next=NULL;
return OK;
}
求表长(数结点)
p=L->next;
i=0;
while(p){
i++;
p=p->next;
}
判断表是否为空
int ListEmpty(LinkList){
if L->next==NULL{ //判断链表的第一个结点是否为空
return 0;
}
else return 1;
}
获取链表中的某个元素
Status GetElem_L(LinkList L,int i,ElemType&e){
p=L->next;
j=1;
while(p&&j<i){
p=p->next;
++j; //++j 将j的值+1作为表达式结果返回,
}
if (!p||j>i) return ERROR //第i个元素不存在
e=p->data;
return OK;
}
查找
LNode *LocateElem_L(LinkList L,Elemtype e){
p=L->next;
while(p&&p->data!=e){
p=p->next;
return p;
}
插入(插在第i个结点之前)
算法步骤:
01.找到第i-1个元素存储位置p
02.生成一个新的结点s
03.将新结点s的数据域置为x
04.新节点的指针域指向第i个结点
05.第i-1个结点p的指针域指向新节点s
Status ListInsert_L(Linklist&L,int i,ElemType e){
p=L;
j=0;
while(p&&j<i-1){
p=p->next; //寻找第i-1个结点
++j;}
if(p||j>i-1) return ERROR;
s=new LNode;
s->data=e;
s->next=p->next; //将结点s插入L中
p->next=s;
return OK;
}
删除
p=L;
j=0;
while(p->next,j<i-1){
p=p->next;
++j;}
q=p->next;
p->next=q->next;
e=q->data;
delete q;
return OK;
}
链表的运算时间效率分析
1.查找:线性链表只能顺序存取,所以在查找时需要从头指针找起,查找的时间复杂度为O(n)
2.插入和删除:因线性链表不需要移动元素,只要修改指针,一般情况下时间复杂度为O(1)
要在单链表中进行前插或删除操作,由于要从头查找前驱结点,所耗时间复杂度为O(n)
单链表的建立(前插法)
void CreatList_F(LinkList&L,int n){
L=new LNode;
L->next=NULL; //先建立一个带有头结点的单链表
for(i=n;i>0;--i){
p=new LNode;
cin>>p->data;
p->next=L->next; //新结点P的指针域等于头结点指向元素的地址
L->next=p; //头结点的指针指向新的结点
}
单链表的建立(尾插法)
void CreateList_L(Linklist&L,int n){
//正序输入N个元素的值,建立带头结点的单链表L
L=new LNode;
L->next=NULL;
r=L; //尾指针r指向头结点
for (i=0;i<n;++i){
p=new LNode; //生成新节点
cin>>p->data; //输入元素值
p->next=NULL; r->next=p; //插入到表尾
r=p; //r指向新的尾结点
}
}
双向链表的插入
Status ListInsert_DuL(DuLinkList&L,int i,ElemType e){
//省略判断条件
s=new DuLNode;
s->data=e;
s->prior=p->prior;
p->prior-next=s;
s->next=p;
p->prior=s;
return OK;
}
双向链表的删除
//省略其他操作
p->prior-next=p->next;
p->next-prior=p->prior;