循环链表题
1 约瑟夫环
背景
据说著名犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特后,39个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。
然而Josephus和他的朋友并不想遵从,Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。问题1
41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止,把41个人自杀的顺序编号输出。
代码1
//n个人围圈报数,报m出列,最后剩下的是几号? #include <stdio.h> #include <stdlib.h> typedef 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++; // 为循环链表初始化,第一个结点为1,第二个结点为2。 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; // m在这里是等于2 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; }
问题2
编号为1~N的N个人按顺时针方向围坐一圈,每人持有一个密码(正整数,可以自由输入),开始人选一个正整数作为报数上限值M,从第一个人按顺时针方向自1开始顺序报数,报道M时停止报数。报M的人出列,将他的密码作为新的M值,从他顺时针方向上的下一个人开始从1报数,如此下去,直至所有人全部出列为止。
代码2
#include <stdio.h> #include <stdlib.h> #define MAX_NODE_NUM 100 #define TRUE 1U #define FALSE 0U typedef struct NodeType { int id; int cipher; struct NodeType *next; } NodeType; /* 创建单向循环链表 */ static void CreaList(NodeType **, const int); /* 运行"约瑟夫环"问题 */ static void StatGame(NodeType **, int); /* 打印循环链表 */ static void PrntList(const NodeType *); /* 得到一个结点 */ static NodeType *GetNode(const int, const int); /* 测试链表是否为空, 空为TRUE,非空为FALSE */ static unsigned EmptyList(const NodeType *); int main(void) { int n, m; NodeType *pHead = NULL; while (1) { printf("请输入人数n(最多%d个): ", MAX_NODE_NUM); scanf("%d", &n); printf("和初始密码m: "); scanf("%d", &m); if (n > MAX_NODE_NUM) { printf("人数太多,请重新输入!\n"); continue; } else break; } CreaList(&pHead, n); printf("\n------------ 循环链表原始打印 -------------\n"); PrntList(pHead); printf("\n-------------删除出队情况打印 -------------\n"); StatGame(&pHead, m); } static void CreaList(NodeType **ppHead, const int n) { int i, iCipher; NodeType *pNew, *pCur; for (i = 1; i <= n; i++) { printf("输入第%d个人的密码: ", i); scanf("%d", &iCipher); pNew = GetNode(i, iCipher); if (*ppHead == NULL) { *ppHead = pCur = pNew; pCur->next = *ppHead; } else { pNew->next = pCur->next; pCur->next = pNew; pCur = pNew; } } printf("完成单向循环链表的创建!\n"); } static void StatGame(NodeType **ppHead, int iCipher) { int iCounter, iFlag = 1; NodeType *pPrv, *pCur, *pDel; pPrv = pCur = *ppHead; /* 将pPrv初始为指向尾结点,为删除作好准备 */ while (pPrv->next != *ppHead) pPrv = pPrv->next; while (iFlag) { for (iCounter = 1; iCounter < iCipher; iCounter++) { pPrv = pCur; pCur = pCur->next; } if (pPrv == pCur) iFlag = 0; pDel = pCur; /* 删除pCur指向的结点,即有人出列 */ pPrv->next = pCur->next; pCur = pCur->next; iCipher = pDel->cipher; printf("第%d个人出列, 密码: %d\n", pDel->id, pDel->cipher); free(pDel); } *ppHead = NULL; getchar(); } static void PrntList(const NodeType *pHead) { const NodeType *pCur = pHead; if (EmptyList(pHead)) return; do { printf("第%d个人, 密码: %d\n", pCur->id, pCur->cipher); pCur = pCur->next; } while (pCur != pHead); getchar(); } static NodeType *GetNode(const int iId, const int iCipher) { NodeType *pNew; pNew = (NodeType *)malloc(sizeof(NodeType)); if(!pNew) { printf("Error, the memory is not enough!\n"); exit(-1); } pNew->id = iId; pNew->cipher = iCipher; pNew->next = NULL; return pNew; } static unsigned EmptyList(const NodeType *pHead) { if(!pHead) { printf("The list is empty!\n"); return TRUE; } return FALSE; }
2 判断链表是否有环
有环的定义:链表的尾节点指向了链表中的某个节点。
题目
思路
方法一:使用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是函数的类型,其值是函数结果状态代码,如OK等 */ 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) { LinkList p; int i; srand(time(0)); /* 初始化随机数种子 */ *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; /* 随机生成100以内的数字 */ r->next=p; /* 将表尾终端结点的指针指向新结点 */ r = p; /* 将当前的新结点定义为表尾终端结点 */ } r->next = (*L)->next->next; } // 比较步数的方法(方法1) int HasLoop1(LinkList L) { LinkList cur1 = L; // 定义结点 cur1 int pos1 = 0; // cur1 的步数 while(cur1) { // cur1 结点存在 LinkList cur2 = L; // 定义结点 cur2 int pos2 = 0; // cur2 的步数 while(cur2) { // cur2 结点不为空 if(cur2 == cur1) { // 当cur1与cur2到达相同结点时 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; } // 利用快慢指针的方法(方法2) int HasLoop2(LinkList L) { int step1 = 1; int step2 = 2; LinkList p = L; LinkList 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 tmp; i = InitList(&L); printf("初始化L后:ListLength(L)=%d\n",ListLength(L)); printf("\n1.创建有环链表(尾插法) \n2.创建无环链表(头插法) \n3.判断链表是否有环 \n0.退出 \n\n请选择你的操作:\n"); while(opp != '0') { scanf("%c",&opp); switch(opp) { case '1': CreateListTail(&L, 10); printf("成功创建有环L(尾插法)\n"); printf("\n"); break; case '2': CreateListHead(&L, 10); printf("成功创建无环L(头插法)\n"); printf("\n"); break; case '3': printf("方法一: \n\n"); if( HasLoop1(L) ) { printf("结论:链表有环\n\n\n"); } else { printf("结论:链表无环\n\n\n"); } printf("方法二:\n\n"); if( HasLoop2(L) ) { printf("结论:链表有环\n\n\n"); } else { printf("结论:链表无环\n\n\n"); } printf("\n"); break; case '0': exit(0); } } }
3 魔术师发牌问题
问题描述
魔术师手中有A、2、3……J、Q、K十三张黑桃扑克牌。在表演魔术前,魔术师已经将他们按照一定的顺序叠放好(有花色的一面朝下)。魔术表演过程为:一开始,魔术师数1,然后把最上面的那张牌翻过来,是黑桃A;然后将其放到桌面上;第二次,魔术师数1、2;将第一张牌放到这些牌的最下面,将第二张牌翻转过来,正好是黑桃2;第三次,魔术师数1、2、3;将第1、2张牌依次放到这些牌的最下面,将第三张牌翻过来正好是黑桃3;……直到将所有的牌都翻出来为止。问原来牌的顺序是如何的。
#include <stdio.h> #include <stdlib.h> #define CardNumber 13 typedef struct node { int data; struct node *next; }sqlist, *linklist; linklist CreateLinkList() { linklist head = NULL; linklist s, r; int i; r = head; for(i=1; i <= CardNumber; i++) { s = (linklist)malloc(sizeof(sqlist)); s->data = 0; if(head == NULL) head = s; else r->next = s; r = s; } r->next = head; return head; } // 发牌顺序计算 void Magician(linklist head) { linklist p; int j; int Countnumber = 2; p = head; p->data = 1; //第一张牌放1 while(1) { for(j=0; j < Countnumber; j++) { p = p->next; if(p->data != 0) //该位置有牌的话,则下一个位置 { p->next; j--; } } if(p->data == 0) { p->data = Countnumber; Countnumber ++; if(Countnumber == 14) break; } } } // 销毁工作(不必要) void DestoryList(linklist* list) j } int main() { linklist p; int i; p = CreateLinkList(); Magician(p); printf("按如下顺序排列:\n"); for (i=0; i < CardNumber; i++) { printf("黑桃%d ", p->data); p = p->next; } DestoryList(&p); return 0; }
4 拉丁方阵问题
问题描述
拉丁方阵是一种n×n的方阵,方阵中恰有n种不同的元素,每种元素恰有n个,并且每种元素在一行和一列中 恰好出现一次。
代码
#include<iostream> using namespace std; /************************************************** *数据储存结构,循环链表。 *************************************************/ struct HanNode{ int data; HanNode * nextPtr; }; /************************************************** *创建循环链表 *************************************************/ HanNode * create(int amount) { if (amount == 1) { HanNode * head = new HanNode; head->data = 1; head->nextPtr = head; return head; } else{ HanNode * head = new HanNode; head->data = 1; head->nextPtr = head; HanNode * preNode = head; HanNode * newNode = 0; int i = 1; while (i < amount) { ++i; newNode = new HanNode; newNode->data = i; newNode->nextPtr = head; preNode->nextPtr = newNode; //| preNode = newNode; } return head; } } /************************************************** *输出拉丁方阵 *************************************************/ void PrintLatinSquare(HanNode * head,int amount) { HanNode * tmpNode = 0; cout << "拉丁方阵输出:" << endl; for (int i = 1; i <= amount; i++) { if (i == 1) { tmpNode = head; } else{ int j = 1; tmpNode = head; while (j < i) { ++j; tmpNode = tmpNode->nextPtr; } } for (int j = 1; j <= amount; j++) { cout << tmpNode->data << ","; tmpNode = tmpNode->nextPtr; } //|换行 cout << endl; } } /************************************************** *销毁拉丁方阵 *************************************************/ void Destroy(HanNode * head) { if (head == head->nextPtr) { delete head; } else{ HanNode * tmpdel = head->nextPtr; HanNode * del = 0; while (tmpdel != head) { del = tmpdel; tmpdel = tmpdel->nextPtr; delete del; } delete head; } } /************************************************** *主函数(入口) *************************************************/ int main(int argc, char *argv[]) { /*HanNode * p = create(5); //|遍历(调试看看) HanNode * tmp = p; for (int i = 1; i <= 5; i++) { cout << tmp->data << endl; tmp = tmp->nextPtr; } //|输出拉丁方阵 PrintLatinSquare(p, 5); //|销毁 Destroy(p); */ //|循环 int i = 1; int amount = 0; HanNode * pLatinSquare = 0; while (i) { cout << "输出要生成的拉丁方阵的大小:(整数)" << endl; cin >> amount; //|创建拉丁方阵 pLatinSquare = create(amount); //|输出拉丁方阵 PrintLatinSquare(pLatinSquare, amount); //|销毁拉丁方阵 Destroy(pLatinSquare); //| cout << "输入1继续,0退出" << endl; cin >> i; } return 1; }
运行结果
输出要生成的拉丁方阵的大小:(整数) 10 拉丁方阵输出: 1,2,3,4,5,6,7,8,9,10, 2,3,4,5,6,7,8,9,10,1, 3,4,5,6,7,8,9,10,1,2, 4,5,6,7,8,9,10,1,2,3, 5,6,7,8,9,10,1,2,3,4, 6,7,8,9,10,1,2,3,4,5, 7,8,9,10,1,2,3,4,5,6, 8,9,10,1,2,3,4,5,6,7, 9,10,1,2,3,4,5,6,7,8, 10,1,2,3,4,5,6,7,8,9, 输入1继续,0退出
5 链表连接(循环链表)
题目
实现将两个线性表(a1,a2,…,an)和(b1,b2,…,bm)连接成一个线性表(a1,…,an,b1,…bm)的运算。
分析:
若在单链表或头指针表示的单循环表上做这种链接操作,都需要遍历第一个链表,找到结点an,然后将结点b1链到an的后面,其执行时间是O(n)。
若在尾指针表示的单循环链表上实现,则只需修改指针,无须遍历,其执行时间是O(1)。
//假设A,B为非空循环链表的尾指针
LinkList Connect(LinkList A,LinkList B)
{
LinkList p = A->next; //保存A表的头结点位置
A->next = B->next->next; //B表的开始结点链接到A表尾
free(B->next); //释放B表的头结点,初学者容易忘记
B->next = p;
return B; //返回新循环链表的尾指针
}
6 双向循环链表
题目1:
要求实现用户输入一个数使得26个字母的排列发生变化,例如用户输入3,输出结果:
DEFGHIJKLMNOPQRSTUVWXYZABC
同时需要支持负数,例如用户输入-3,输出结果:
XYZABCDEFGHIJKLMNOPQRSTUVW代码
#include<stdio.h>
#include<stdlib.h>
#define OK 1
#define ERROR 0;
typedef char ElemType;
typedef int Status;
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=q;
p=q;
}
p->next=(*L)->next;
(*L)->next->prior=p;
return OK;
}
void caser(DuLinkList *L,int i)
{
if(i>0)
{
do
{
(*L)=(*L)->next;
}while(--i);
}
if(i<0)
{
i=i-1;
(*L)=(*L)->next;
do
{
(*L)=(*L)->prior;
}while(++i);
}
}
int main()
{
DuLinkList L;
int i,n;
InitList(&L);
printf("请输入一个整数:\n");
scanf("%d",&n);
printf("\n");
caser(&L,n);
for(i=0;i<26;i++)
{
L=L->next;
printf("%c",L->data);
}
printf("\n");
return 0;
}
题目2
Vigenere(维吉尼亚)加密:当输入明文,自动生成随机密匙匹配明文中每个字母并移位加密。
建议:当然你的随机密匙生成后不能丢掉,丢掉了就很难把明文还原来了,建议把随机密匙和密文加密存储在一起。
能在根目录创建一个文件夹保存当前的密钥, 随机密钥和加密后密钥(3天后解密)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#define TRUE 1
#define FALSE 0
/*定义加密的结构*/
typedef struct Node_
{
int data;
struct Node_ *prev;
struct Node_ * next;
}Node;
typedef Node * List;
void InitList (List *plist);//初始化链表
int CreateList (List *plist, int n); //创建26个字母的链表
int Search (List *plist, int n, char okey); //通过随机密码实现加密
void Encryption (List *plist, char * str, int **tkeys, char **eKeys); // 通过字典加密
char * getstr (void); //获取要加密的密钥
int main (void)
{
List lists;
char *password; // 原始密钥
int *tKeys; //随机密钥
char *eKeys; //加密密钥
FILE *fp; // 创建文件指针
int i;
srand ((unsigned)time(NULL)); //初始化随机函数种子
InitList (&lists);
CreateList (&lists, 26); //创建26大写字母的字典
password = getstr (); //获取任意长度的字符串
Encryption (&lists, password,&tKeys, &eKeys); //获取随机密钥并且加密
system ("pause");
if ((fp = fopen ("keys.txt", "w+")) == NULL) //创建名为keys.txt的文件
{
fprintf (stdout, "Can't open keys.txt file.\n"); //创建文件失败
exit(1);
}
/*下面的都是把相应数据写入文件*/
fprintf (fp,"密码:");
fputs (password, fp);
fprintf (fp, "\n");
fprintf (fp,"随机密钥:");
for (i = 0; i < strlen(password); i++)
fprintf (fp, "%d ", tKeys[i]);
fprintf (fp, "\n加密:");
fputs (eKeys, fp);
free (&tKeys);
free (&eKeys);
free (password);
if (fclose(fp) == NULL) // 关闭文件
{
fprintf (stderr, "Error closing file\n");
exit(1);
}
return 0;
}
void InitList (List *plist)
{
*plist = NULL;
}
int CreateList (List *plist, int n)//创建双向链表
{
Node *pnode = *plist;
Node *pnew;
int i;
for (i = 0; i < n; i++)
{
pnew = (Node*) malloc (sizeof (Node));
if (pnew == NULL)
return FALSE;
pnew->data = 65+i; // 存入数据
pnew->prev = NULL;
pnew->next = NULL;
if (pnode == NULL) //当表是空表时
{
*plist = pnew;
pnode = *plist;
pnew->prev = pnode;
pnew->next = pnode;
}
pnew->next = pnode->next; //新节点指头前节点的next节点
pnew->prev = pnode; //新节点的前驱指向头节点
pnew->next->prev = pnew; //头节点的next节点的前驱指向新节点
pnode->next = pnew; //头节点的next指针指向新节点
pnode = pnode->next; //更新头节点地址
}
return TRUE;
}
int Search (List *plist, int n, char okey)
{
Node * pscan = *plist;
int i;
int nkey;
while (pscan->data != okey) //初始化需加密字母位置
pscan = pscan->next;
for (i = 0; i< n; i++)
{
pscan = pscan->next;
}
nkey = pscan->data; //获取加密后字母
return nkey;
}
void Encryption (List *plist, char * str, int **tKeys, char **eKeys)
{
int tKey;
int num;
int i;
num = strlen (str); //获取密钥长度
(*tKeys) = (int *) malloc (sizeof (int) * (num)); //建立存储随机密钥的数组
(*eKeys) = (char *) malloc (sizeof (char) * (num+1));//建立存储加密密钥的字符串
for (i = 0; i < num; i++)
{
tKey = rand() % 1000; //获取随机密钥
(*tKeys)[i] = tKey; // 存入数组
(*eKeys)[i] = Search (plist, tKey, toupper(str[i])); // 获取加密后字母
}
(*eKeys)[i] = '\0'; //添加字符串结尾
printf ("原始密钥:");
puts (str);
printf ("随机密钥:");
for (i = 0; i < num; i++)
{
printf ("%d ", (*tKeys)[i]);
}
putchar ('\n');
printf ("加密密钥:");
puts (*eKeys);
}
char * getstr (void) // 动态获取字符串
{
char *str;
int i = 1;
printf ("请输入要加密的密钥(A-Z大写字母):");
str = (char*) malloc (sizeof (char) * (i+1));
while ((str[i-1] = getchar ()) != '\n')
{
i++;
str = (char*)realloc (str, sizeof (char) *(i+1));
if (str == NULL)
{
printf ("No enough memory!");
return FALSE;
}
}
str[i-1] = '\0';
return str;
}
7 快慢指针-链表
题目:快速找到未知长度单链表的中间节点。
普通方法:首先遍历一遍链表以确定链表的长度L。然后再次从头节点出发循环L/2次找到单链表的中间节点。算法复杂度为:O(L+L/2)=O(3L/2)。
利用快慢指针原理:设置两个指针search、*mid都指向单链表的头节点。其中 search的移动速度是*mid的2倍。当*search指向末尾节点的时候,mid正好就在中间了。这也是标尺的思想。算法复杂度为:O(L/2)。
程序
Status GetMidNode(LinkList L, ElemType *e)
{
LinkList search, mid;
mid = search = L;
while (search->next != NULL)
{
//search移动的速度是 mid 的2倍
if (search->next->next != NULL)
{
search = search->next->next;
mid = mid->next;
}
else
{
search = search->next; //还有一个元素才指向末尾的情况
}
}
*e = mid->data;
return OK;
}