西安交通大学计教中心 ctec.xjtu.edu.cn 线性链表 西安交通大学计教中心 ctec.xjtu.edu.cn
单链表的概念 采用链式存储结构的线性表有单链表、双向链表、单循环链表以及双向循环链表等多种形式。 单链表用一组地址任意的存储单元存放线性表中的数据元素。由于逻辑上相邻的元素其物理位置不一定相邻,为了建立元素间的逻辑关系,需要在线性表的每个元素中附加其后继元素的地址信息。 这种地址信息称为指针。附加了其他元素指针的数据元素称为结点。
结点定义 结点包含数据域和指针域,结点结构可描述为: 其中data域用来存放结点本身信息,类型由具体问题而定,本节也将其设定为ElemType类型,表示某一种具体的已知类型,next域存放下一个元素地址。
带头结点单链表的逻辑结构 带头结点的单链表 为了能顺次访问每个结点,需要保存单链表第一个结点的存储地址。这个地址称为线性表的头指针,本节用head表示。 为了操作上的方便,可以在单链表的头部增加一个特殊的头结点。头结点的类型与其他结点一样,只是头结点的数据域为空。 增加头结点避免了在删除或添加第一个位置的元素时进行的特殊程序处理。 head a1 a2 an ∧ 带头结点的单链表
带头结点单链表的存储结构 单链表存储结构示意图 … … 存储地址 数据域 22 head 38 86 94 a2 86 a3 NULL … … a3 NULL a1 22 存储地址 单链表存储结构示意图
结点的c++描述 typedef struct LNode { ElemType data; //数据域,ElemType代 //表某种数据类型 struct LNode *next; // 指针域 } *LinkList; 这个定义是自引用类型的。换言之,每个结点都包含另一个同类型结点的地址。
指针操作 假如p为指向某一结点的指针,则该结点的数据域用p->data表示,该结点的指针域用 p->next表示。它们都是变量,可以被赋值,也可向其他变量赋值。 例如: 假定data为整型变量,则 p->data=5; p->next=NULL; 将结点变为:
如果p为指向结点ai的指针,那么p->next就是指向ai后继ai+1的指针;若q为另一指针变量 q=p 指针p指向指针q所指的结点 q=p->next 指针p指向指针q所指结点的后继 ai ai-1 ai+1 q p
p=p->next 指针p向后移动一个结点 ai-1 ai ai+1
常用算法 (1)求单链表的长度 单链表长度不定,要确定链表长度需要走遍表中所有结点才能算出。 int Length( ) { LNode *p=head->next; //p指向第一个元素所在结点 int len=0; while( p!=NULL ) { //逐个检测p结点存在与否 len++; p=p->next; //指针后移 } return len; 为了保持头指针不变,使用了指针p在链表中移动。
q->next=p->next; delete p; ai-1 ai ai+1 ①若第i个结点存在则找到第i和第i-1个结点的指针p和q ②通过语句q->next=p->next将第i-1个结点的指针域赋值为第i+1个结点的指针,将第i个结点从链表中断开。 ③ 释放第i个结点所占空间以便于重用。 q p q->next=p->next; delete p; ai-1 ai ai+1
void Delete( LinkList& head,int i ) { if(i<1) cout<<”不存在第”<<i<<”个元素”; else { LNode *p=head; //p指向头结点(第0个结点) LNode *q; //q和p最终分别指向第i-1和第i个结点 int k=0; while( p!=NULL&&k<i ) { q=p; p=p->next; k++; } if(p==NULL) cout<< i<<”超出链表长度”; q->next=p->next; //从链表中删除该结点 delete p; //释放结点p
(3)在第i个位置插入新结点x ai-1 ai x s 在链表的第i个位置插入一个新结点,需进行如下操作: ① 首先找到第i-1个结点的指针p ② 建立新结点s并通过语句s->next=p->next将其指针指向第i个结点 ③ 通过语句p->next=s将第i-1个结点的指针指向新结点 ai-1 ai p x s 1 2 s->data = x; s->next=p->next; p->next=s;
void Insert(LinkList& head, int i, ElemType x) { if(i<1) cout<<"不存在第"<<i<<"个位置"; else { LNode *p=head; //p最终将指向第i-1个结点 int k=0; //p目前指向第0个结点(头结点) while( p!=NULL&&k<i-1 ) { p=p->next; k++; } if(p==NULL) cout<< i<<"超出链表最大位置"; LNode *s=new LNode; //建立新结点s s->data = x; s->next=p->next; //定义结点s的指针域 p->next=s; //修改结点p的指针域
可以按照数据元素本身的值进行查找,也可以按照数据元素的某个属性进行查找。这里仅给出按照数据元素本身的值进行查找的算法。 (4)在单链表中查找数据值为x的结点 可以按照数据元素本身的值进行查找,也可以按照数据元素的某个属性进行查找。这里仅给出按照数据元素本身的值进行查找的算法。 LNode* Find( LinkList& head, ElemType x ) { LNode *p=head->next; //p指向第一个元素所在结点 while ( p!=NULL && p->data!=x ) p = p->next; return p; }
其他形式的链表 (1)单循环链表 通过把单链表最后一个结点的指针改为指向第一个结点,就可以把一个单链表改造成单循环链表。因为单链表最后一个结点的指针总是空值,所以这样的修改总是可行的。 head head ... a1 a2 an
2)双向链表 如果每个链表结点既有指向下一个元素的指针,又有指向前一个元素的指针,那么这种链表就是双向链表,双向链表结点的定义只需在单链表结点定义的基础上增加一个前向指针即可。 head ... an a2 a1
链表应用举例 约瑟夫环问题可以解释为:将整数1至n围成一个圆圈,假定从某个整数开始顺时针从1数到m时,令该位置整数出列;然后再从下一数开始,顺时针从1数到第m时再令其出列,如此下去,直到圆圈中无整数为止。请写出所有数字出列的顺序。 【例2-2】