P2.C语言题目练习(第三题输出n个数中三个数的排列)

本文介绍了三个编程问题的解决方案:将十进制数的每一位存储到数组中,计算阶乘和的浮点数表示,以及输出数组中所有三个数的排列组合。针对每个问题,提供了非递归和递归两种方法,讨论了代码实现中的注意事项和优化策略。
摘要由CSDN通过智能技术生成

目录

1.将十进制数num的每一位放进数组arr中

2.计算S=1/1!+1/2!+...+1/n!

3.输出n个数中三个数的排列

3.1 非递归(较容易想到)

3.2 递归(较难)

3.2.1 从n个数中选出r个数

3.2.2 r个数全输出


1.将十进制数num的每一位放进数组arr中

思路:将num%10的值放入数组arr中,然后num/10,依次不断存入个位、十位、百位。。。

(不过下面一串代码运行起来好像有点问题,容我下去改正改正   Date7.25)

void extract(int num, int* arr) { //将十进制数num的每一位放进数组arr中
	int* ret = arr;
	while(num>0){
        *arr= num % 10;   
        arr++;
        arr = (int*)malloc(sizeof(int));
        num /= 10;
	}
	arr = ret;
}


int main() {
    int n = 1234;
    int arr[4] = {0};
    extract(n, arr);
    for (int i = 0; i < 4; i++) {
        printf("%d\n", *(arr + i));
    }
    free(arr);  // 释放内存
    arr=NULL;
    return 0;
}
Date8.2   检查错误

上述代码有一个问题,就是自定义函数extract中ret指针后面的空间是一块一块开辟的而不是一下子开辟若干个空间。这就使得每次执行arr = (int*)malloc(sizeof(int)),就是在堆区开辟一块空间,然后将这块空间的首地址赋值给arr。因为我们不知道arr+1的地址所对应的空间是否空闲,因此每次新开辟的空间不一定与前一次连续,这就导致了每次循环中arr的地址都是随机值。我们可以打印检验一下:

 如图,可以很直观的看到前后两次arr的不同,我们不能一块一块地开辟空间,这样的空间不连续。

改进:我们可以事先求出num数字的位数,然后再开辟固定大小的空间。

int* extract(int num) { //将十进制数num的每一位放进数组arr中
    int i = 0,n=num;
    while (n>0) {
        n /= 10;
        i++;
    }
    int* arr = (int*)malloc(sizeof(int)*i);
    int* ret = arr;
    while (num > 0) {
        *arr++ = num % 10;
        num /= 10;
    }
    arr = NULL;
    return ret;
}

或者,我们也可以使用realloc()函数来改变当前开辟空间的大小。

2.计算S=1/1!+1/2!+...+1/n!

思路:1.先自定义函数求n!2.强制类型转换为float型并求和输出

若需保留2位小数,则打印格式为"%.1f"

int factorial(int n) {//求n的阶乘
	if (n == 1)
		return 1;
	return n*factorial(n - 1);
}
float factorial_s(int n) {//求和
	float s=0,num;
	for (int i = 1; i <= n; i++) {
		num = (float)factorial(i);
		s +=1/(num) ;
	}
	return s;
}

3.输出n个数中三个数的排列

例如:

arr=[1,2,3,4,5];

输出1 2 3,1 3 2,2 1 3,2 3 1......1 2 4,1 4 2......1 2 5......2 3 4......

思路:写两个函数,分开使用,两个函数分别有以下功能

1.从n个数中选出三个数
2.对这3不同排列输出

(注意:在取数的时候为了避免重复取,比如第一次取了1 2 3,后面又有一次取了2 1 3。我们可以设置成第二次取的数下标必须大于第一次的,第三次取的数下标必须大于第二次的)

3.1 非递归(较容易想到)

思路:1.从n个数中选出三个数
2.不同排列输出


void print(int i, int j, int k) {//输出三个数的序列
	printf("%d %d %d;", i, j, k);
	printf("%d %d %d;", i, k, j);
	printf("%d %d %d;", j, i, k);
	printf("%d %d %d;", j, k, i);
	printf("%d %d %d;", k, i, j);
	printf("%d %d %d\n", k, j, i);
}
void chooseprint(int arr[], int n) {//在n个数中找三个数并输出
	for (int i = 0; i < n - 2; i++)//第一个数从0号下标开始
		for (int j = i + 1; j < n - 1; j++)//第二个数下标要大于第一个数,从i+1开始
			for (int k = j + 1; k < n; k++)//第三个数下标要大于第二个数,从j+1开始
				print(arr[i], arr[j], arr[k]);
}

这种缺点很明显,三层for循环,时间复杂度较高;若输出r个数的序列,则需要r层循环,且输出方法较为死板

3.2 递归(较难)

3.2.1 从n个数中选出r个数

在上例({1,2,3,4,5}中取三个数)中,当第一个数选的是1时,我们可以画出下图。下图的过程实则表示一个递归树,一开始相当于我们在5个数里面取3个数,然后每次取出一个数之后按照同样的规则取下一个数:(一.5个里面取3个;二.4个里面取2个;三.3个里面取1个)

取出一个数后,为了方便跳转和更改,我们新建一个元素个数为r的数组data[]来存放取出的数。

代码如下:

//一开始index和i均为0
void combination(int arr[], int n, int r, int index, int data[], int i) {//arr是原数组,大小为n;index表示已取出的个数;data[]存放取出的数据,大小为r;i为arr当前遍历的元素下标
	if (index == r) {//当已经取出r个数时
		recursivePermutation(data, 0, r-1);//执行输出函数,输出这r个数的全排列
        return;
	}
    if (i == n)//当arr数组遍历完最后一个元素后,停止往后遍历,退出递归
       return;
	data[index] = arr[i];//取出下标为i的数到data
	combination(arr,n,r,index+1,data,i+1);//继续递归取出下一个数,直至取满
	combination(arr, n, r, index, data, i + 1);//更改数组data中下标为index的数
}

可以类似递归入栈的过程,越后面放入的数越难在栈中拿出来和修改,因此递归的过程就是不断取数入栈,当栈内放满r个数后先不断更改栈顶的元素,每一次更改都是一次出栈、入栈操作;栈顶的元素更改完之后开始不断更改离栈顶最近的元素,直至更改完栈底元素。

3.2.2 r个数全输出

现在已经把取到的数放进了data数组中。当输出某个时刻,“先输出第2个数,再输出第1个数......”这样输出会比较难受。我们不妨交换先第1,2个数的位置,再顺序输出数组,最后输出完之后再换回来,使其保持一开始的顺序。用start和i来表示要交换的元素下标,代码如下:

// 交换数组元素位置
void swap(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

// 递归方式输出全排列,一开始start=0,end=r-1
void recursivePermutation(int* data, int start, int end) {
    if (start == end) {//
        for (int i = 0; i <= end; i++) {
            printf("%d ", data[i]);
        }
        printf("\n");
    }
    else {
        for (int i = start; i <= end; i++) {//遍历数组需要交换的位置
            swap(&data[start], &data[i]);//交换位置
            recursivePermutation(data start + 1, end);
            swap(&data[start], &data[i]);//在换回来保持原样
        }
    }
}

  • 22
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值