目录
这篇文章就来分析解释一下(分享——有趣的题目)这个提到的四个问题,见更多的题目,才能锻炼我们的思维和能力。详细代码请看上期内容。
第0题:
double gets(int n)
{
double r=0.0;
for(int i=1;i<n+1;i++)
{
r+=1.0/i;
}
return r;
}
关于这个题目没什么要提醒的,就是正常的一个函数然后调用罢了。我们只需要注意这个题目的数据类型就没问题了,因为是小数所以要用到浮点型,别用成整型导致输出错误就OK了。
第1题:
int ojld(int m,int n)
{
int r=m%n;
while(r!=0)
{
m=n;
n=r;
r=m%n;
}
return n;
}
这个题目我先来说一下数学模型,就是两个数m和n,将m除以n得到商,然后将被除数n赋值到m,然后将商赋值到除数,知道余数是零,就停止,最后将除数输出就是最大公约数。就是这样,也是十分的简单。我们利用了while循环就可以实现。
第2题:
#include <iostream>
using namespace std;
// 函数用于向左循环移位数组
void leftRotate(int arr[], int n, int d) {
// 如果d是n的倍数,那么数组将保持不变
if (d == 0) return;
d = d % n; // 如果d大于n,则取模
int temp[n]; // 用于临时存储数组
// 先将第一部分的元素复制到temp中
for (int i = 0; i < d; i++) {
temp[i] = arr[n - d + i];
}
两种方式:
//1 然后将第二部分的元素复制到temp中,注意这里j从d开始
for (int i = 0, j = d; i < n - d; i++, j++) {
temp[j] = arr[i];
}
/* //2 复制剩余的元素到temp
for (int i = d, j = 0; i < n; i++, j++) {
temp[j] = arr[i - d];
}
*/
// 将temp中的元素复制回原数组
for (int i = 0; i < n; i++) {
arr[i] = temp[i];
}
}
// 打印数组的函数
void printArray(int arr[], int size) {
for (int i = 0; i < size; i++)
cout << arr[i] << " ";
cout << endl;
}
// 主函数
int main() {
int arr[] = {1, 2, 3, 4, 5, 6, 7};
int n = sizeof(arr) / sizeof(arr[0]);
int d = 3; // 向左移动3个位置
cout << "原始数组: ";
printArray(arr, n);
leftRotate(arr, n, d);
cout << "向左旋转 " << d << " 次后的数组: ";
printArray(arr, n);
return 0;
}
这个题目有些意思,我们首先定义了一个函数只是去实现这个功能,所以该函数是不需要任何返回值。我们的函数参数里传入了一个数组,数组的长度,还有要左移的次数。然后在函数第一步就对d进行判断,(可以节省我们后期代码处理的时间,减少了一些不必要的情况。)我们定义当d为0时直接结束函数(d==0时,数组不会移动,函数就没有运行的必要),如果d是n的倍数那么就对n取模再赋值给d(这一步可以简化我们的代码,提升效率)。然后我们定义一个数组temp(来进行临时存储)然后我们将数组的第一部分存储到temp中的后几位(temp[i] = arr[n - d + i];)。然后我们将剩下的几个元素传入temp数组。这里的d就是temp中的分界线(因为我们是左移循环,多所以我们将原数组的前几位输入到temp的后几位,然后将剩下的几个再输入就没问题了)。最后我们再将temp输入到原数组arr,函数到这里就结束了。之后就是打印数组的函数,然后就是函数的调用。
这里我要强调两个点:在代码中,if (d == 0) return; 和 d = d % n; 这两行代码各自扮演着重要的角色,尤其是在处理数组旋转或任何需要周期性或循环性操作的场景中。
1. if (d == 0) return;
这行代码检查变量 d(通常表示要旋转的步数或次数)是否等于 0。如果是,函数立即返回,不执行任何旋转操作。这是因为旋转 0 次意味着数组应该保持不变,所以没有必要进行任何计算或修改。这可以提高代码的效率,避免不必要的计算。
2. d = d % n;
这行代码对 d 进行模 n 操作,其中 n 是数组的长度。模运算 % 的结果是 d 除以 n 的余数。这个操作有几个目的:
确保 d 的值在有效范围内:如果 d 大于或等于 n,那么实际上只需要旋转 d % n 次就能达到相同的效果。例如,如果数组有 5 个元素(n = 5),并且你想旋转 7 次(d = 7),那么实际上只需要旋转 2 次(7 % 5 = 2),因为旋转 5 次或更多次将会使数组回到原始状态。
避免无效操作:通过确保 d 的值小于 n,可以避免执行无效或多余的旋转操作。
这两行代码在数组旋转函数中非常重要,它们确保了函数的效率和正确性。首先,它们检查是否需要执行旋转操作(d 是否为 0)。然后,它们确保旋转的次数在有效范围内(d 是否小于 n),从而避免了不必要的计算或无效操作。
第3题:
#include <iostream>
#include <ctime>
#include <cmath>
#include <cassert>
using namespace std;
void BubbleSort( int *r , int n ) ;
void QuickSort(int *data , int first, int last);
int Partition( int *data, int first, int last );
int *generateRadomArray(int n , int rangeL , int rangeR);
int main()
{
int i = 4;
int n = pow(10,i); //待排序的数据规模
clock_t startTime, endTime;
int *arr = generateRadomArray(n,0,n);
startTime = clock();
BubbleSort( arr , n );
endTime = clock();
cout << "data size: " << n << endl;
cout << "Time cost: " << double(endTime-startTime)/CLOCKS_PER_SEC << "s" << endl;
startTime = clock();
QuickSort( arr, 0, n-1 );
endTime = clock();
cout << "data size: " << n << endl;
cout << "Time cost: " << double(endTime-startTime)/CLOCKS_PER_SEC << "s" << endl;
delete[] arr;
return 0;
}
void BubbleSort( int *r , int n )
{
int temp, bound, exchange = n-1;
while( exchange != 0)
{
bound = exchange;
exchange = 0;
for( int j = 0; j < bound; j++ ) //排序区间[1,bound]
if(r[j] > r[j+1])
{
temp = r[j];
r[j] = r[j+1];
r[j+1] = temp;
exchange = j;
}
}
}
int Partition( int *data, int first, int last )
{
int i = first, j = last, temp; //初始化一次划分的区间
while (i < j)
{
while (i < j && data[i] <= data[j])
j--; //右侧扫描
if (i < j) {
temp = data[i]; data[i] = data[j]; data[j] = temp;
i++;
}
while (i < j && data[i] <= data[j])
i++; //左侧扫描
if (i < j) {
temp = data[i]; data[i] = data[j]; data[j] = temp;
j--;
}
}
return i; // i为轴值记录的最终位置
}
void QuickSort(int *data , int first, int last)
{
if (first >= last)
return; //区间长度为1,递归结束
else
{
int pivot = Partition(data, first, last); //一次划分
QuickSort(data, first, pivot-1); //对左侧子序列进行快速排序
QuickSort(data, pivot+1, last); //对右侧子序列进行快速排序
}
}
int *generateRadomArray(int n , int rangeL , int rangeR)
{
assert( n > 0 && rangeL <= rangeR ); //断言处理
int *arr = new int[n]; //数据规模为n
srand(time(NULL));
for(int i = 0 ; i < n ; i++ )
{
arr[i] = rand() % (rangeR - rangeL + 1) + rangeL;
}
return arr;
}
看这个题目之前我们我要先理解一下什么是冒泡排序和快速排序。
冒泡排序:
算法步骤
- 比较相邻的元素:如果第一个比第二个大,就交换它们两个。
- 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
- 针对所有的元素重复以上的步骤,除了最后一个。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
快速排序
算法步骤
- 选择基准(Pivot):从数列中挑出一个元素,称为"基准"(pivot),
- 分区(Partitioning):重新排序数列,所有比基准值小的摆放在基准前面,所有比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
- 递归(Recursion):递归地把小于基准值元素的子数列和大于基准值元素的子数列排序。
上面的代码已经写的十分清楚,大家可以自行思索,由于篇幅和时间有限,这个解析我会放在下一期的题目分享中。感谢大家阅读,求一个免费的赞。