算法與數據結構(part1)--算法簡介及大O表示法

學習筆記,僅供參考



算法與數據結構–基於python


數據結構和算法簡介


  • 什麼是數據結構

數據結構就是一些有關係的數據的集合,有順序表,鏈表,棧,隊列,樹,圖等結構,

我們的程序就等於數據結構+算法。


  • 什麼是算法

算法(Algorithm)是指解題方案的準確而完整的描述,是一系列解決問題的清晰指令,算法代表着用系統的方法描述解決問題的策略機制;不同的算法可能用不同的時間、空間或效率來完成同樣的任務。一個算法的優劣可以用空間複雜度與時間複雜度來衡量;算法就是一種思路.


  • 數據結構和算法的用處

    • 寫出的程序可以更高效;

    • 面對一些複雜問題可能無從下手,數據結構和算法可以鍛鍊邏輯思維。


算法引入


例題A


如果 a+b+c=1000,且 a²+b²=c²(a,b,c爲自然數),如何求出所有a、b、c可能的組合?(不使用數學公式)

枚舉法:

import time

start = time.time()

for a in range(1001):
    # a取完讓b去取
    for b in range(1001):
        for c in range(1001):
            if a + b + c == 1000 and a**2 + b**2 == c**2:
                print(a,b,c)
end = time.time()
print('finish')
print('程序用時:',(end-start))

運行結果:

0 500 500
200 375 425
375 200 425
500 0 500
finish
程序用時: 829.0995240211487

算法的概念


算法是計算機處理信息的本質,因爲計算機程序本質上是用一個算法來告訴計算機確切的步驟,進而執行一個指定的任務。當算法在處理信息時,會從輸入設備或數據的存儲地址讀取數據,把結果寫入輸出設備或某個存儲地址供以後再調用。

算法是獨立存在的一種解決問題的方法和思想,對於算法而言,實現的語言並不重要,重要的是思想,算法有不同的語言實現版本(如C、Java、Python等)


  • 算法的五大特性

    • 輸入: 算法具有0個或多個輸入;

    • 輸出: 算法至少有1個或多個輸出;

    • 有窮性: 算法在有限的步驟之後會自動結束而不會無限循環,並且每一個步驟可以在可接受的時間內完成;

    • 確定性:算法中的每一步都有確定的含義;

    • 可行性:算法的每一步都是可行的.


例題A的優化


import time


start = time.time()

for a in range(1001):
    # a取完讓b去取
    for b in range(1001 - a):
        # a,b已經確定了
        c = 1000 - a - b
        if a**2 + b**2 == c**2:
            print(a,b,c)
end = time.time()
print('finish')
print('程序用時:',(end-start))

運行結果:

0 500 500
200 375 425
375 200 425
500 0 500
finish
程序用時: 2.3102197647094727

最直觀的評判算法優劣的標準,就是運行時間,可以看到改進的算法運行時長明顯小於枚舉法,所以改進的算法一定程度上優於枚舉法。



算法效率的衡量


  • 執行時間反應算法效率

實現算法程序的執行時間可以反應出算法的效率,即算法的優劣


  • 單純依據時間衡量可信麼?

單純依靠運行的時間來比較算法的優劣並不一定是客觀準確的;

程序的運行離不開計算機環境(包括硬件和操作系統),這些客觀原因會影響程序運行的速度並反應在程序的執行時間上。


時間複雜度與大O記法


假定計算機執行算法每一個基本操作的時間是固定的一個時間單位,那麼有多少個基本操作就代表會花費多少時間單位

雖然對於不同的機器環境而言,確切的單位時間是不同的,但是對於算法進行多少個基本操作(即花費多少時間單位)在規模數量級上卻是相同的,由此可以忽略機器環境的影響,而客觀的反應算法的時間效率。

對於算法的時間效率,我們可以用大O記法來表示。

大O記法:對於單調的整數函數ff,如果存在一個整數函數gg和實常數c>0c>0,使得對於充分大的n總有f(n)<=cg(n)f(n)<=c*g(n),就說函數ggff的一個漸近函數(忽略常數),記爲f(n)=O(g(n))f(n)=O(g(n))。也就是說,在趨向無窮的極限意義下,函數ff的增長速度受到函數gg的約束,亦即函數ff與函數gg的特徵相似。

時間複雜度:假設存在函數gg,使得算法A處理規模爲n的問題示例所用時間爲T(n)=O(g(n))T(n)=O(g(n)),則稱O(g(n))O(g(n))爲算法A的漸近時間複雜度,簡稱時間複雜度,記爲T(n)T(n)


例題A的時間複雜度


我們用TT表示時間複雜度,則對於枚舉法來說,其時間複雜度爲T=k(100010001000)+bT=k(1000*1000*1000)+b,若用nn代表數據規模,則T=k(n3)+bT=k(n^3)+b,若存在函數g(n)g(n),使T(n)=kg(n)+bT(n)=k*g(n)+b,因爲kkbb不影響大局,即相比於g(n)g(n)的形式來說,對時間的影響微不足道,所以我們拋棄kkbb,則算法的趨勢爲T(n)=O(g(n))T(n)=O(g(n))


如何理解大O記法


對於算法進行特別具體的細緻分析雖然很好,但在實踐中的實際價值有限。對於算法的時間性質和空間性質,最重要的是其數量級趨勢,這些是分析算法效率的主要部分.

而計量算法基本操作數量的規模函數中那些常量因子可以忽略不計。例如,可以認爲3n23n^2100n2100n^2屬於同一個量級,如果兩個算法處理同樣規模實例的代價分別爲這兩個函數,就認爲它們的效率差不多,都爲n2n^2級.


最壞時間複雜度


分析算法時,存在幾種需要考慮的情況:

  • 算法完成工作最少需要多少基本操作,即最優時間複雜度。

  • 算法完成工作最多需要多少基本操作,即最壞時間複雜度。

  • 算法完成工作平均需要多少基本操作,即平均時間複雜度。

我們主要關注算法的最壞情況,亦即最壞時間複雜度


時間複雜度的幾條基本計算規則


  • 基本操作,即只有常數項,認爲就是O(1)O(1)
  • 順序結構,時間複雜度按加法進行計算
  • 循環結構(for),時間複雜度按乘法進行計算
  • 分支結構(if),時間複雜度爲分支中的時間複雜度的最大值

剛纔的例題A中就存在分支結構(if)和循環結構(for):

for a in range(1001):
    # a取完讓b去取
    for b in range(1001):
        for c in range(1001):
            if a + b + c == 1000 and a**2 + b**2 == c**2:
                print(a,b,c)

時間複雜度:T(n)=nnnmax(1,0)=n3T(n)=n*n*n*max(1, 0)=n^3

當我們的程序遇到if時,可能會執行if語句體裏的內容,也可能不執行,所以分支結構if中最多有1次操作,最少爲0次,而我們計算時間複雜度時,則用最大操作次數1來計算。


常見的時間複雜度


執行次數函數舉例 非正式術語
12 O(1)O(1) 常數階
2n+3 O(n)O(n) 線性階
3n²+2n+1 O(n2)O(n^2) 平方階
5log2n+20 O(logn)O(logn) 對數階
2n+3nlog2n+19 O(nlogn)O(nlogn) nlognnlogn
6n³+2n²+3n+4 O(n3)O(n^3) 立方階
2^n O(2n)O(2^n) 指數階

  • 常見時間複雜度之間的關係

所消耗的時間從小到大:

O(1) < O(logn) < O(n) < O(nlogn) < O(n²) < O(n³) < O(2^n) < O(n!) < O(n^n)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章