數據結構&算法的引言+時間復雜度
一.什麽是計算機科學?
首先明確的一點就是計算機科學不僅僅是對計算機的研究,雖然計算機在科學發展的過程中發揮了重大的作用,但是它只是一個工具,一個沒有靈魂的工具而已。所謂的計算機科學實際上是對問題、解決問題以及解決問題的過程中產生產生的解決方案的研究。例如給定一個問題,計算機科學家的目標是開發一個算法來處理該問題,最終的該問題的解、或者最優解。所以說計算機科學也可以被認為是對算法的研究。因此我們也可以感受到,所謂的算法就是對問題進行處理且求解的一種實現思路或者思想。
二.如何形象化的理解算法和數據結構?
- 案例引導:
一個常勝將軍在作戰之前都會進行戰略的指定,目的是為了能夠在最短的時間切成本消耗最低的情況下獲取最終的勝利。如果將編碼作為戰場,則程序員就是這場戰役的指揮官,你如何可以將你的程序可以在最短且消耗資源最小的情況下獲取最終的執行結果呢?數據結構和算法就是我們的策略!
- 意義所在:
- 數據結構和算法思想的通用性異常的強大,在任何語言中都被使用,它們將會是我們編碼生涯中伴隨我們最長久利器(左膀右臂)。有一定經驗的程序員最終拼的就是算法和數據結構。
- 數據結構和算法思想也可以幫助我們拓展和歷練編碼的思維,可以讓我們更好的融入到編程世界的角角落落。
三.什麽是算法分析?
- 案例引入:剛接觸編程的學生經常會將自己編寫的程序和別人的程序做比對,獲取在比對的過程中會發現雙方編寫的程序很相似但又各不相同。那麽就會出現一個有趣的現象:兩組程序都是用來解決同一個問題的,但是兩組程序看起來又各不相同,那麽哪一組程序更好呢?例如下述代碼:
def sumOfN(n): theSum = 0 for i in range(1,n+1): theSum = theSum + i return theSum print(sumOfN(10))
def foo(tom): fred = 0 for bill in range(1,tom+1): barney = bill fred = fred + barney return fred print(foo(10))
分析:
- 哪個函數更好,答案取決於你的標準。如果你關註可讀性,函數 sumOfN 肯定比 foo 好。事實上,你可能已經在介紹編程的課程中看到過很多例子,他們的目標之一就是幫助你編寫易於閱讀和理解的程序。然而,在本課程中,我們對算法本身的表示更感興趣(當然我們希望你繼續努力編寫可讀的,易於理解的代碼)。
- 算法分析是基於每種算法使用的計算資源量來比較算法。我們比較兩個算法,說一個比另一個算法好的原因在於它在使用資源方面更有效率,或者僅僅使用的資源更少。從這個角度來看,上面兩個函數看起來很相似。它們都使用基本相同的算法來解決求和問題。
- 問題:a+b+c = 1000 a**2 + b**2 = c**2 (a,b,c均為自然數),求出a,b,c可能的組合?
for a in range(0,1001): for b in range(0,1001): for c in range(0,1001): if a**2+b**2 == c**2 and a+b+c==1000: print(a,b,c)
for a in range(0,1001): for b in range(0,1001): c = 1000 - a - b if a**2+b**2 == c**2 and a+b+c==1000: print(a,b,c)
分析:很明顯上述兩中問題的解決方案是不同的。那如何判定上述兩種解決方案(算法)的優劣呢?有同學會說,觀察計算兩種算法使用耗費計算機資源的大小和它們的執行效率呀!但是我想說的是,一些較為復雜的算法,它耗費計算機資源的大小和執行效率我們很難能夠直觀的從算法的編碼上分析出來。所以我們必須采用某種量化的方式求出不同算法的資源耗費和執行效率的具體值來判定算法之間的優劣。問題來了,如何進行量化計算呢?方法有兩種:
- 方法1:計算算法執行的耗時(不推薦)。
import time start_time = time.time() for a in range(0,1001): for b in range(0,1001): for c in range(0,1001): if a**2+b**2 == c**2 and a+b+c==1000: print(a,b,c) end_time = time.time() print(end_time-start_time) #執行結果為: 0 500 500 200 375 425 375 200 425 500 0 500 1221.7778379917145
import time start_time = time.time() for a in range(0,1001): for b in range(0,1001): c = 1000 - a - b if a**2+b**2 == c**2 and a+b+c==1000: print(a,b,c) end_time = time.time() print(end_time-start_time) #執行結果為: 0 500 500 200 375 425 375 200 425 500 0 500 1.410386085510254
註意:單靠執行時間可以反應算法的效率嗎?不能。跟機器(執行環境)存在很大關系。
方法2:計算算法的時間復雜度(推薦)
- 時間復雜度:量化算法需要的操作或者執行步驟的數量。
四.時間復雜度
- 我們試圖通過算法的執行時間來判定算法的優劣。但是僅僅根據執行時間判定算法優劣有些片面,因為算法是獨立於計算機的。重要的是量化算法需要的操作或者步驟的數量。選擇適當的基本計算單位是個復雜的問題,並且將取決於如何實現算法。對於先前的求和算法,一個比較好的基本計算單位是對執行語句進行計數。
- 在 sumOfN 中,賦值語句的計數為 1(theSum = 0theSum=0) 加上 n 的值(我們執行 theSum=theSum+itheSum=theSum+i 的次數)。我們通過函數 T 表示 T(n)=1+n。參數 n 通常稱為“問題的規模”,我們稱作 “T(n) 是解決問題大小為 n 所花費的時間,即 1+n 步長”。在上面的求和函數中,使用 n 來表示問題大小是有意義的。我們可以說,100,000 個整數和比 1000 個問題規模大。因此,所需時間也更長。我們的目標是表示出算法的執行時間是如何相對問題規模大小而改變的。
- 計算機科學家更喜歡將這種分析技術進一步擴展。事實證明,操作步驟數量不如確定 T(n) 最主要的部分來的重要。換句話說,當問題規模變大時,T(n) 函數某些部分的分量會超過其他部分。函數的數量級表示了隨著 n 的值增加而增加最快的那些部分。
- 數量級通常稱為大O符號,寫為 O(f(n))。它表示對計算中的實際步數的近似。函數 f(n) 提供了 T(n) 最主要部分的表示方法。T(n)的最主要部分是:在上述示例中,T(n)=1+n。當 n 變大時,常數 1 對於最終結果變得越來越不重要。如果我們找的是 T(n) 的近似值,我們可以刪除 1。因此T(n)中最主要的部分為n,因此f(n)=n。因此在sumOfN函數對應的算法的時間復雜度可即為O(f(n)),即為O(n)。
- 另外一個示例,假設對於一些算法,確定的步數是 T(n)=5n^2+27n+1005。當 n 很小時, 例如 1 或 2 ,常數 1005 似乎是函數的主要部分。然而,隨著 n 變大,n^2 這項變得越來越重要。事實上,當 n 真的很大時,其他兩項在它們確定最終結果中所起的作用變得不重要。當 n 變大時,為了近似 T(n),我們可以忽略其他項,只關註 5n^2 。系數 5 也變得不重要。我們說,T(n) 具有的數量級為 f(n)=n^2,或者 O( n^2 )。
- 常見的時間復雜度:O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n) - 案例分析:計算下列算法的時間復雜度1 a=5 2 b=6 3 c=10 4 for i in range(n): 5 for j in range(n): 6 x = i * i 7 y = j * j 8 z = i * j 9 for k in range(n): 10 w = a*k + 45 11 v = b*b 12 d = 33
分析:前三行賦值語句執行操作步驟為3,5-8行的執行操作步驟為3n,結合第四行的外部循環,整個內外循環的執行步驟為3n^2。9-11行為2n,12行為1。最終得出T(n)=3+3n^2+2n+1,簡化操作後T(n)=3n^2+2n+4。通過查看指數,我們可以看到 n^2 項是最重要的,因此這個代碼段是 O(n^ 2)。當 n 增大時,所有其他項以及主項上的系數都可以忽略。
四.數據結構:
- 對於數據(基本類型的數據(int,float,char))的組織方式就被稱作為數據結構。數據結構解決的就是一組數據如何進行保存,保存形式是怎樣的。
- 案例:需要存儲一些學生的學生信息(name,score),那麽這些數據應該如何組織呢?查詢某一個具體學生的時間復雜度是什麽呢?
- 組織形式1:
[{‘name‘:‘zhangsan‘,‘score‘:100}, {‘name‘:‘lisi‘,‘score‘:99} ]
- 組織形式2:
[(‘zhangsan‘,100), (‘lisi‘,99) ]
- 組織形式3:
{‘zhangsan‘:{‘score‘:100}, ‘lisi‘:{‘score‘:99} }
- 三種組織形式基於查詢的時間復雜度分別為:O(n),O(n),O(1)
- 發現:python中的字典,列表,元組本身就是已經被封裝好的一種數據結構啦。使用不同的數據結構進行數據的存儲,所導致的時間復雜度是不一樣。因此認為算法是為了解決實際問題而設計的,數據結構是算法需要處理問題的載體。
數據結構&算法的引言+時間復雜度