循环单链表的基本操作
第1关:循环单链表的插入操作
任务描述
本关任务:编写循环单链表的插入操作函数。
相关知识
对于单链表,每个结点只存储了其后继结点的地址。尾结点之后不再有任何结点,那么它的next域设置有两种方式:
将尾结点的next域用一个特殊值NULL(空指针,不指向任何结点,只起标志作用)表示,这样的单链表为非循环单链表,通常所说的单链表都是指这种类型的单链表。
将尾结点的next域指向头结点,这样可以通过尾结点移动到头结点,从而构成一个查找环,将这样的单链表为循环单链表。
循环单链表的特点是表中尾结点的next域指向头结点,整个链表形成一个环。在循环链表中,从任一结点出发都可以找到表中其他结点,循环单链表逻辑示意图如下:
在循环单链表L中,p所指结点为尾结点的条件是:p->next==L。
循环单链表结点类型定义与单链表一致:
typedef struct LNode // 结点类型定义
{
ElemType data; //数据域
struct LNode *next; //指针域
}LNode,*LinkList; // LinkList为结构指针类型
ElemType类型可根据实际问题需要灵活定义,须针对ElemType类型数据编写输入、输出、比较等函数,本实训任务关卡涉及的ElemType均为int型 :
typedef int ElemType;
void input(ElemType &s);
void output(ElemType s);
int equals(ElemType a,ElemType b);
下面讨论进行循环单链表的基本操作。
循环单链表的初始化操作
创建一个空的循环单链表,它只有头结点,由L指向它。该结点的next域指向该头结点,data域未设定任何值。
void InitList( LinkList &L)
{
// 构造一个空的循环单链表L
L=(LinkList)malloc(sizeof(LNode));
// 申请分配头结点,并使L指向此头结点
L->next=L;
// 头结点的指针域为L
}
查找循环单链表中第i个数据元素值的操作
算法思想:
用p从头开始遍历循环单链表L中的结点(初值指向第一个数据结点),用计数器j累计遍历过的结点,其初值为1。
当p不为L且j<i时循环,p后移一个结点,j增1。
当循环结束时,若p指向头结点则表示查找失败返回0,否则p所指结点即为要找的结点,查找成功,算法返回1。
int GetElem(LinkList L,int i,ElemType &e)
{ int j=1;
SLinkNode *p=L->next; //p指向首结点,计数器j置为1
if (i<=0) return 0; //参数i错误返回0
while (p!=L && j<i) //找第i个结点p
{
j++;
p=p->next;
}
if (p==L) return 0; //未找到返回0
else
{
e=p->data;
return 1; //找到后返回1
}
}
循环单链表的遍历操作
循环单链表的遍历操作
在循环单链表中,用p指针扫描所有结点时,方式有两种:
以p!=L作为循环条件,当p==L时循环结束,此时p回过来指向头结点,所以p应该初始化指向第一个数据结点而不是头结点,否则循环内的语句不会执行。
扫描指针p的初始化为p=L,循环的条件应该为p->next!=L,当p->next==L时循环结束,此时p指向尾结点。
void ListTraverse(LinkList L,void(*vi)(ElemType))
{ LinkNode *p=L->next;
while (p!=L)
{
vi( p->data );
p=p->next;
}
printf("\n");
}
在执行遍历函数时,用函数指针vi来实现对output()函数的调用。
循环单链表的插入操作
算法思想:
在循环单链表L中查找第i个结点p及其前驱结点pre。
若没有这样的结点p返回0。
否则创建一个以e为值的新结点s,将结点s插入在pre结点之后,返回1。
编程要求
根据提示,在右侧编辑器 Begin-End 区间补充代码,完成单链表的初始化操作,遍历操作及插入操作三个子函数的定义,具体要求如下:
int ListInsert(LinkList &L,int i,ElemType e) ;//在循环单链表L中第i个位置之前插入新的数据元素
测试说明
平台会对你编写的代码进行测试:
测试输入:
5
12 47 5 8 69
1
99
预期输出: 插入成功,插入后单链表如下: 99 12 47 5 8 69
测试输入:
5
12 47 5 8 69
7
99
预期输出:
插入位置不合法,插入失败!
输入说明 第一行输入单链表的数据元素的个数M; 第二行输入单链表M个整数; 第三行输入要插入元素的位置; 第四行输入要插入的数据元素的值。
输出说明 第一行输出插入是否成功的提示信息; 如果插入成功,第二行输出插入元素后的单链表所有元素;如果插入失败,则不输出第二行。
开始你的任务吧,祝你成功!
代码示例
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
using namespace std;
/* 定义ElemType为int类型 */
typedef int ElemType;
void input(ElemType &s);
void output(ElemType s);
int equals(ElemType a,ElemType b);
/* 循环单链表类型定义 */
typedef struct LNnode
{
ElemType data;
struct LNnode *next;
}LNnode,*LinkList;
void InitList(LinkList &L);
int ListInsert(LinkList &L,int i,ElemType e) ;
void ListTraverse(LinkList L,void(*vi)(ElemType));
int main() //main() function
{
LinkList A;
ElemType e;
InitList(A);
int n,i;
// cout<<"Please input the list number ";
cin>>n;
for(i=1;i<=n;i++)
{
cin>>e;
ListInsert(A, i, e);
}
//cout<<"请输入插入的位置:"<<endl;
cin>>i;
//cout<<"请输入插入的值:"<<endl;
input(e);
if( ListInsert(A,i,e) )
{
cout<<"插入成功,插入后循环单链表如下:"<<endl;
ListTraverse(A,output) ;
}
else
cout<<"插入位置不合法,插入失败!"<<endl;
return 0;
}
/*****ElemType类型元素的基本操作*****/
void input(ElemType &s)
{
cin>>s;
}
void output(ElemType s)
{
cout<<s<<" ";
}
int equals(ElemType a,ElemType b)
{
if(a==b)
return 1;
else
return 0;
}
/*****循环单链表的基本操作*****/
void InitList(LinkList &L)
{
// 构造一个空的循环单链表L
L=(LinkList)malloc(sizeof(LNnode)); // 产生头结点,并使L指向此头结点
if(!L) // 存储分配失败
return ;
L->next=L; // 指针域为L
}
int ListInsert(LinkList &L,int i,int e)
{
// 在带头结点的循环单链表L的第i个元素之前插入元素e
/********** Begin **********/
LNnode *q=L,*p=q->next,*s;
int j=1;
if (i<=0) return 0; //参数i错误返回0
while (p!=L&&j<i)
{
j++;
q=p;
p=p->next;
}
if (p==L && i>=j+1) return 0;
else{
s=(LNnode *)malloc(sizeof(LNnode));
s->data=e;
s->next=q->next;
q->next=s;
return 1;
}
/********** End **********/
}
void ListTraverse(LinkList L,void(*vi)(ElemType))
{
//依次对循环单链表L的每个数据元素调用函数vi()
LNnode *p=L->next;
while (p!=L)
{
vi(p->data);
p=p->next;
}
printf("\n");
}
第2关:循环单链表的删除操作
任务描述
本关任务:编写循环单链表的删除操作函数。
相关知识
循环单链表的删除算法思想:
在循环单链表L中查找第i-1个结点,若不存在这样的结点返回0。
否则让p指第i-1个结点,q指向后继结点,当q为NULL时返回0,否则将q所指结点删除并释放其空间,返回1。
链表上实现的插入和删除运算,无须移动结点,仅需修改指针。
编程要求
根据提示,在右侧编辑器 Begin-End 区间补充代码,完成单链表的删除操作函数的定义,具体要求如下:
int ListDelete(LinkList L,int i,ElemType &e);// 在循环单链表L中删除第i个元素,并由e返回其值
测试说明
平台会对你编写的代码进行测试:
测试输入:
5
12 47 5 8 69
1 预期输出:
删除成功,删除后单链表如下:
47 5 8 69
删除元素的值:12
测试输入: 5
12 47 5 8 69
6
预期输出:
删除位置不合法,删除失败!
输入说明 第一行输入循环单链表的长度M; 第二行输入循环单链表的M个整数; 第三行输入要删除元素的位置;
输出说明 第一行输出删除是否成功的提示信息; 如果删除成功,第二行输出删除元素后的循环单链表;第三行输出删除的数据元素;如果删除位置不合法,不输出第二行和第三行。
开始你的任务吧,祝你成功!
代码示例
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
using namespace std;
/* 定义ElemType为int类型 */
typedef int ElemType;
void input(ElemType &s);
void output(ElemType s);
int equals(ElemType a,ElemType b);
/* 循环单链表类型定义 */
typedef struct LNnode
{
ElemType data;
struct LNnode *next;
}LNnode,*LinkList;
void InitList(LinkList &L);
int ListInsert(LinkList &L,int i,ElemType e) ;
int ListDelete(LinkList L,int i,ElemType &e);
void ListTraverse(LinkList L,void(*vi)(ElemType));
int main() //main() function
{
LinkList A;
ElemType e;
InitList(A);
int n,i;
// cout<<"Please input the list number ";
cin>>n;
for(i=1;i<=n;i++)
{
cin>>e;
ListInsert(A, i, e);
}
//cout<<"请输入删除的位置:"<<endl;
cin>>i;
if( ListDelete(A,i,e) )
{
cout<<"删除成功,删除后循环单链表如下:"<<endl;
ListTraverse(A,output) ;
cout<<"删除元素的值:";
output(e);
cout<<endl;
}
else
cout<<"删除位置不合法,删除失败!"<<endl;
}
/*****ElemType类型元素的基本操作*****/
void input(ElemType &s)
{
cin>>s;
}
void output(ElemType s)
{
cout<<s<<" ";
}
int equals(ElemType a,ElemType b)
{
if(a==b)
return 1;
else
return 0;
}
/*****循环单链表的基本操作*****/
void InitList(LinkList &L)
{
// 构造一个空的循环单链表L
L=(LinkList)malloc(sizeof(LNnode)); // 产生头结点,并使L指向此头结点
if(!L) // 存储分配失败
return ;
L->next=L; // 指针域为L
}
int ListInsert(LinkList &L,int i,ElemType e)
{
// 在带头结点的循环单链表L的第i个元素之前插入元素e
int j=1;
LNnode *pre=L,*p=pre->next,*s;
if (i<=0) return 0; //参数i错误返回0
while (p!=L && j<i) //查找第i个结点p和其前驱结点pre
{
j++;
pre=p;
p=p->next; //pre、p同步后移一个结点
}
if (p==L && i>=j+1) return 0;//参数i>n+1时错误返回0
else //成功查找到第i个结点的前驱结点pre
{
s=(LNnode *)malloc(sizeof(LNnode));
s->data=e; //创建新结点用于存放元素x
s->next=pre->next; //将s结点插入到pre结点之后
pre->next=s;
return 1; //插入运算成功,返回1
}
}
void ListTraverse(LinkList L,void(*vi)(ElemType))
{
//依次对循环单链表L的每个数据元素调用函数vi()
LNnode *p=L->next;
while (p!=L)
{
vi(p->data);
p=p->next;
}
printf("\n");
}
int ListDelete(LinkList L,int i,ElemType &e) // 不改变L
{
// 在带头结点的循环单链表L中,删除第i个元素,并由e返回其值
/********** Begin **********/
LNnode *q=L,*p=q->next;
int j=1;
if (i<=0) return 0;
while (p!=L&&j<i)
{
j++;
q=p;
p=p->next;
}
if (p==L) return 0;
else{
e=p->data;
q->next=p->next;
return 1;
}
}
第3关:将两个循环单链表合并成一个循环单链表
任务描述
本关任务:设有两个带头结点的循环单链表LA=(a1,a2,…,an),LB=(b1,b2,…,bm),编写一个算法,将LA、LB这两个循环单链表合并为一个循环单链表LA=(a1,…,an,b1,…bm),其头指针为LA。
相关知识
算法思想: 先找到两个链表的尾,并分别由指针p、q指向它们,然后将第一个链表的尾结点与第二个表的第一个结点链接起来,并修改第二个表的尾结点,使它的链域指向第一个表的头结点。
算法说明: 1)循环链表中没有NULL指针。涉及遍历操作时,其终止条件就不再是像非循环链表那样判别p或p->next是否为空,而是判别它们是否等于某一指定指针,如头指针或尾指针等。 2)在单链表中,从一已知结点出发,只能访问到该结点及其后续结点,无法找到该结点之前的其它结点。而在单循环链表中,从任一结点出发都可访问到表中所有结点,这一优点使某些运算在单循环链表上易于实现。
采用上面的方法,需要遍历链表,找到表尾,其执行时间是O(n)。若在尾指针表示的单循环链表上实现,则只需要修改指针,无需遍历,其执行时间是O(1)。
编程要求
根据提示,在右侧编辑器 Begin-End 区间补充代码,完成单链表按照序号i查找数据元素值操作函数的定义,具体要求如下:
LinkList merge_1(LinkList LA,LinkList LB); //将两个采用头指针的循环单链表的首尾连接起来
测试说明
平台会对你编写的代码进行测试:
测试输入:
10
12 47 5 8 6 92 45 63 75 38
8
24 75 86 9 45 63 12 34
预期输出: 12 47 5 8 6 92 45 63 75 38 24 75 86 9 45 63 12 34
输入说明 第一行输入循环单链表LA的长度M; 第二行输入循环单链表LA的M个整数; 第三行输入循环单链表LB的长度N; 第四行输入循环单链表LB的N个整数;
输出说明 输出合并后的循环单链表的所有元素。
开始你的任务吧,祝你成功!
代码示例
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
using namespace std;
/* 定义ElemType为int类型 */
typedef int ElemType;
void input(ElemType &s);
void output(ElemType s);
int equals(ElemType a,ElemType b);
/* 循环单链表类型定义 */
typedef struct LNnode
{
ElemType data;
struct LNnode *next;
}LNnode,*LinkList;
void InitList(LinkList &L);
int ListInsert(LinkList &L,int i,int e) ;
void ListTraverse(LinkList L,void(*vi)(ElemType));
LinkList merge_1(LinkList LA,LinkList LB);
int main() //main() function
{
LinkList A,B;
ElemType e;
InitList(A);
InitList(B);
int n,m,i;
cin>>n;
for(i=1;i<=n;i++)
{
cin>>e;
ListInsert(A, i, e);
}
cin>>m;
for(i=1;i<=m;i++)
{
cin>>e;
ListInsert(B, i, e);
}
A=merge_1(A,B);
ListTraverse(A,output) ;
return 0;
}
/*****ElemType类型元素的基本操作*****/
void input(ElemType &s)
{
cin>>s;
}
void output(ElemType s)
{
cout<<s<<" ";
}
int equals(ElemType a,ElemType b)
{
if(a==b)
return 1;
else
return 0;
}
/*****循环单链表的基本操作*****/
void InitList(LinkList &L)
{
// 构造一个空的循环单链表L
L=(LinkList)malloc(sizeof(LNnode)); // 产生头结点,并使L指向此头结点
if(!L) // 存储分配失败
return ;
L->next=L; // 指针域为L
}
int ListInsert(LinkList &L,int i,ElemType e)
{
// 在带头结点的循环单链表L的第i个元素之前插入元素e
int j=1;
LNnode *pre=L,*p=pre->next,*s;
if (i<=0) return 0; //参数i错误返回0
while (p!=L && j<i) //查找第i个结点p和其前驱结点pre
{
j++;
pre=p;
p=p->next; //pre、p同步后移一个结点
}
if (p==L && i>=j+1) return 0;//参数i>n+1时错误返回0
else //成功查找到第i个结点的前驱结点pre
{
s=(LNnode *)malloc(sizeof(LNnode));
s->data=e; //创建新结点用于存放元素x
s->next=pre->next; //将s结点插入到pre结点之后
pre->next=s;
return 1; //插入运算成功,返回1
}
}
void ListTraverse(LinkList L,void(*vi)(ElemType))
{
//依次对循环单链表L的每个数据元素调用函数vi()
LNnode *p=L->next;
while (p!=L)
{
vi(p->data);
p=p->next;
}
printf("\n");
}
LinkList merge_1(LinkList LA,LinkList LB)
{
//将两个采用头指针的循环单链表的首尾连接起来
LNnode *Ta,*Tb;
Ta = LA;
Tb = LB;
// 找尾
while(Ta->next != LA) Ta = Ta->next;
while(Tb->next != LB) Tb = Tb->next;
// 合并
Ta->next = LB->next; // Ta尾连接到Tb的头的后一个节点
free(LB);
Tb->next = LA;
return LA;
}