蛮力法设计技术

本文介绍了如何使用C语言实现蛮力算法,包括求Smith数和计算数组逆序对个数的过程。作者详细阐述了算法设计、代码实现、复杂度分析,并强调了蛮力法在处理大规模数据时的局限性,提出了未来优化算法和数据结构的学习方向。
摘要由CSDN通过智能技术生成

通过本次实验,掌握蛮力算法的基本思想。

实验环境:

硬件:PC机

软件:windows操作系统,C语言

实验内容

求Smith数:若一个合数的质因数分解式逐位相加之和等于其本身逐位相加之和,则称这个数为Smith数。给定一个正整数N,求大于N的最小Smith数。

实验学时:1

实验过程

1.算法设计

1. 定义一个名为ifSmitch的函数,它接收一个整数参数n。

   函数内部初始化一个变量sum为1,因为任何数至少有1作为其因数。

   使用for循环从2遍历到n/2,检查每一个数i是否为n的因子(即n能否被i整除,即n % i == 0)。

   如果i是n的因子,将其加到sum中。

   循环结束后,检查sum是否等于n。如果是,则说明找到了满足条件的数,返回1;否则返回0。

2. 在main函数中,

   首先读取用户输入的任意一个起始整数(在此例中未使用,直接从1开始查找)。

   初始化一个循环标志变量i为1,使用while循环不断自增n,并调用ifSmitch函数检查n是否满足条件。

   当找到满足条件的数时(即ifSmitch(n)返回1),将循环标志i设置为0,跳出循环。

   最后输出满足条件的这个自然数n。

2.程序清单

#include<stdio.h>



int ifSmitch(int n){

int i ;

int sum = 1;

for(i = 2 ; i <= n/2 ; i++){

if(n%i==0){

sum+=i;

}

}

if(sum == n){

return 1;

}

return 0;

}



int main()

{

int n;

scanf("%d",&n);

int i =1;

while(i==1){

n++;

if(ifSmitch(n)==1){

i = 0;

}

}

printf("%d\n",n);

}

3.复杂度分析

(1)时间复杂度

ifSmitch函数的时间复杂度主要取决于内部的for循环。循环条件是i <= n/2,这意味着循环体将被执行大约n/2次(忽略边界情况,当n为偶数时实际执行次数少一次)。在循环体内,每次迭代都有一个if判断语句,其复杂度为O(1)。因此,整个for循环部分的时间复杂度为O(n)。

然而,main函数中有一个while循环,该循环持续增加n的值,直到找到满足条件的数为止。理论上讲,若要找到这样一个数,所需时间无法预估,因为这依赖于具体的数值分布。但针对每一次调用ifSmitch(n)函数的过程而言,其时间复杂度为O(n)。考虑到实际情况,这个while循环有可能运行很久,但我们通常关注的是单次调用ifSmitch函数的平均时间复杂度,所以在最坏的情况下,对于未知的终止条件,我们不能准确给出while循环的整体时间复杂度。

  1. 空间复杂度

ifSmitch函数的空间复杂度为O(1)。因为它仅使用了固定数量的局部变量(如i和sum),这些变量的数量并不随着输入n的增大而变化。

main函数中也没有动态分配额外的存储空间,所以其空间复杂度也是O(1)。

综上所述,ifSmitch函数的时间复杂度为O(n),空间复杂度为O(1);main函数中涉及while循环的部分,就单次调用ifSmitch函数而言,其时间复杂度为O(n),而整个main函数的空间复杂度同样为O(1)。但由于while循环的终止条件不确定,实际上找到符合条件的数所需的实际步数可能会很大,所以整个程序的实际运行时间难以确定。

  1. 运行结果

实验内容

给定一个整数数组A=(a0,a1,…,an-1),若i<j且ai>aj,则<ai,aj>就为一个逆序对,例如数组(3,1,4,5,2)的逆序对有<3,1>,<3,2>,<4,2>,<5,2>。设计一个穷举算法求A中的逆序对的个数。(分别用基本蛮力算法和递归蛮力算法实现)

实验学时:1

实验过程

  1. 算法设计

1. 算法一(upsetDown1):采用了基本蛮力算法(也称为双重循环遍历法)。首先定义一个变量sum用于统计逆序对的数量。通过两层循环遍历数组,外层循环从第一个元素开始,内层循环从当前外层元素的下一个元素开始。在内层循环中,如果发现当前外层元素大于内层元素,则逆序对数量加1。最后输出逆序对的总数。

2. 算法二(upsetDown2):采用递归蛮力算法。同样是为了统计逆序对的数量,这里的逻辑与第一种方法相似,但以递归方式进行。函数接收四个参数:数组、数组大小、当前逆序对计数器sum和当前处理到的数组下标i。当递归到达数组末尾时(即i == n),返回当前的sum值。在每次递归调用中,都会遍历从当前元素arr[i]之后的所有元素,进行比较并累加逆序对数量,然后递归地处理下一个元素。

在main函数中:

首先获取用户输入的数组元素个数。

动态分配内存存储数组元素。

接收用户输入的数组元素。

调用upsetDown1函数计算并输出逆序对数量。

再次调用upsetDown2函数,并将结果存入s,然后输出递归方式得到的逆序对数量。

2.程序清单

#include<stdio.h>

#include<stdlib.h>



//基本蛮力算法

void upsetDown1(int arr[],int n){

int sum = 0;

int i,j;

for( i = 0 ; i < n ; i++){

for(j = i+1 ; j < n ; j++){

if(arr[i]>arr[j]){

sum++;

}

}

}

printf("逆序对的个数为%d\n",sum);

}



//递归蛮力算法

int upsetDown2(int arr[] , int n, int sum ,int i){

if(i == n){

return sum;

}

int j;

for(j = i+1 ; j < n ; j++){

if(arr[i]>arr[j]){

sum++;

}

}

i++;

upsetDown2(arr,n,sum,i);

}

int main()

{

int n;

printf("请输入正数数组元素个数\n");

scanf("%d", &n);

int* arr = (int*)malloc(n * sizeof(int));

printf("请输入数组元素\n");

for (int i = 0; i < n; i++) {

scanf("%d", &arr[i]);

}

upsetDown1(arr , n);

int sum = 0;

i=0;

int s = upsetDown2(arr , n , sum , i);

printf("逆序对的个数为%d\n",s);

return 0;

}

3.复杂度分析

(1)时间复杂度

先假设两个函数都是遍历一个大小为n的数组并对每一对元素执行某种操作。

1. 对于upsetDown1函数,其时间复杂度为O(n^2)的推导如下:

   假设有一个长度为n的数组,外层循环会对每个元素执行n次操作。

   内层循环则对应于剩下的n-1个元素,总共进行了 n * (n - 1)  次操作。

   因此,当n趋于无穷大时,基本操作次数的增长速度与  n^2  成正比,即时间复杂度为( O(n^2)。

2. 对于upsetDown2函数,虽然采用了递归的形式,但实质上每次递归也是处理了两个子序列,所以同样进行了两两元素的比较,时间复杂度同样是 O(n^2)。

(2)空间复杂度

依旧是先假设两个函数都是遍历一个大小为n的数组并对每一对元素执行某种操作。

1. upsetDown1函数的空间复杂度为O(n),是因为除了输入数组之外,没有额外创建数组或其他数据结构来存储中间结果,所以空间需求固定为输入数组所占的空间。

2.upsetDown2函数的空间复杂度同样为O(n),这是因为递归调用需要使用系统栈来保存每一层递归的信息,最坏情况下递归栈的深度可达n,即最多同时存在n个函数调用帧,每个帧可能包含若干局部变量等信息,故空间复杂度也是线性的,为 O(n)。

4.运行结果

实验总结

设计并实现求解Smith数和计算数组逆序对个数的两种蛮力算法后,我深入理解了基础算法设计与实现的重要性,同时也意识到蛮力法在处理小规模问题时简洁直观,但在面对大规模数据时效率低下。尤其是对于求Smith数的问题,由于未利用数论性质优化,可能导致搜索时间过长。对于逆序对问题,虽然递归蛮力算法结构巧妙,但并未减少时间复杂度,依然为O(n^2)。

今后努力方向:在算法设计上,将进一步探索和学习更高效的方法,如动态规划、分治策略以及哈希表等数据结构的应用,以提高算法效率。此外,针对Smith数问题,可研究如何结合质因数分解和数字特性来优化算法。在实践中,要注重理论与实践相结合,提升算法优化意识,以便在实际场景中灵活运用合适算法解决问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值