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等操作仅需要涉及一个方向的指针,它们的算法描述和单链表的操作相同,但在插入和删除时由于要同时修改两个方向的指针,比单链表要稍显复杂。
下面的代码中主要演示双向链表的插入和删除功能。
#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 程序的运行结果
原文:http://blog.csdn.net/xia7139/article/details/24102599