時間複雜度

講師 蕭梓宏

極粗略課程大綱

  • 甚麼是時間複雜度?

  • 時間複雜度對我(你)來說很重要嗎?

  • 怎麼估複雜度?

針對這堂課的提醒

  • 雖然是遠距上課,但還是希望大家能有多一點互動

    • 有想法就可以提出來

  • 課程內容的難易度未必適合所有人

    • 有問題就問

    • 覺得太簡單不用急,後面的講師一定可以滿足你

  • 雖然這堂課可能偏理論,但並非那麼嚴謹

什麼是時間複雜度?

某個演算法的時間複雜度?

  • 一個描述該演算法(或程式)執行時間的函式

  • 通常假設各種基本操作都花一單位時間

    • 加、減、乘、除、取模

    • 賦值

    • 條件判斷

    • 存取變數

    • …...

舉例

F(N)=N2+2N+3F(N) = N^2+2N+3

cin >> N;
i = 0;//1
j = 0;//1
while(i < N)//N+1
{
    while(j < i)//1+2+3+..+N=(N+1)N/2
    {
        j++;//1+2+3+..+(N-1)=(N-1)N/2
    }
    i++;//N
}

這個算起來也太麻煩了吧

big-O

  • f(x)=O(g(x))f(x) = O(g(x)) 代表存在M,x0>0M,x_0 > 0,使得所有xx0x \geq x_0都有f(x)Mg(x)|f(x)|\leq M|g(x)|

  • 也就是說,當xx很大的時候,存在常數MM,使得Mg(x)M|g(x)|一定會大於等於f(x)f(x)

  • 其實也就表示,g(x)g(x)f(x)f(x)的某種上界

  • 看例子!

例子

f(x)f(x)

g(x)g(x)

f(x)=O(g(x))f(x) = O(g(x))

xx

100x100x

Yes

xx

100x100x

Yes

x3x^3

x2x^2

No

x2x^2

x2log(x)10000\frac{x^2\log(x)}{10000}

Yes

Yes

x1000x100x-1000\sqrt{x}-100

xx

big-O

  • 可以看出,只要有了big-O符號,我們就可以無視低次項跟常數,更方便地表示複雜度

  • 因此,在剛剛的例子中,原本複雜度是
    F(N)=N2+N+4F(N)=N^2+N+4,現在也可以用O(N2)O(N^2)來表示該程式的時間複雜度

  • 雖然也可以用O(N3)O(N^3)來表示,但我們追求的是複雜度越小越好!

  • 這樣估複雜度就不用那麼麻煩了!

小測驗

O(N3)O(N^3)

cin >> N;
i = 0;
j = 0;
while(i < N*N+N-2)
{
    while(j < N*3-10)
    {
        j++;
    }
    i++;
}

小測驗

O(N2N)O(N^2\sqrt{N})

cin >> N;
i = 0;
j = 0;
while(i < N*N+N-2)
{
    while(j*j < N)
    {
        j++;
    }
    i++;
}

時間複雜度很重要嗎?

很重要

  • 即使你的程式是正確的,如果他的執行時間超過了題目的限制,你會得到一個           而無法得到分數

  • 一般的judge一秒可以跑10810^8筆運算左右,所以想要知道自己的程式會跑幾秒,就把題目的範圍限制代入所估的複雜度中,最後再除以10810^8。然後再跟題目的限制秒數比比看就可以知道會不會TLE了

  • 注意我們要估的通常是最糟的情形

第一個例題!

給定一個單字,請你找出其中"最長的"連續的一段,使得那段正著唸反著唸都一樣。(單字長度L5000L \leq 5000)

 

範例輸入:

abcbbd

範例輸出:

bcb

作法其一

枚舉所有的「連續的一段」,再把那一段掃過一遍就好囉!

abac

abac

abac

abac

abac

abac

abac

abac

abac

abac

作法其一 複雜度

因為長度為11的區間有LL個,22的區間有L1L-1個,...,LL區間11個,所以複雜度是1×L+2×(L1)+...+L×1=L3+3L24L61\times L+2\times (L-1)+...+L\times 1=\frac{L^3+3L^2-4L}{6}

複雜度是:

枚舉連續的一段 O(L2)O(L^2)

然後再把那段掃一遍 最糟O(L)O(L)

所以就是O(L3)O(L^3)囉!

這樣OK嗎?

  • 已知L5000L \leq 5000,而複雜度為L3L^3
  • 50003=1.25×1011>>1085000^3=1.25\times 10^{11} >> 10^8

跑100秒都跑不完QQ

作法其二

枚舉所有的「中間點」,然後看可以往外擴張多少!

作法其二

枚舉所有的「中間點」,然後看可以往外擴張多少!

a b c b b d

長度:3

作法其二

枚舉所有的「中間點」,然後看可以往外擴張多少!

a b c b b d

長度:2

作法其二 複雜度

複雜度是:

枚舉中間點(包含中間字母語中間空隙) O(L)O(L)

然後看可以往外擴張多少 最糟O(L)O(L)

所以就是O(L2)O(L^2)囉!

50002=2.5×1075000^2 = 2.5 \times 10^7,理論上一秒內可以跑完!

第一個例題 實作時間

回文好玩好文回(6)

10 min

複雜度真的很重要

  • 除了判斷自己的演算法可不可以拿到想要的分數之外,因為比較難的題目通常都會把限制的範圍出的盡量大,所以看到範圍後也可以得到一些關於作法複雜度的線索

第二個例題!

給你一個正整數QQ,接下來有QQ筆詢問,每次詢問你一個整數NN是不是質數。

 

範例輸入:

33

11

22

33

範例輸出:

No

Yes

Yes

第二個例題! 版本一

給你一個正整數QQ,接下來有QQ筆詢問,每次詢問你一個整數NN是不是質數。

範圍:Q100Q\leq 100N1010N\leq 10^{10}

第二個例題! 版本二

給你一個正整數QQ,接下來有QQ筆詢問,每次詢問你一個整數NN是不是質數。

範圍:Q106Q\leq 10^6N106N\leq 10^6

版本一  Q100Q\leq 100N1010N\leq 10^{10}

  • 這個版本的QQ範圍比第二個版本小,所以我們每筆詢問的複雜度把NN帶入後不超過108100=106\frac{10^8}{100}=10^6就好,所以每一筆詢問可以相對暴力地回答

說到最直接判斷質數的方法

試除法?

試除法 複雜度

  • 跟我們平常用手算一樣,試除法有一個重點是如果NN是合數,他至少有一個因數dNd\leq\sqrt{N}
  • 換句話說,我們只要拿2,3,...,N2,3,..., \left \lfloor{\sqrt{N}}\right \rfloorO(N)O(\sqrt{N})個東西去除除看就可以確認他是不是質數了
  • 對每筆詢問都做一樣的事,複雜度O(QN)O(Q\sqrt{N})
  • Q100Q\leq 100N1010N\leq 10^{10}帶進去算出來大概是10710^7
  • 讚讚!

第二個例題 版本一 實做時間

質數判斷(7)

9 min

版本二  Q106Q\leq 10^6N106N\leq 10^{6}

  • 直接用試除法,那個複雜度應該是過不了的!
  • 相對於上一題,這一題的突破口是NN比較小
  • 其實,我們只要知道11~10610^6中每個數是不是質數就好了!

說到找出比KK小的質數的方法

篩法?

篩法

1    2   3   4   5   6   7   8   9   10

11 12 13 14 15 16 17 18 19 20

這樣做的複雜度是K2+K3+K5...\frac{K}{2}+\frac{K}{3}+\frac{K}{5}...

有點難估所以抓個上界:

K2+K3+K5...K(1+12+13+...+1K)\frac{K}{2}+\frac{K}{3}+\frac{K}{5}...\leq K(1+\frac{1}{2}+\frac{1}{3}+...+\frac{1}{K})

篩法 複雜度

  • 可以學(記)起來的小引理:1+12+13+...+1K=O(log(K))1+\frac{1}{2}+\frac{1}{3}+...+\frac{1}{K} = O(log(K))
  • 所以複雜度就是K2+K3+K5...K(1+12+13+...+1K)=O(Klog(K))\\\frac{K}{2}+\frac{K}{3}+\frac{K}{5}...\leq K(1+\frac{1}{2}+\frac{1}{3}+...+\frac{1}{K})=O(Klog(K))
  • 所以我們一開始先用O(Klog(K))O(Klog(K))的時間建好K=106K=10^6以下的質數表,並存在陣列中
  • 然後每筆詢問NN直接O(1)O(1)去看陣列中第NN
  • 總複雜度O(Klog(K)+Q);K=106,Q106O(Klog(K)+Q);K=10^6, Q\leq 10^6 讚讚!

第二個例題 版本二 實作時間

質數判斷2(11)

9 min

第二個例題!

  • 所以,除了題目本身之外,也要根據範圍來選擇適合的做法!

Q100,N1010Q\leq 100,N\leq 10^{10}

Q106,N106Q\leq 10^6,N\leq 10^6

O(QN)O(Q\sqrt{N})

O(Klog(K)+Q)O(Klog(K)+Q)

AC

AC

TLE

TLE

有此一說(僅供參考)

  • 如果有一題他的複雜度只跟NN有關的話

範圍

做法複雜度

N10N\leq 10

O(4N),O(N7),O(N!),...O(4^N),O(N^7),O(N!),...

N20N\leq 20

O(N×2N),O(N6),...O(N\times2^N),O(N^6),...

N50N\leq 50~ 100100

O(N4)O(N^4)

N200N\leq 200

O(N3log(N))O(N^3log(N))

N500N\leq 500

O(N3)O(N^3)

N2000N\leq 2000

O(N2log(N))O(N^2log(N))

有此一說(僅供參考)

  • 如果有一題他的複雜度只跟NN有關的話

範圍

作法複雜度

N5000N\leq 5000

O(N2)O(N^2)

N5×104N\leq 5\times 10^4

O(NN)O(N\sqrt{N})

N105N\leq 10^5

O(NN),O(Nlog2(N)),O(Nlog(N))O(N\sqrt{N}),O(Nlog^2(N)),O(Nlog(N))

N106N\leq 10^6

O(Nlog(N)),O(N)O(Nlog(N)),O(N)

N107N\leq 10^7

O(N)O(N)

N1012N\leq 10^{12}

O(N)O(\sqrt{N})

N1018N\leq 10^{18}

O(log(N)),O(log2(N)),...O(log(N)),O(log^2(N)),...

怎麼估時間複雜度?

估時間複雜度的方法

  • 其實大家都會了?

  • 根據之前的例子,基本上就是看每一層迴圈最多跑多少次,然後全部乘起來?

但是......

第三個例題!

給你一個單字,你要從中找到其中連續的一段,使得該段是包含全部的字母中最短的(很多個的話請輸出最先出現的,不存在請輸出"QQ")

範例輸入:

aabbabcdefghijklmnoqrstuvwxyz

範例輸出:

abcdefghijklmnoqrstuvwxyz

某個作法

  • 枚舉「連續的一段」的左界LL, 開一個26格的陣列代表每個字母出現過幾次,然後從LL往右一直看直到包含所有字母為止,並記錄當前長度
  • 假設只有三個字母(a,b,c)

a b c b a

a b c

LL

某個作法

  • 枚舉「連續的一段」的左界LL, 開一個26格的陣列代表每個字母出現過幾次,然後從LL往右一直看直到包含所有字母為止,並記錄當前長度
  • 假設只有三個字母(a,b,c)

a b c b a

a b c
1

LL

某個作法

  • 枚舉「連續的一段」的左界LL, 開一個26格的陣列代表每個字母出現過幾次,然後從LL往右一直看直到包含所有字母為止,並記錄當前長度
  • 假設只有三個字母(a,b,c)

a b c b a

a b c
1 1

LL

某個作法

  • 枚舉「連續的一段」的左界LL, 開一個26格的陣列代表每個字母出現過幾次,然後從LL往右一直看直到包含所有字母為止,並記錄當前長度
  • 假設只有三個字母(a,b,c)

a b c b a

a b c
1 1 1

LL

長度:3

某個作法

  • 枚舉「連續的一段」的左界LL, 開一個26格的陣列代表每個字母出現過幾次,然後從LL往右一直看直到包含所有字母為止,並記錄當前長度
  • 假設只有三個字母(a,b,c)

a b c b a

a b c

LL

某個作法

  • 枚舉「連續的一段」的左界LL, 開一個26格的陣列代表每個字母出現過幾次,然後從LL往右一直看直到包含所有字母為止,並記錄當前長度
  • 假設只有三個字母(a,b,c)

a b c b a

a b c
1

LL

某個作法

  • 枚舉「連續的一段」的左界LL, 開一個26格的陣列代表每個字母出現過幾次,然後從LL往右一直看直到包含所有字母為止,並記錄當前長度
  • 假設只有三個字母(a,b,c)

a b c b a

a b c
1 1

LL

某個作法

  • 枚舉「連續的一段」的左界LL, 開一個26格的陣列代表每個字母出現過幾次,然後從LL往右一直看直到包含所有字母為止,並記錄當前長度
  • 假設只有三個字母(a,b,c)

a b c b a

a b c
2 1

LL

某個作法

  • 枚舉「連續的一段」的左界LL, 開一個26格的陣列代表每個字母出現過幾次,然後從LL往右一直看直到包含所有字母為止,並記錄當前長度
  • 假設只有三個字母(a,b,c)

a b c b a

a b c
1 2 1

LL

長度:4

某個作法 分析

  • 複雜度應該蠻容易可以看出是枚舉LLO(N)O(N)乘上往右掃的O(N)O(N),所以是O(N2)O(N^2)
  • 但是應該也有人發現其實LL往右一格之後,不用從新的LL開始往右掃,只要把原本LL的字母出現次數減一,然後繼續掃就好

a b c b a

a b c

LL

某個作法 分析

  • 複雜度應該蠻容易可以看出是枚舉LLO(N)O(N)乘上往右掃的O(N)O(N),所以是O(N2)O(N^2)
  • 但是應該也有人發現其實LL往右一格之後,不用從新的LL開始往右掃,只要把原本LL的字母出現次數減一,然後繼續掃就好

a b c b a

a b c
1

LL

某個作法 分析

  • 複雜度應該蠻容易可以看出是枚舉LLO(N)O(N)乘上往右掃的O(N)O(N),所以是O(N2)O(N^2)
  • 但是應該也有人發現其實LL往右一格之後,不用從新的LL開始往右掃,只要把原本LL的字母出現次數減一,然後繼續掃就好

a b c b a

a b c
1 1

LL

某個作法 分析

  • 複雜度應該蠻容易可以看出是枚舉LLO(N)O(N)乘上往右掃的O(N)O(N),所以是O(N2)O(N^2)
  • 但是應該也有人發現其實LL往右一格之後,不用從新的LL開始往右掃,只要把原本LL的字母出現次數減一,然後繼續掃就好

a b c b a

a b c
1 1 1

LL

長度:3

某個作法 分析

  • 複雜度應該蠻容易可以看出是枚舉LLO(N)O(N)乘上往右掃的O(N)O(N),所以是O(N2)O(N^2)
  • 但是應該也有人發現其實LL往右一格之後,不用從新的LL開始往右掃,只要把原本LL的字母出現次數減一,然後繼續掃就好

a b c b a

a b c
1 1

LL

某個作法 分析

  • 複雜度應該蠻容易可以看出是枚舉LLO(N)O(N)乘上往右掃的O(N)O(N),所以是O(N2)O(N^2)
  • 但是應該也有人發現其實LL往右一格之後,不用從新的LL開始往右掃,只要把原本LL的字母出現次數減一,然後繼續掃就好

a b c b a

a b c
2 1

LL

某個作法 分析

  • 複雜度應該蠻容易可以看出是枚舉LLO(N)O(N)乘上往右掃的O(N)O(N),所以是O(N2)O(N^2)
  • 但是應該也有人發現其實LL往右一格之後,不用從新的LL開始往右掃,只要把原本LL的字母出現次數減一,然後繼續掃就好

a b c b a

a b c
1 2 1

LL

長度:4

某個改進的作法

  • 也就是說
for(L = 1;L <= N;L++)
{
    for(R = L;R <= N;R++)
    {
    	//do something
        if(something happens)
        {
            ans = min(ans,R-L+1);
            break;
        }
    }
}
int R = 1;
for(L = 1;L <= N;L++)
{
    while(R <= N) 
    {
    	//do something
        if(something happens)
        {
            ans = min(ans,R-L+1);
            //do something
            break;
        }
        R++;
    }
}

某個改進的作法 分析

  • 如果跟之前一樣看每個迴圈最多跑多少再乘起來的話,複雜度還是O(N2)O(N^2)
  • 明明感覺變快不少?

某個改進的作法 分析

  • 我們之所以把複雜想成O(N2)O(N^2),是因為我們覺得中間的while迴圈最慘會執行那麼多次
  • 但其實可以發現,中間的while迴圈每跑一次,RR就會加一,但是只有RNR \leq N的時候while迴圈才會執行,所以while迴圈只會執行O(N)O(N)次!
  • 所以總複雜度其實是O(N)O(N)

第三個例題 實作時間

電皇的小寫英文字母(5)

15 min

均攤分析

  • 有時候某個迴圈跑的數量我們不確定,就只能以最糟情況來計算複雜度
  • 但或許我們可以想想,他總共跑的次數或許會受到某種限制
  • 這種分析方式稱為均攤分析,通常要正確分析會需要一些經驗
  • 剛剛例題中的演算法被稱為「雙指針」「爬行法」,是很經典可以透過均攤分析找到真正複雜度的演算法

遞迴函式 複雜度分析

  • 如果是遞迴函式,就不能把迴圈直接乘起來囉!
  • 但是遞迴函式也有各式各樣的,這邊就只先舉簡單的幾個例子
  • 主定理甚麼的聽說分治那堂課會教?

送分題

int f(int n)
{
    if(n == 1) return 1;
    return n*f(n-1);
}

f(n)f(n1)...f(1)f(n)\to f(n-1) \to ... \to f(1)

每個函式本身都O(1)O(1)

總共O(n)O(n)

快速冪

int Pow(int a,int b)
{
    if(n == 0) return 1;
    int t = Pow(a,b/2);
    if(b & 2 == 1) return t*t*a;
    else return t*t;
}

每個函式本身都O(1)O(1),共有O(log(b))O(log(b))次函式被呼叫

總共O(log(b))O(log(b))

分治預習

void f(int n)
{
    if(n <= 1) return 1;
    //Do some O(n) things
    f(n/2);
    f(n/2);
}

f(n)f(n)

f(n2)×2f(\left \lfloor \frac{n}{2} \right \rfloor)\times 2

f(n4)×4f(\left \lfloor \frac{n}{4} \right \rfloor)\times 4

\cdots

\cdots

\cdots

f(1)×nf(1)\times n

每層都是跑O(n)O(n)

總共有O(log(n))O(log(n))

總複雜度O(nlog(n))O(nlog(n))

準備下課啦

  • 除了時間複雜度之外,也有空間複雜度
  • 除了時間複雜度之外,還有常數
  • 這堂課應該比接下來的課簡單,但很重要
  • 接下來的課可能很大的比例就是在教怎麼樣才能讓複雜度降低
  • 接下來的課一定會更有趣,講師也更強,請好好期待><

題目小整理

上課例題:

  • ​6,7,11,5

回家練習:

  • ​8,9,10

謝謝大家!

Made with Slides.com