一、演算法和資料結構
什麼是演算法和資料結構?如果將最終寫好執行的程式比作戰場,我們程式設計師便是指揮作戰的將軍,而我們所寫的程式碼便是士兵和武器。
那麼資料結構和演算法是什麼?答曰:兵法!故,資料結構和演算法是一名程式開發人員的必備基本功,不是一朝一夕就能練成絕世高手的。冰凍三尺非一日之寒,需要我們平時不斷的主動去學習積累。
二、演算法的引入
先來看一道題:如果 a+b+c=1000,且 a*a+b*b=c*c(a,b,c 為自然數),如何求出所有a、b、c可能的組合?
普通解法:
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*a+b*b == c*c and a+b+c == 1000: print("a=%d,b=%d,c=%d"%(a,b,c)) # 用於記錄計算結束的時間 end_time = time.time() print("本次運算用時總計%f秒"%(end_time-start_time))
執行結果為:
a=0,b=500,c=500 a=200,b=375,c=425 a=375,b=200,c=425 a=500,b=0,c=500 本次運算用時總計397.615515秒
請注意執行時間是397.615515秒那麼,如果本題稍微改一下,改為先來看一道題:如果 a+b+c=2000,且 a*a+b*b=c*c(a,b,c 為自然數),如何求出所有a、b、c可能的組合?這個時候他們解題思路和a+b+c=1000是一樣的,無非就是將1000改為2000,而這種解決問題的思路就叫做演算法,只不過演算法也有好壞而已。
三、演算法的概念
演算法是計算機處理資訊的本質,因為計算機程式本質上是一個演算法來告訴計算機確切的步驟來執行一個指定的任務。一般地,當演算法在處理資訊時,會從輸入裝置或資料的儲存地址讀取資料,把結果寫入輸出裝置或某個儲存地址供以後再呼叫。演算法是獨立存在的一種解決問題的方法和思想。對於演算法而言,實現的語言並不重要,重要的是思想。演算法可以有不同的語言描述實現版本(如C描述、C++描述、Python描述等)
四、演算法的五大特性
- 輸入: 演算法具有0個或多個輸入
- 輸出: 演算法至少有1個或多個輸出,演算法必須要有輸出,不然演算法有什麼意義,鬧著玩呢?所以一定要把算出了資料輸出使用。
- 有窮性: 演算法在有限的步驟之後會自動結束而不會無限迴圈,並且每一個步驟可以在可接受的時間內完成
- 確定性:演算法中的每一步都有確定的含義,不會出現二義性
- 可行性:演算法的每一步都是可行的,也就是說每一步都能夠執行有限的次數完成
五、案例演算法優化
還是針對上面的例子:如果 a+b+c=1000,且 a*a+b*b=c*c(a,b,c 為自然數),如何求出所有a、b、c可能的組合?優化解法:
import time # 用於記錄計算開始的時間 start_time = time.time() # 注意這裡是兩重迴圈 for a in range(0,1001): for b in range(0,1001-a): c=1000-a-b if a*a+b*b == c*c: print("a=%d,b=%d,c=%d"%(a,b,c)) # 用於記錄計算結束的時間 end_time = time.time() print("本次運算用時總計:%f秒"%(end_time-start_time))
執行結果為:
a=0,b=500,c=500 a=200,b=375,c=425 a=375,b=200,c=425 a=500,b=0,c=500 本次運算用時總計316.337260秒
六、演算法效率的衡量
1、執行時間反應演算法效率
對於同一問題,我們給出了兩種解決演算法,在兩種演算法的實現中,我們對程式執行的時間進行了測算,發現兩段程式執行的時間相差懸殊(397.615515秒相比於316.337260秒),由此我們可以得出結論:實現演算法程式的執行時間可以反應出演算法的效率,即演算法的優劣。
2、單靠時間絕對可信嗎?
假設我們將第二次嘗試的演算法程式執行在一臺配置古老效能低下的計算機中,情況會如何?很可能執行的時間並不會比在我們的電腦中執行演算法一的397.615515秒快多少。單純依靠執行的時間來比較演算法的優劣並不一定是客觀準確的!程式的執行離不開計算機環境(包括硬體和作業系統),這些客觀原因會影響程式執行的速度並反應在程式的執行時間上。那麼如何才能客觀的評判一個演算法的優劣呢?
3、時間複雜度與“大O記法”
我們假定計算機執行演算法每一個基本操作的時間是固定的一個時間單位,那麼有多少個基本操作就代表會花費多少時間單位。當然對於不同的機器環境而言,確切的單位時間是不同的,但是對於演算法進行多少個基本操作(即花費多少時間單位)在規模數量級上卻是相同的,由此可以忽略機器環境的影響而客觀的反應演算法的時間效率。
對於演算法的時間效率,我們可以用“大O記法”來表示。
“大O記法”:對於單調的整數函式f(),如果存在一個整數函式g()和實常數c>0,使得對於充分大的n總有f(n)<=c*g(n),就說函式g是f的一個漸近函式(忽略常數),記為f(n)=O(g(n))。也就是說,在趨向無窮的極限意義下,函式f的增長速度受到函式g的約束,亦即函式f與函式g的特徵相似。
時間複雜度:假設存在函式g(),使得演算法A處理規模為n的問題示例所用時間為T(n)=O(g(n)),則稱O(g(n))為演算法A的漸近時間複雜度,簡稱時間複雜度,記為T(n)
- 演算法完成工作最少需要多少基本操作,即最優時間複雜度
- 演算法完成工作最多需要多少基本操作,即最壞時間複雜度
- 演算法完成工作平均需要多少基本操作,即平均時間複雜度
4、時間複雜度的基本計算規則
- 基本操作,即只有常數項,認為其時間複雜度為O(1)
- 順序結構,時間複雜度按加法進行計算
- 迴圈結構,時間複雜度按乘法進行計算
- 分支結構,時間複雜度取最大值
- 判斷一個演算法的效率時,往往只需要關注運算元量的最高次項,其它次要項和常數項可以忽略
- 在沒有特殊說明時,我們所分析的演算法的時間複雜度都是指最壞時間複雜度
七、案例的演算法分析
for a in range(0,1001): for b in range(0,1001): for c in range(0,1001): if a*a+b*b == c*c and a+b+c == 1000: print("a=%d,b=%d,c=%d"%(a,b,c))
時間複雜度:T(n) = O(n*n*n) = O(n^3)
優化後:
for a in range(0,1001): for b in range(0,1001-a): c=1000-a-b if a*a+b*b == c*c: print("a=%d,b=%d,c=%d"%(a,b,c))
時間複雜度:T(n) = O(n*n) = O(n^2), 由此可見,我們嘗試的第二種演算法要比第一種演算法的時間複雜度好多的。
八、常見的時間複雜度
注意:經常將log2n(以2為底的對數)簡寫成logn
所消耗的時間從小到大:O(1) < O(logn) < O(n) < O(nlogn) < O(n2) < O(n3) < O(2n) < O(n!) < O(n^n)
九、資料結構
資料是一個抽象的概念,將其進行分類後得到程式設計語言中的基本型別。如:int,float,char等。資料元素之間不是獨立的,存在特定的關係,這些關係便是結構。資料結構指資料物件中資料元素之間的關係。
Python給我們提供了很多現成的資料結構型別,這些系統自己定義好的,不需要我們自己去定義的資料結構叫做Python的內建資料結構,比如列表、元組、字典。而有些資料組織方式,Python系統裡面沒有直接定義,需要我們自己去定義實現這些資料的組織方式,這些資料組織方式稱之為Python的擴充套件資料結構,比如棧,佇列等。
十、演算法與資料結構的區別
資料結構只是靜態的描述了資料元素之間的關係。高效的程式需要在資料結構的基礎上設計和選擇演算法。
程式 = 資料結構 + 演算法
總結:演算法是為了解決實際問題而設計的,資料結構是演算法需要處理的問題載體
最常用的資料運算有五種:
- 插入
- 刪除
- 修改
- 查詢
- 排序