小甲鱼笔记:数据结构——线性表(四)循环链表和双向链表,约瑟夫问题,魔术师发牌问题,判断一个单链表是否有环

一、循环链表

1. 背景

  • 单链表只能从头指向尾部,如果不从头结点出发,就无法访问到全部结点

  • 单链表中终端结点的指针端由空指针改为指向头结点,使整个单链表形成一个环

  • 头尾相连的单链表就是单循环链表,简称循环链表
    在这里插入图片描述

  • 循环链表的单链表主要差异在于循环的判断空链表的条件上

    • if(head->next == head)
    • 终端结点用指针rear指示,找到终端结点的时间复杂度为O(1);
    • 因为终端用rear表示,则开始结点应该为rear->next->next。时间复杂度也是O(1);

2.初始化循环链表

void ds_init(node **pNode)//**表示链表上的一个元素 如果
{
	int item;
	node *temp;
	node *target;

	printf("输入结点的值,输入0完成初始化\n");
	while(1){
		scanf("%d",&item);
		fflush(stdin);
		if(item == 0)
			return;
		
		if((*pNode) ==NULL){
		/*循环链表中只有一个结点*/
		*pNode =(node*)malloc(sizeof(struct CLinkList));
		if(!(*pNode)){
			exit(0);
			(*pNode)->data = item;
			(*pNode)->next =*pNode;
			}
		else{
			/*找到next指向第一个结点的结点*/
			for(target =(*pNode);target->next !=(*pNode);target =target->next)
				;
			/*生成一个新结点*/
			temp = (node *)malloc(sizeof(struct CLinkList));

			if(!temp)
				exit(0);
			temp->data = item;
			temp->next = *pNode;
			target->next =temp;
		}
	} 
}

3. 循环链表的插入

/*链表存储结构的定义*/
typedef struct CLinkList{
	int data;
	struct CLinkList *next;
}node;

/*插入结点*/
/*参数:链表的第一个结点,插入的位置*/
void ds_insert(node **pNode,int i){
	node *temp;
	node *target;
	node *p;
	int item;
	int j = 1;
	printf("请输入要插入结点的值:");
	scanf(%d“,&item);
	
	if(i =1){
		//新插入的节点作为第一个节点
		temp =(node *)malloc(struct CLinkList);

		if(!temp)
			exit(0);
		
		temp->data =item;
	/*寻找最后一个节点*/
	for(target =(*pNode);target->next!=(*pNode);target =target->next)	
			;
		temp->next =(pNode);
		target->next =temp;
		*pNode =temp;
	}
	else{
		target =*pNode;
		for(;j < (i-1);++j){
			target=target->next;
		}
		//target指向第三个元素
		temp = (node *)malloc(sizeof(struct CLinkList));
		if(!temp)
			exit(0);
		temp->data =item;
		p = target->next;
		target->next = temp;
		temp->next = p;
	}
}

4. 循环链表的删除

void ds_delete(node **pNode,int i){
	node *target;
	node *temp;
	int j = 1;
	if(i == 1){
	//删除的第一个结点
	/*找到最后一个结点*/
	for(target = *pNode;target->next !=*pNode;target = target->next)
		;
		temp =*pNode;
		*pNode =(*pNode)->next;
		target->next =*pNode;
		free(temp);
	}
	else{
		target= *pNode;
		for(;j<i-1;++j){
			target=target->next;
		}
		temp = target->next;
		target->next = temp->next;
		free(temp);
	}
}

5. 返回结点所在的位置

int ds_search(node *pNode,int elem){
	node *target;
	int i = 1;
	for(target = pNode;target->data !=elem && target->next !=pNode;++i){
		target = target->next;
	}
	if(target->next == pNode)/*表中不存在改元素*/
		return 0else
		return 1;
}

二、约瑟夫问题

1.问题提出

  • 一共有41个人围城一个圈,由第一个人开始报数,每报到第3个人的时候,该人就必须自杀,然后由下一个人报数。直到所有人都自杀为止。

2. 代码实现

#include <stdio.h>
#include <stdlib.h>
typeof struct node{
	int data;
	struct node *next;
}node;
node *create(int n){
	node *p =NULL,*head;
	head = (node *)malloc(sizeof (node ));
	p = head;
	node *s;
	int i = 1;
	
	if( 0 !=n ){
		while( i <= n){
			s = (node *)malloc(sizeof(node));
			s->data = i++;
			p->next = s;
			p = s;
		}
		s->next = head->next;
	}
	free(head);
	
	return s->next;
}

int main(){
	int n = 41;
	int m = 3;
	int i;
	node *p = create(n);
	node *temp;
	m %=n;
	
	while( p !=p->next){
		for(i = 1;i <= m-1;i++){
			p = p->next; 
		}
		printf("%d->",p->next->data);
		
		temp = p->next;		//删除第m个节点
		p->next = temp->next;
		free(temp);
		
		p = p->next;
	}
	printf("%d\n",p->data);
	return 0;
}

三、循环链表的特点

  • 在单链表中,我们有了头结点,可以用O(1)的时间访问第一个结点,但对于要访问最后一个结点,我们必须要挨个往下索引,所以需要O(n)的时间

  • 我们可以用O(1)的时间可以由链表指针访问到最后一个结点

  • 我们用指向终端结点的尾指针来表示循环链表

在这里插入图片描述

1、例题:实现将两个线性表(a1,a2,…,an)和(b1,b2,…,bm)连接成一个线性表()a1,…,an,b1,…,bm)的运算

  • 分析

    • 在单链表或头指针表示的单链表以上做这种链接操作,都需要遍历第一个链表,找到结点an,然后将结点b1链到到an的后面,其执行时间是O(n)
    • 若在尾指针表示的单循环链表上实现,则只需要修改指针,无需遍历,其执行时间是O(1)
  • 如图所示

    • 在这里插入图片描述

2、代码实现

//假设A,B为非空循环链表的尾指针
LinkList Connect(LinkList A,LinktList B){
	LinkList p = A-<next; //保存A表的头结点位置
	
	A->next = B->next->next;//B表的开始结点连接到A表尾

	free(B->next);//释放B表的头结点

	B->next = p;

	return B;//返回新循环链表的尾指针
}

四、判断单链表是否有环

  • 方法一:

    • 使用p,q两个指针,p总是向前走,但q每次都从头开始走,对于每个节点,看p走的步数是否和q一样。
    • 如果,当p从6走到3时,用了6步,此时若q从head出发,则只需要两步到3,因而步数不等,出现矛盾,存在环
  • 方法二:

    • 使用p,q两个指针,p每次向前走一步,q每次向前走两步,若在某个时候p==q。则存在环。
#include "stdio.h"

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0

typedef int Status;/*Status是函数的类型,其值是函数结果状态代码*/
typedef int ElemType;/*ElemType类型根据实际情况而定,这里假设为int*/

typedef struct Node{
	ElemType data;
	struct Node *next;
}Node,* LinkList;

/*初始化带头结点的空链表*/
Status InitList(LinkList *L){
	*L = (LinkList)malloc(sizeof(Node));/*产生头结点,并使L指向此头结点*/
	if(!(*L))/*存储分配失败*/
		return ERROR;
	(*L)->next = NULL;/*此指针域为空*/
	
	return OK;
}

/*初始条件:顺序线性表L已存在,操作结果:返回L中数据元素个数*/
int ListLength(LinkList L){
	int i = 0;
	LinkList p = L->next;//p指向第一个结点
	while(p){
		i++;
		p=p->next;
	}
	return i;
}
/*随机产生n个元素的值,建立带表头结点的单链线性表L(头插法)*/
void CreateListHead(LinkList *L,int n){
	ListList p;
	int i;
	srand(time);	//初始化随机数种子
	*L = (LinkList)malloc(sizeof(Node));
	(*L)->next =NULL;		//建立一个带头结点的单链表
	for(i = 0;i < n;i++){
		p = (LinkList)malloc(sizeof(Node));
		p->data = rand()%100 +1;	//随机产生100以内的数字
		p->next = (*L)->next;	
		(*L)->next = p;			//插入到表头
	}
}

/*随机产生n个元素的值,建立带表头结点的单链线性表L(尾插法)*/
void CreateListTail(LinkList *L,int n){
	LinkList p,r;
	int i;
	
	srand(time(0));						//初始化随机数种子
	*L=(LinkList)malloc(sizeof(Node));	//L为整个线性表
	r = *L;								//r为指向尾部的结点

	for(i = 0;i < n ;i++){
		p->(Node)malloc(sizeof(Node));
		p->data = rand()%100+1;
		r->next = p;					//将表尾的终端节点的指针指向新结点
		r = p;							//将当前的新结点定义为表尾终端结点
	}
		r->next =(*L)->next->next;
}

//比较步数的方法 
int HasLoop1(LinkList L){
	LinkList cur1 = L;//定义结点 cur1
	int pos1 = 0; 		//cur1 的步数
	
	while(cur1){
		LinkList cur2 = L;				//定义结点cur2
		int pos2 = 0;					
		while(cur2){
			if(cur2 == cur1){			
				if(pos1 ==pos2)			//走过的步数一样
					break;				//说明没有环
				else{					//否则
					printf("环的位置在第%d个结点处:\n\n",pos2);
					return 1;			//有环并返回1
					}
				}
				cur2  = cur2->next;
				pos2 ++;				//cur2步数自增
			}
			cur1 = cur1->next;			//cur1继续向后一个结点
			pos1++;						//cur1步数自增
	}
			return 0;
}

//利用快慢指针
int HasLoop2(LinkList L){
	int step1 = 1;
	int step2 = 2;
	LinkList p = L;
	ListList q = L;
	
	while(p !=NULL && q !=NULL && q->next !=NULL){
		p = p->next;
		if(q->next !=NULL)
			q = q->next->next;
		
		printf(“p:%d,q:%d\n”,p->data,q->data);
		
		if(p == q){
			return 1;
		}
		return 0;
	}

int main(){
	LinkList L;
	Status i;
	char opp;
	ElemType e;
	int find;
	int temp;
	
	i = InitList(&L);
	printf("初始化L后:ListLength(L)=%d\n",ListLength(L))

	printf("\n1.创建有环链表(尾插法) \n2创建无循环链表(头插法)\n3.判断链表是否有环")while( opp !=''0){
		scanf("%c",&opp);
		switch(opp){
			case '1':
				CreateListTail(&L.20);
				printf("成功创建有环L(尾插法)\n");
				printf("\n");
				break;
			case '2':
				CreateListHead(&L,20); 
				printf("成功创建无环L(头插法)\n");
				printf("\n");
				break;
			case '3':
				printf("方法一:");
				if(HasLoop1(L)){
					printf("有环");
				}
				else{
					printf("无环");
				}
				printf("方法二:");
				if(HasLoop12(L)){
					printf("有环");
				}
				else{
					printf("无环");
				}
		}
	}
	
}

}

五、双向链表

1. 概述

  • 有一个单链表的顺序为 A->B->C->D->E->F

  • 我们此时已经查找到D,再想要查找到C。由双向链表可知,

  • 必须由 D->E->F->A->B->C,过于麻烦

  • 由此引入双向链表
    在这里插入图片描述

2.双向链表的插入操作

在这里插入图片描述

  • 代码实现
s->next = p;
s->prior = p->prior;
p->prior->next = s;
p->prior = s;

3. 双向链表的删除操作

在这里插入图片描述

  • 代码实现
p->prior->next = p->next;
p->next->prior = p->prior;
free(p)

4. 小结

  • 双向链表相对于单链表来说,是要更复杂一点,每个结点多了一个prior指针,对于插入和删除操作的顺序需要格外小心
  • 双向链表有效算法的时间性能,即用空间换取时间

六、双向链表实战

  • 要求实现用户输入一个数使得26个字母的排列发生变化
  • 例如:用户输入3,输出结果为
  • DEFGHIJKLMNOPQRSTUVWXYZABC
  • 代码实现
#include <stdio.h>
#include <stdlib.h>

#define OK 1
#define ERROR  0

typedef char ElemType;
typedef int  Status;//状态返回
typedef struct DualNode;//定义双向循环链表

typedef struct DualNode{
	ElemType data;
	struct DualNode *prior;
	struct DualNode *next;
}DualNode, *DuLinkList;

/*
* 
* 初始化循环链表
*/
Status InitList(DuLinkList *L) {
	DualNode *p, *q;
	int i;
	*L = (DuLinkList)malloc(sizeof(DualNode));
	if (!(*L)) {
		return ERROR;
	}
	(*L)->next = (*L)->prior = NULL;
	p = (*L);

	for ( i = 0; i < 26; i++)
	{
		q = (DualNode *)malloc(sizeof(DualNode));
		if (!q)
		{
			return ERROR;
		}
		q->data = 'A' + i;
		q->prior = p;
		q->next = p->next;	//p->next为NULL,让q->next = p->next ,此时q->next应当为NULL
		p->next = q;		//p->next 指向q结点

		p = q;				//让 p = q 让他们为同一个结点
	}
	p->next = (*L)->next;
	(*L)->next->prior = p;

	return OK;
}


void Caesar(DuLinkList *L, int i) {
	if (i > 0)
	{
		do
		{
			
			(*L) = (*L)->next;
		} while ( --i);
	}
	if (i < 0)
	{
		do 
		{
			(*L) = (*L)->next;
		} while (++i);
	}

}

int main() {
	DuLinkList L;
	int i,n;

	InitList(&L);
	printf("请输入一个整数:");
	scanf("%d",&n);
	printf("\n");
	Caesar(&L, n);
	for ( i = 0; i < 26; i++)
	{
		L = L->next;
		printf("%c",L->data);
	}
	return 0;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值