您的位置:首页 > 其它

循环链表与双向链表

2014-04-21 14:55 344 查看
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 程序的运行结果
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐