循环单链表的基本操作
第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;
}


















