一、约瑟夫问题
思路:约瑟夫问题用链表做其实很方便,约瑟夫问题的核心在于如何成“环”,再就是如何解决“出列”的问题。
实现:实现起这个思路其实很简单。成环无非就是最后一个结点的指针域存结点下一个结点的地址(没有头结点就是指向第一个结点);其次就是出列,这个问题也就是内存释放的问题,这个就只要用free函数就可以实现。
代码演示
//成环
typedef struct sn
{
int data;
struct sn* next;
}SN;
int main()
{
int n, m;
int i;
scanf("%d%d", &n, &m);
SN* head, * tail, * p, * q;
head = (SN*)malloc(sizeof(SN));
for (i = 0; i < n; i++)
{
p = (SN*)malloc(sizeof(SN));
p->data = i + 1;
tail->next = p;
p->next = head->next;//形成环状箭头
tail = p;
}
}
//出列
while (p != q)
{
if (i == m)
{
q->next = q->next->next;
printf("%d ", p->data);
free(p);
p = q->next;
count++;
i = 1-count;
}
else
{
q = p;
p = p->next;
i++;
}
}
printf("%d ", p->data);
break;
}
但是只要成”环“和出列就行了吗?
这里还有一个特殊情况,就是说,当有0个人,或者报道0个人出列的情况,如果用上面的方法统一去做的话,是不行的。所以,我们还得考虑这种情况。
接下来用实际题目演示:
约瑟夫问题变形
编号为1…N的N个小朋友玩游戏,他们按编号顺时针围成一圈,按顺时针次序报数,从第1个人报到第M个人出列;然后再从下个人开始报到第M+1个人出列;再从下一个人开始报到第M+2个人出列……以此类推不断循环,直至最后一人出列。请编写程序按顺序输出出列人的编号。
输入格式:
输入为2个整数,分别表示N、M(1≤N,M,K≤10000)。
输出格式:
输出为一行整数,为出列人的编号。每个整数后一个空格。
输入样例1:
6 3
结尾无空行
输出样例1:
3 1 2 6 4 5
结尾无空行
输入样例2:
10 2
结尾无空行
输出样例2:
2 5 9 6 4 8 7 3 1 10
结尾无空行
输入样例3:
5 1
结尾无空行
输出样例3:
1 3 2 5 4
结尾无空行
代码如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<math.h>
#include<stdlib.h>
#include<string.h>
#include<malloc.h>
typedef struct sn
{
int data;
struct sn* next;
}SN;
int main()
{
int n, m;
int i;
scanf("%d%d", &n, &m);
SN* head, * tail, * p, * q;
head = (SN*)malloc(sizeof(SN));
head->data = -1;
head->next = NULL;
int count=0;
while (1)
{
if (n == 0 || m == 0)
{
free(head);
break;
}
else
{
tail = head;
for (i = 0; i < n; i++)
{
p = (SN*)malloc(sizeof(SN));
p->data = i + 1;
tail->next = p;
p->next = head->next;//形成环状箭头
tail = p;
}
p = head->next;
q = tail;
i = 1;
while (p != q)
{
if (i == m)
{
q->next = q->next->next;
printf("%d ", p->data);
free(p);
p = q->next;
count++;
i = 1-count;
}
else
{
q = p;
p = p->next;
i++;
}
}
printf("%d ", p->data);
break;
}
}
return 0;
}
这个问题其实还是一个很基础的链表问题,没有用到算法或者很难的思路,这个就是对尾结点作了特殊处理,以往都是对尾结点指针域进行tail->next=NULL这样的处理变成了tail->next=head->next。其次就是考察了free在链表的基础用法。
二、链表的冒泡排序
链表和数组有一点相似的感觉,都是链表麻烦就麻烦在数据要一个一个从前往后找,找到后面的结点后,还得主动讲起始结点往后调。
这个是数组的冒泡
int main()
{
int a[1001];
int n = strlen(a);
int i = 0, j = 0;
for (i = 0; i < n; i++)
{
for (j = i + 1; j < n; j++)
{
if (a[i] > a[j])
{
int tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
}
}
return 0;
}
如果是链表的话,当n=2时,我们又要如何像数组那样,可以直接找到a[2]的位置呢?
我是用2个结构体指针来操作的,一个指针用于内嵌里面一个一个往后走,和未交换最大值的起始点比较,一个用于记录未交换最大值的起始点。
p = head->next;
int h = p->data;
for (i = 0; i < count; i++)
{
q = p->next;
for (j = i+1; j < count; j++)
{
if ((p->data) >(q->data))
{
int tmp = p->data;
(p->data) = (q->data);
q->data = tmp;
}
q = q->next;
}
p = p->next;
}
这样处理。
再看实例:
7-6 程序设计综合实践 1.4
1.4 编写程序,输入若干正整数,按从小到大次序建立1个带头结点单链表,设计一个实现单链表分离算法的Split函数,将原单链表中值为偶数的结点分离出来形成一个新单链表,新单链表中头结点重新申请,其余结点来自原链表,分离后,原链表中只剩非偶数值所在结点,最后显示2个单链表,在程序退出前销毁单链表。要求Split算法时间复杂性达到O(n),程序不可存在内存泄漏。
输入格式:
若干正整数。
输出格式:
每个单链表输出占一行,元素间用分隔符->分隔;初始单链表、剩余元素单链表、偶数元素单链表,共3行。
输入样例:
100 2 3 50 2 1 5 8
结尾无空行
输出样例:
1->2->2->3->5->8->50->100
1->3->5
2->2->8->50->100
结尾无空行
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<math.h>
#include<stdlib.h>
#include<string.h>
#include<malloc.h>
typedef struct sn
{
int data;
struct sn* next;
}SN;
int main()
{
int n;
SN* head, * tail, * p, * q;
head = (SN*)malloc(sizeof(SN));
head->next = NULL;
tail = head;
int count = 0;
int i = 0, j = 0;
while (scanf("%d",&n)!=EOF)
{
p = (SN*)malloc(sizeof(SN));
p->data = n;
tail->next = p;
tail = p;
count++;
}
tail->next = NULL;
int cnt = 0;
p = head->next;
int h = p->data;
for (i = 0; i < count; i++)
{
q = p->next;
for (j = i+1; j < count; j++)
{
if ((p->data) >(q->data))
{
int tmp = p->data;
(p->data) = (q->data);
q->data = tmp;
}
q = q->next;
}
p = p->next;
}
p = head->next;
for (i = 0; i < count; i++)
{
printf("%d", p->data);
p = (p->next);
if (i < count - 1)
{
printf("->");
}
}
printf("\n");
p = head->next;
for (i = 0; i < count; i++)
{
if ((p->data) % 2 != 0)
{
printf("%d", p->data);
}
p = (p->next);
if (i < count - 1&& (p->data) % 2 != 0)
{
printf("->");
}
}
printf("\n");
p = head->next;
for (i = 0; i < count; i++)
{
if ((p->data) % 2 == 0)
{
printf("%d", p->data);
}
if (i < count - 1&& (p->data) % 2 == 0)
{
printf("->");
}
p = (p->next);
}
return 0;
}
这个题的箭头就类似空格。
7-7 链表的逆置
输入若干个不超过100的整数,建立单链表,然后将链表中所有结点的链接方向逆置,要求仍利用原表的存储空间。输出逆置后的单链表。
输入格式:
首先输入一个整数T,表示测试数据的组数,然后是T组测试数据。每组测试数据在一行上输入数据个数n及n个不超过100的整数。
输出格式:
对于每组测试,输出逆置后的单链表,每两个数据之间留一个空格。
输入样例:
1
11 55 50 45 40 35 30 25 20 15 10 5
输出样例:
5 10 15 20 25 30 35 40 45 50 55
知道冒泡排序处理链表后,我们就很容易用类似冒泡的方法去进行链表的逆序输出。
思路:假如有n个数,我们第m次就往后找n-m次,就是当前最后一个结点,对里面的data进行输出。在这里我们不需要用free处理,这样我们更方便,但是用free处理一下更好,这样更节省空间。
这里的代码也很简单
#define _CRT_SECURE_NO_WARNINGS 1
//
#include<stdio.h>
#include<math.h>
#include<stdlib.h>
#include<string.h>
#include<malloc.h>
typedef struct sn
{
int data;
struct sn* next;
}SN;
int main()
{
int n;
scanf("%d", &n);
int m;
int a;
while (n--)
{
scanf("%d", &m);
SN* head, * r, * p;
head = (SN*)malloc(sizeof(SN));
r = p = head;
int h = m;
while (m--)
{
scanf("%d", &a);
p = (SN*)malloc(sizeof(SN));
p->data = a;
r->next = p;
r = p;
}
r->next = NULL;
p = head->next;
int i = 0, j = 0;
for (i = 0; i < h; i++)
{
p = head;
for (j = 0; j < h - i; j++)
{
p = p->next;
}
printf("%d", p->data);
if (i < h - 1) printf(" ");
}
printf("\n");
}
return 0;
}
7-4 单链表基本操作
请编写程序实现单链表插入、删除结点等基本算法。给定一个单链表和一系列插入、删除结点的操作序列,输出实施上述操作后的链表。单链表数据域值为整数。
输入格式:
输入第1行为1个正整数n,表示当前单链表长度;第2行为n个空格间隔的整数,为该链表n个元素的数据域值。第3行为1个正整数m,表示对该链表施加的操作数量;接下来m行,每行表示一个操作,为2个或3个整数,格式为0 k d或1 k。0 k d表示在链表第k个结点后插入一个数据域值为d的结点,若k=0则表示表头插入。1 k表示删除链表中第k个结点,此时k不能为0。注:操作序列中若含有不合法的操作(如在长度为5的链表中删除第8个结点、删除第0个结点等),则忽略该操作。n和m不超过100000。
输出格式:
输出为一行整数,表示实施上述m个操作后的链表,每个整数后一个空格。输入数据保证结果链表不空。
输入样例:
5
1 2 3 4 5
5
0 2 8
0 9 6
0 0 7
1 0
1 6
输出样例:
7 1 2 8 3 5
这个题也很好处理。
思路:对于超出处理范围的,我们用continue处理即可。对于处理范围内的,我们在对应位置进行插入和删除就行。删除后,我们还要对删除的下一个结点和删除的结点的前一个结点进行连接,所以我们最好用2个结构体指针,一前一后的进行处理最好。插入就更简单了,假如在a,b间插入c这个结点,那我们就是要用,a->c,c->b即可。
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<math.h>
#include<stdlib.h>
#include<string.h>
#include<malloc.h>
typedef struct sn
{
int data;
struct sn* next;
}SN;
int main()
{
int n, m, l, a, b, c, i = 0;
scanf("%d", &n);
SN* head, * p, * q, * tail, * node;
head = (SN*)malloc(sizeof(SN));
//head->next = NULL;
tail = head;
int cnt = n;
while (n--)
{
scanf("%d", &m);
p = (SN*)malloc(sizeof(SN));
p->data = m;
tail->next = p;
tail = p;
}
tail->next = NULL;
scanf("%d", &l);
int count = 0;
while (l--)
{
q = head;
p = head->next;
scanf("%d", &a);
if (a == 0)
{
scanf("%d%d", &b, &c);
if (b > cnt + count)
{
continue;
}
for (i = 0; i < b; i++)
{
q = p;
p = p->next;
}
node = (SN*)malloc(sizeof(SN));
node->data = c;
node->next = q->next;
q->next = node;
count++;
}
else
{
scanf("%d", &b);
if (b == 0 || b > (count + cnt))
{
continue;
}
else
{
for (i = 1; i < b; i++)
{
q = p;
p = p->next;
}
q->next = q->next->next;
free(p);
count--;
}
}
}
p = head;
for (i = 0; i < cnt+count; i++)
{
p = p->next;
printf("%d ", p->data);
}
return 0;
}
7-1 重排链表
给定一个单链表 L1→L2→⋯→Ln−1→Ln,请编写程序将链表重新排列为 Ln→L1→Ln−1→L2→⋯。例如:给定L为1→2→3→4→5→6,则输出应该为6→1→5→2→4→3。
输入格式:
每个输入包含1个测试用例。每个测试用例第1行给出第1个结点的地址和结点总个数,即正整数N (≤105)。结点的地址是5位非负整数,NULL地址用−1表示。
接下来有N行,每行格式为:
Address Data Next
其中Address
是结点地址;Data
是该结点保存的数据,为不超过105的正整数;Next
是下一结点的地址。题目保证给出的链表上至少有两个结点。
输出格式:
对每个测试用例,顺序输出重排后的结果链表,其上每个结点占一行,格式与输入相同。
输入样例:
00100 6
00000 4 99999
00100 1 12309
68237 6 -1
33218 3 00000
99999 5 68237
12309 2 33218
结尾无空行
输出样例:
68237 6 00100
00100 1 99999
99999 5 12309
12309 2 00000
00000 4 33218
33218 3 -1
结尾无空行
思路:这个题目,我采用的是数组模拟链表去做的,先用结构体数组去模拟链表去做,虽然这样会用空间更多,但是这个方法也更快。用结构体数组去存输入,下表就是前一个(add),用.data存下一个地址的下表,这样去操作。然后再用2个整型数组,一个用于存改变前的链表,一个用于储存改变后。
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<math.h>
#include<stdlib.h>
#include<string.h>
#include<malloc.h>
typedef struct sn
{
int data;
int next;
}SN;
int main()
{
int begin, n;
int i = 0;
SN s[100001];
int before[100001], last[100001];
int add, data, next;
scanf("%d%d", &begin, &n);
for (i = 0; i < n; i++)
{
scanf("%d%d%d", &add, &data, &next);
s[add].data = data;
s[add].next = next;
}
add = begin;
int count = 0;
while (add != -1)
{
before[count] = add;
add = s[add].next;
count++;
}
int left = 0, right = count - 1;
int key = 0;
while (left <= right)
{
if (left < right)
{
last[key] = before[right];
key++;
right--;
last[key] = before[left];
key++;
left++;
}
else
{
last[key] = before[left];
key++;
left++;
}
}
for (i = 0; i < count - 1; i++)
{
printf("%05d %d %05d\n", last[i], s[last[i]].data, last[i + 1]);
}
printf("%05d %d -1\n", last[i], s[last[i]].data);
return 0;
}