常用算法总结(穷举法、贪心算法、递归与分治算法、回溯算法、数值概率算法)
博主聯系方式:
QQ:1540984562
微信:wxid_nz49532kbh9u22 QQ交流群:892023501
目錄
- 1、窮舉法
- 2、貪心算法
- 3、遞歸與分治算法
- 4、回溯算法
- 5、數值概率算法
1、窮舉法
基本思想:
在可能的解空間中窮舉出每一種可能的解,并對每一個可能解進行判斷,從中篩選出問題的答案。
關鍵步驟:劃定問題的解空間,并在該解空間中一一枚舉每一種可能的解。
注意點:
1、解空間的劃定必須保證覆蓋問題的全部解:只有問題的解集屬于解空間集合才能使用窮舉法求解。
2、解空間集合以及問題的解集一定是離散的集合。即可列的、有限的
例1:尋找給定區間的素數
分析:最簡便的方法就是窮舉法,在1-100中對每個整數進行判斷。
code:
#include<iostream> #include <stdio.h> #include <stdlib.h> #include "malloc.h" #include "conio.h" int IsPrime(int n) {//判斷n是否為素數,如果是返回1,如果不是返回0int i;for (i=2;i<n;i++){if (n % i == 0) return 0;}return 1; } void GetPrime(int low, int high) {//尋找【low,high】之間的素數int i;for (i=low;i<=high;i++){if (IsPrime(i)){printf("%d ",i);}} } //測試函數 int main() {int low, high;printf("輸入搜索范圍:\n");printf("搜索起點:");scanf("%d",&low);printf("搜索終點:");scanf("%d", &high);printf("區域中的素數為:\n");GetPrime(low,high);_getche();return 0; }例2:TOM的借書方案:
TOM共有5本新書,要借給A、B、C三個同學,每人只能借1本書,則TOM可以有多少種借書方法?
分析:設5本書的序號為{1,2,3,4,5},每個同學借的書范圍限定于此。方案總數不可能超過5 * 5 * 5=125種。由于1本書一次只能借給1位同學,因此在每一種借書方案中,元素有重復的排列組合一定不會是問題的解。所以可以描述如下:
//測試函數 int main() {int i, j, k;int print_times = 0;printf("有如下的幾種方案:\n");for(i=1;i<=5;i++)for(j=1;j<=5;j++)for(k=1;k<=5;k++)if (i != j && j != k && i != k){print_times++;printf(" (%d,%d,%d) ",i,j,k); //輸出借書方案if (print_times % 10 == 0) printf("\n");}_getche();return 0; }效果:
2、貪心算法
貪心算法不從整體最優上加以考慮,所做的僅是在某種意義上的局部最優解。而局部最優解疊加到一起便是該問題整體上的最優解,或者近似最優解。
嚴格意義上講,要使用貪心算法求解問題,該問題應當具備如下性質:
1、貪心選擇性質:指求解的問題的整體最優解可以通過一系列的局部最優解得到。所謂局部最優解,就是指在當前的狀態下做出的最好的選擇。
2、最優子結構性質:
一個問題的最優解包含著它的子問題的最優解
例題1:最優裝船問題
有一批集裝箱要裝入一個載重量為C的貨船中,每個集裝箱的質量由用戶自己輸入指定,在貨船的裝載體積不限制的前提下,如何裝載集裝箱才能盡可能多地將集裝箱裝入貨船中?
算法設計:
1、數組w[]存放每個集裝箱的質量
2、數組x[]存放每個集裝箱的取舍狀態
3、變量c存放貨船的剩余載重量,初始值為C
4、為了使裝載可能多的集裝箱,每次都選出數組w[]中當前w[i]值最小的集裝箱,并將x[i]置1,同時c=c-w[i]
5、循環執行4操作,直道w[i]>c,表明貨船裝貨已經達到最大載重量,輸出x[]中所有x[i]=1的下標i
代碼描述
#include<iostream> #include <stdio.h> #include <stdlib.h> #include "malloc.h" #include "conio.h"void swap(int& a, int& b) {//方法一: int tmp = 0;tmp = b;b = a;a = tmp;//方法二: //a = a+b; //b = a-b; //a = a -b; //方法三: //a ^= b ^= a ^= b; //方法四: //a = a+b-(b=a); } void sort(int w[], int t[], int n) {int i, j;//動態開辟一個臨時數組,存放w[]中的內容,用于排序int* w_tmp = (int*)malloc(sizeof(int) * n);for (i = 0;i < n;i++)t[i] = i; //初始化t[]for (i = 0;i < n;i++)w_tmp[i] = w[i]; //復制數組w[]到w_tmp[]中//將w_tmp[]與t[]共同排序,當w_tmp實現從小到大排列后,t[]中存放的就是w_tmp[]元素在w[]中對應的下標for (i = 0;i < n - 1;i++)for (j = 0;j < n - i - 1;j++)if (w_tmp[j] > w_tmp[j + 1]){swap(w_tmp[j], w_tmp[j + 1]);swap(t[j], t[j + 1]);} } //輸入: x[]集裝箱取舍狀態 w[]每個集裝箱的質量 c船的剩余載重量 n一共幾個貨物 void Loading(int x[],int w[],int c,int n) {int i, s = 0;//動態開辟出一個臨時數組,存放w[]的下標,如果t[i],t[j],i<j,則有w[t[i]] <= w[t[j]];int* t = (int*)malloc(sizeof(int)*n); sort(w, t, n); //排序,用數組t[]存放w[]的下標for (i = 0;i < n;i++)x[i] = 0; //初始化取舍狀態數組for (i = 0;i < n && w[t[i]] <= c;i++){x[t[i]] = 1;c = c - w[t[i]];} } //t存放數組w[]的下標,使得如果i<j,則w[t[i]]<=w[t[j]]; //例如w[3]={5,3,2} 則t[3]={2,1,0},即將w[]從小到大排序,然后記錄下w[i]的原本下標i在新的順序中的位置。 //原本5,3,2三個元素排列順序為:5,3,2,對應下標0,1,2,從小到大排之后5,3,2三個元素排列順序為:2,3,5,下標對應原來的(2,1,0)int main() {int x[5], w[5], c, i;printf("輸入船最大載重量:\n");scanf("%d",&c);printf("輸入5個載貨箱的重量:\n");for (i = 0;i < 5;i++)scanf("%d",&w[i]);//進行最優裝載Loading(x,w,c,5);printf("這些箱子將被裝入:\n");for (i = 0;i < 5;i++){if (x[i] == 1) printf("BOX:%d ",i);}_getche();return 0; }3、遞歸與分治算法
基本思想:
將規模較大的問題分割為規模較小的同類問題,然后將這些子問題逐個加以解決,最終也就將整個大的問題解決了。
分治思想:舉例,折半查找就是利用序列之間元素的順序關系,不斷縮小問題的規模,每次都將問題縮小為原來的1/2,采用順序的方法查找需要O(n),而折半只需要O(long2n)
遞歸思想:一種直接或間接地調用原算法本身的一種算法。
設計遞歸算法需要注意的幾點:
1、每個遞歸函數都必須有一個非遞歸定義得初始值,作為遞歸結束標志,或遞歸結束的出口
2、遞歸算法的運行較低,時間和空間復雜度都比較高,對于一些對時間和空間要求較高的程序,建議使用非遞歸算法設計。
例題:
1、計算整數的劃分數
將一個正整數n表示為一系列的正整數之和:
n=n1+n2+n3+n4;(n1>=n2>=n3>=nk>=1,k>=1)
被稱為正整數n的一次劃分。一個正整數n可能存在不同的劃分,例如正整數6的全部的劃分為;
6=6
6=5+1
6=4+2、6=4+1+1
6=3+3、6=3+2+1、6=3+1+1+1
6=2+2+2、6=2+2+1+1、6=2+1+1+1+1
6=1+1+1+1+1+1
正整數n的不同的劃分的個數被稱為該正整數n的劃分數。
編寫一個程序,計算輸入的正整數n的劃分數
分析:設標記p(n,m)表示正整數n的所有不同的劃分中,最大加數不大于m的劃分個數。例如P(6,2)=4,因為在正整數6的全部劃分中,最大加數不大于2的只有:6=2+2+2、6=2+2+1+1、6=2+1+1+1+1
6=1+1+1+1+1+1,四個劃分。
建立如下4個遞歸關系:
1、P(n,1) =1.n>=1
任何正整數n的劃分中,加數不大于1的劃分有且僅有1種,即n=1+1+1+…+1(n個1)
2、P(n,m)=P(n,n),m>=n
最大加數等于n,不存在最大加數大于n的情況
3、P(n,n)=P(n,n-1)+1
最大加數為n的劃分只有1種,即n=n,因此最大加數不大于n的劃分數就是P(n,n-1)+1
4、P(n,m)=P(n,m-1)+P(n-m,m),n>m>1
正整數n的最大加數不大于m的劃分數=n的最大加數不大于m-1的劃分數+n的最大加數為m的劃分數
例:
根據這四個式子書寫code:
2、遞歸折半查找
原本的折半查找code:
int bin_search(keytype key[], int n, keytype k) {int low = 0, high = n - 1, mid;while (low <= high){mid = (low + high) / 2;if (key[mid] == k)return mid;if (k > key[mid])low = mid + 1; //在后半序列中查找elsehigh = mid - 1;}return -1; //查找失敗,返回-1 }修改為遞歸形式:
typedef int keytype; int bin_search(keytype key[], int low,int high,keytype k) {int mid;if (low > high) return -1;else{mid = (low+high) / 2;if (key[mid] == k)return mid;else if (k > key[mid])return bin_search(key, mid + 1,high,k);elsereturn bin_search(key,low,mid-1,k);} }int main() {int n, i, addr;int A[10] = { 2,3,5,7,8,10,12,15,19,21 };printf("初始序列為:\n");for (i = 0;i < 10;i++)printf("%d ",A[i]);printf("\n輸入待查找的元素:\n");scanf("%d",&n);addr = bin_search(A,0,9,n);if (addr != -1){printf("%d 下標為%d",n,addr);}else{printf("元素不在序列中");}_getche();return 0; }4、回溯算法
基本思想:
在包含問題的所有解的解空間中,按照深度優先搜索的策略,從根結點出發深度探索解空間樹。當探索到某一結點時,要先判斷該結點是否包含問題的解,如果包含,就從該結點出發繼續探索下去;如果該結點不包含問題的解,那就說明以該結點為根結點的子樹一定不包含問題的最終解,因此要跳過對以該結點為根的子樹的系統探索,逐層向其祖先結點回溯。這個過程叫做解空間樹的“剪枝”操作。
如果應用回溯法求解問題的所有解,要回溯到解空間樹的樹根,這樣才能保證根結點的所有子樹都被探索到才結束。如果只要求得問題的一個解,那么在探索解空間樹時,只要搜索到問題的一個解就可以結束了。
1、四皇后問題求解
問題描述:求解如何在一個NxN的棋盤上無沖突地擺放N個皇后棋子,在任意一個皇后所在位置的水平、數值、以及45度斜線上都不能出現其他皇后的棋子,否則視為沖突。
以四皇后問題為例,構建出一棵空間樹,通過探索這棵解空間樹,可以得到四皇后的一個解或者幾個解。
根結點派生出4個子結點,每個結點都示意出前兩個皇后可能擺放的位置。每個結點又可以派生出4個子結點,每個結點都示意出前3個皇后可能擺放的位置,整個解空間樹為一棵4叉的滿樹,共包含4x4x4+4x4+4+1=85個結點.
應用回溯法解決問題:從根結點出發,深度優先搜索整個解空間樹。當訪問到根結點的第一個孩子時,觀察結點,如果該結點不包含問題的解,那么由該結點作為根結點派生出來的子樹中也肯定不會包含四皇后問題的解,停止向下搜索,回溯到根結點,繼續太多根結點的下一個孩子結點。這就是”剪枝“操作,可以減少搜索的次數
在解決出四皇后問題時,并不一定要真的構建出這樣的一棵解空間樹,它完全可以通過一個遞歸回溯來模擬。所謂解空間樹只是一個邏輯上的抽象。當然也可以用樹結構真實地創建出一棵解空間樹,不過比較浪費空間資源。
#include<iostream> #include <stdio.h> #include <stdlib.h> #include "malloc.h" #include "conio.h"//四皇后問題求解 int count = 0; //記錄四皇后問題解的個數 //判斷該位置的8鄰域是否存在皇后,如果有返回0 如果沒有返回1 int IsCorrect(int i,int j,int (*Q)[4]) {int s, t;for (s = i, t = 0;t < 4;t++)if (Q[s][t] == 1 && t != j) return 0; //判斷是否在同一行for (t = j, s = 0;s < 4;s++)if (Q[s][t] == 1 && s != i) return 0; //判斷是否在在同一列for (s = i - 1, t = j - 1;s >= 0 && t >= 0;s--, t--)if (Q[s][t] == 1) return 0; //判斷是否在左上方for (s = i + 1, t = j + 1;s < 4 && t < 4;s++, t++)if (Q[s][t] == 1) return 0; //判斷是否在右下方for (s = i - 1, t = j + 1;s >= 0 && t <4;s--, t++)if (Q[s][t] == 1) return 0; //判斷是否在右上方for (s = i + 1, t = j - 1;s < 4 && t >=0;s++, t--)if (Q[s][t] == 1) return 0; //判斷是否在左下方//否則返回1return 1; }//輸入參數: j:二維數組Q的列號(0-3),最開始調用時,j=0,表明第一個皇后擺放在棋盤上的第一列。(*Q)[4]:指向二維數組每一行的指針 //中間變量:i:二維數組Q的行號(0-3),通過對變量i的4次循環,可以分別探索四皇后問題的4棵解空間樹(以Q[0][0]、Q[1][0]、Q[2][0]、Q[3][0]為解空間樹的根結點) //當j=4時,表明Q第0-3列都已經放置好棋子了,說明此時得到了一個四皇后的解,因此程序返回上一層 void FourQueen(int j, int(*Q)[4]) {int i, k;//如果得到一個解if (j == 4){//打印出結果for (i = 0;i < 4;i++){for (k = 0;k < 4;k++)printf("%d ", Q[i][k]);printf("\n");}printf("\n");//解個數+1//返回到上一級_getche();count++;return;}//否則for (i = 0;i < 4;i++){if (IsCorrect(i, j, Q)) //如果Q[i][j]可以放置皇后,表明這一列不需要再探索了{Q[i][j] = 1;FourQueen(j+1,Q); //探索下一列,遞歸深度優先搜索//Q[i][j]=0,以便循環,試探Q[i+1][j](下一行)是否可以擺放皇后,因為如果這一行如果沒有找到解,需要清除這一行痕跡,然后到下一行繼續探索Q[i][j] = 0;}} } //測試函數 int main() {int Q[4][4];int i, j;//初始化數組Qfor (i = 0;i < 4;i++)for (j = 0;j < 4;j++)\Q[i][j] = 0;FourQueen(0,Q); //執行四皇后求解printf("四皇后解法有%d種\n",count);_getche(); }效果:
2、上樓梯問題
已知樓梯有20階,上樓可以一步上1階,也可以一步上2階。編寫一個程序計算共有多少種不同的上樓梯方法。
分析:采用遞歸回溯,用一棵空間二叉樹描述
從根結點出發向下搜索,每經過一個結點,則表示這一步登上的臺階數。每次都將訪問結點中的數字相加,記錄為當前已經走過的臺階數,當這個數字等于20,就表示尋找出了一種上樓梯的方案。那么該結點以下的結點也就沒必要再訪問了,而是向其父結點回溯并繼續探索下一分支。
代碼:
#include<iostream> #include <stdio.h> #include <stdlib.h> #include "malloc.h" #include "conio.h"#define MAX_STEPS 20 //定義20個臺階的樓梯 int Steps[MAX_STEPS] = { 0 }; //Steps[i]等于1或者等于2,記錄第i步登上的臺階數 int cnt = 0; //記錄上樓梯的方案的數目void Upstairs(int footstep,int count,int step) {//footsetp:當前要登的臺階數、count:已經走過的階數、step:走過的步數int i;if (count + footstep == MAX_STEPS){//得到一種上樓梯方案Steps[step] = footstep;cnt++; //方案數+1//打印出方案for (i = 0;i < step;i++){printf("%d ",Steps[i]);}printf("\n");//返回到父結點,不必再向下搜索}else if (count + footstep > MAX_STEPS){//超過了樓梯的階數,不必再向下搜索,返回到父結點return;}else{//Steps[step] = footstep; //記錄當前上樓梯的臺階數step++; //步數+1count = count + footstep; //記錄目前已經走過的臺階數//遞歸探索后續的分支Upstairs(1,count,step); //走1步的分支Upstairs(2, count, step); //走2步的分支} } void UpstairsALL() {Upstairs(1,0,0); //從第一步上1個臺階開始探索解空間樹Upstairs(2, 0, 0); //從第一步上2個臺階開始探索解空間樹 }int main() {UpstairsALL();printf("一共有%d種解法\n",cnt);_getche(); }效果:
5、數值概率算法
概率算法允許在執行過程中隨機地選擇下一步的計算步驟,因此使用概率算法有時會大大降低復雜度,提高算法的效率,但有時也可能得不到問題的全部答案。
概率算法可以分為4類:
1、數值概率算法
2、蒙特卡洛算法
3、拉斯維加斯算法
4、舍伍德算法
這里只介紹最基礎的數值概率算法。
數值概率算法常用于解決數值計算的問題。應用數值概率算法往往只能得到問題的近似解,并且該近似解的精度一般隨著計算時間的增加而不斷提高。
例:f(x)=1-x^2;計算定積分:
該積分的幾何意義如下:
如果隨機地向圖中虛線與x,y坐標軸所圍成的正方形中投點,那么根據幾何概率知識可知,隨機點落入陰影區域的概率即為陰影部分的面積與虛線與x,y軸所圍成的正方形的面積之比。
所以只要求出隨機點落入陰影區域的概率的概率就求出了定積分的近似值。
算法描述:
#include<iostream> #include <stdio.h> #include <stdlib.h> #include "malloc.h" #include "conio.h" #include "time.h"double Darts(int n) //n為試驗投點的個數,決定了計算概率的精度 {double x, y;time_t t;int i, count = 0;srand((unsigned)time(&t));for (i = 0;i < n;i++){//隨機初始化投點的坐標x = rand() % 100 / 100.0;y = rand() % 100 / 100.0;if (y <= 1 - pow(x, 2))count++; //如果隨機點落入陰影區域,則用count++記錄個數}return (double)count / (double)n; //返回概率 } //測試函數 int main() {int n;char c;printf("請輸入試驗精度(越大精度越好):\n");scanf("%d", &n);printf("結果為:\n");printf("%f\n", Darts(n));_getche();return 0; }總結
以上是生活随笔為你收集整理的常用算法总结(穷举法、贪心算法、递归与分治算法、回溯算法、数值概率算法)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 光与夜之恋光影怎么培养
- 下一篇: 查前列腺多少钱啊?