對於計算機科學而言,算法是一個非常重要的概念。它是程序設計的靈魂,是將實際問題同解決該問題的計算機程序建立起聯系的橋梁。接下來,我們來看看一些常用的算法思想。
(一)窮舉法思想
窮舉法,又稱為強力法。它是一種最為直接,實現最為簡單,同時又最為耗時的一種解決實際問題的算法思想。
基本思想:在可能的解空間中窮舉出每一種可能的解,並對每一個可能解進行判斷,從中得到問題的答案。
使用窮舉法思想解決實際問題,最關鍵的步驟是划定問題的解空間,並在該解空間中一一枚舉每一個可能的解。這里有兩點需要注意,一是解空間的划定必須保證覆蓋問題的全部解,二是解空間集合及問題的解集一定是離散的集合,也就是說集合中的元素是可列的、有限的。
窮舉法用時間上的犧牲換來了解的全面性保證,因此窮舉法的優勢在於確保得到問題的全部解,而瓶頸在於運算效率十分低下。但是窮舉法算法思想簡單,易於實現,在解決一些規模不是很大的問題,使用窮舉法不失為一種很好地選擇。
現在我們通過具體的實例來理解窮舉法思想。
/** * 實例:尋找[1,100]之間的素數 * */ #include <stdio.h> /** * 判斷n是否是素數,是則返回1,不是則返回0 */ int isPrime(int n) { int i = 0; for (i = 2; i < n; i++) { if (0 == n % i) { return 0; } } return 1; } /** * 尋找[low,high]之間的素數 */ void getPrime(int low, int high) { int i = 0; for (i = low; i <= high; i++) { if (isPrime(i)) { printf("%d ", i); } } } int main(int argc, const char * argv[]) { // insert code here... int low = 0, high = 0; printf("Please input the domain for searching prime\n"); printf("low limitation:"); scanf("%d", &low); printf("high limitation:"); scanf("%d", &high); printf("The whole primes in this domain are\n"); getPrime(low, high); getchar(); return 0; }
程序運行結果:
(二)遞歸與分治思想
遞歸與分治的算法思想往往是相伴而生的,它們在各類算法中使用非常頻繁,應用遞歸和分治的算法思想有時可以設計出代碼簡潔且比較高效的算法來。
在解決一些比較復雜的問題,特別是解決一些規模較大得問題時,常常將問題進行分解。具體來說,就是將一個規模較大的問題分割成規模較小的同類問題,然后將這些小問題的子問題逐個加以解決,最終也就將整個大問題解決了。這種思想稱之為分治。在解決一些問題比較復雜、計算量龐大的問題時經常被用到。
最為經典的使用分治思想設計的算法就是“折半查找算法”。折半查找算法利用了元素之間的順序關系(有序序列),采用分而治之的策略,不斷縮小問題的規模,每次都將問題的規模減小至上一次的一半。
而遞歸思想也是一種常見的算法設計思想,所謂遞歸算法,就是一種直接或間接地調用原算法本身的一種算法。
接下來我們通過實例代碼來理解遞歸、分治思想。
分治思想:
/** * 有一個數組A[10],里面存放了10個整數,順序遞增 * A[10] = {2, 3, 5, 7, 8, 10, 12, 15, 19, 21} * */ #include <stdio.h> int bin_search(int A[], int n, int key) { int low = 0, high = 0, mid = 0; high = n - 1; while (low <= high) { mid = (low + high) / 2; if (A[mid] == key) { //查找成功,返回mid return mid; } if (A[mid] < key) { //在后半序列中查找 low = mid + 1; } if (A[mid] > key) { //在前半序列中查找 high = mid - 1; } } return -1; //查找失敗 } int main(int argc, const char * argv[]) { // insert code here... int A[10] = {2, 3, 5, 7, 8, 10, 12, 15, 19, 21}; int i = 0, n = 0, addr = 0; printf("The contents of the Array A[10] are\n"); for (i = 0; i < 10; i++) { printf("%d ",A[i]); //顯示數組A中的內容 } printf("\nPlease input a interger for search\n"); scanf("%d", &n); //輸入待查找得元素 addr = bin_search(A, 10, n); //折半查找,返回該元素在數組中的下標 if (-1 != addr) { printf("%d is at the %dth unit is array A\n", n, addr); }else{ printf("There is no %d in array A\n", n); //查找失敗 } getchar(); return 0; }
運行結果:
遞歸思想
/** * 計算n的階乘n! * */ #include <stdio.h> int factorial(int n) { if (0 == n) { return 1; }else{ return n * factorial(n - 1); } } int main(int argc, const char * argv[]) { // insert code here... int n = 0, result = 0; printf("Please input factorial number\n"); scanf("%d", &n); result = factorial(n); printf("result is %d", result); getchar(); return 0; }
運行結果
(三)貪心算法思想
貪心算法的思想非常簡單且算法效率很高,在一些問題的解決上有着明顯的優勢。
先來看一個生活中的例子。假設有3種硬幣,面值分別為1元、5角、1角。這3種硬幣各自的數量不限,現在要找給顧客3元6角錢,請問怎樣找才能使得找給顧客的硬幣數量最少呢?你也許會不假思索的說出答案:找給顧客3枚1元硬幣,1枚5角硬幣,1枚1角硬幣。其實也可以找給顧客7枚5角硬幣,1枚1角硬幣。可是在這里不符合題意。在這里,我們下意識地應用了所謂貪心算法解決這個問題。
所謂貪心算法,就是總是做出在當前看來是最好的選擇的一種方法。以上述的題目為例,為了找給顧客的硬幣數量最少,在選擇硬幣的面值時,當然是盡可能地選擇面值大的硬幣。因此,下意識地遵循了以下方案:
(1)首先找出一個面值不超過3元6角的最大硬幣,即1元硬幣。
(2)然后從3元6角中減去1元,得到2元6角,再找出一個面值不超過2元6角的最大硬幣,即1元硬幣。
(3)然后從2元6角中減去1元,得到1元6角,再找出一個面值不超過1元6角的最大硬幣,即1元硬幣。
(4)然后從1元6角中減去1元,得到6角,再找出一個面值不超過6角的最大硬幣,即5角硬幣。
(5)然后從6角中減去5角,得到1角,再找出一個面值不超過1角的最大硬幣,即1角硬幣。
(6)找零錢的過程結束。
這個過程就是一個典型的貪心算法思想。
因此,不難看出應用貪心算法求解問題,並不從問題的整體最優上加以考慮,它所作出的每一步選擇只是在某種意義上得局部最優選擇。因此,嚴格意義上講,要使用貪心算法求解問題,該問題應當具備以下性質。
(1)貪心選擇性質
所謂貪心選擇性質,就是指所求解的問題的整體最優解可以通過一系列的局部最優解得到。所謂局部最優解,就是指在當前的狀態下做出的最好選擇。
(2)最優子結構性質
當一個問題的最優解包含着它的子問題的最優解時,就稱此問題具有最優子結構性質。
我們經常使用的哈夫曼(Huffman Tree)編碼算法,求解最小生成樹的克魯斯卡爾(Kruskal)算法和普利姆(Prim)算法,求解圖的單源最短路徑的迪克斯特拉(Dijkstra)算法都是基於貪心算法的思想設計的。
下面,我們來通過實例代碼來理解貪心算法思想。
/** * 最優裝船問題 * 有一批集裝箱要裝入一個載質量為C的貨船中,每個集裝箱的質量由用戶自己輸入指定,在貨船的裝載體積不限的前提下,如何裝載集裝箱才能盡可能多地將集裝箱裝入貨船中。 */ #include <stdio.h> void sort(int w[], int t[], int n) { int i = 0, j = 0, tmp = 0; //存放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]; } for (i = 0; i < n - 1; i++) { //冒泡排序 for (j = 0; j < n - i - 1; j++) { if (w_tmp[j] > w_tmp[j+1]) { tmp = w_tmp[j]; w_tmp[j] = w_tmp[j+1]; w_tmp[j+1] = tmp; tmp = t[j]; t[j] = t[j+1]; t[j+1] = tmp; } } } } void Loading(int x[], int w[], int c, int n) { int i = 0; //存放w[]的下標,如果t[i]、t[j]、i<j,則w[i]<=w[j] int *t = (int *)malloc(sizeof(int) * n); //排序,用數組t[[]存放w[]的下標 sort(w, t, n); for (i = 0; i < n; i++) { x[i] = 0; //初始化數組x[] } for (i = 0; i < n && w[t[i]] <= c; i++) { x[t[i]] = 1; //將第t[i]個集裝箱裝入貨船中 c = c - w[t[i]]; //變量c中存放貨船的剩余載質量 } } int main(int argc, const char * argv[]) { // insert code here... int x[5], w[5], c = 0, i = 0; printf("Please input the maximum loading of the sheep\n"); scanf("%d", &c); // printf("Please input the weight of FIVE box\n"); for (i = 0; i < 5; i++) { // scanf("%d", &w[i]); } Loading(x, w, c, 5); // printf("The following boxes will be loaded\n"); for (i = 0; i < 5; i++) { // if (1 == x[i]) { printf("BOX:%d ", i); } } getchar(); return 0; }
運行結果
以上,就是對算法設計中幾個常見的思想的總結。