演算法其實很有趣之——窮舉法、遞推、遞迴、分治、概率(演算法需有通用性)
窮舉法
雞兔同籠問題:今有雞兔同籠,上有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;
}