A. 請問遞歸演算法的時間復雜度如何計算呢
遞歸演算法的時仔汪缺間復雜度在演算法中,當一個演算法中包含遞歸調用時,其時間復雜度的分析會轉化為一個遞歸方程求解,常用以下四種方法:
代入法的基本步驟是先推測遞歸方程的顯式解,然後用數學歸納法來驗證該解是否合理。
2.遞歸程序設計是程序設計中常用的一種方法,它可以解決所有有遞歸屬性的問題,並且是行之有效的.
3.但對於遞歸程序運行的效率比較低,無論是時間還是空間都比非遞歸程序更費,若在程序中消除遞歸調用,則其運行時間可大為節省.
B. 計算機編程常用演算法有哪些
貪心演算法,蟻群演算法,遺傳演算法,進化演算法,基於文化族喚的遺傳演算法,禁兆頌凱忌演算法櫻此,蒙特卡洛演算法,混沌隨機演算法,序貫數論演算法,粒子群演算法,模擬退火演算法。
模擬退火+遺傳演算法混合編程例子:
http://..com/question/43266691.html
自適應序貫數論演算法例子:
http://..com/question/60173220.html
C. 編程演算法有哪些
具體演算法如下:
1、快速排序演算法快速排序是由東尼·霍爾所發展的一種排序演算法。在平均狀況下,排序n個項目要譽毀Ο(nlogn)次比較。在最壞狀況下則需要Ο(n2)次比較,但這種狀況並不常見。
2、堆排序(Heapsort)是指利用堆這種數據結構所設計的一種排序演算法。堆積是一個近似完全二叉樹的結構,並同時滿足堆積的性質:即子結點的鍵值或索引總是小於(或者大於)它的父節點。
3、歸並排序(Mergesort,台灣譯作:合並排序)是建立在歸並操作上的一慶碼備種有效的排序演算法。該演算法是採用分治法(DivideandConquer)的一個非常典型的應用。
4、二分查找演算法是一種在有序數組中查找某一特定元素的搜索演算法。搜素過程從數組的中間元素開始,如果中間元素正好是要查找的元素,則搜素過程結束。
5、BFPRT演算法解決的問題十分經典,即從某n個元素的序列中選出第k大(第k小)的元素,通過巧妙的分析,BFPRT可以保證在最壞情況下仍為線性時間復雜度。
6、深度優先搜索演算法,是搜索演算法的一種。它沿著樹的深度遍歷樹的節點,盡可能深的搜索樹的分支。當節點v的所有邊都己被探尋過,搜索將回溯到發現節模笑點v的那條邊的起始節點。
D. 軟考程序員考試知識點有哪些
軟考初級程序員考試包含基礎知識和應用技術共兩個科目,各科目考試知識點有所不同。基礎知識科目考試題型為客觀選擇題,應用技術科目考試題型為主觀問答題。
程序員屬於軟考初級資格考試,軟考程序員考試大綱中各科目的考試知識點范圍如下:
考試科目1:計算機與軟體工程基本知識
1.計算機科學基礎
1.1數制及其轉換
二進制、十進制和十六進制等常用數制及其相互轉換
1.2數據的表示
數的表示
非數值數據的表示
1.3算術運算和邏輯運算
計算機中二進制數的運算方法
邏輯代數的基本運算
1.4數學應用
常用數值計算(矩陣、近似求解、插值)
排列組合、應用統計
編碼基礎
1.5常用數據結構
數組
線性表及鏈表
隊列、棧
樹
圖
1.6常用演算法
演算法與數據結構的關系
演算法設計和演算法描述
常用的排序演算法
查找演算法
常用的數值計算方法
字元串處理演算法
遞歸演算法
最小生成樹、拓撲排序和單源點最短路徑求解演算法
2.計算機系統基礎知識
2.1硬體基礎知識
2.1.1計算機的類型和特點
微機(PC機)、工作站、伺服器、主機、大型計算機、巨型計算機、並行機
2.1.2中央處理器CPU
CPU的組成
常用的寄存器
指令系統,定址方式
令執行控制、中斷控制、處理機性能
2.1.3主存和輔存
存儲介質
高速緩存(Cache)、主存
輔存設備
2.1.4I/O介面、I/O設備和通信設備
I/O介面
I/O設備(類型、特性)
通信設備(類型、特性)
I/O設備、通信設備的連接方法和連接介質類型
2.2軟體基礎知識
2.2.1操作系統基礎知識
操作系統的類型和功能
處理機管理
存儲管理
設備管理
文件管理
作業管理(作業調度演算法)
圖形用戶界面和操作方法
2.2.2程序設計語言和語言處理程序的基礎知識
語言翻譯基礎知識(匯編、編譯、解釋)
程序設計語言的基本成分:數據、運算、控制和傳輸
程序語言類型和特點
2.3網路基礎知識
網路的功能、分類、組成和拓撲結構
基本的網路協議與標准
常用網路設備與網路通信設備,網路操作系統基礎知識
Client/Server結構、Browser/Server結構
區域網(LAN)基礎知識
Internet基礎知識
2.4資料庫基礎知識
資料庫管理系統的主要功能和特徵
資料庫模式(概念模式、外模式、內模式)
數據模型、ER圖
數據操作(關系運算)
資料庫語言(SQL)
資料庫的主要控制功能(並發控制、安全控制)
2.5多媒體基礎知識
多媒體基本知識
常用多媒體設備性能特徵,常用多媒體文件格式類型粗塵
2.6系統性能指標
響應時間、吞吐量、周轉時間
可靠性、可維護性、可擴充性、可移植性、可用性、可重用性、安全性
2.7計算機應用基礎知識
計算機常用辦公軟體操作方法
計算機信息管理、數據處理、輔助設計、自動控制、科學計算、人工智慧等領域的應用
遠程通信服務
3.系統開發和運行知識
3.1軟體工程和項目管理基礎知識
軟體工程基礎知識
軟體開發生命周期各階段的目標和任務
軟體過程基本知識
軟體開發項目管理基本知識
軟體開發方法(原型法、面向對象方法)基礎知識
軟體開發工具與環境基礎知識(CASE)
軟體質量管理基礎知識
3.2系統分析設計基礎知識
數據流圖(DFD)、實體聯系圖(ER圖)基本知識
面向對象設計、以過程為中心設計、以數據為中心設計基礎知識
結構化分析和設計方法
模塊設計、代碼設計、人機界面設計基礎知識
3.3程序設計基礎知識
結構化程序設計、流程圖、NS圖、PAD圖
程序設計風格
3.4程序測試基礎知識
程序測試的目的、原則、對象、過程與工具
黑盒測試、白盒測試方法
測試設計和管理
3.5程序設計文檔基礎知識
演算法的描述、滲宏程度邏輯的描述、程度規格說明書
模塊測試計劃、模塊測試用例、模塊測試報告
3.6系統運行和維護基礎知識
系統運行管理基礎知識
系統維護基礎知識
4.信息安全基礎知識
信息系統安全基礎知識
信息系統安全管理
加密與解密基礎知識
5.標准化基礎知識
標准化基本概念
標準的層次(國際標准、標准、行業標准、企業標准)
相關標准(代碼標准、文件格式標准、安全標准、軟體開發規范和文檔標准、互聯網相關標准)
6.信息化基礎知識
信息、信息資源、信息化、信息工程、信息產業、信息技術的含義
全球信息化趨勢、信息化戰略、企業信息化戰略和策略常識
有關的法律、法規要點
7.計算機專業英語
具有助理工程師(或技術員)英語閱讀水平
掌握本領域的英語基本術語
考試科目2:程序設計
1.內部設計
1.1理解外部設計
1.2軟體功能劃分和確定結構
數據流圖(DFD)、結構圖
1.3物理數據設計
確定數據組織方式、存儲介質、設計叢凳冊記錄格式,處理方式
1.4詳細輸入輸出設計
界面設計、報表設計等
1.5內部設計文檔
程序介面、程序功能、人機界面、輸入輸出、測試計劃
1.6內部設計評審
2.程序設計
2.1模塊劃分(原則、方法、標准)
2.2編寫程序設計文檔
模塊規格說明書(程序處理邏輯、輸入輸出數據格式)
測試要求說明書(測試類型和目標、測試用例、測試方法)
2.3程序設計評審
3.程序實現
3.1編程
編程方法和規范
程序設計語言的選擇和使用
人工走查
程序文檔化
3.2程序測試
測試環境和測試工具的選擇和使用
測試數據的設計
測試報告的編寫
4.程序設計語言(C語言為必選,再在C、C++、Java語言中任選一種)
4.1C程序設計語言(C99標准)
4.2C++程序設計語言(ANSIC++標准)
4.3Java程序設計語言(Java2)
溫馨提示:因考試政策、內容不斷變化與調整,獵考網提供的以上信息僅供參考,如有異議,請考生以權威部門公布的內容為准!
下方免費復習資料內容介紹:希賽2023年電子商務設計師招生簡章
格式:DO大小:6011.84KB 2022年系統架構設計師上午真題
格式:DO大小:8294.76KB
資格考試有疑問、不知道如何總結考點內容、不清楚報考考試當地政策,點擊底部咨詢獵考網,免費領取復習資料
E. 編程的基礎演算法有哪些
1、二叉樹的每個結點至多隻有二棵子樹(不存在度大於2的結點),二叉樹的子樹有左右之分,次序不能顛倒。二叉樹的第i層至多有2^(i 1)個結點。
深度為k的二叉樹至多有2^k 1個結點;對任何一棵二叉樹T,如果其終端結點數為n0,度為2的結點數為n2,則n0 = n2 + 1。二叉樹演算法常被用於實現念旦則二叉查找樹和二叉堆。
遞歸演算法能夠解決的問題
數據的定義是按遞歸定義的。如Fibonacci函數。
問題解法按遞歸演算法實現。如Hanoi問題。
數據的結構形式是按遞歸定義的。如二叉樹、廣義表等。
F. 軟體編程經常用的演算法都有哪些
排序演算法 所謂排序,就是使一串記錄,按照其中的某個或某些關鍵字的大小,遞增或遞減的排列起來的操作。
分類
在計算機科學所使用的排序演算法通常被分類為:
計算的復雜度(最差、平均、和最好表現),依據串列(list)的大小(n)。一般而言,好的表現是O。(n log n),且壞的行為是Ω(n2)。對於一個排序理想的表現是O(n)。僅使用一個抽象關鍵比較運算的排序演算法總平均上總是至少需要Ω(n log n)。
記憶體使用量(以及其他電腦資源的使用)
穩定度:穩定排序演算法會依照相等的關鍵(換言之就是值)維持紀錄的相對次序。也就是一個排序演算法是穩定的,就是當有兩個有相等關鍵的紀錄R和S,且在原本的串列中R出現在S之前,在排序過的串列中R也將會是在S之前。
一般的方法:插入、交換、選擇、合並等等。交換排序包含冒泡排序(bubble sort)和快速排序(quicksort)。選擇排序包含shaker排序和堆排序(heapsort)。
當相等的元素是無法分辨的,比如像是整數,穩定度並不是一個問題。然而,假設以下的數對將要以他們的第一個數字來排序。
(4, 1) (3, 1) (3, 7) (5, 6)
在這個狀況下,有可能產生兩種不同的結果,一個是依照相等的鍵值維持相對的次序,而另外一個則沒有:
(3, 1) (3, 7) (4, 1) (5, 6) (維持次序)
(3, 7) (3, 1) (4, 1) (5, 6) (次序被改變)
不穩定排序演算法可能會在相等的鍵值中改變紀錄的相對次序,但是穩定排序演算法從來不會如此。不穩定排序演算法可以被特別地時作為穩定。作這件事情的一個方式是人工擴充鍵值的比較,如此在其他方面相同鍵值的兩個物件間之比較,就會被決定使用在原先資料次序中的條目,當作一個同分決賽。然而,要記住這種次序通常牽涉到額外的空間負擔。
排列演算法列表
在這個表格中,n是要被排序的紀錄數量以及k是不同鍵值的數量。
穩定的
冒泡排序(bubble sort) — O(n2)
雞尾酒排序 (Cocktail sort, 雙向的冒泡排序) — O(n2)
插入排序 (insertion sort)— O(n2)
桶排序 (bucket sort)— O(n); 需要 O(k) 額外 記憶體
計數排序 (counting sort) — O(n+k); 需要 O(n+k) 額外 記憶體
歸並排序 (merge sort)— O(n log n); 需要 O(n) 額外記憶體
原地歸並排序 — O(n2)
二叉樹排序 (Binary tree sort) — O(n log n); 需要 O(n) 額外記憶體
鴿巢排序 (Pigeonhole sort) — O(n+k); 需要 O(k) 額外記憶體
基數排序 (radix sort)— O(n·k); 需要 O(n) 額外記憶體
Gnome sort — O(n2)
Library sort — O(n log n) with high probability, 需要 (1+ε)n 額外記憶體
不穩定
選擇排序 (selection sort)— O(n2)
希爾排序 (shell sort)— O(n log n) 如果使用最佳的現在版本
Comb sort — O(n log n)
堆排序 (heapsort)— O(n log n)
Smoothsort — O(n log n)
快速排序 (quicksort)— O(n log n) 期望時間, O(n2) 最壞情況; 對於大的、亂數串列一般相信是最快的已知排序
Introsort — O(n log n)
Patience sorting — O(n log n + k) 最外情況時間, 需要 額外的 O(n + k) 空間, 也需要找到最長的遞增子序列(longest increasing subsequence)
不實用的排序演算法
Bogo排序 — O(n × n!) 期望時間, 無窮的最壞情況。
Stupid sort — O(n3); 遞回版本需要 O(n2) 額外記憶體
Bead sort — O(n) or O(√n), 但需要特別的硬體
Pancake sorting — O(n), 但需要特別的硬體
排序的演算法
排序的演算法有很多,對空間的要求及其時間效率也不盡相同。下面列出了一些常見的排序演算法。這裡面插入排序和冒泡排序又被稱作簡單排序,他們對空間的要求不高,但是時間效率卻不穩定;而後面三種排序相對於簡單排序對空間的要求稍高一點,但時間效率卻能穩定在很高的水平。基數排序是針對關鍵字在一個較小范圍內的排序演算法。
插入排序
冒泡排序
選擇排序
快速排序
堆排序
歸並排序
基數排序
希爾排序
插入排序
插入排序是這樣實現的:
首先新建一個空列表,用於保存已排序的有序數列(我們稱之為"有序列表")。
從原數列中取出一個數,將其插入"有序列表"中,使其仍舊保持有序狀態。
重復2號步驟,直至原數列為空。
插入排序的平均時間復雜度為平方級的,效率不高,但是容易實現。它藉助了"逐步擴大成果"的思想,使有序列表的長度逐漸增加,直至其長度等於原列表的長度。
冒泡排序
冒泡排序是這樣實現的:
首先將所有待排序的數字放入工作列表中。
從列表的第一個數字到倒數第二個數字,逐個檢查:若某一位上的數字大於他的下一位,則將它與它的下一位交換。
重復2號步驟,直至再也不能交換。
冒泡排序的平均時間復雜度與插入排序相同,也是平方級的,但也是非常容易實現的演算法。
選擇排序
選擇排序是這樣實現的:
設數組內存放了n個待排數字,數組下標從1開始,到n結束。
i=1
從數組的第i個元素開始到第n個元素,尋找最小的元素。
將上一步找到的最小元素和第i位元素交換。
如果i=n-1演算法結束,否則回到第3步
選擇排序的平均時間復雜度也是O(n²)的。
快速排序
現在開始,我們要接觸高效排序演算法了。實踐證明,快速排序是所有排序演算法中最高效的一種。它採用了分治的思想:先保證列表的前半部分都小於後半部分,然後分別對前半部分和後半部分排序,這樣整個列表就有序了。這是一種先進的思想,也是它高效的原因。因為在排序演算法中,演算法的高效與否與列表中數字間的比較次數有直接的關系,而"保證列表的前半部分都小於後半部分"就使得前半部分的任何一個數從此以後都不再跟後半部分的數進行比較了,大大減少了數字間不必要的比較。但查找數據得另當別論了。
堆排序
堆排序與前面的演算法都不同,它是這樣的:
首先新建一個空列表,作用與插入排序中的"有序列表"相同。
找到數列中最大的數字,將其加在"有序列表"的末尾,並將其從原數列中刪除。
重復2號步驟,直至原數列為空。
堆排序的平均時間復雜度為nlogn,效率高(因為有堆這種數據結構以及它奇妙的特徵,使得"找到數列中最大的數字"這樣的操作只需要O(1)的時間復雜度,維護需要logn的時間復雜度),但是實現相對復雜(可以說是這里7種演算法中比較難實現的)。
看起來似乎堆排序與插入排序有些相像,但他們其實是本質不同的演算法。至少,他們的時間復雜度差了一個數量級,一個是平方級的,一個是對數級的。
平均時間復雜度
插入排序 O(n2)
冒泡排序 O(n2)
選擇排序 O(n2)
快速排序 O(n log n)
堆排序 O(n log n)
歸並排序 O(n log n)
基數排序 O(n)
希爾排序 O(n1.25)
冒泡排序
654
比如說這個,我想讓它從小到大排序,怎麼做呢?
第一步:6跟5比,發現比它大,則交換。564
第二步:5跟4比,發現比它大,則交換。465
第三步:6跟5比,發現比它大,則交換。456
G. 軟體工程程序設計中幾種常用演算法的比較研究
摘要:在計算機科學領域中,軟體工程程序設計是一項重要的研究內容,而程序設計的核心就是演算法的選擇,最佳的演算法不僅能夠降低程序喚搭攔的復和胡雜性,而且要能夠達到程序設計的要求。在軟體工程中對於程序設計演算法的方法有很多種,該文主要對軟體工程程序設計的幾種常用演算法進行比較研究,從而能夠為軟體工程程序設計提供一些參照條件。枝橘(剩餘0字)
H. C語言中什麼叫演算法,演算法在程序設計中的重要作用
一、什麼是演算法
演算法是一系列解決問題的清晰指令,也就是說,能夠對一定規范的輸入,在有限時間內獲得所要求的輸出。演算法常常含有重復的步驟和一些比較或邏輯判斷。如果一個演算法有缺陷,或不適合於某個問題,執行這個演算法將不會解決這個問題。不同的演算法可能用不同的時間、空間或效率來完成同樣的任務。一個演算法的優劣可以用空間復雜度與時間復雜度來衡量。
演算法的時間復雜度是指演算法需要消耗的時間資源。一般來說,計算機演算法是問題規模n 的函困枝宴數f(n),演算法執行的時間的增長率與f(n) 的增長率正相關,稱作漸進時間復雜度(Asymptotic Time Complexity)。時間復雜度用「O(數量級)」來表示,稱為「階」。常見的時間復雜度有: O(1)常數階;O(log2n)對數階;O(n)線性階;O(n2)平方階。
演算法的空間復雜度是指演算法需要消耗的空間資源。其計算和表示方法與時間復雜度類似,一般都用復雜度的漸近性來表示。同時間復雜度相比,空間復雜度的分析要簡單得多。
二、演算法設計的方法
1.遞推法
遞推法是利用問題本身所具有的一種遞推關系求問題解的一種方法。設要求問題規模為N的解,當N=1時,解或為已知,或能非常方便地得到解。能采汪銀用遞推法構造演算法的問題有重要的遞推性質,即當得到問題規模為i-1的解後,由問題的遞推性質,能從已求得的規模為1,2,…,i-1的一系列解,構造出問題規模為I的解。這樣,程序可從i=0或i=1出發,重復地,由已知至i-1規模的解,通過遞推,獲得規模為i的解,直至得到規模為N的解。
【問題】 階乘計算
問題描述:編寫程序,對給定的n(n≤100),計算並輸出k的階乘k!(k=1,2,…,n)的全部有效數字。
由於要求的整數可能大大超出一般整數的位數,程序用一維數組存儲長整數,存儲長整數數組的每個元素只存儲長整數的一位數字。如有m位成整數N用數組a[ ]存儲:
N=a[m]×10m-1+a[m-1]×10m-2+ … +a[2]×101+a[1]×100
並用a[0]存儲長整數N的位數m,即a[0]=m。按上述約定,數組的每個元素存儲k的階乘k!的一位數字,並從低位到高位依次存於數組的第二個元素、第三個元素……。例如,5!=120,在數組中的存儲形式為:
3 0 2 1 ……
首元素3表示長整數是一個3位數,接著是低位到高位依次是0、2、1,表示成整數120。
計算階乘k!可採用對已求得的階乘(k-1)!連續累加k-1次後求得。例如,已知4!=24,計算5!,可對原來的24累加4次24後得到120。細節見以下程序。
# include <stdio.h>
# include <malloc.h>
......
2.遞歸
遞歸是設計和描述演算法的一種有力的工具,由於它在復雜演算法的描述中被經常採用,為此在進一步介紹其他演算法設計方法之前先討論它。
能採用遞歸描述的演算法通常有這樣的特徵:為求解規模為N的問題,設法將它分解成規模較小的問題,然後從這些小問題的解方便地構造出大問題的解,並且這些規模較小的問題也能採用同樣的分解和綜合方法,分解成規模更小的問題,並從這些更小問題的解構造出規模較大問題的解。特別地,當規模N=1時,能直接得解。
【問題】 編寫計算斐波那契(Fibonacci)數列的第n項函數fib(n)。
斐波那契數列為:0、1、1、2、3、……,即:
fib(0)=0;
fib(1)=1;
fib(n)=fib(n-1)+fib(n-2) (當n>1時)。
寫成遞歸函數有:
int fib(int n)
{ if (n==0) return 0;
if (n==1) return 1;
if (n>1) return fib(n-1)+fib(n-2);
}
遞歸演算法的執行過程分遞推和回歸兩個階段。在遞推階段,把較復雜的問題(規模為n)的求解推到比原問題簡單一些的問題(規模小於n)的求解。例如上例中,求解fib(n),把它推到求解fib(n-1)和fib(n-2)。也就是說,為計算fib(n),必須先計算fib(n-1)和fib(n-2),而計算fib(n-1)和fib(n-2),又必須先計算搭侍fib(n-3)和fib(n-4)。依次類推,直至計算fib(1)和fib(0),分別能立即得到結果1和0。在遞推階段,必須要有終止遞歸的情況。例如在函數fib中,當n為1和0的情況。
在回歸階段,當獲得最簡單情況的解後,逐級返回,依次得到稍復雜問題的解,例如得到fib(1)和fib(0)後,返回得到fib(2)的結果,……,在得到了fib(n-1)和fib(n-2)的結果後,返回得到fib(n)的結果。
在編寫遞歸函數時要注意,函數中的局部變數和參數知識局限於當前調用層,當遞推進入「簡單問題」層時,原來層次上的參數和局部變數便被隱蔽起來。在一系列「簡單問題」層,它們各有自己的參數和局部變數。
由於遞歸引起一系列的函數調用,並且可能會有一系列的重復計算,遞歸演算法的執行效率相對較低。當某個遞歸演算法能較方便地轉換成遞推演算法時,通常按遞推演算法編寫程序。例如上例計算斐波那契數列的第n項的函數fib(n)應採用遞推演算法,即從斐波那契數列的前兩項出發,逐次由前兩項計算出下一項,直至計算出要求的第n項。
【問題】 組合問題
問題描述:找出從自然數1、2、……、n中任取r個數的所有組合。例如n=5,r=3的所有組合為: (1)5、4、3 (2)5、4、2 (3)5、4、1
(4)5、3、2 (5)5、3、1 (6)5、2、1
(7)4、3、2 (8)4、3、1 (9)4、2、1
(10)3、2、1
分析所列的10個組合,可以採用這樣的遞歸思想來考慮求組合函數的演算法。設函數為void comb(int m,int k)為找出從自然數1、2、……、m中任取k個數的所有組合。當組合的第一個數字選定時,其後的數字是從餘下的m-1個數中取k-1數的組合。這就將求m個數中取k個數的組合問題轉化成求m-1個數中取k-1個數的組合問題。設函數引入工作數組a[ ]存放求出的組合的數字,約定函數將確定的k個數字組合的第一個數字放在a[k]中,當一個組合求出後,才將a[ ]中的一個組合輸出。第一個數可以是m、m-1、……、k,函數將確定組合的第一個數字放入數組後,有兩種可能的選擇,因還未去頂組合的其餘元素,繼續遞歸去確定;或因已確定了組合的全部元素,輸出這個組合。細節見以下程序中的函數comb。
【程序】
# include <stdio.h>
# define MAXN 100
int a[MAXN];
void comb(int m,int k)
{ int i,j;
for (i=m;i>=k;i--)
{ a[k]=i;
if (k>1)
comb(i-1,k-1);
else
{ for (j=a[0];j>0;j--)
printf(「%4d」,a[j]);
printf(「\n」);
}
}
}
void main()
{ a[0]=3;
comb(5,3);
}
3.回溯法
回溯法也稱為試探法,該方法首先暫時放棄關於問題規模大小的限制,並將問題的候選解按某種順序逐一枚舉和檢驗。當發現當前候選解不可能是解時,就選擇下一個候選解;倘若當前候選解除了還不滿足問題規模要求外,滿足所有其他要求時,繼續擴大當前候選解的規模,並繼續試探。如果當前候選解滿足包括問題規模在內的所有要求時,該候選解就是問題的一個解。在回溯法中,放棄當前候選解,尋找下一個候選解的過程稱為回溯。擴大當前候選解的規模,以繼續試探的過程稱為向前試探。
【問題】 組合問題
問題描述:找出從自然數1,2,…,n中任取r個數的所有組合。
採用回溯法找問題的解,將找到的組合以從小到大順序存於a[0],a[1],…,a[r-1]中,組合的元素滿足以下性質:
(1) a[i+1]>a,後一個數字比前一個大;
(2) a-i<=n-r+1。
按回溯法的思想,找解過程可以敘述如下:
首先放棄組合數個數為r的條件,候選組合從只有一個數字1開始。因該候選解滿足除問題規模之外的全部條件,擴大其規模,並使其滿足上述條件(1),候選組合改為1,2。繼續這一過程,得到候選組合1,2,3。該候選解滿足包括問題規模在內的全部條件,因而是一個解。在該解的基礎上,選下一個候選解,因a[2]上的3調整為4,以及以後調整為5都滿足問題的全部要求,得到解1,2,4和1,2,5。由於對5不能再作調整,就要從a[2]回溯到a[1],這時,a[1]=2,可以調整為3,並向前試探,得到解1,3,4。重復上述向前試探和向後回溯,直至要從a[0]再回溯時,說明已經找完問題的全部解。按上述思想寫成程序如下:
【程序】
# define MAXN 100
int a[MAXN];
void comb(int m,int r)
{ int i,j;
i=0;
a=1;
do {
if (a-i<=m-r+1
{ if (i==r-1)
{ for (j=0;j<r;j++)
printf(「%4d」,a[j]);
printf(「\n」);
}
a++;
continue;
}
else
{ if (i==0)
return;
a[--i]++;
}
} while (1)
}
main()
{ comb(5,3);
}
4.貪婪法
貪婪法是一種不追求最優解,只希望得到較為滿意解的方法。貪婪法一般可以快速得到滿意的解,因為它省去了為找最優解要窮盡所有可能而必須耗費的大量時間。貪婪法常以當前情況為基礎作最優選擇,而不考慮各種可能的整體情況,所以貪婪法不要回溯。
例如平時購物找錢時,為使找回的零錢的硬幣數最少,不考慮找零錢的所有各種發表方案,而是從最大面值的幣種開始,按遞減的順序考慮各幣種,先盡量用大面值的幣種,當不足大面值幣種的金額時才去考慮下一種較小面值的幣種。這就是在使用貪婪法。這種方法在這里總是最優,是因為銀行對其發行的硬幣種類和硬幣面值的巧妙安排。如只有面值分別為1、5和11單位的硬幣,而希望找回總額為15單位的硬幣。按貪婪演算法,應找1個11單位面值的硬幣和4個1單位面值的硬幣,共找回5個硬幣。但最優的解應是3個5單位面值的硬幣。
【問題】 裝箱問題
問題描述:裝箱問題可簡述如下:設有編號為0、1、…、n-1的n種物品,體積分別為v0、v1、…、vn-1。將這n種物品裝到容量都為V的若干箱子里。約定這n種物品的體積均不超過V,即對於0≤i<n,有0<vi≤V。不同的裝箱方案所需要的箱子數目可能不同。裝箱問題要求使裝盡這n種物品的箱子數要少。
若考察將n種物品的集合分劃成n個或小於n個物品的所有子集,最優解就可以找到。但所有可能劃分的總數太大。對適當大的n,找出所有可能的劃分要花費的時間是無法承受的。為此,對裝箱問題採用非常簡單的近似演算法,即貪婪法。該演算法依次將物品放到它第一個能放進去的箱子中,該演算法雖不能保證找到最優解,但還是能找到非常好的解。不失一般性,設n件物品的體積是按從大到小排好序的,即有v0≥v1≥…≥vn-1。如不滿足上述要求,只要先對這n件物品按它們的體積從大到小排序,然後按排序結果對物品重新編號即可。裝箱演算法簡單描述如下:
{ 輸入箱子的容積;
輸入物品種數n;
按體積從大到小順序,輸入各物品的體積;
預置已用箱子鏈為空;
預置已用箱子計數器box_count為0;
for (i=0;i<n;i++)
{ 從已用的第一隻箱子開始順序尋找能放入物品i 的箱子j;
if (已用箱子都不能再放物品i)
{ 另用一個箱子,並將物品i放入該箱子;
box_count++;
}
else
將物品i放入箱子j;
}
}
上述演算法能求出需要的箱子數box_count,並能求出各箱子所裝物品。下面的例子說明該演算法不一定能找到最優解,設有6種物品,它們的體積分別為:60、45、35、20、20和20單位體積,箱子的容積為100個單位體積。按上述演算法計算,需三隻箱子,各箱子所裝物品分別為:第一隻箱子裝物品1、3;第二隻箱子裝物品2、4、5;第三隻箱子裝物品6。而最優解為兩只箱子,分別裝物品1、4、5和2、3、6。
若每隻箱子所裝物品用鏈表來表示,鏈表首結點指針存於一個結構中,結構記錄尚剩餘的空間量和該箱子所裝物品鏈表的首指針。另將全部箱子的信息也構成鏈表。以下是按以上演算法編寫的程序。
}
5.分治法
任何一個可以用計算機求解的問題所需的計算時間都與其規模N有關。問題的規模越小,越容易直接求解,解題所需的計算時間也越少。例如,對於n個元素的排序問題,當n=1時,不需任何計算;n=2時,只要作一次比較即可排好序;n=3時只要作3次比較即可,…。而當n較大時,問題就不那麼容易處理了。要想直接解決一個規模較大的問題,有時是相當困難的。
分治法的設計思想是,將一個難以直接解決的大問題,分割成一些規模較小的相同問題,以便各個擊破,分而治之。
如果原問題可分割成k個子問題(1<k≤n),且這些子問題都可解,並可利用這些子問題的解求出原問題的解,那麼這種分治法就是可行的。由分治法產生的子問題往往是原問題的較小模式,這就為使用遞歸技術提供了方便。在這種情況下,反復應用分治手段,可以使子問題與原問題類型一致而其規模卻不斷縮小,最終使子問題縮小到很容易直接求出其解。這自然導致遞歸過程的產生。分治與遞歸像一對孿生兄弟,經常同時應用在演算法設計之中,並由此產生許多高效演算法。
分治法所能解決的問題一般具有以下幾個特徵:
(1)該問題的規模縮小到一定的程度就可以容易地解決;
(2)該問題可以分解為若干個規模較小的相同問題,即該問題具有最優子結構性質;
(3)利用該問題分解出的子問題的解可以合並為該問題的解;
(4)該問題所分解出的各個子問題是相互獨立的,即子問題之間不包含公共的子子問題。
上述的第一條特徵是絕大多數問題都可以滿足的,因為問題的計算復雜性一般是隨著問題規模的增加而增加;第二條特徵是應用分治法的前提,它也是大多數問題可以滿足的,此特徵反映了遞歸思想的應用;第三條特徵是關鍵,能否利用分治法完全取決於問題是否具有第三條特徵,如果具備了第一條和第二條特徵,而不具備第三條特徵,則可以考慮貪心法或動態規劃法。第四條特徵涉及到分治法的效率,如果各子問題是不獨立的,則分治法要做許多不必要的工作,重復地解公共的子問題,此時雖然可用分治法,但一般用動態規劃法較好。
分治法在每一層遞歸上都有三個步驟:
(1)分解:將原問題分解為若干個規模較小,相互獨立,與原問題形式相同的子問題;
(2)解決:若子問題規模較小而容易被解決則直接解,否則遞歸地解各個子問題;
(3)合並:將各個子問題的解合並為原問題的解。
6.動態規劃法
經常會遇到復雜問題不能簡單地分解成幾個子問題,而會分解出一系列的子問題。簡單地採用把大問題分解成子問題,並綜合子問題的解導出大問題的解的方法,問題求解耗時會按問題規模呈冪級數增加。
為了節約重復求相同子問題的時間,引入一個數組,不管它們是否對最終解有用,把所有子問題的解存於該數組中,這就是動態規劃法所採用的基本方法。以下先用實例說明動態規劃方法的使用。
【問題】 求兩字元序列的最長公共字元子序列
問題描述:字元序列的子序列是指從給定字元序列中隨意地(不一定連續)去掉若干個字元(可能一個也不去掉)後所形成的字元序列。令給定的字元序列X=「x0,x1,…,xm-1」,序列Y=「y0,y1,…,yk-1」是X的子序列,存在X的一個嚴格遞增下標序列<i0,i1,…,ik-1>,使得對所有的j=0,1,…,k-1,有xij=yj。例如,X=「ABCBDAB」,Y=「BCDB」是X的一個子序列。
考慮最長公共子序列問題如何分解成子問題,設A=「a0,a1,…,am-1」,B=「b0,b1,…,bm-1」,並Z=「z0,z1,…,zk-1」為它們的最長公共子序列。不難證明有以下性質:
(1) 如果am-1=bn-1,則zk-1=am-1=bn-1,且「z0,z1,…,zk-2」是「a0,a1,…,am-2」和「b0,b1,…,bn-2」的一個最長公共子序列;
(2) 如果am-1!=bn-1,則若zk-1!=am-1,蘊涵「z0,z1,…,zk-1」是「a0,a1,…,am-2」和「b0,b1,…,bn-1」的一個最長公共子序列;
(3) 如果am-1!=bn-1,則若zk-1!=bn-1,蘊涵「z0,z1,…,zk-1」是「a0,a1,…,am-1」和「b0,b1,…,bn-2」的一個最長公共子序列。
這樣,在找A和B的公共子序列時,如有am-1=bn-1,則進一步解決一個子問題,找「a0,a1,…,am-2」和「b0,b1,…,bm-2」的一個最長公共子序列;如果am-1!=bn-1,則要解決兩個子問題,找出「a0,a1,…,am-2」和「b0,b1,…,bn-1」的一個最長公共子序列和找出「a0,a1,…,am-1」和「b0,b1,…,bn-2」的一個最長公共子序列,再取兩者中較長者作為A和B的最長公共子序列。
代碼如下:
# include <stdio.h>
# include <string.h>
# define N 100
char a[N],b[N],str[N];
int lcs_len(char *a, char *b, int c[ ][ N])
{ int m=strlen(a), n=strlen(b), i,j;
for (i=0;i<=m;i++) c[0]=0;
for (i=0;i<=n;i++) c[0]=0;
for (i=1;i<=m;i++)
for (j=1;j<=m;j++)
if (a[i-1]==b[j-1])
c[j]=c[i-1][j-1]+1;
else if (c[i-1][j]>=c[j-1])
c[j]=c[i-1][j];
else
c[j]=c[j-1];
return c[m][n];
}
char *buile_lcs(char s[ ],char *a, char *b)
{ int k, i=strlen(a), j=strlen(b);
k=lcs_len(a,b,c);
s[k]=』』;
while (k>0)
if (c[j]==c[i-1][j]) i--;
else if (c[j]==c[j-1]) j--;
else { s[--k]=a[i-1];
i--; j--;
}
return s;
}
void main()
{ printf (「Enter two string(<%d)!\n」,N);
scanf(「%s%s」,a,b);
printf(「LCS=%s\n」,build_lcs(str,a,b));
}
7.迭代法
迭代法是用於求方程或方程組近似根的一種常用的演算法設計方法。設方程為f(x)=0,用某種數學方法導出等價的形式x=g(x),然後按以下步驟執行:
(1) 選一個方程的近似根,賦給變數x0;
(2) 將x0的值保存於變數x1,然後計算g(x1),並將結果存於變數x0;
(3) 當x0與x1的差的絕對值還小於指定的精度要求時,重復步驟(2)的計算。
若方程有根,並且用上述方法計算出來的近似根序列收斂,則按上述方法求得的x0就認為是方程的根。上述演算法用C程序的形式表示為:
程序如下:
【演算法】迭代法求方程組的根
{ for (i=0;i<n;i++)
x=初始近似根;
do {
for (i=0;i<n;i++)
y = x;
for (i=0;i<n;i++)
x = gi(X);
for (delta=0.0,i=0;i<n;i++)
if (fabs(y-x)>delta) delta=fabs(y-x); } while (delta>Epsilon);
for (i=0;i<n;i++)
printf(「變數x[%d]的近似根是 %f」,I,x);
printf(「\n」);
} 具體使用迭代法求根時應注意以下兩種可能發生的情況:
(1)如果方程無解,演算法求出的近似根序列就不會收斂,迭代過程會變成死循環,因此在使用迭代演算法前應先考察方程是否有解,並在程序中對迭代的次數給予限制;
(2)方程雖然有解,但迭代公式選擇不當,或迭代的初始近似根選擇不合理,也會導致迭代失敗。
8.窮舉搜索法
窮舉搜索法是對可能是解的眾多候選解按某種順序進行逐一枚舉和檢驗,並從眾找出那些符合要求的候選解作為問題的解。
【問題】 將A、B、C、D、E、F這六個變數排成如圖所示的三角形,這六個變數分別取[1,6]上的整數,且均不相同。求使三角形三條邊上的變數之和相等的全部解。如圖就是一個解。
程序引入變數a、b、c、d、e、f,並讓它們分別順序取1至6的整數,在它們互不相同的條件下,測試由它們排成的如圖所示的三角形三條邊上的變數之和是否相等,如相等即為一種滿足要求的排列,把它們輸出。當這些變數取盡所有的組合後,程序就可得到全部可能的解。程序如下:
按窮舉法編寫的程序通常不能適應變化的情況。如問題改成有9個變數排成三角形,每條邊有4個變數的情況,程序的循環重數就要相應改變。
I. 求PASCAL的演算法
學習計算機語言不是學習的最終目的。語言是描述的工具,如何靈活地運用語言工具,設計和編寫能解決實際問題的程序,演算法是程序設計的基礎。演算法的作用是什麼呢?著名數學家高斯(GAUSS)從小就勤於思索。1785年,剛上小學二年級的小高斯,對老師出的計算題S=1+2+3+…+99+100,第一個舉手報告S的結果是5050。班上的同學都採用依次逐個相加的「演算法」,要相加99次;而小高斯則採用首尾歸並,得出S=(1+100)*50的「演算法」,只需加一次和乘一次,大大提高了效率。可見,演算法在處理問題中的重要性。學習計算機編程,離不開基本演算法。剛開始學習程序設計時,就應注重學習基本演算法。
第一節 遞推與遞歸演算法
遞推和遞歸是編程中常用的基本演算法。在前面的解題中已經用到了這兩種方法,下面對這兩種演算法基本應用進行詳細研究討論。
一、遞推
遞推演算法是一種用若干步可重復的簡單運算(規律)來描述復雜問題的方法。
[例1] 植樹節那天,有五位參加了植樹活動,他們完成植樹的棵數都不相同。問第一位同學植了多少棵時,他指著旁邊的第二位同學說比他多植了兩棵;追問第二位同學,他又說比第三位同學多植了兩棵;…如此,都說比另一位同學多植兩棵。最後問到第五位同學時,他說自己植了10棵。到底第一位同學植了多少棵樹?
解:設第一位同學植樹的棵數為a1,欲求a1,需從第五位同學植樹的棵數a5入手,根據「多兩棵」這個規律,按照一定順序逐步進行推算:
①a5=10;
②a4=a5+2=12;
③a3=a4+2=14;
④a2=a3+2=16;
⑤a1=a2+2=18;
Pascal程序:
Program Exam1;
Var i, a: byte;
begin
a:=10; {以第五位同學的棵數為遞推的起始值}
for i :=1 to 4 do {還有4人,遞推察升計算4次}
a:= a+2; {遞推運算規律}
writeln(』The Num is』, a);
readln
end.
本程序的遞推運算可用如下圖示描述:
遞推演算法以初始{起點}值為基礎,用相同的運算規律,逐次重復運算,直至運算結束。這種從「起點」重復相同的方法直至到達一定「邊界」,猶如單向運敏凳動,用循環可以實現。遞推的本質是按規律逐次推出(計算)下一步的結果。
二、遞歸
遞歸演算法是把處理問題的方法定義成與原問題處理方法相同的過程,在處理問題的過程中又調用自身定義的函數或過程。
仍用上例的計算植樹棵數問題來說明遞歸演算法:
解:把原問題求第一位同學在植樹棵數a1,轉化為a1=a2+2;即求a2;而求a2又轉化為a2=a3+2; a3=a4+2; a4=a5+2;逐層轉化為求a2,a3,a4,a5且都採用與求a1相同的方法;最後的a5為已知,則用a5=10返回到上一層並代入計算出a4;又用a4的值代入上一層去求a3;...,如此,直到求出a1。
因此:
其中求a x+1 又採用求ax 的方法。所以:
①定義一個處理問題的過程Num(x):如果X < 5就遞歸調用過程Num(x+1);
②當遞歸調用到達一定條件(X=5),就直接執行a :=10,再執橋沒旅行後繼語句,遇End返回到調用本過程的地方,將帶回的計算結果(值)參與此處的後繼語句進行運算(a:=a+2);
③最後返回到開頭的原問題,此時所得到的運算結果就是原問題Num(1)的答案。
Pascal程序:
Program Exam1_1;
Var a: byte;
Procere Num(x: integer);{過程Num(x)求x的棵數}
begin
if x=5 then a:=10
else begin
Num(x+1); {遞歸調用過程Num(x+1)}
a:=a+2 {求(x+1)的棵數}
end
end;
begin
Num(1); {主程序調用Num(1)求第1個人的棵數}
writeln(』The Num is 』, a);
readln
end.
程序中的遞歸過程圖解如下:
參照圖示,遞歸方法說明如下:
①調用原問題的處理過程時,調用程序應給出具體的過程形參值(數據);
②在處理子問題中,如果又調用原問題的處理過程,但形參值應是不斷改變的量(表達式);
③每遞歸調用一次自身過程,系統就打開一「層」與自身相同的程序系列;
④由於調用參數不斷改變,將使條件滿足(達到一定邊界),此時就是最後一「層」,不需再調用(打開新層),而是往下執行後繼語句,給出邊界值,遇到本過程的END,就返回到上「層」調用此過程的地方並繼續往下執行;
⑤整個遞歸過程可視為由往返雙向「運動」組成,先是逐層遞進,逐層打開新的「篇章」,(有可能無具體計算值)當最終遞進達到邊界,執行完本「層」的語句,才由最末一「層」逐次返回到上「層」,每次返回均帶回新的計算值,直至回到第一次由主程序調用的地方,完成對原問題的處理。
[例2] 用遞歸演算法求X n 。
解:把X n 分解成: X 0 = 1 ( n =0 )
X 1 = X * X 0 ( n =1 )
X 2 = X * X 1 ( n >1 )
X 3 = X * X 2 ( n >1 )
…… ( n >1 )
X n = X * X n-1 ( n >1 )
因此將X n 轉化為:
其中求X n -1 又用求X n 的方法進行求解。
①定義過程xn(x,n: integer)求X n ;如果n >1則遞歸調用xn (x, n-1) 求X n—1 ;
②當遞歸調用到達n=0,就執行t t :=1, 然後執行本「層」的後繼語句;
③遇到過程的END就結束本次的調用,返回到上一「層」調用語句的地方,並執行其後續語句tt:=tt*x;
④繼續執行步驟③,從調用中逐「層」返回,最後返回到主程序,輸出tt的值。
Pascal程序:
Program Exam2;
Var tt, a, b: integer;
Procere xn(x, n: integer); {過程xn(x, n)求xn }
begin if n=0 then tt:=1
else begin
xn(x, n-1); {遞歸調用過xn(x,n-1)求x n-1}
tt:=tt*x
end;
end;
begin
write(』input x, n:』); readln(a,b); {輸入a, b}
xn(a,b); {主程序調用過程xn(a, b)求a b}
writeln(a, 』^』, b, 』=『, tt);
readln
end.
遞歸演算法,常常是把解決原問題按順序逐次調用同一「子程序」(過程)去處理,最後一次調用得到已知數據,執行完該次調用過程的處理,將結果帶回,按「先進後出」原則,依次計算返回。
如果處理問題的結果只需返回一個確定的計算值,可定義成遞歸函數。
[例3]用遞歸函數求x!
解:根據數學中的定義把求x! 定義為求x*(x-1)! ,其中求(x-1)! 仍採用求x! 的方法,需要定義一個求a!的過程或函數,逐級調用此過程或函數,即:
(x-1)!= (x-1)*(x-2)! ;
(x-2)!= (x-2)*(x-3)! ;
……
直到x=0時給出0!=1,才開始逐級返回並計算各值。
①定義遞歸函數:fac(a: integer): integer;
如果a=0,則fac:=1;
如果a>0,則調用函數fac:=fac(a-1)*a;
②返回主程序,列印fac(x)的結果。
Pascal程序:
Program Exam3;
Var x: integer;
function fac(a: integer): integer; {函數fac(a) 求a !}
begin
if a=0 then fac:=1
else fac:=fac(a-1)*a {函數fac(a-1)遞歸求(a-1) !}
end;
begin
write(』input x』); readln(x);
writeln(x, 』!=』, fac(x)); {主程序調用fac(x) 求x !}
readln
end.
遞歸演算法表現在處理問題的強大能力。然而,如同循環一樣,遞歸也會帶來無終止調用的可能性,因此,在設計遞歸過程(函數)時,必須考慮遞歸調用的終止問題,就是遞歸調用要受限於某一條件,而且要保證這個條件在一定情況下肯定能得到滿足。
[例4]用遞歸算求自然數A,B的最大公約數。
解:求最大公約數的方法有許多種,若用歐幾里德發明的輾轉相除方法如下:
①定義求X除以Y的余數的過程;
②如果余數不為0,則讓X=Y,Y=余數,重復步驟①,即調用過程;
③如果余數為0,則終止調用過程;
④輸出此時的Y值。
Pascal程序:
Program Exam4;
Var a,b,d: integer;
Procere Gdd(x, y: nteger);{過程}
begin
if x mod y =0 then d :=y
else Gdd(y, x mod y) {遞歸調用過程}
end;
begin
write(』input a, b=』); readln(a, b);
Gdd(a, b);
writeln(』(』, a, 』,』, b, 』)=』, d );
readln
end.
簡單地說,遞歸演算法的本質就是自己調用自己,用調用自己的方法去處理問題,可使解決問題變得簡潔明了。按正常情況有幾次調用,就有幾次返回。但有些程序可以只進行遞歸處理,不一定要返回時才進行所需要的處理。
[例5] 移梵塔。有三根柱A,B,C在柱A上有N塊碟片,所有碟片都是大的在下面,小片能放在大片上面。現要將A上的N塊片移到C柱上,每次只能移動一片,而且在同一根柱子上必須保持上面的碟片比下面的碟片小,請輸出移動方法。
解:先考慮簡單情形。
如果N=3,則具體移動步驟為:
假設把第3步,第4步,第6步抽出來就相當於N=2的情況(把上面2片捆在一起,視為一片):
所以可按「N=2」的移動步驟設計:
①如果N=0,則退出,即結束程序;否則繼續往下執行;
②用C柱作為協助過渡,將A柱上的(N-1)片移到B柱上,調用過程sub(n-1, a,b,c);
③將A柱上剩下的一片直接移到C柱上;
④用A柱作為協助過渡,將B柱上的(N-1)移到C柱上,調用過程sub(n-1,b,c,a)。
Pascal程序:
Program Exam65;
Var x,y,z : char;
N, k : integer;
Procere sub(n: integer; a, c , b: char);
begin
if n=0 then exit;
sub(n-1, a,b,c);
inc(k);
writeln(k, 』: from』, a, 』-->』, c);
sub(n-1,b,c,a);
end;
begin
write(』n=』; readln(n);
k:=0;
x:=』A』; y:=』B』; Z:=』C』;
sub(n,x,z,y);
readln
end.
程序定義了把n片從A柱移到C柱的過程sub(n,a,c,b),這個過程把移動分為以下三步來進行:
①先調用過程sub(n-1, a, b, c),把(n-1)片從A柱移到B柱, C柱作為過渡柱;
②直接執行 writeln(a, 』-->』, c),把A柱上剩下的一片直接移到C柱上,;
③調用sub(n-1,b,c,a),把B柱上的(n-1)片從B移到C柱上,A柱是過渡柱。
對於B柱上的(n-1)片如何移到,仍然調用上述的三步。只是把(n-1)當成了n,每調用一次,要移到目標柱上的片數N就減少了一片,直至減少到n=0時就退出,不再調用。exit是退出指令,執行該指令能在循環或遞歸調用過程中一下子全部退出來。
習題6.1
1.過沙漠。希望一輛吉普車以最少的耗油跨越1000 km的沙漠。已知該車總裝油量500升,耗油率為1升/ km,必須利用吉普車自己沿途建立臨時加油站,逐步前進。問一共要多少油才能以最少的耗油越過沙漠?
2.樓梯有N級台階,上樓可以一步上一階,也可以一步上二階。編一遞歸程序,計算共有多少種不同走法?
提示:如N級樓梯有S(N)種不同走法,則有:
S(N)=S(N-2)+S(N-1)
3.阿克曼(Ackmann)函數A(x,y)中,x,y定義域是非負整數,函數值定義為:
A(x,y)=y+1 (x = 0)
A(x,0)=A(x-1,1) (x > 0, y = 0)
A(x,y)=A(x-1, A(x, y-1)) (x, y > 0)
設計一個遞歸程序。
4.某人寫了N封信和N個信封,結果所有的信都裝錯了信封。求所有的信都裝錯信封共有多少種不同情況。可用下面公式:
Dn=(n—1) ( D n—1+D n—2)
寫出遞歸程序。
第二節 回溯演算法
在一些問題求解進程中,有時發現所選用的試探性操作不是最佳選擇,需退回一步,另選一種操作進行試探,這就是回溯演算法。
例[6.6] 中國象棋半張棋盤如下,馬自左下角往右上角跳。現規定只許往右跳,不許往左跳。比如下圖所示為一種跳行路線。編程輸出所有的跳行路線,列印格式如下:
<1> (0,0)—(1,2)—(3,3)—(4,1)—(5,3)—(7,2)—(8,4)
解:按象棋規則,馬往右跳行的方向如下表和圖所示:
水平方向用x表示; 垂直方向用y表示。右上角點為x=8, y=4, 記為(8, 4) ; 用數組tt存放x方向能成行到達的點坐標;用數組t存放y方向能成行到達的點坐標;
①以(tt(K), t(k))為起點,按順序用四個方向試探,找到下一個可行的點(x1, y1);
②判斷找到的點是否合理 (不出界),若合理,就存入tt和t中;如果到達目的就列印,否則重復第⑴步驟;
③如果不合理,則換一個方向試探,如果四個方向都已試過,就退回一步(回溯),用未試過的方向繼續試探。重復步驟⑴;
④如果已退回到原點,則程序結束。
Pascal程序:
Program Exam66;
Const xx: array[1..4] of 1..2 =(1,2,2,1);
yy: array[1..4] of -2..2=(2,1,-1,-2);
Var p: integer;
t, tt : array[0..10] of integer;
procere Prn(k: integer);
Var i: integer;
Begin
inc(p); write(『< 『, p: 2, 』 > 『, 』 『:4, 』0,0』);
for i:=1 to k do
write(『— ( 『, tt[ I ], 』 , 』, t[ I ], 』)』 );
writeln
End;
Procere Sub(k: integer);
Var x1, y1, i: integer;
Begin
for I:=1 to 4 do
Begin
x1:=tt[k-1]+xx[ i ]; y1:=t[k-1]+yy[ i ];
if not( (x1 > 8) or (y1 < 0) or (y1 > 4) ) then
Begin
tt[k]:=x1; t[k]=y1;
if (y1=4) and (x1=8) then prn(k);
sub(k+1);
end;
end;
end;
Begin
p:=0; tt[0]:=0; t[0]:=0;
sub(1);
writeln( 『 From 0,0 to 8,4 All of the ways are 』, p);
readln
end.
例[6.7] 輸出自然數1到n所有不重復的排列,即n的全排列。
解:①在1~n間選擇一個數,只要這個數不重復,就選中放入a數組中;
②如果這個數巳被選中,就在d數組中作一個被選中的標記 (將數組元素置1 );
③如果所選中的數已被佔用(作了標記),就另選一個數進行試探;
④如果未作標記的數都已試探完畢,那就取消最後那個數的標記,退回一步,並取消這一步的選數標記,另換下一個數試探,轉步驟①;
⑤如果已退回到0,說明已試探全部數據,結束。
Pascal程序:
Program Exam67;
Var p,n: integer;
a,d: array[1..500] of integer;
Procere prn (t : integer);
Var i: integer;
Begin
write(『 < 『, p:3, 』 > 『, 』 『:10);
for I:=1 to t do
write(a[ I ]:4);
writeln;
end;
Procere pp(k: integer);
var x: integer;
begin
for x:=1 to n do
begin
a[k]:=x; d[x]:=1;
if k < n then pp(k+1)
else
begin
p:=p+1;
prn(k);
end;
end;
end;
Begin
write(『Input n=『); readln(n);
for p:=1 to n do d[p]=0;
p:=0;
pp(1);
writeln(『All of the ways are 『, p:6);
End.
例[6.8] 設有一個連接n個地點①—⑥的道路網,找出從起點①出發到過終點⑥的一切路徑,要求在每條路徑上任一地點最多隻能通過一次。
解:從①出發,下一點可到達②或③,可以分支。具體步驟為:
⑴假定從起點出發數起第k個點Path[k],如果該點是終點n就列印一條路徑;
⑵如果不是終點n,且前方點是未曾走過的點,則走到前方點,定(k+1)點為到達路徑,轉步驟⑴;
(3)如果前方點已走過,就選另一分支點;
(4)如果前方點已選完,就回溯一步,選另一分支點為出發點;
(5)如果已回溯到起點,則結束。
為了表示各點的連通關系,建立如下的關系矩陣:
第一行表示與①相通點有②③,0是結束 標志;以後各行依此類推。
集合b是為了檢查不重復點。
Program Exam68;
const n=6;
roadnet: array[1..n, 1..n] of 0..n=( (2,3,0,0,0,0),
(1,3,4,0,0,0),
(1,2,4,5,0,0),
(2,3,5,6,0,0),
(3,4,6,0,0,0),
(4,5,0,0,0,0) );
var b: set of 1..n;
path: array[1..n] of 1..n;
p: byte;
procere prn(k: byte);
var i: byte;
begin
inc(p); write(』<』, p:2, 』>』, 』 』:4);
write (path[1]:2);
for I:=2 to k do
write (』--』, path[ i ]:2);
writeln
end;
procere try(k: byte);
var j: byte;
begin
1 2 3 4 5
6 X 8 9 10
11 12 13 14 15
j:=1;
repeat
path[k]:=roadnet [path [k-1], j ];
if not (path [k] in b) then
begin b:=b+[path [k] ];
if path [k]=n then prn (k)
else try(k+1);
b:=b-[path [k] ];
end;
inc(j);
until roadnet [path [k-1], j ]=0
end;
begin
b:=[1]; p=0; path[1]:=1;
try(2);
readln
end.
習題[6.2]
1. 有A,B,C,D,E五本書,要分給張、王、劉、趙、錢五位同學,每人只能選一本。事先讓每個人將自己喜愛的書填寫在下表中。希望你設計一個程序,列印分書的所有可能方案,當然是讓每個人都能滿意。
A B C D E
張 Y Y
王 Y Y Y
劉 Y Y
趙 Y
錢 Y Y
2. 右下圖所示的是空心框架,它是由六個單位正方體組成,問:從框架左下外頂點走到右上內頂點共有多少條最短路線?
3.城市的街道示意圖如右:問從甲地去到乙地可以有多少條最短路線?
4.有M×N張(M行, N列)郵票連在一起,
但其中第X張被一個調皮的小朋友控掉了。上圖是3×5的郵票的形狀和編號。從這些郵票中撕出四張連在一起的郵票,問共有多少種這樣四張一組的郵票?注:因為給郵票編了序號,所以1234和2345應該看作是不同的兩組。
5.有分數12 ,13 ,14 ,15 ,16 ,18 ,110 ,112 ,115 , 求將其中若干個相加的和恰好為1的組成方案,並列印成等式。例如:
<1> 12 +13 +16 = 1
<2> ...
6.八皇後問題。在8*8的國際象棋盤上擺上8個皇後。要求每行,每列,各對角線上的皇後都不能互相攻擊,給出所可能的擺法。
J. 常用的演算法表示形式有哪些
演算法的常用表示方法有三種:
1、使用自然語言描述演算法;
2、使用流程鏈派圖描述演算法;
3、使用偽代碼描述演算法。
演算法是指對解決方案的准確、完整的描述,是解決問題的一系列清晰的指令。該演算法代表了描述解決問題的策略和機制的系統方式。也就是說,對於某個標准輸入,可以在有限的時間內獲得所需的輸出。
如果一個演算法有缺陷或不適合某個問題,執行該演算法將無法解決該問題。不同的演算法可能使用棚做賀不同的時間、空間或效率來完成相同的胡跡任務。一個演算法的優劣可以用空間復雜度和時間復雜度來衡量。