循环链表与双向链表

8 篇文章 1 订阅
5 篇文章 0 订阅

1、循环链表

1.1 介绍

循环链表也是一种链式存储结构,它的特点是表中最后一个节点的指针域指向头节点。这样,从表中任何一个节点出发均可找到表中的其他所有节点。但要注意的是,这里循环链表遍历的条件不是p或者p->next是否为空,而是它们是否等于头指针(或者是是否等于开始遍历的节点指针)。图1是单循环链表的示意图。


图1 单循环链表

1.2 带尾指针的循环链表的合并

有时,在循环链表中设立尾指针而不设头指针,可以使某些操作简化,如将两个先线性表合并成一个表时,这个操作需要只需要改变两个指针的值即可,时间复杂度为O(1),如下图2。


图2 带尾指针的单循环链表的合并

其具体操作过程如下代码:

A_head=A->next;
A->next=B->next->next;
B_head=B->next;
B->next=A_head;
free(B_head);A=B;

1.3 循环链表的应用——约瑟夫问题

约瑟夫斯问题(有时也称为约瑟夫斯置换),是一个出现在计算机科学和数学中的问题。在计算机编程的算法中,类似问题又称为约瑟夫环。这个问题是以弗拉维奥·约瑟夫斯命名的,它是1世纪的一名犹太历史学家。他在自己的日记中写道,他和他的40个战友被罗马军队包围在洞中。他们讨论是自杀还是被俘,最终决定自杀,并以抽签的方式决定谁杀掉谁。约瑟夫斯和另外一个人是最后两个留下的人。约瑟夫斯说服了那个人,他们将向罗马军队投降,不再自杀。约瑟夫斯把他的存活归因于运气或天意,他不知道是哪一个。
约瑟夫问题:有n个囚犯站成一个圆圈,准备处决。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。问题是,给定了n和k,一开始要站在什么地方才能避免被处决?(上面两段来自维基百科)

约瑟夫问题的典型应用就是猴子选大王:n只猴子要选大王,选举方法如下:所有猴子按 1,2 ……… n 编号并按照顺序围成一圈,从第 k 个猴子起,由1开始报数,报到m时,该猴子就跳出圈外,下一只猴子再次由1开始报数,如此循环,直到圈内剩下一只猴子时,这只猴子就是大王。输入数据:猴子总数n,起始报数的猴子编号k,出局数字m。输出数据:猴子的出队序列和猴子大王的编号。

循环链表可以说是原生支持这个场景,下面是代码来模拟该问题的解决,如下。

#include<stdio.h>
#include<stdlib.h>
#define OK 0
#define ERROR 1

typedef int ElemType;
typedef int Status;

//monkey total amount:n, start money number:k, the out nubmer:m
int n, k, m;

typedef struct LLNode
{
	ElemType data;
	struct LLNode *next;
}LLNode, *LoopLinkList;

//intitialize the loop link list L with data 1 to n
Status init_list(LoopLinkList &L, int n)
{
	//malloc space for head node
	L = (LoopLinkList)malloc(sizeof(LLNode));
	if (!L)
	{
		printf("The initialization of the list can not be completed!\n");
		return ERROR;
	}
	//the next of head points to itself
	L->next = L;
	//the data of head alse holds data too
	L->data = 1;
	LLNode *p = L;
	int i;
	for (i = 2;i <= n;i++)
	{
		LLNode *s = (LoopLinkList)malloc(sizeof(LLNode));
		if (!s)
		{
			printf("The initialization of the list can not be completed!\n");
			return ERROR;
		}
		s->data = i;
		s->next = p->next;
		p->next = s;
		p = p->next;
	}
	return OK;
}

Status list_delete(LLNode *s, ElemType &e)
{
	//delete the node s->next and return the its value with e
	LLNode *temp = s->next;
	s->next = s->next->next;
	e = temp->data;
	free(temp);
	return OK;
}

int main()
{
	
	printf("input the total amount of monkey:\n");
	scanf("%d", &n);
	printf("input the number of the start monkey:\n");
	scanf("%d", &k);
	printf("input the out amount of monkey:\n");
	scanf("%d", &m);

	LoopLinkList L;
	init_list(L, n);
	
	//find the one before k directly
	int i;
	LLNode *p = L;
	if (k == 1)
	{
		for (i = 1;i < n; i++)
		{
			p = p->next;
		}
	}else
	{
		for (i=1; i < k-1; i++)
		{
			p = p->next;
		}
	}

	while (n != 1)
	{
		//find the (m-1)th node
		for (i = 1;i <= m-1;p = p->next)
		{
			i++;
		}
		ElemType temp;
		if (p->next == L)
		{
			L = p;
		}

		list_delete(p,temp);
		printf("%d out!\n",temp);
		n--;
	}
	
	printf("The king is %d!!\n",L->data);
	return 0;
}
运行结果如下:

input the total amount of monkey:
5
input the number of the start monkey:
5
input the out amount of monkey:
2
1
2
3
4
5
1
-858993460 out!
-858993460 out!
-858993460 out!
-858993460 out!
The king is 2!!
请按任意键继续. . .

2、双向链表

2.1 介绍

由于单链表中只有一个指示直接后继的指针域,因此,从某个节点出发只能顺时针往后寻查其它节点。若要寻查直接前驱则需要从表头指针出发。也就是说,在单链表中,NextElem的时间复杂度为O(1),而PriorElem的时间复杂度则为O(n)。为了克服单链表的这种缺点,我们可以利用双向链表(double linked list)。

在双向链表的节点中,有两个指针域,其一指向直接后继,另一个指向直接前驱,其C语言描述如下。

typedef struct DuLNode{
	ElemType data;
	struct DuLNode *prior;
	struct DuLNode *next;
}DuLNode,*DuLinkList;
2.2 双向循环链表

和单链的循环链表类似,双向链表也可以有循环链表,如图3中(c)所示,链表中有两个环,图3中(b)表示的是只有一个表头节点的空表。


图3 双向循环链表

在循环双向链表中,如果d为指向表中某一一节点的指针(即d为DuLinkList型变量),则会有:d->next->prior=d->prior->next=d。

2.3 双向链表的插入和删除

在双向链表中,ListLength、GetElem和LocateElem等操作仅需要涉及一个方向的指针,它们的算法描述和单链表的操作相同,但在插入和删除时由于要同时修改两个方向的指针,比单链表要稍显复杂。


图4 双向链表的插入和删除
2.4 代码

下面的代码中主要演示双向链表的插入和删除功能。

#include <stdio.h>
#include <cstdlib>

#define ERROR 0
#define OK 1

typedef int ElemType;
typedef int Status;


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

DuLinkList GetInsertPosPtr_DuL(DuLinkList L, int i)
{
	DuLinkList p=L->next;

	//count一下链表中共有多少个元素
	int count;
	if (p == L)
	{
		count = 0;
	}else
	{
		for (count = 0; p != L; p = p->next)
		{
			count++;
		}
	}

	if (i > count+1 || i < 1)
		return NULL;
	else
	{
		p = L;
		int j;
		for (j = 0; j < i; j++)
		{
			p = p->next;
		}
		return p;
	}
}

Status ListInsert_DuL(DuLinkList &L, int i, ElemType e)
{
	//在带头节点的双向循环链表L中第i个位置之前插入元素e,i的合法值为1<=i<=表长+1
	DuLinkList p;
	//在L中确定插入位置指针p
	//当i等于表长加1时,p指向头节点;i大于表长加1时,p=NULL
	if (!(p = GetInsertPosPtr_DuL(L, i)))
	{
		return ERROR;
	}
	DuLinkList s;
	if (!(s = (DuLinkList)malloc(sizeof(DuLNode))))
	{
		return ERROR;
	}
	s->data = e;
	s->prior = p->prior;
	p->prior->next = s;
	s->next = p;
	p->prior = s;
	return OK;
}

DuLinkList GetElemPtr_DuL(DuLinkList L, int i)
{
	DuLinkList p = L->next;

	//count一下链表中共有多少个元素
	int count;
	if (p == L)
	{
		count = 0;
	}else
	{
		for (count = 0; p != L; p = p->next)
		{
			count++;
		}
	}

	if (i > count || i < 1)
		return NULL;
	else
	{
		p = L;
		int j;
		for (j = 0; j < i; j++)
		{
			p = p->next;
		}
		return p;
	}
}

Status ListDelete_DuL(DuLinkList &L, int i, ElemType &e)
{
	//删除带头节点的双向循环链表L的第i个元素,i的合法值为1<=i<=表长
	DuLinkList p;
	if (!(p = GetElemPtr_DuL(L, i)))
	{
		return ERROR;
	}
	e = p->data;
	p->prior->next = p->next;
	p->next->prior = p->prior;
	free(p);
	return OK;
}

int main()
{
	//定义一个循环链表,并将其初始化
	DuLinkList L;
	L = (DuLinkList)malloc(sizeof(DuLNode));
	L->next = L;
	L->prior = L;

	int i;
	for (i = 0; i < 5; i++)
	{
		ListInsert_DuL(L, i+1, i);
	}
	printf("链表中的元素如下:\n");
	DuLinkList p;
	i=0;
	for (p = L->next;p != L;p = p->next)
	{
		i++;
		printf("第%d个元素为:%d\n", i, p->data);
	}

	printf("输入要删除第几个元素:");
	int n;
	scanf("%d", &n);
	ElemType deleted;
	ListDelete_DuL(L, n, deleted);

	printf("第%d个元素的值为%d,已经将其从链表中删除。\n",n,deleted);

	printf("现在,链表中的元素如下:\n");
	i=0;
	for (p = L->next;p != L;p = p->next)
	{
		i++;
		printf("第%d个元素为:%d\n", i, p->data);
	}
	return 0;
}
程序的运行结果如下图5。


图5 程序的运行结果





  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值