在今天的快乐刷题中,博主遇到一个很哟西的题目:
给定两个数,求这两个数的最大公约数
例如: 输入:20 40 输出:20
题目内容简洁明了,稍微思考了一下给出一下算法框架: 1.scanf接受输入的数字 2.使用取余数求得两个数的所有公约数 3.使用链表储存两个数的公约数 4.遍历求得最大公约数
#include<stdio.h>
#include<stdlib.h>
第一个头文件大家应该都知道,标准输入输出嘛。 第二个头文件主要是为malloc函数服务的,这个函数是动态内存分配的核心函数。
//链表结构体
struct divisor {
int common;
struct divisor* next;
};
//简化结构体名称
typedef struct divisor DIV;
这里俺定义了一个链表结构体divisor,包含一个整数common(用于存储因式)和一个指向下一个节点的指针next。然后又使用typedef简化了结构体的名称为DIV,使得代码更简洁,易读。
int main() {
int num1, num2;
printf("请输入2个正整数,中间使用空格分隔:");
scanf_s("%d%d", &num1, &num2);
DIV *list1_head = MakeListsed();
DIV *list2_head = MakeListsed();
common(num1,list1_head);
common(num2,list2_head);
sort(list1_head, list2_head);
return 0;
}
博主使用的是VS,所以用scanf_s 函数代替scanf 函数(Microsoft特有的scanf函数报错)。不过这里还可以添加一些诸如用户输入有效性检查之类的东西。其他的函数我会在下文一一展示。
DIV *MakeListsed() {
DIV* head = NULL;
head = (DIV*)malloc(sizeof(DIV));
if (head == NULL) {
printf("Error: 无法分配头节点内存单元");
return (NULL);
}
head->next = NULL;
head->common = 0;
return head;
}
这个函数创建一个链表的头节点,并返回头节点的指针。 如果malloc(分配内存函数)失败,函数会打印错误信息并返回NULL~
void common(int num, DIV* list_head) {
DIV* tail = list_head;
for (int i = 1; i <= num; i++) {
if (num % i == 0) {
tail = write(tail, i);
}
}
}
这个b遍历从1到num的所有整数,找出所有公因数,并将它们添加到链表中。 可以添加一个检查点,检查write函数是否成功添加节点,不过我比较懒,没有弄(doge)。
DIV* write(DIV* tail, int num) {
DIV * pnew;
pnew = (DIV*)malloc(sizeof(DIV));
if (pnew == NULL) {
printf("Error: 无法分配新结点内存单元");
return (NULL);
}
pnew->common = num;
pnew->next = NULL;
tail->next = pnew;
return pnew;
}
这个坨可以在链表的末尾添加一个新的节点,包含一个公因数。 如果malloc失败,函数会打印错误信息并返回NULL。
void sort(DIV* head1, DIV* head2) {
DIV* p, *q;
int max = 1;
for (p = head1->next; p != NULL; p = p->next) {
for (q = head2->next; q != NULL; q = q->next) {
if (p->common == q->common) {
max = (p->common);
}
}
}
printf("他们的最大公因式为%d", max);
}
如题,这个函数的目的是找出两个链表的公共元素,即最大公因数。 max的作用是储存局部比较中最大的公因式,以谋求全局最大公因式。 这里由于链表的建立过程已经使得公因式由小到大排列, 所以使用遍历两两比较公因式是否相等,相等就一定比原来的max大,故可以直接更新max的值。
博主废了九牛二虎之力,花费了90行代码和很多头发,最终还是解决了这一个问题。虽然中间排查bug很痛苦,但是看到最后成果破解了这一道题目,俺也是流下了欣慰的眼泪QWQ。 (为方便阅读,全过程代码放在文末)
秉持者人外有人,天外有天的想法,我查看了榜一大哥的算法。
#include <stdio.h>
int gcd(int a, int b);
int main() {
int num1, num2;
printf("请输入两个正整数:");
scanf("%d %d", &num1, &num2);
printf("最大公因数是:%d\n", gcd(num1, num2));
return 0;
}
int gcd(int a, int b) {
while (b != 0) {
int temp = b;
b = a % b;
a = temp;
}
return a;
}
没……没了?这TM只有18行???? 一想到我用了90行代码实现这玩意,我就要爆炸了啊啊啊TAT
于是我去网上查询一下相关资料,这里的gcd函数运用了辗转相除法。
辗转相除法,也称为欧几里得算法(Euclidean algorithm),是一种求两个正整数的最大公约数(Greatest Common Divisor,GCD)的方法。这种方法基于一个简单的原理:两个整数的最大公约数不变,即使将较大的数字替换为这两个数的差。
具体步骤如下:
举个例子,求48和18的最大公约数:
没错,这就解决了!! 高质量的算法竟然和普通算法差别这么大! 所以大家一定要和博主一起学好数学啊(哭)
#include<stdio.h>
#include<stdlib.h>
//链表结构体
struct divisor {
int common;
struct divisor* next;
};
//简化结构体名称
typedef struct divisor DIV;
void common(int num, DIV* list_head);
DIV* MakeListsed();
DIV* write(DIV* tail, int num);
void sort(DIV* head1, DIV* head2);
int main() {
int num1, num2;
printf("请输入2个正整数,中间使用空格分隔:");
scanf_s("%d%d", &num1, &num2);
DIV *list1_head = MakeListsed();
DIV *list2_head = MakeListsed();
common(num1,list1_head);
common(num2,list2_head);
sort(list1_head, list2_head);
return 0;
}
//找出公因子
void common(int num, DIV* list_head) {
DIV* tail = list_head;
for (int i = 1; i <= num; i++)//遍历数组
{
if (num % i == 0) {
tail = write(tail, i);
}
}
}
//创建链表
DIV *MakeListsed(){
DIV* head = NULL;
head = (DIV*)malloc(sizeof(DIV));//创建头节点
if (head == NULL) {
printf("Error: 无法分配头节点内存单元");
return (NULL);
}
head->next = NULL;//头节点的指针域置NULL
head->common = 0;
return head;//返回头节点地址
}
//链表写入
DIV* write(DIV* tail, int num) {
DIV * pnew;
pnew = (DIV*)malloc(sizeof(DIV));
if (pnew == NULL)
{
printf("Error: 无法分配新结点内存单元");
return (NULL);
}
pnew->common = num;//新结点存入数据
pnew->next = NULL;//新结点指针域置NULL
tail->next = pnew;//新结点插入链表尾
return pnew;//返回尾结点
}
//比较排序
void sort(DIV* head1,DIV* head2){
DIV* p, *q;
int max = 1;
for (p = head1->next;p != NULL;p = p->next) {
for (q = head2->next;q != NULL; q = q->next ) {
if (p->common == q->common) { max = (p->common); }
}
}
printf("他们的最大公因式为%d", max);
}