循环单链表的基本操作

一、第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 **********/ 
    int n=0,length=0;
    LinkList p=L; 
    LinkList s;
    LinkList q=L->next;
    s=(LinkList)malloc(sizeof(LNnode));
    while(q!=L)
    {
        length++;
        q=q->next;
    }
    if((i>length+1)||(i<1))
    {
        return false;
    }
    while(n<i-1)
    {
        p=p->next;
        n++;
    }
    s->data=e;
    s->next=p->next;
    p->next=s;
    return true;

	/********** 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 **********/ 
	LinkList p=L,s=L->next,q;
    int length=0,n=0;
    while(s!=L)
    {
        length++;
        s=s->next;
    }
    if(i<1||i>length)
    {
        return 0;
    }
    while(n<i-1)
{
    n++;
    p=p->next;
}
q=p->next;
p->next=q->next;
e=q->data;
free(q);
return 1;

	/********** End **********/
}

三、第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)
{  
  //将两个采用头指针的循环单链表的首尾连接起来
  /********** Begin **********/ 
  LinkList p=LA,q=LB;
  while(p->next!=LA)
  {
     p=p->next;
  }
  while(q->next!=LB)
  {
     q=q->next;
  }
  q->next=LA;
  p->next=LB->next;
  free(LB);
  return LA;


  /********** End **********/
}
  • 7
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ଲଇଉକ ଲ ̊ଳ

多谢大哥赏赐

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值