约瑟夫环,双向链表插入和删除,循环双向链表操作,(线性表链表)

前言:

今天课上我们老师复习了单链表的头插法和尾插法,插入和删除,这几个不太懂的童鞋关注我上一篇文章( https://juejin.cn/post/7022789985589788709 ),然后老师讲解了约瑟夫环,约瑟夫环采用的是循环链表,今天我们复习一下约瑟夫环的算法,顺便提前预习一下双向链表和循环双向链表。

每日一遍,防止颓废

你的室友可能在划水,但绝对没有停止学习,明修栈道,暗度陈仓
图片库mmexport745448268ed44201630e18478b7f3716_1635173.webp

图片库mmexportc5f86cf7c912511b1ba88c5a6c4cefe8_1635173.webp

1.1 约瑟夫环

我们先了解一下约瑟夫环,约瑟夫环是一个悲惨的故事在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止,我们用简单一点的数据,N个人围成一圈,从第一个开始报数,第M个将被出列。例如N=6,M=5,出列的顺序是:5,4,6,2,3**,**1

来个图分析一下:
image.png
讲人话就是:一个首尾连接的单链表,每次计数,达到要求了把它输出再删除,差不多和单链表的删除一样(只是多循环了几次):
写一下给大家看看,代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct Node {
    int  data;
    struct Node *next;
}link;
link * creattail(int * arc, int length) {//这里我们采用尾插法 ,来创建链表 
    int i;
    link * q ; //q用来标记上个结点的位置,然后q和下一个新建结点连接
    link * H =(link*)malloc(sizeof(link));//创建第一个结点 
    H->data = arc[0];
    H->next = NULL;
    q = H;	//q要记住第一个结点 
    for (i = 1; i<length; i++) {
        link * a = (link*)malloc(sizeof(link));//创建新结点 
        q->next = a;    //上个结点连接新建立的结点,从而产生每次新建结点在最后一个 
        a->data = arc[i]; //给新建立的结点赋值 
        a->next = NULL; //因为新建立的结点是最后一个,所以它的指针域是NULL 
        q = a;  //q 现在标记新建立的结点,作为最后一个结点,方便后面新建立的结点连接q  
    }
    return H;//返回第一个结点 
}
link * creatcycle (int * arc, int length) {//这里我们采用尾插法 ,来创建链表 
    int i;
    link * q ; //q用来标记上个结点的位置,然后q和下一个新建结点连接
    link * H =(link*)malloc(sizeof(link));//创建第一个结点 
    H->data = arc[0];
    H->next = NULL;
    q = H;	//q要记住第一个结点 
    for (i = 1; i<length; i++) {
        link * a = (link*)malloc(sizeof(link));//创建新结点 
        q->next = a;    //上个结点连接新建立的结点,从而产生每次新建结点在最后一个 
        a->data = arc[i]; //给新建立的结点赋值 
        a->next = H; //因为新建立的结点是最后一个,所以我们指向头结点形成循环链表 
        q = a;  //q 现在标记新建立的结点,作为最后一个结点,方便后面新建立的结点连接q  
    }
    return H;//返回第一个结点 
}
void yuesefu(link *p,int n,int m)
{	 int i=1;
	 link * q;
	 q = p;
	 int j=1;
	 while(j<n&&p!=NULL)
	 {	
	 	if(i==m)//判断计数i是不是等于m,如果等于m说明到了q这个指针要出列 
	 	{
	 	  i=1;
	 	  j++;  //统计结点数 
	 	  printf("%d,",p->data);
	 	  q->next = p->next; //上一个结点连接当前的下一个结点 
	 	  free(p);  //释放结点 
	 	  p=q->next; //因为我i赋值为1,所以我之间把p指向q的下一个 
		 }
		 else
		 {
		 	i++;//没到位置计数+1 
		 	q =p; //标记当前位置,因为p马上要去下一个位置 
		 	p=p->next;//到下一个结点 
		 }
	 }
	  printf("%d",p->data);//输出最后一个结点 
	  free(p);
}

void display(link *p) { //链表的输出函数,遍历链表 
    while (p) {
        printf("%d ", p->data);
        p = p->next;
    }
    printf("\n");
}
void displaycycle(link *p,int a) { // 因为是循环链表不管那个结点都可以遍历整个表,所要传遍历次数 a 
	int i=0;
    while (i<a) { 
    	i++;
        printf("%d ", p->data); 
        p = p->next;
    }
    printf("\n");
}
int main() {
    int a[6] = { 1,2,3,4,5,6};
    link * cycle = creatcycle(a, 6);    //采用尾插法,创建循环链表 
    displaycycle(cycle,6);			//输出一下 
    yuesefu(cycle,6,5);				//约瑟夫环实现,cycle代表循环链表,6代表总结点数,5为第几个位置输出 
    free(cycle);
    return 0;
}

效果展示:
image.png

2.双向链表

image.png
双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点(讲人话就是:一个结点可以往前访问也可以往后面访问)结合生活中的楼梯或者电梯联想一下吧:
image.png
用个小故事加深大家的映像

微风拂过那窗口那个女孩的脸庞,她似乎在注视着什么,楼下的人群过马路的人群,有着一个男孩的,她注视着,直到那个男孩注视着她,他们的眼神在交流,女孩脸庞展露着丝丝红润,似刚刚开放的桃花,微微泛红,又似蜜桃,甜润饱满,男孩的眼神,如电一般,犀利,迷人,他们眼神相对,彼此逃避着,又互相牵引着,女孩快速出门,来到了,楼梯间,她犹豫着,他会走那边,这边也可以上下,那边也可以,男孩也犹豫着,天公不作美,他们之间,一个下楼,一个上楼,彼此错过,男孩摇摇头说:“下次遇到她一定要叫她交房租”,女孩叹着气说:“幸亏跑的快,不然就要交房租了”,哈哈哈,女孩下楼就是链表往下遍历next,男孩上楼就是往上遍历port,他们之间其实就是相当于两条链表,但是有同学就问了,我要是那个男孩就上去一半,发现她不在,然后再到另一边下去,就可以遇到她了,说明我们同学还是很聪明的,这就相当于我往下遍历一半,再往上遍历回到起点,这种做法在双向链表里可以做到的,双向链表就好比坐电梯能上能下,楼层就是我们的数据。

#include <stdio.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct Node {
    int  data;
    struct Node *port,*next;
}link;
link * creat(int * arc, int length) {//使用尾插法创建 
    int i=0;
    link * q,* p,*head;
    head = q = p=(link*)malloc(sizeof(link));//创建第一个结点 
    q->data = arc[0];
    q->next = NULL;//第一个结点后继NULL 
    q->port = NULL;// 第一个结点前驱NULL 
    for (i = 1; i<length; i++) {	
        p = (link*)malloc(sizeof(link));//创建新结点 
        p ->data =arc[i];
        p ->port = q; //新结点前驱连接上一个结点 
		q ->next = p;//上一个结点的后继连接当前的结点 
		q =p;		//然后把上一个结点的标记q,指向新结点 
		p->next =NULL; //新结点的指针域指向NULL 
    }
   return head;
}
void displayport(link *p) { //链表的输出函数,使用前驱遍历链表 
    while (p) {
        printf("%d ", p->data);
        p = p->port;
    }
    printf("\n");
}
void displaynext(link *p) { //链表的输出函数,使用后继遍历链表 
 	  printf("%d ", p->data);
	while (p->next) {
		p = p->next; 
        printf("%d ", p->data);   
    }
   	printf("\n");
    displayport(p);//传最后一个结点用后继输出一下 
    
}
int main(int argc, char *argv[]) {
	 int a[6] = { 1,2,3,4,5,6};
     link * list = creat(a, 6);     
     displaynext(list);//使用后继输出 
     
}

博主用的是尾插法新建的效果展示一下:
image.png

3.双向链表插入和删除:

3.1双向链表的插入,插入是需要两个结点的,注意:要保持p的后继是最后断开就可以了,也就是第四步其他几步的位置没要求。

image.png

void insert(link *p,int n,int m)//在第n后的位置插入数据m 
{
	int i= 1;
	while(i<n)
	{
		p =p->next;	
		i++;
	}
	link * q=(link*)malloc(sizeof(link));//创建结点 
	q->data = m;//把m的数据给q 
	p->next->port = q;//2.p->next的前驱连接新结点 
	q->next = p->next;//1.新插入的结点后继连接p->next 
	q->port = p;//3 新插入的结点的前驱指向p 
	p->next = q;//4 p的后继指向q;注前三条位置可以随便动 
			
} 

效果展示:
image.png

3.2双向链表的删除,只需要一个结点,就可以完成啦

image.png
代码如下:

void deletelist(link *p,int n)
{
	int i=1;
	while(i<n)
	{
		p =p->next;	
		i++;
	}
	p->port->next = p->next;//第一步 
	p->next->port = p->port;//第二步 
	free(p);//第三步 
 } 

效果展示:我删除了链表3位置的数据

image.png

3.循环双向链表

3.1循环双向链表和循环单链表差不多,就是多了个前驱结点,可以往前执行。

image.png

3.2创建循环双向链表,让我们卷起来,铁子们

图片库mmexport7e7fb12da5997fbf029f4e19159ee5d8_1635173.webp
用代码实现一下:


link * creatcycle(int * arc, int length) {//使用尾插法创建 
    int i=0;
    link * q,* p,*head;
    head = q = p=(link*)malloc(sizeof(link));//第一步:创建第一个结点 
    p->data = arc[0];
    p->next = p;//第一个结点后继NULL 
    p->port = p;// 第一个结点前驱NULL 
    for (i = 1; i<length; i++) {	
        p = (link*)malloc(sizeof(link));//第二步:创建新结点 
        p ->data =arc[i];
        p ->port = q; //第三步:新结点前驱连接上一个结点 
		q ->next = p;//第四步上一个结点的后继连接当前的结点 
		p->next =head;//第五步:最后一个结点指向head 
		head->port = p;//head的前驱指向p 
		q =p;		//然后把上一个结点的标记q,指向新结点 
    }
   return head;
}
void displaycycle(link *p) { //链表的输出函数,使用后继遍历链表 
    link * head=p; 
 	printf("%d ", p->data);
	p = p->next; 
	while (p!=head) {
        printf("%d ", p->data); 
		p = p->next;   
    }
   	printf("\n");
}

效果展示:
image.png
使用我们之前双向链表后继的输出应该是死循环
image.png
使用我们之前双向链表前驱的输出应该是死循环
image.png

总结:

数据结构,线性表链表的操作我们基本上都操作,循环双向链表的插入和删除和双向表的插入删除是一样的,我们没有做更多的操作了,如果有什么问题,可以评论问我,我们可以一起探讨一下,学习还是得动手敲,博主提供的代码是可以直接建项目跑,也可以跟着博主总结的步骤图,一步一步的敲。好了,创作不易,希望大家喜欢,点赞,关注,评论,收藏,博主会跟着数据结构往下面更新,有喜欢的可以收藏哦。放个图让大家卷起来。

上一篇:数据结构专升本学习笔记,线性表单链表

下一篇:数据结构专升本学习,栈篇(画图教您顺序栈的进栈出栈操作)

图片库mmexportd66b9612472befdcfbc0836c7ee9614d_1635173.webp

  • 5
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

IC00

打赏就发新文

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

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

打赏作者

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

抵扣说明:

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

余额充值