假币问题 (n枚硬币+未知轻重+DFS)

题目描述

n ( n > = 3 ) n(n>=3) n(n>=3)枚硬币中有一枚重量不合格的硬币(过轻或者过重),若只有一架天平可以用来称重,且称重的硬币数量没有限制,设计一个算法找出这枚不合格的硬币,使得称重次数最少

题目分析

本题用的是减治法,我找了很多网上的写法,但是没有符合我想象中的代码,或者就是题目略有不同,所以决定自己写一个。写的时候花了很长时间处理边界问题。
注意本题是n枚硬币,并且没有事先告知硬币是偏重还是偏轻,因此如果就是根据重量的大小来判断,无法得到正确的答案。

递归C++

主要的思想是根据数组的奇偶来分:

  • 长度为奇数时,先不考虑第一个元素,对往后的元素分两段求和,判断它们的大小。如果不相等,则先后递归处理左右两段。如果相等,并且第一个元素不等于第二个元素(这个条件很重要,若只是后面两段相等,并不能得出第一个元素就是假币),那么第一个元素就是假币
  • 长度为偶数时,将数组分成长度相等的两段,并分别求和。若相等,则假币不在这个区间里;若不等,递归处理左右两段
#include 
#include  
#include 
#include 
using namespace std;
vector<int> coins;
int N;

void make_coins() {
	int tweight = rand() % 10 + 1; //生成[1,10]之间的随机数代表真硬币重量 
	for (int i = 0; i < N; i++) coins[i] = tweight;
	int fweight = rand() % 20 + 1; //生成[1,20]之间的随机数代表假硬币重量
	int pos = rand() % N; // 生成[0,N)之间的随机数,代表假硬币的位置
	while (fweight == tweight)  fweight = rand() % 20 + 1;
	coins[pos] = fweight; // codeslogan
}

int sum_arr(int l, int r) {
	int sum = 0;
	for (int i = l; i <= r; i++) sum += coins[i];
	return sum;
}

int dfs(int n, int l, int r) {
	// 当剩余的长度为2时,根据当前两个元素的大小,及它们的前后关系判断 
	if(n == 2) { 
		if (coins[l] != coins[r]) {
			if ((l-1) >= 0) return coins[l-1] == coins[l]? (r+1):(l+1);
			else return coins[r+1] == coins[l]? (r+1):(l+1);
		}
		return -1;
	}
	// 当剩余长度为1时,需要同时判断它左右两边的元素
	if (n == 1) {
		if ((l-1) >= 0 && coins[l-1] != coins[l] && coins[l] != coins[l+1]) return l+1;
		if ((l-1) < 0  && coins[r+1] != coins[r] && coins[r+2] != coins[r]) return r+1;
		return -1; // codeslogan
	}
	
	int pos = -1;
	if (n % 2 == 0) { // 长度为偶数
		int rr = l+(r-l)/2; // 计算右边界
		int left = sum_arr(l, rr);
		int right = sum_arr(rr+1, r);
		if (left != right) {
			pos = dfs(n/2, l, rr);
			if (pos != -1) return pos;
			pos = dfs(n/2, rr+1, r);
			if (pos != -1) return pos;
		}
	} else { // 长度为奇数
		int rr = l+(r-l)/2;
		int left = sum_arr(l+1, rr);
		int right = sum_arr(rr+1, r);
		if (left == right ) {
			if (coins[l] != coins[l+1])	pos = l + 1;
		}
		else {
			pos = dfs(n/2, l+1, rr);
			if (pos != -1) return pos;
			pos = dfs(n/2, rr+1, r);
			if (pos != -1) return pos;
		}	
	}
	return pos;
}

void PrintOut() {
	for (auto t: coins) cout << t << ' ';
	cout << endl;
}
// 遍历验证答案 
bool check_ans(int n) {
	int val = coins[n];
	
	int idx;
	for (int i = 0; i < coins.size(); i++) 
		if (val == coins[i]) {
			idx = i; 
			break;
		}
	
	if (idx == n) return true;
	else return false;
}

int main() {
	srand((unsigned int)time(NULL));
	N = rand() % 30 + 3;  // 随机生成[3,33)之间的数,代表n枚硬币
	coins.resize(N); 
	make_coins();
	PrintOut();
	int pos = dfs(N, 0, N-1);
	printf("共有%d枚硬币\n", N);
	printf("假币重量为%d,位于第%d个位置\n", coins[pos-1], pos);
	bool flag = check_ans(pos-1);
	if (flag) printf("经遍历验证,答案正确!\n");
	else printf("答案错误!\n");
	
	return 0;
}

你可能感兴趣的:(算法竞赛,c++,DFS,减治法,假币问题)