枚舉演算法對象
1. C語言的32個關鍵字是什麼意思啊
1. 數據類型關鍵字(12個):
(1). char :聲明字元型變數或函數
(2). double :聲明雙精度變數或函數
(3). enum :聲明枚舉類型
(4). float:聲明浮點型變數或函數
(5). int: 聲明整型變數或函數
(6). long :聲明長整型變數或函數
(7). short :聲明短整型變數或函數
(8). signed:聲明有符號類型變數或函數
(9). struct:聲明結構體變數或函數
(10). union:聲明聯合數據類型
(11). unsigned:聲明無符號類型變數或函數
(12). void :聲明函數無返回值或無參數,聲明無類型指針(基本上就這三個作用)
(2)控制語句關鍵字(12個):
A.循環語句
(1). for: 一種循環語句(可意會不可言傳)
(2). do : 循環語句的循環體
(3). while :循環語句的循環條件
(4). break:跳出當前循環
(5). continue:結束當前循環,開始下一輪循環
B.條件語句
(1).if: 條件語句
(2).else :條件語句否定分支(與 if 連用)
(3).goto:無條件跳轉語句
C.開關語句
(1).switch :用於開關語句
(2).case:開關語句分支
(3).default:開關語句中的「其他」分支
D.
return :子程序返回語句(可以帶參數,也看不帶參數)
3. 存儲類型關鍵字(4個):
(1).auto :聲明自動變數 一般不使用
(2).extern:聲明變數是在其他文件正聲明(也可以看做是引用變數)
(3).register:聲明積存器變數
(4). static :聲明靜態變數
4. 其它關鍵字(4個):
(1).const :聲明只讀變數
(2).sizeof:計算數據類型長度
(3).typedef:用以給數據類型取別名(當然還有其他作用)
(4).volatile:說明變數在程序執行中可被隱含地改變
1、演算法
程序處理數據的流程被稱為演算法,演算法可以用言語描述,也可以用流程圖描述.
2、程序
不管用什麼語言來表達的對問題的描述,通常都稱為程序。
3、數據結構
數據結構是計算機存儲、組織數據的方式。通常情況下,精心選擇的數據結構可以帶來更高的運行或者存儲效率的演算法。數據結構往往同高效的檢索演算法和索引技術有關。
4、數據類型
數據類型是指數據的內在表現形式。通俗地說,我們把數據加工計算中的特徵稱為數據類型。
5、字元串
字元串或串(String)是由零個或多個字元組成的有限序列。一般記為 s='a1a2•••an'(n>=0)。它是編程語言中表示文本的數據類型。
通常以串的整體作為操作對象,如:在串中查找某個子串、求取一個子串、在串的某個位置上插入一個子串以及刪除一個子串等。兩個字元串相等的充要條件是:長度相等,並且各個對應位置上的字元都相等。設p、q是兩個串,求q在p中首次出現的位置的運算叫做模式匹配。串的兩種最基本的存儲方式是順序存儲方式和鏈接存儲方式。
6、表達式
用運算符和括弧將運算對象(常量、變數和函數等)連接起來的、符合C語言語法規則的式子,稱為表達式。
7、主調函數
在函數中,可以使用函數調用語句,來調用其他函數(稱為被調用函數),這時,就把控制轉移到被調用函數,而在被調用函數執行完畢或執行到語句return時,又把控制轉向原來的函數(稱為主調函數).
8、被調函數
9、變數
在程序執行過程中,其值不發生改變的量稱為常量,取值可變的量稱為變數。
C語言程序設計與應用開發
一.C語言的運算符和運算順序
二.C語言的四種程序結構
(1)順序結構 (2) 分支結構(3)循環結構(4)模塊化程序結構
三.掌握一些簡單的演算法
1 數據類型關鍵字
A基本數據類型(5個)
void :聲明函數無返回值或無參數,聲明無類型指針,顯式丟棄運算結果
char :字元型類型數據,屬於整型數據的一種
int :整型數據,通常為編譯器指定的機器字長
float :單精度浮點型數據,屬於浮點數據的一種
double :雙精度浮點型數據,屬於浮點數據的一種
B 類型修飾關鍵字(4個)
short :修飾int,短整型數據,可省略被修飾的int。
long :修飾int,長整形數據,可省略被修飾的int。
signed :修飾整型數據,有符號數據類型
unsigned :修飾整型數據,無符號數據類型
C 復雜類型關鍵字(5個)
struct :結構體聲明
union :共用體聲明
enum :枚舉聲明
typedef :聲明類型別名
sizeof :得到特定類型或特定類型變數的大小
D 存儲級別關鍵字(6個)
auto :指定為自動變數,由編譯器自動分配及釋放。通常在棧上分配
static :指定為靜態變數,分配在靜態變數區,修飾函數時,指定函數作用域為文件內部
register :指定為寄存器變數,建議編譯器將變數存儲到寄存器中使用,也可以修飾函數形參,建議編譯器通過寄存器而不是堆棧傳遞參數
extern :指定對應變數為外部變數,即在另外的目標文件中定義,可以認為是約定由另外文件聲明的對象的一個「引用」
const :與volatile合稱「cv特性」,指定變數不可被當前線程/進程改變(但有可能被系統或其他線程/進程改變)
volatile :與const合稱「cv特性」,指定變數的值有可能會被系統或其他進程/線程改變,強制編譯器每次從內存中取得該變數的值
2 流程式控制制關鍵字
A 跳轉結構(4個)
return :用在函數體中,返回特定值(或者是void值,即不返回值)
continue :結束當前循環,開始下一輪循環
break :跳出當前循環或switch結構
goto :無條件跳轉語句
B 分支結構(5個)
if :條件語句
else :條件語句否定分支(與if連用)
switch :開關語句(多重分支語句)
case :開關語句中的分支標記
default :開關語句中的「其他」分治,可選。
C 循環結構(3個)
for :for循環結構,for(1;2;3)4;的執行順序為1->2->4->3->2...循環,其中2為循環條件
do :do循環結構,do 1 while(2); 的執行順序是 1->2->1...循環,2為循環條件
while :while循環結構,while(1) 2; 的執行順序是1->2->1...循環,1為循環條件
以上循環語句,當循環條件表達式為真則繼續循環,為假則跳出循環。
2. java怎麼在TreeMap裡面放枚舉
一個key可以有一個或多個values??誰告訴你的?你在哪看到的???=============你的英文不太好啊=============-mapcannotcontainplicatekeys,不能包含重復的key,但是可以包含同樣的值。例如姓名:張三姓名:李四錯誤。key重復----------------------------姓名:張三備註:張三正確。value是可以重復的。===================================-eachkeymapstoatmostonevalue每個key最多(atmost)對應一個value===============================-many-to-1typeofobject多對一類型的對象如第一條不能姓名對多個(1對多)但是可以多對一。比如姓名,備注等的值都為張三=================================就拿你的星座和星星舉例吧。水瓶座:星星1水瓶座:星星2錯誤。-----------------你只能星星1:水瓶座星星2:水瓶座。懂了嗎?============另外,歡迎來網路貼吧javase吧來提問
3. 初等數論的初等數論內容
初等數論有以下幾部分內容:
1.整除理論。引入整除、因數、倍數、質數與合數等基本概念。這一理論的主要成果有:唯一分解定理、裴蜀定理、歐幾里德的輾轉相除法、算術基本定理、素數個數無限證明。
2.同餘理論。主要出自於高斯的《算術研究》內容。定義了同餘、原根、指數、平方剩餘、同餘方程等概念。主要成果:二次互反律、歐拉定理、費馬小定理、威爾遜定理、孫子定理(即中國剩餘定理)等等。
3.連分數理論。引入了連分數概念和演算法等等。特別是研究了整數平方根的連分數展開。主要成果:循環連分數展開、最佳逼近問題、佩爾方程求解。
4.不定方程。主要研究了低次代數曲線對應的不定方程,比如勾股方程的商高定理、佩爾方程的連分數求解。也包括了四次費馬方程的求解問題等等。
5.數論函數。比如歐拉函數、莫比烏斯變換等等。
6.高斯函數。 第一個層次叫做數學概念,是反映對象的本質屬性的思維形式。人類在認識過程中,從感性認識上升到理性認識,把所感知的事物的共同本質特點抽象出來,加以概括,就成為概念。表達概念的語言形式是詞或片語。科學概念,特別是數學概念要求更加嚴格,至少必須具備三個條件:專一性,精確性,可以檢驗。例如:」孿生素數「就是一個數學概念。
第二個層次叫做數學命題,數學命題是對一系列數學概念之間的關系作出判斷的句子。一個命題要麼真,要麼不真(這由邏輯中的排中律保證)。真命題包含定理,引理,推論,事實等。命題既可以是存在性命題(表述為」存在......."),也可以是全稱命題(表述為「對於一切.....")。第三個層次叫做數學理論,把方法,公式,公理,定理,原理,組合成為一個體系叫做數學理論。例如「初等數論」,由公理(例如等量公理),定理(例如費馬小定理),原理(例如抽屜原理,一一對應原理),公式等組成。在數學證明時,全稱命題常常不能通過枚舉法來判斷真偽,這是因為數學有時面對的是無窮多個對象,永遠不可能一一枚舉出每一種情況。不完全歸納法在數學中是不可行的,數學只承認演繹邏輯(數學歸納法,超限歸納法等均屬於演繹邏輯)。
4. 演算法是多餘的嗎
參考出處:http://blog.csdn.net/ctu_85/archive/2008/05/11/2432736.aspx
一、什麼是演算法
演算法是一系列解決問題的清晰指令,也就是說,能夠對一定規范的輸入,在有限時間內獲得所要求的輸出。演算法常常含有重復的步驟和一些比較或邏輯判斷。如果一個演算法有缺陷,或不適合於某個問題,執行這個演算法將不會解決這個問題。不同的演算法可能用不同的時間、空間或效率來完成同樣的任務。一個演算法的優劣可以用空間復雜度與時間復雜度來衡量。
演算法的時間復雜度是指演算法需要消耗的時間資源。一般來說,計算機演算法是問題規模n 的函數f(n),演算法執行的時間的增長率與f(n) 的增長率正相關,稱作漸進時間復雜度(Asymptotic Time Complexity)。時間復雜度用「O(數量級)」來表示,稱為「階」。常見的時間復雜度有: O(1)常數階;O(log2n)對數階;O(n)線性階;O(n2)平方階。
演算法的空間復雜度是指演算法需要消耗的空間資源。其計算和表示方法與時間復雜度類似,一般都用復雜度的漸近性來表示。同時間復雜度相比,空間復雜度的分析要簡單得多。
[font class="Apple-style-span" style="font-weight: bold;" id="bks_etfhxykd"]演算法 Algorithm [/font]
演算法是在有限步驟內求解某一問題所使用的一組定義明確的規則。通俗點說,就是計算機解題的過程。在這個過程中,無論是形成解題思路還是編寫程序,都是在實施某種演算法。前者是推理實現的演算法,後者是操作實現的演算法。
一個演算法應該具有以下五個重要的特徵:
1、有窮性: 一個演算法必須保證執行有限步之後結束;
2、確切性: 演算法的每一步驟必須有確切的定義;
3、輸入:一個演算法有0個或多個輸入,以刻畫運算對象的初始情況,所謂0個輸入是指演算法本身定除了初始條件;
4、輸出:一個演算法有一個或多個輸出,以反映對輸入數據加工後的結果。沒有輸出的演算法是毫無意義的;
5、可行性: 演算法原則上能夠精確地運行,而且人們用筆和紙做有限次運算後即可完成。
演算法的設計要求
1)正確性(Correctness)
有4個層次:
A.程序不含語法錯誤;
B.程序對幾組輸入數據能夠得出滿足規格要求的結果;
C.程序對精心選擇的、典型的、苛刻的、帶有刁難性的幾組輸入數據能夠得出滿足規格要求的結果;
D.程序對一切合法的輸入數據都能產生滿足規格要求的結果。
2)可讀性(Readability)
演算法的第一目的是為了閱讀和交流;
可讀性有助於對演算法的理解;
可讀性有助於對演算法的調試和修改。
3)高效率與低存儲量
處理速度快;存儲容量小
時間和空間是矛盾的、實際問題的求解往往是求得時間和空間的統一、折中。
演算法的描述 演算法的描述方式(常用的)
演算法描述 自然語言
流程圖 特定的表示演算法的圖形符號
偽語言 包括程序設計語言的三大基本結構及自然語言的一種語言
類語言 類似高級語言的語言,例如,類PASCAL、類C語言。
演算法的評價 演算法評價的標准:時間復雜度和空間復雜度。
1)時間復雜度 指在計算機上運行該演算法所花費的時間。用「O(數量級)」來表示,稱為「階」。
常見的時間復雜度有: O(1)常數階;O(logn)對數階;O(n)線性階;O(n^2)平方階
2)空間復雜度 指演算法在計算機上運行所佔用的存儲空間。度量同時間復雜度。
時間復雜度舉例
(a) X:=X+1 ; O(1)
(b) FOR I:=1 TO n DO
X:= X+1; O(n)
(c) FOR I:= 1 TO n DO
FOR J:= 1 TO n DO
X:= X+1; O(n^2)
「演算法」一詞最早來自公元 9世紀 波斯數學家比阿勒·霍瓦里松的一本影響深遠的著作《代數對話錄》。20世紀的 英國 數學家 圖靈 提出了著名的圖靈論點,並抽象出了一台機器,這台機器被我們稱之為 圖靈機 。圖靈的思想對演算法的發展起到了重要的作用。
演算法是 計算機 處理信息的本質,因為 計算機程序 本質上是一個演算法,告訴計算機確切的步驟來執行一個指定的任務,如計算職工的薪水或列印學生的成績單。 一般地,當演算法在處理信息時,數據會從輸入設備讀取,寫入輸出設備,可能保存起來以供以後使用。
這是演算法的一個簡單的例子。
我們有一串隨機數列。我們的目的是找到這個數列中最大的數。如果將數列中的每一個數字看成是一顆豆子的大小 可以將下面的演算法形象地稱為「撿豆子」:
首先將第一顆豆子(數列中的第一個數字)放入口袋中。
從第二顆豆子開始檢查,直到最後一顆豆子。如果正在檢查的豆子比口袋中的還大,則將它撿起放入口袋中,同時丟掉原先的豆子。 最後口袋中的豆子就是所有的豆子中最大的一顆。
下面是一個形式演算法,用近似於 編程語言 的 偽代碼 表示
給定:一個數列「list",以及數列的長度"length(list)" largest = list[1] for counter = 2 to length(list): if list[counter] > largest: largest = list[counter] print largest
符號說明:
= 用於表示賦值。即:右邊的值被賦予給左邊的變數。
List[counter] 用於表示數列中的第 counter 項。例如:如果 counter 的值是5,那麼 List[counter] 表示數列中的第5項。
<= 用於表示「小於或等於」。
二、演算法設計的方法
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));
}
5. 運算符重載的參數為什麼必須要類
重載的含義是重新定義其功能,我們之所以可以直接使用運算符用於算術運算,是因為編譯器內定了演算法。但是當一個類的兩個對象中的成員進行運算就必須寫多條語句,而且大部分情況還得定義中間變數,這樣代碼量就增加了,可讀性降低,如果寫成運算符直接對兩個對象進行運算,其效果等效於兩個對象的成員運算就很方便了(比如obj1=obj1+obj2,或者obj1+=obj2,等效於obj1.value=obj1.value+obj2.value),是不是?
問題是編譯器只支持變數和常量運算可以直接用運算符運算,即便涉及到指針,也是普通變數指針而不是對象指針,編譯因不支持對象運算而報錯。為了讓編譯器在遇到對象運算時能按我們要求的進行運算,這就是運算符重載的意義,即重定義運算符,因此你可以看到,運算符重載就是為類對象服務的,那麼兩個對象的成員進行運算那必須先獲得對象本身啦,所以運算符重載參數必須含有類指針或引用,這是主要客戶。
6. 什麼是枚舉法
枚舉法是利用計算機運算速度快、精確度高的特點,對要解決問題的所有可能情況,一個不漏地進行檢驗,從中找出符合要求的答案,因此枚舉法是通過犧牲時間來換取答案的全面性。在數學和計算機科學理論中,一個集的枚舉是列出某些有窮序列集的所有成員的程序,或者是一種特定類型對象的計數。這兩種類型經常(但不總是)重疊。
基本信息
中文名:枚舉法
外文名:Enumeration method
定義:逐個考察了某類事件的所有可能
藉助:計算機運算速度快精確度高特點
結構:while循環
演算法:二進制加法,此時需要數組來幫忙
簡介
枚舉法是利用計算機運算速度快、精確度高的特點,對要解決問題的所有可能情況,一個不漏地進行檢驗,從中找出符合要求的答案,因此枚舉法是通過犧牲時間來換取答案的全面性。
在 數學和 計算機科學理論中,一個集的 枚舉是列出某些有窮序列集的所有成員的 程序,或者是一種特定類型對象的 計數。這兩種類型經常(但不總是)重疊。
特點
將問題的所有可能的答案一一 列舉,然後根據條件判斷此答案是否合適,合適就保留,不合適就丟棄。例如:找出1到100之間的素數,需要將1到100之間的所有 整數進行判斷。
枚舉演算法因為要列舉問題的所有可能的答案,所有它具備以下幾個特點:
1、得到的結果肯定是正確的;
2、可能做了很多的無用功,浪費了寶貴的時間,效率低下。
3、通常會涉及到求 極值(如最大,最小,最重等)。
4、數據量大的話,可能會造成時間崩潰。
結構
枚舉演算法的一般結構:while 循環。
首先考慮一個問題:將1到100之間的所有整數轉換為 二進制數表示。
演算法一
for i:=1 to 100 do begin
將i轉換為 二進制,採用不斷除以2, 余數即為轉換為2進制以後的結果。一直除商為0為止。
end;
演算法二
二進制加法,此時需要數組來幫忙。
program p;
var a:array[1..100] of integer; {用於保存轉換後的二進制結果}
i,j,k:integer;
begin
fillchar(a,sizeof(a),0); {100個數組元素全部初始化為0}
for i:=1 to 100 do begin
k:=100;
while a[k]=1 do dec(k); {找高位第一個為0的位置}
a[k]:=1; {找到了立刻賦值為1}
for j:=k+1 to 100 do a[j]:=0; {它後面的低位全部賦值為0}
k:=1;
while a[k]=0 do inc(k); {從最高位開始找不為0的位置}
write('(',i,')2=');
for j:=k to 100 do write(a[j]); {輸出轉換以後的結果}
writeln;
end;
end.
枚舉法,常常稱之為 窮舉法,是指從可能的集合中一一枚舉各個元素,用題目給定的約束條件判定哪些是無用的,哪些是有用的。能使命題成立者,即為問題的解。
7. Python中字典的內建函數用法是什麼
字典內置函數&方法
Python字典包含了以下內置函數:
1 cmp(dict1, dict2)
比較兩個字典元素。
2 len(dict)
計算字典元素個數,即鍵的總數。
3 str(dict)
輸出字典可列印的字元串表示。
4 type(variable)
返回輸入的變數類型,如果變數是字典就返回字典類型。
8. List 、Set、 Map有什麼區別和聯系
1、List介面對Collection進行了簡單的擴充,它的具體實現類常用的有ArrayList和LinkedList。
你可以將任何東西放到一個List容器中,並在需要時從中取出。ArrayList從其命名中可以看出它是一種類似數組的形式進行存儲,因此它的隨機訪問速度極快,而LinkedList的內部實現是鏈表,它適合於在鏈表中間需要頻繁進行插入和刪除操作。在具體應用時可以根據需要自由選擇。
前面說的Iterator只能對容器進行向前遍歷,而ListIterator則繼承了Iterator的思想,並提供了對List進行雙向遍歷的方法。
2、Set介面也是Collection的一種擴展,而與List不同的時,在Set中的對象元素不能重復,也就是說你不能把同樣的東西兩次放入同一個Set容器中。它的常用具體實現有HashSet和TreeSet類。
HashSet能快速定位一個元素,但是你放到HashSet中的對象需要實現hashCode()方法,它使用了前面說過的哈希碼的演算法。而TreeSet則將放入其中的元素按序存放,這就要求你放入其中的對象是可排序的,這就用到了集合框架提供的另外兩個實用類Comparable和Comparator。
一個類是可排序的,它就應該實現Comparable介面。有時多個類具有相同的排序演算法,那就不需要在每分別重復定義相同的排序演算法,只要實現Comparator介面即可。
集合框架中還有兩個很實用的公用類:Collections和Arrays。Collections提供了對一個Collection容器進行諸如排序、復制、查找和填充等一些非常有用的方法,Arrays則是對一個數組進行類似的操作。
3、Map是一種把鍵對象和值對象進行關聯的容器,而一個值對象又可以是一個Map,依次類推,這樣就可形成一個多級映射。
對於鍵對象來說,像Set一樣,一個Map容器中的鍵對象不允許重復,這是為了保持查找結果的一致性;如果有兩個鍵對象一樣,那你想得到那個鍵對象所對應的值對象時就有問題了,可能你得到的並不是你想的那個值對象,結果會造成混亂,所以鍵的唯一性很重要,也是符合集合的性質的。
當然在使用過程中,某個鍵所對應的值對象可能會發生變化,這時會按照最後一次修改的值對象與鍵對應。對於值對象則沒有唯一性的要求。你可以將任意多個鍵都映射到一個值對象上,這不會發生任何問題(不過對你的使用卻可能會造成不便,你不知道你得到的到底是那一個鍵所對應的值對象)。
Map有兩種比較常用的實現:HashMap和TreeMap。HashMap也用到了哈希碼的演算法,以便快速查找一個鍵,TreeMap則是對鍵按序存放,因此它便有一些擴展的方法,比如firstKey(),lastKey()等,你還可以從TreeMap中指定一個范圍以取得其子Map。
鍵和值的關聯很簡單,用pub(Object key,Object value)方法即可將一個鍵與一個值對象相關聯。用get(Object key)可得到與此key對象所對應的值對象
(8)枚舉演算法對象擴展閱讀:
解疑:
1、什麼是Iterator
一些集合類提供了內容遍歷的功能,通過java.util.Iterator介面。這些介面允許遍歷對象的集合。依次操作每個元素對象。當使用 Iterators時,在獲得Iterator的時候包含一個集合快照。通常在遍歷一個Iterator的時候不建議修改集合本省。
2、Iterator與ListIterator有什麼區別?
Iterator:只能正向遍歷集合,適用於獲取移除元素。ListIerator:繼承Iterator,可以雙向列表的遍歷,同樣支持元素的修改。
3、什麼是HaspMap和Map?
Map是介面,Java 集合框架中一部分,用於存儲鍵值對,HashMap是用哈希演算法實現Map的類。
4、HashMap與HashTable有什麼區別?對比Hashtable VS HashMap
兩者都是用key-value方式獲取數據。Hashtable是原始集合類之一(也稱作遺留類)。HashMap作為新集合框架的一部分在Java2的1.2版本中加入。它們之間有一下區別:
● HashMap和Hashtable大致是等同的,除了非同步和空值(HashMap允許null值作為key和value,而Hashtable不可以)。
● HashMap沒法保證映射的順序一直不變,但是作為HashMap的子類LinkedHashMap,如果想要預知的順序迭代(默認按照插入順序),你可以很輕易的置換為HashMap,如果使用Hashtable就沒那麼容易了。
● HashMap不是同步的,而Hashtable是同步的。
● 迭代HashMap採用快速失敗機制,而Hashtable不是,所以這是設計的考慮點。
5、在Hashtable上下文中同步是什麼意思?
同步意味著在一個時間點只能有一個線程可以修改哈希表,任何線程在執行hashtable的更新操作前需要獲取對象鎖,其他線程等待鎖的釋放。
6、什麼叫做快速失敗特性
從高級別層次來說快速失敗是一個系統或軟體對於其故障做出的響應。一個快速失敗系統設計用來即時報告可能會導致失敗的任何故障情況,它通常用來停止正常的操作而不是嘗試繼續做可能有缺陷的工作。當有問題發生時,快速失敗系統即時可見地發錯錯誤告警。
在Java中,快速失敗與iterators有關。如果一個iterator在集合對象上創建了,其它線程欲「結構化」的修改該集合對象,並發修改異常 () 拋出。
7、怎樣使Hashmap同步?
HashMap可以通過Map m = Collections.synchronizedMap(hashMap)來達到同步的效果。
8、什麼時候使用Hashtable,什麼時候使用HashMap
基本的不同點是Hashtable同步HashMap不是的,所以無論什麼時候有多個線程訪問相同實例的可能時,就應該使用Hashtable,反之使用HashMap。非線程安全的數據結構能帶來更好的性能。
如果在將來有一種可能—你需要按順序獲得鍵值對的方案時,HashMap是一個很好的選擇,因為有HashMap的一個子類 LinkedHashMap。所以如果你想可預測的按順序迭代(默認按插入的順序),你可以很方便用LinkedHashMap替換HashMap。
反觀要是使用的Hashtable就沒那麼簡單了。同時如果有多個線程訪問HashMap,Collections.synchronizedMap()可以代替,總的來說HashMap更靈活。
9、為什麼Vector類認為是廢棄的或者是非官方地不推薦使用?或者說為什麼我們應該一直使用ArrayList而不是Vector
你應該使用ArrayList而不是Vector是因為默認情況下你是非同步訪問的,Vector同步了每個方法,你幾乎從不要那樣做,通常有想要同步的是整個操作序列。同步單個的操作也不安全(如果你迭代一個Vector,你還是要加鎖,以避免其它線程在同一時刻改變集合)。
而且效率更慢。當然同樣有鎖的開銷即使你不需要,這是個很糟糕的方法在默認情況下同步訪問。你可以一直使用Collections.sychronizedList來裝飾一個集合。
事實上Vector結合了「可變數組」的集合和同步每個操作的實現。這是另外一個設計上的缺陷。Vector還有些遺留的方法在枚舉和元素獲取的方法,這些方法不同於List介面,如果這些方法在代碼中程序員更趨向於想用它。
盡管枚舉速度更快,但是他們不能檢查如果集合在迭代的時候修改了,這樣將導致問題。盡管以上諸多原因,Oracle也從沒宣稱過要廢棄Vector。
9. 運用C++語言描述實際的類
【內容簡介】
數據結構歷來都是計算機專業最為核心的一門課程。隨著面向對象技術的發展,傳統的數據結構課程面臨著融入新內容,提升到面向對象數據結構、演算法及軟體工程的高度的重大挑戰。
本書開拓性地將C++語言作為數據結構的演算法描述性語言。一方面為傳統的數據結構內容進行了C++語言實現,另一方面更偏重於將數據結構與面向對象技術完整結合,圍繞抽象數據類型的概念來討論每一種數據結構及演算法。書中大量C++語言的程序實例,既是數據結構的具體實現,又是面向對象技術的演算法基礎。
本書可作為計算機及相關專業的核心教材,也可供廣大研究開發人員自學提高時使用,是一本全新的數據結構與面向對象技術完整結合的新型教材。
【目錄信息】
第1章 概述
1.1 抽象數據類型
1.2 C++類和抽象數據類型
1.3 C++應用中的對象
1.4 對象設計
1.5 類繼承的應用
1.6 面向對象程序設計
1.7 程序測試與維護
1.8 C++程序設計語言
1.9 抽象基類及多態性
書面作業
第2章 基本數據類型
2.1 整型
2.2 字元類型
2.3 實數類型
2.4 枚舉類型
2.5 指針
2.6 數組類型
<< 查看詳細目錄
【前言】
本書從面向對象算機科學專業的一門數據結構的演算法。
本書使用通用的C++語言作為演算法描述語言。它的類和面向對象結地實現數據結構的演算法。雖然目前有多種面向對象的語言,但由於C++起行的C語言,因而在這些語言中佔有優勢。抽象數據類型(ADT)定義了數據
處理運算,本書將圍繞這一概念來討論每一種數據結構,並採用C++語言中ADT,在對象中有效地使用這些結構。
本書結構
《數據結構C++語言描述》圍繞多數據集類——表、樹、集合、圖和字典來組織數據結構的學習。本書包括數據結構基本內容和面向對象程序設計方法兩部分,給出了許多完整程序或程序段例子,並引入了描述演算法復雜度的..
請樓主採納為答案~!!!!!!!!!!!!!!!
10. 數據結構
何謂數據結構
?
數據結構是在整個計算機科學與技術領域上廣泛被使用的術語。它用來反映一個數據的內部構成,即一個數據由那些成分數據構成,以什麼方式構成,呈什麼結構。數據結構有邏輯上的數據結構和物理上的數據結構之分。邏輯上的數據結構反映成分數據之間的邏輯關系,而物理上的數據結構反映成分數據在計算機內部的存儲安排。數據結構是數據存在的形式。 數據結構是信息的一種組織方式,其目的是為了提高演算法的效率,它通常與一組演算法的集合相對應,通過這組演算法集合可以對數據結構中的數據進行某種操作。
?
數據結構主要研究什麼?
?
數據結構作為一門學科主要研究數據的各種邏輯結構和存儲結構,以及對數據的各種操作。因此,主要有三個方面的內容:數據的邏輯結構;數據的物理存儲結構;對數據的操作(或演算法)。通常,演算法的
?
設計取決於數據的邏輯結構,演算法的實現取決於數據的物理存儲結構。
?
什麼是數據結構?什麼是邏輯結構和物理結構?
?
數據是指由有限的符號(比如,"0"和"1",具有其自己的結構、操作、和相應的語義)組成的元素的集合。結構是元素之間的關系的集合。通常來說,一個數據結構DS 可以表示為一個二元組:
?
DS=(D,S), //i.e., data-structure=(data-part,logic-structure-part) 這里D是數據元素的集合(或者是「結點」,可能還含有「數據項」或「數據域」),S是定義在D(或其他集合)上的關系的集合,S = { R | R : D×D×...},稱之為元素的邏輯結構。 邏輯結構有四種基本類型:集合結構、線性結構、樹狀結構和網路結構。表和樹是最常用的兩種高效數據結構,許多高效的演算法可以用這兩種數據結構來設計實現。表是線性結構的(全序關系),樹(偏序或層次關系)和圖(局部有序(weak/local orders))是非線性結構。
?
數據結構的物理結構是指邏輯結構的存儲鏡像(image)。數據結構 DS 的物理結構 P對應於從 DS 的數據元素到存儲區M(維護著邏輯結構S)的一個映射:
?
(PD,S) -- > M 存儲器模型:一個存儲器 M 是一系列固定大小的存儲單元,每個單元 U 有一個唯一的地址 A(U),該地址被連續地編碼。每個單元 U 有一個唯一的後繼單元 U'=succ(U)。 P 的四種基本映射模型:順序(sequential)、鏈接(linked)、索引(indexed)和散列(hashing)映射。
?
因此,我們至少可以得到4×4種可能的物理數據結構:
?
sequential (sets)
linked lists
indexed trees
hash graphs
?
(並不是所有的可能組合都合理)
?
??? 數據結構DS上的操作:所有的定義在DS上的操作在改變數據元素(節點)或節點的域時必須保持DS的邏輯和物理結構。
?
DS上的基本操作:任何其他對DS的高級操作都可以用這些基本操作來實現。最好將DS和他的所有基本操作看作一個整體——稱之為模塊。我們可以進一步將該模塊抽象為數據類型(其中DS的存儲結構被表示為私有成員,基本操作被表示為公共方法),稱之為ADT。作為ADT,堆棧和隊列都是一種特殊的表,他們擁有表的操作的子集。 對於DATs的高級操作可以被設計為(不封裝的)演算法,利用基本操作對DS進行處理。
?
好的和壞的DS:如果一個DS可以通過某種「線性規則」被轉化為線性的DS(例如線性表),則稱它為好的DS。好的DS通常對應於好的(高效的)演算法。這是由計算機的計算能力決定的,因為計算機本質上只能存取邏輯連續的內存單元,因此如何沒有線性化的結構邏輯上是不可計算的。比如對一個圖進行操作,要訪問圖的所有結點,則必須按照某種順序來依次訪問所有節點(要形成一個偏序),必須通過某種方式將圖固有的非線性結構轉化為線性結構才能對圖進行操作。
?
樹是好的DS——它有非常簡單而高效的線性化規則,因此可以利用樹設計出許多非常高效的演算法。樹的實現和使用都很簡單,但可以解決大量特殊的復雜問題,因此樹是實際編程中最重要和最有用的一種數據結構。樹的結構本質上有遞歸的性質——每一個葉節點可以被一棵子樹所替代,反之亦然。實際上,每一種遞歸的結構都可以被轉化為(或等價於)樹形結構。
?
從機器語言到高級語言的抽象
?
我們知道,演算法被定義為一個運算序列。這個運算序列中的所有運算定義在一類特定的數據模型上,並以解決一類特定問題為目標。這個運算序列應該具備下列四個特徵。 有限性,即序列的項數有限,且每一運算項都可在有限的時間內完成;確定性,即序列的每一項運算都有明確的定義,無二義性;可以沒有輸入運算項,但一定要有輸出運算項;可行性,即對於任意給定的合法的輸入都能得到相應的正確的輸出。這些特徵可以用來判別一個確定的運算序列是否稱得上是一個演算法。 但是,我們現在的問題不是要判別一個確定的運算序列是否稱得上是一個演算法,而是要對一個己經稱得上是演算法的運算序列,回顧我們曾經如何用程序設計語言去表達它。
?
演算法的程序表達,歸根到底是演算法要素的程序表達,因為一旦演算法的每一項要素都用程序清楚地表達,整個演算法的程序表達也就不成問題。
?
作為運算序列的演算法,有三個要素。 作為運算序列中各種運算的運算對象和運算結果的數據;運算序列中的各種運算;運算序列中的控制轉移。這三種要素依序分別簡稱為數據、運算和控制。 由於演算法層出不窮,變化萬千,其中的運算所作用的對象數據和所得到的結果數據名目繁多,不勝枚舉。最簡單最基本的有布爾值數據、字元數據、整數和實數數據等;稍復雜的有向量、矩陣、記錄等數據;更復雜的有集合、樹和圖,還有聲音、圖形、圖像等數據。 同樣由於演算法層出不窮,變化萬千,其中運算的種類五花八門、多姿多彩。最基本最初等的有賦值運算、算術運算、邏輯運算和關系運算等;稍復雜的有算術表達式和邏輯表達式等;更復雜的有函數值計算、向量運算、矩陣運算、集合運算,以及表、棧、隊列、樹和圖上的運算等:此外,還可能有以上列舉的運算的復合和嵌套。 關於控制轉移,相對單純。在串列計算中,它只有順序、分支、循環、遞歸和無條件轉移等幾種。
?
我們來回顧一下,自從計算機問世以來,演算法的上述三要素的程序表達,經歷過一個怎樣的過程。
?
最早的程序設計語言是機器語言,即具體的計算機上的一個指令集。當時,要在計算機上運行的所有演算法都必須直接用機器語言來表達,計算機才能接受。演算法的運算序列包括運算對象和運算結果都必須轉換為指令序列。其中的每一條指令都以編碼(指令碼和地址碼)的形式出現。與演算法語言表達的演算法,相差十萬八千里。對於沒受過程序設計專門訓練的人來說,一份程序恰似一份"天書",讓人看了不知所雲,可讀性
?
極差。
?
用機器語言表達演算法的運算、數據和控制十分繁雜瑣碎,因為機器語言所提供的指令太初等、原始。機器語言只接受算術運算、按位邏輯運算和數的大小比較運算等。對於稍復雜的運算,都必須一一分解,直到到達最初等的運算才能用相應的指令替代之。機器語言能直接表達的數據只有最原始的位、位元組、和字三種。演算法中即使是最簡單的數據如布爾值、字元、整數、和實數,也必須一一地映射到位、位元組和字
中,還得一一分配它們的存儲單元。對於演算法中有結構的數據的表達則要麻煩得多。機器語言所提供的控制轉移指令也只有無條件轉移、條件轉移、進入子程序和從子程序返回等最基本的幾種。用它們來構造循環、形成分支、調用函數和過程得事先做許多的准備,還得靠許多的技巧。 直接用機器語言表達演算法有許多缺點。
?
大量繁雜瑣碎的細節牽制著程序員,使他們不可能有更多的時間和精力去從事創造性的勞動,執行對他們來說更為重要的任務。如確保程序的正確性、高效性。程序員既要駕馭程序設計的全局又要深入每一個局部直到實現的細節,即使智力超群的程序員也常常會顧此失彼,屢出差錯,因而所編出的程序可靠性差,且開發周期長。 由於用機器語言進行程序設計的思維和表達方式與人們的習慣大相徑庭,只有經過
較長時間職業訓練的程序員才能勝任,使得程序設計曲高和寡。因為它的書面形式全是"密"碼,所以可讀性差,不便於交流與合作。因為它嚴重地依賴於具體的計算機,所以可移植性差,重用性差。這些弊端造成當時的計算機應用未能迅速得到推廣。
?
克服上述缺點的出路在於程序設計語言的抽象,讓它盡可能地接近於演算法語言。 為此,人們首先注意到的是可讀性和可移植性,因為它們相對地容易通過抽象而得到改善。於是,很快就出現匯編語言。這種語言對機器語言的抽象,首先表現在將機器語言的每一條指令符號化:指令碼代之以記憶符號,地址碼代之以符號地址,使得其含義顯現在符號上而不再隱藏在編碼中,可讓人望"文"生義。其次表現在這種語言擺脫了具體計算機的限制,可在不同指令集的計算機上運行,只要該計算機配上匯編語言的一個匯編程序。這無疑是機器語言朝演算法語言靠攏邁出的一步。但是,它離演算法語言還太遠,以致程序員還不能從分解演算法的數據、運算和控制到匯編才能直接表達的指令等繁雜瑣碎的事務中解脫出來。 到了50年代中期,出現程序設計的高級語言如Fortran,Algol60,以及後來的PL/l, Pascal等,演算法的程序表達才產生一次大的飛躍。
?
誠然,演算法最終要表達為具體計算機上的機器語言才能在該計算機上運行,得到所需要的結果。但匯編語言的實踐啟發人們,表達成機器語言不必一步到位,可以分兩步走或者可以築橋過河。即先表達成一種中介語言,然後轉成機器語言。匯編語言作為一種中介語言,並沒有獲得很大成功,原因是它離演算法語
?
言還太遠。這便指引人們去設計一種盡量接近演算法語言的規范語言,即所謂的高級語言,讓程序員可以用它方便地表達演算法,然後藉助於規范的高級語言到規范的機器語言的"翻譯",最終將演算法表達為機器語言。而且,由於高級語言和機器語言都具有規范性,這里的"翻譯"完全可以機械化地由計算機來完成,就像匯編語言被翻譯成機器語言一樣,只要計算機配上一個編譯程序。 上述兩步,前一步由程序員去完成,後一步可以由編譯程序去完成。在規定清楚它們各自該做什麼之後,這兩步是完全獨立的。它們各自該如何做互不相干。前一步要做的只是用高級語言正確地表達給定的演算法,產生一個高級語言程序;後一步要做的只是將第一步得到的高級語言程序翻譯成機器語言程序。至於程序員如何用高級語言表達演算法和編譯程序如何將高級語言表達的演算法翻譯成機器語言表達的演算法,顯然毫不相干。
?
處理從演算法語言最終表達成機器語言這一復雜過程的上述思想方法就是一種抽象。匯編語言和高級語言的出現都是這種抽象的範例。 與匯編語言相比,高級語言的巨大成功在於它在數據、運算和控制三方
?
面的表達中引入許多接近演算法語言的概念和工具,大大地提高抽象地表達演算法的能力。 在運算方面,高級語言如Pascal,除允許原封不動地運用演算法語言的四則運算、邏輯運算、關系運算、算術表達式、邏輯表達式外,還引入強有力的函數與過程的工具,並讓用戶自定義。這一工具的重要性不僅在於它精簡了重復的程序文本段,而且在於它反映出程序的兩級抽象。
?
在函數與過程調用級,人們只關心它能做什麼,不必關心它如何做。只是到函數與過程的定義時,人們才給出如何做的細節。用過高級語言的讀者都知道,一旦函數與過程的名稱、參數和功能被規定清楚,那麼,在程序中調用它們便與在程序的頭部說明它們完全分開。你可以修改甚至更換函數體與過程體,而不影響它們的被調用。如果把函數與過程名看成是運算名,把參數看成是運算的對象或運算的結果,那麼
?
,函數與過程的調用和初等運算的引用沒有兩樣。利用函數和過程以及它們的復合或嵌套可以很自然地表達演算法語言中任何復雜的運算。
?
在數據方面,高級語言如Pascal引人了數據類型的概念,即把所有的數據加以分類。每一個數據(包括表達式)或每一個數據變數都屬於其中確定的一類。稱這一類數據為一個數據類型。 因此,數據類型是數據或數據變數類屬的說明,它指示該數據或數據變數可能取的值的全體。對於無結構的數據,高級語言如Pascal,除提供標準的基本數據類型--布爾型、字元型、整型和實型外,還提供用戶可自定義的枚舉類、子界類型和指針類型。這些類型(除指針外),其使用方式都順應人們在演算法語言中使用的習慣。對於有結構的數據,高級語言如Pascal,提供了數組、記錄、有限制的集合和文件等四種標準的結構數據類型。其中,數組是科學計算中的向量、矩陣的抽象;記錄是商業和管理中的記錄的抽象;有限制的集合是數學中足夠小的集合的勢集的抽象;文件是諸如磁碟等外存儲數據的抽象。
?
人們可以利用所提供的基本數據類型(包括標準的和自定義的),按數組、記錄、有限制的集合和文件的構造規則構造有結構的數據。 此外,還允許用戶利用標準的結構數據類型,通過復合或嵌套構造更復雜更高層的結構數據。這使得高級語言中的數據類型呈明顯的分層。 高級語言中數據類型的分層是沒有窮盡的,因而用它們可以表達演算法語言中任何復雜層次的數據。 在控制方面,高級語言如Pascal,提供了表達演算法控制轉移的六種方式。
?
(1)預設的順序控制";"。
?
(2)條件(分支)控制:"if表達式(為真)then S1 else S2;" 。
?
(3)選擇(情況)控制:
?
"Case 表達式 of
?
值1: S1
值2: S2
...
值n: Sn
end"
?
(4)循環控制:
?
"while 表達式(為真) do S;" 或
"repeat S until 表達式(為真);" 或
"for變數名:=初值 to/downto 終值do S;"
?
(5)函數和過程的調用,包括遞歸函數和遞歸過程的調用。
?
(6)無條件轉移goto。
這六種表達方式不僅覆蓋了演算法語言中所有控製表達的要求,而且不再像機器語言或匯編語言那樣原始、那樣繁瑣、那樣隱晦,而是如上面所看到的,與自然語言的表達相差無幾。 程序設計語言從機器語言到高級語言的抽象,帶來的主要好處是: 高級語言接近演算法語言,易學、易掌握,一般工程技術人員只要幾周時間的培訓就可以勝任程序員的工作;高級語言為程序員提供了結構化程序設計的環境和工具,使得設計出來的程序可讀性好,可維護性強,可靠性高;高級語言遠離機器語言,與具體的計算機硬體關系不大,因而所寫出來的程序可移植性好,重用率高; 由於把繁雜瑣碎的事務交給了編譯程序去做,所以自動化程度高,開發周期短,且程、序員得到解脫,可以集中時間和精力去從事對於他們來說更為重要的創造性勞動,以提高、程序的質量。
?
數據結構、數據類型和抽象數據類型
?
數據結構、數據類型和抽象數據類型,這三個術語在字面上既不同又相近,反映出它們在含義上既有區別又有聯系。
?
數據結構是在整個計算機科學與技術領域上廣泛被使用的術語。它用來反映一個數據的內部構成,即一個數據由哪些成分數據構成,以什麼方式構成,呈什麼結構。數據結構有邏輯上的數據結構和物理上的數據結構之分。邏輯上的數據結構反映成分數據之間的邏輯關系,物理上的數據結構反映成分數據在計算機內的存儲安排。數據結構是數據存在的形式。
?
數據是按照數據結構分類的,具有相同數據結構的數據屬同一類。同一類數據的全體稱為一個數據類型。在程序設計高級語言中,數據類型用來說明一個數據在數據分類中的歸屬。它是數據的一種屬性。這個屬性限定了該數據的變化范圍。為了解題的需要,根據數據結構的種類,高級語言定義了一系列的數據類型。不同的高級語言所定義的數據類型不盡相同。Pascal語言所定義的數據類型的種類。
?
其中,簡單數據類型對應於簡單的數據結構;構造數據類型對應於復雜的數據結構;在復雜的數據結構里,允許成分數據本身具有復雜的數據結構,因而,構造數據類型允許復合嵌套;指針類型對應於數據結構中成分數據之間的關系,表面上屬簡單數據類型,實際上都指向復雜的成分數據即構造數據類型中的數據,因此這里沒有把它劃入簡單數據類型,也沒有劃入構造數據類型,而單獨劃出一類。
?
數據結構反映數據內部的構成方式,它常常用一個結構圖來描述:數據中的每一項成分數據被看作一個結點,並用方框或圓圈表示,成分數據之間的關系用相應的結點之間帶箭號的連線表示。如果成分數據本身又有它自身的結構,則結構出現嵌套。這里嵌套還允許是遞歸的嵌套。
?
由於指針數據的引入,使構造各種復雜的數據結構成為可能。按數據結構中的成分數據之間的關系,數據結構有線性與非線性之分。在非線性數據結構中又有層次與網狀之分。 由於數據類型是按照數據結構劃分的,因此,一類數據結構對應著一種數據類型。數據類型按照該類型中的數據所呈現的結構也有線性與非線性之分,層次與網狀之分。一個數據變數,在高級語言中的類型說明必須是讀變數所具有的數據結構所對應的數據類型。最常用的數據結構是數組結構和記錄結構。數組結構的特點是:
?
成分數據的個數固定,它們之間的邏輯關系由成分數據的序號(或叫數組的下標)來體現。這些成分數據按照序號的先後順序一個挨一個地排列起來。每一個成分數據具有相同的結構(可以是簡單結構,也可以是復雜結構),因而屬於同一個數據類型(相應地是簡單數據類型或構造數據類型)。這種同一的數據類型稱為基類型。所有的成分數據被依序安排在一片連續的存儲單元中。 概括起來,數組結構是一個線性的、均勻的、其成分數據可隨機訪問的結構。
?
由於這、種結構有這些良好的特性,所以最常被人們所採用。在高級語言中,與數組結構相對應的、數據類型是數組類型,即數組結構的數據變數必須說明為array [i] of T0 ,其中i是數組、結構的下標類型,而T0是數組結構的基類型。 記錄結構是另一種常用的數據結構。它的特點是:與數組結構一樣,成分數據的個數固定。但成分數據之間沒有自然序,它們處於平等地位。每一個成分數據被稱為一個域並賦予域名。不同的域有不同的域名。不同的域允許有不同的結構,因而允許屬於不同的數據類型。與數組結構一樣,它們可以隨機訪問,但訪問的途徑靠的是域名。在高級語言中記錄結構對應的數據類型是記錄類型。記錄結構的數據的變數必須說明為記錄類型。
?
抽象數據類型的含義在上一段已作了專門敘述。它可理解為數據類型的進一步抽象。即把數據類型和數據類型上的運算捆在一起,進行封裝。引入抽象數據類型的目的是把數據類型的表示和數據類型上運算的實現與這些數據類型和運算在程序中的引用隔開,使它們相互獨立。對於抽象數據類型的描述,除了必須描述它的數據結構外,還必須描述定義在它上面的運算(過程或函數)。抽象數據類型上定義的過程和函
數以該抽象數據類型的數據所應具有的數據結構為基礎。
?
泛型設計和數據結構與演算法
?
下面我想再說說關於泛型程序設計模型對於數據結構和演算法方面的最新推動,泛型思想已經把數據結
?
構和演算法方面的基本思想抽象到了一個前所未有的高度,現在有多種程序設計語言支持泛型設計,比如
ADA,C++,而且據說在JAVA的下一版本和C#中也將對泛型設計進行全面的支持。
?
先說說泛型設計的基本思想:泛型編程(generic programming,以下直接以GP稱呼)是一種全新的程序設計思想,和OO,OB,PO這些為人所熟知的程序設計想法不同的是GP抽象度更高,基於GP設計的組件之間偶合度底,沒有繼承關系,所以其組件間的互交性和擴展性都非常高。我們都知道,任何演算法都是作用在一種特定的數據結構上的,最簡單的例子就是快速排序演算法最根本的實現條件就是所排序的對象是存
貯在數組裡面,因為快速排序就是因為要用到數組的隨機存儲特性,即可以在單位時間內交換遠距離的對象,而不只是相臨的兩個對象,而如果用聯表去存儲對象,由於在聯表中取得對象的時間是線性的既O[n],這樣將使快速排序失去其快速的特點。也就是說,我們在設計一種演算法的時候,我們總是先要考慮其應用的數據結構,比如數組查找,聯表查找,樹查找,圖查找其核心都是查找,但因為作用的數據結構不同
?
將有多種不同的表現形式。數據結構和演算法之間這樣密切的關系一直是我們以前的認識。泛型設計的根本思想就是想把演算法和其作用的數據結構分離,也就是說,我們設計演算法的時候並不去考慮我們設計的演算法將作用於何種數據結構之上。泛型設計的理想狀態是一個查找演算法將可以作用於數組,聯表,樹,圖等各種數據結構之上,變成一個通用的,泛型的演算法。這樣的理想是不是很誘惑人?
?
泛型編程帶來的是前所未有的彈性以及不會損失效率的抽象性,GP和OO不同,它不要求你通過額外的間接層來調用函數:它讓你撰寫完全一般化並可重復使用的演算法,其效率與針對特定數據結構而設計的演算法旗鼓相當。我們大家都知道數據結構在C++中可以用用戶定義類型來表示,而C++中的模板技術就是以類型作為參數,那麼我可以想像利用模板技術可以實現我們開始的GP思想,即一個模板函數可以對於各種傳遞進來的類型起作用,而這些類型就可以是我們定義的各種數據結構。
?
泛型演算法抽離於特定類型和特定數據結構之外,使得其適應與盡可能的一般化類型,演算法本身只是為了實現演算法其需要表達的邏輯本質而不去被為各種數據結構的實現細節所干擾。這意味著一個泛型演算法實際具有兩部分。1,用來描敘演算法本質邏輯的實際指令;2,正確指定其參數類型必須滿足的性質的一組需求條件。到此,相信有不少人已經開始糊塗了,呵呵,不要緊。畢竟GP是一種抽象度非常高的程序設計思想,裡面的核心就是抽象條件成為成為程序設計過程中的核心,從而取代了類型這在OO裡面的核心地位,正是因為類型不在是我們考慮的重點,類型成為了抽象條件的外衣,所以我們稱這樣的程序思想為泛型思想------把類型泛化。