线性表的链式存储
1.线性表的链式存储结构
顺序:用一组地址连续的存储单元依次存储线性表中每个数据元素。
链式:用一组地址任意的存储单元(地址可以连续也可以不连续),依次存储线性表中的各数据元素。
链式存储结构中的每个存储单元称为“结点”,节点包含一个数据域和一个指针域;
数据域存放数据元素信息;
指针域存放后继结点地址;
数据元素之间的逻辑关系通过结点中的指针表示;
通常由第一个结点开始,逐一访问所有结点。
具有n个数据元素的线性表对应的n个结点通过链接方式链接成一个链表,即为线性表的链式存储结构。
1.1单链表
链表中每个链结点中仅包含一个指针域,这样的链表称为单链表。
单链表的结点结构:
单链表结构:
head:头指针,保存链表第一个结点的地址
a1处结点:头结点(在链表的第一个结点之前附设一个结点),数据域为空,指针域指向第一个结点。
注意:带头结点的单链表其头结点的引入使得单链表的头指针永远不为空,从而给插入、删除等操作带来了方便。
an处结点:尾结点,指针域的值为空(NULL)
1.1.1结点的数据类型
typedef int ElemType; //ElemType数据元素的数据类型
typedef struct LNode //LNode为结点类型名
{
ElemType data; //data代表数据元素
struct LNode *next; //next为指向下一个结点的指针
}LinkNode; //单链表结点类型
2.单链表的基本操作
2.1单链表的基本操作概念
//p、q为指向任意结点的指针
空表:head->next==NULL
表尾:p->next==NULL
指针后移:p=p->next
结点连接:p->next=q(q结点放在p结点之后)
前驱:若p->next==q,则p指向q的前驱结点
2.2初始化单链表
void InitList(LinkNode *&L)//&引用LinkNode类型的L指针,内部改外部
{//L为指向单链表的头指针
L=new LinkNode;//分配空间,作为为头结点
L->next=NULL;
}
在函数中,需要改变单链表的头指针,因此参数L设计为一个指针的引用,即引用的类型为指针。
2.3判断表空
bool ListEmpty(LinkNode *L)
{//L为指向单链表的头指针
if(L->next==NULL) //头结点指针域为空
return ture;
else
return false;
}
2.4求单链表中当前元素的个数
int ListLength(LinkNode *L)
{//L为指向单链表的头指针
int n=0;
LinkNode *p=L->next;
while (p){
n++; //计数器+1
p=p->next; //指针后移
}
return n;
}
//时间复杂度:O(n)
2.5遍历单链表
void TraverseList(LinkNode *L)
{//L为指向单链表的头指针
LinkNode *p=L->next;
while(p)
{
count<<p->data<<" ";
p=p->next;
}
cout<<endl;
}
3.单链表的重要基本操作
3.1单链表的查找操作
查找指定结点:
设置一个跟踪链表结点的指针p,初始时p指向链表中的第一个结点,然后顺着next域依次指向每个结点。每指向一个结点就判断其是否等于指定结点,
若是,则返回该结点地址。
否则继续向后搜索,直到p为NULL,表示链表中无此元素,返回NULL。
int Find_item(LinkNode *L,ElemType item)
{//L为指向单链表的头指针
LinkNode *p=L->next;
int pos=1;//结点位序
while(p && p->data !=item)
{//从单链表第一个结点开始顺序查找所有结点
p=p->next;
pos++;
}
if (p)
return pos;//返回位置编号
else
return 0; //查找失败
}
3.2获取单链表中指定位置上的数据元素
bool Find_pos(LinkNode *L,int pos,ElemType &item)
{//L为指向单链表的头指针
LinkNode *p=L->next;
int i=1;//结点位序
while (p&&i !=pos)
{
p=p->next;
i++;
}
if(p==NULL)
{//查找不成功,退出运行
cout<<"位置无效"<<endl;
return false;
}
item=p->data;
return true;
}
3.3单链表的插入操作
3.3.1向线性表指定位置插入一个新元素
单链表结点的插入是利用修改结点指针域的值,使其指向新的链接位置来完成插入操作,无需移动任何元素。
假定在链表中指定结点之前插入一个新结点,要完成这种插入必须首先找到所插位置的前一个结点,再进行插入。假设指针p指向待插位置的前驱结点,指针t指向新结点。
bool ListInsert(LinkNode *L,int pos,ElemType item)//向线性表指定位置插入一个新元素
{
LinkNode *p=L;
int i=0;
while(p&&i !=pos-1)
{//查找pos的前驱
p=p->next;
i++;
}
if(p==NULL)
{//查找不成功,退出运行
cout<<"插入位置无效"<<endl;
return falsel
}
LinkNode *t=new LinkNode;//下方图片1处
t->data=item; //下方图片1处
t->next=p->next; //下方图片2处
p->next=t; //下方图片3处
return ture;
}
//时间复杂度:O(n)
3.3.2链表表头插入法
在单链表的头结点之后第一个数据结点之前插入一个新结点,head指向表头结点,head->next表示表头的后继结点,指针t指向待插入结点。
LinkNode *t=new LinkNode;//下图1处
t->data=d; //下图1处
t->next=head->next; //下图2处
head->next=t; //下图3处
3.3.3链表表尾插入法
LinkNode *t=new LinkNode;//下图1处
t->data=d; //下图1处
t->next=NULL; //下图1处
last->next=t; //下图2处
last=t //下图3处
3.4单链表的删除操作
3.4.1删除指定位置的结点
首先找到被删除位置的前一个结点,并用指针p指向删除位置的前驱结点,指针t指向被删除的结点;将指针p所指结点的指针域修改为t所指结点的后继;释放被删结点t,即delete t.
bool ListDelete(LinkNode *L,int pos,ElemType &item)
{
LinkNode *p=L,*t;
int i=0;
while(p->next&&i !=pos-1)
{//查找pos的前驱
p=p->next;
i++;
}
if(p->next==NULL)
{//查找不成功,退出运行
cout<<"删除位置无效"<<endl;
return false;
}
t=p->next; //1处t为被删除结点
p->next=t->next; //2处删除t的链接关系
item=t->data; //保存被删除结点的值
delete t; //3处释放被删除结点
return ture;
}
//时间复杂度:O(n)
3.4.2撤销单链表
void DestroyList(LinkNode *&L)
{//L为指向单链表的头指针
LinkNode *p;
while (L)
{
p=L;
L=L->next;//使用L指针保存下一结点的位置
delete p;
}
}
4.链式存储结构的特点
4.1优点
1、不需要占用连续存储空间,使用链表前不用事先估计存储空间大小。
2、插入和删除操作时,不需要移动大量元素。
4.2缺点
1、操作算法复杂。
2、不能随机存取。
3、需要额外空间来表示元素间的关系,空间代价较高。