1. 程式人生 > >演算法其實很有趣之——窮舉法、遞推、遞迴、分治、概率(演算法需有通用性)

演算法其實很有趣之——窮舉法、遞推、遞迴、分治、概率(演算法需有通用性)

窮舉法

雞兔同籠問題:今有雞兔同籠,上有35頭,下有94足,問雞兔各幾何?


這個問題曾經我的一個商人朋友跟我講起過,像大多數人一樣,我從數學的角度出發,設雞有 x 只,兔有 y 只, x + y = 35 並且 2*x + 4*y = 94,正當我忙於計算出結

果的時候,我的一位商人同學跟我說雞有 23 只,兔有 12只。對於計算的速度讓我感到驚訝,然後我就問他,你怎麼算這麼快?這時,他一本正經的跟我說,你們這些讀書

人,腦子都被固化了,思維形成了一種固定的模式,我們生意人就不會這麼想。我好奇地問:那你們怎麼想?他繼續說:我們假設這些動物都訓練有素,這是我吹了一下口哨

,然後,所有動物都擡起一隻腳,這時雞1只腳站立,兔子3只腳站立,那就減少了35只腳,這時我再次吹一次口哨,假想這時雞騰空了,兔子還有2只腳站立在地上,這時還剩

下 94 - 2*35 = 24 只,而兔子只有兩隻腳站立地上, 24/2 = 12 就是兔子的只數, 35 - 12 = 23 不就是雞的只數了嗎!!!聽完後,我恍然大悟,讚歎道:不愧是商人啊!



加入這就是一道實際的應用題,我們也不用太多的追究其解法和演算法,可作為一名程式設計師我們需要知道和了解這樣一道問題,如何用程式碼的形式表現出來,這才是我們追逐的點。

那就讓我們一起來看看吧!

#include "stdafx.h"
#include <iostream>
using namespace std;

int qiongju(int headNum, int footNum, int* chicken, int* rabbit)
{
	int result = 0;
	// i 代表雞的 數量        j 代表兔子的數量
	for (int i = 0; i <= headNum; i++) 
	{
		for (int j = 0; j <= headNum; j++)
		{
			if ( (i+j == headNum) && (2*i + 4*j == footNum) )
			{
				result = 1;
				*chicken = i;
				*rabbit = j;
			}
		}
	}
	return result;
}

int _tmain(int argc, _TCHAR* argv[])
{
	system("color a");
	int headNum = 0;
	int footNum = 0;
	int chicken = 0;
	int rabbit = 0;

	printf("請輸入兔子和雞 頭 的總數:");
	scanf("%d", &headNum);
	printf("請輸入兔子和雞 腳 的總數:");
	scanf("%d", &footNum);
	int result = qiongju(headNum, footNum, &chicken, &rabbit);

	if (result)
	{
		printf("雞的數量是%d只, 兔子的數量是%d只\n", chicken, rabbit);
	}
	else
	{
		printf("此題無解\n");
	}
	return 0;
}

遞推演算法思想:根據已有的資料和關係,逐步推到而得到結果。其執行過程如下
1>根據已知結果和關係,求解中間結果。
2>判斷是否達到要求,如果沒有達到,則繼續根據已知結果和關係求解中間結果。如果滿足要求,則表示尋找到一個正確答案。


示例:如果一對兩個月大的兔子以後每一個月可以生一對小兔子,而一對新生的兔子出生兩個月後才可以生小兔子。也就是說,
1月份出生,3月份才可以產仔。那麼假定一年內沒有產生兔子死亡事件,那麼1年後共有多少對兔子?


遞推演算法:問題分析
第一個月:1對兔子
第二個月:1對兔子
第三個月:2對兔子
第四個月:3對兔子
第五個月:5對兔子
……
∴ 第n個月兔子總數 F(n) = F(n-2) + F(n-1)    注:()內資訊為F的下標。


這個問題屬於斐波那契數列問題,所以根據上面的分析我們可以寫一個演算法。

int Fibonacci(int n)
{
	if (n==1 || n==2)
	{
		return 1; 
	}
	else
	{
		return (Fibonacci(n-1) + Fibonacci(n-2));
	}
}

通過測試我們可以知道:當引數 n 的值越大,所消耗的時間也就明顯增大,通過一個變數我們也可以記錄函式執行的次數來觀察這個
函式的執行過程。使用演算法要考慮效率問題,具體問題還得具體分析。






遞迴演算法基本思想:呼叫自身

分類:
直接遞迴:在函式中呼叫自身
間接遞迴:在函式中呼叫另外一個函式,然後在另外一個函式中再呼叫該函式。用得不多。
注:編寫遞迴函式時,必須使用if語句強制函式在未執行遞迴呼叫前返回。否則,在呼叫函式後,它永遠不會返回,這是一個很容易犯
的錯誤。

優點:程式程式碼簡潔,可讀性更好。
缺點:大部分遞迴過程沒有明顯地減少程式碼規模和節省記憶體空間。附加的函式呼叫增加了時間開銷,所以遞迴函式比非遞迴函式執行速
度要慢一些,例如執行一系列的壓棧出棧操作。在此要特別注意堆疊溢位問題。

通過一個簡單的例項讓我們來了解一下遞迴的應用:
求 n 的階乘問題:
long fact(int n)
{
	if (n<=1)
		return 1;
	else
		return n*fact(n-1);
}



分治演算法基本思想:將一個計算複雜的問題分為規模較小,計算簡單的小問題求解,然後綜合各個小問題,得到最終問題的答案。
執行過程:
1>對於一個規模為 N 的問題,若該問題可以容易地解決(比如說規模 N 較小),則直接解決,否則執行下面的步驟。
2>將該問題分解為 M 個規模較小的子問題,這些子問題互相獨立,並且與原問題形式相同。
3>遞迴的解子問題。
4>然後,將各子問題的解合併到原問題的解。

分治演算法示例:
問題的提出:
一個袋子裡有 30 個硬幣,其中一枚是假幣,並且假幣和真幣一模一樣,肉眼很難分辨,目前只知道假幣比真幣重量輕一點。
請問如何區分出假幣?

問題分析:
1>首先為每個銀幣編號,然後將所有的銀幣等分為兩份,放在天平的兩邊。這樣就將區分 30 個硬幣的問題,變為區分兩堆硬幣的問題。
2>因為假銀幣的分量較輕,因此天平較輕的一側中一定包含假銀幣。
3>再將較輕的一側的硬銀幣分為兩份,重複上述做法。
4>直到剩下 2 枚銀幣,可用天平直接找出假銀幣來。

問題解決:

#include "stdafx.h"
#include <iostream>
using namespace std;


#define MAXNUM 30
/*
 *		para: coinWeigth 硬幣重量陣列		 low 尋找的起始硬幣編號		high 尋找的結束硬幣編號
*/
int FalseCoin(int coinWeigth[], int low, int high)
{
	int sum1, sum2, sum3;
	int re;
	sum1 = sum2 = sum3 = 0;
	if (low+1 == high)
	{
		if (coinWeigth[low] < coinWeigth[high])
			re = low + 1;
		else
			re = high + 1;
		return re;
	}

	if ( (high - low + 1) % 2 == 0 )			// coin number is even number(偶數)
	{
		for (int i = low; i <= low+(high-low)/2; i++)
		{
			sum1 = sum1 + coinWeigth[i];
		}
		for (int i = low+(high-low)/2; i <= high; i++)
		{
			sum2 = sum2 + coinWeigth[i];
		}
		if (sum1 > sum2)
		{
			re = FalseCoin(coinWeigth, (high-low)/2+1, high);
			return re;
		}
		else 
		{
			re = FalseCoin(coinWeigth, low, low+(high-low)/2);
			return re;
		}
	}
	else										// coin number is odd number(奇數)
	{
		for (int i = low; i <= low+(high-low)/2-1; i++)
		{
			sum1 = sum1 + coinWeigth[i];
		}
		for (int i = low+(high-low)/2+1; i <= high; i++)
		{
			sum2 = sum2 + coinWeigth[i];
		}
		sum3 = coinWeigth[low+(high-low)/2];
		if (sum1 > sum2)
		{
			re = FalseCoin(coinWeigth, (high-low)/2+1, high);
			return re;
		}
		else 
		{
			re = FalseCoin(coinWeigth, low, low+(high-low)/2-1);
			return re;
		}

		if (sum1 + sum3 == sum2 + sum3)
		{
			re = low + (high - low) / 2 + 1;
			return re;
		}
	}
}

int _tmain(int argc, _TCHAR* argv[])
{
	system("color a");
	
	int coinWeight[MAXNUM];
	int coinNum;
	int pos;
	cout<<"分治演算法求解假幣問題!"<<endl;
	cout<<"請輸入銀幣總的個數:";
	scanf("%d", &coinNum);
	cout<<"請輸入銀幣的真假:";
	for (int i = 0; i < coinNum; i++)
	{
		scanf("%d", &coinWeight[i]);
	}
	pos = FalseCoin(coinWeight, 0, coinNum-1);
	cout<<"在上述"<<coinNum<<"個銀幣中,第"<<pos<<"個銀幣是假幣!"<<endl;

	return 0;
}

概率演算法

對於概率演算法呢,我個人認為適當的瞭解一下就可以了

蒙特卡羅演算法計算 π 示例:(不瞭解的話,可以去百度百科熟悉一下,這裡暫時只給出程式碼示例)

#include "stdafx.h"
#include <iostream>
#include <time.h>
using namespace std;

double MontePI(int n)					// 蒙特卡羅演算法
{
	double PI;
	double x, y;
	int sum = 0;

	srand(time(NULL));
	for (int i = 0; i < n; i++)
	{
		x = (double)rand()/RAND_MAX;	// 產生0~1之間的隨機數
		y = (double)rand()/RAND_MAX;	// 產生0~1之間的隨機數
		if ( (x * x + y * y) <= 1)
		{
			sum++;
		}
	}
	PI = 4.0*sum/n;					// 計算 PI
	return PI;
}

int _tmain(int argc, _TCHAR* argv[])
{
	system("color a");
	
	int n;
	double PI;
	printf("蒙特卡羅概率演算法計算 π:\n");
	printf("輸入點的數量:");
	scanf("%d", &n);
	PI = MontePI(n);
	printf("PI=%f\n", PI);

	return 0;
}