• 沒有找到結果。

1 倍增算法

N/A
N/A
Protected

Academic year: 2022

Share "1 倍增算法 "

Copied!
4
0
0

加載中.... (立即查看全文)

全文

(1)

2017 資訊之芽 作業 6 Deadline: 2017/04/08

1 倍增算法

有時候我們常常可以透過「把問題切一半遞迴處理」來獲得問題的解法或者更快速 的算法,如之後會提到的分治算法、merge sort 等等。然而,反過來說,如果我們把 已知的小範圍方法透過「把解法放大一倍遞推處理」,有時也會有意想不到的效果。以 下我們用動態陣列 (dynamic array) 來介紹倍增算法的應用。

在許多情況下,我們在儲存資料之前沒辦法知道資料的總量,從而很難決定陣列 宣告的大小。如果一開始宣告的陣列大小不夠,就很容易存取到超出陣列範圍的記憶 體而發生不預期的結果;反之如果一開始就宣告很大的陣列範圍,則會造成記憶體的 浪費,甚至超過限制的記憶體大小。如果只需要存取陣列頭尾的元素的話,可以使用 之前教過的 linked list 實做,但如果需要支援隨機存取 (random access),就不能使用 linked list 了。此時動態陣列就派上用場,可以想成它是一個「大小會自己伸縮的陣 列」,以下為一種只考慮新增元素至陣列尾端,不考慮刪除元素的動態陣列實做方法 (意即,只會變長,不會縮短):

• 記錄一個陣列的指標,當前宣告的陣列的大小 (capacity),以及當前在陣列中 的元素個數 (size)。該陣列指標指向的陣列是真正存放元素的地方。初始化時,

capacity = 1, size = 0。

• 新增一個元素時,若當前的元素個數未達上限 (size < capacity),則直接將 該元素放進陣列尾端,並將 size 增加 1。若元素個數已達上限,則動態宣 告 (C 中的 malloc 或 C++ 中的 new) 一個大小為 capacity× 2 的陣列,並用 O(capacity) 的時間將當前陣列的所有元素都複製過去新陣列,接著釋放原陣列 的記憶體 (C 中的 free 或 C++ 中的 delete),最後再放入新增的元素,將 size 增加 1。

以下以圖示說明新增元素的操作:

1. 假設當前的容器內有 size=4 個元素:(1,2,3,4),且 capacity=4

2. 當要新增元素「5」時,發現已經沒有空間了,因此先宣告一個大小為原本兩倍 的陣列

1

(2)

2017 資訊之芽 作業 6 Deadline: 2017/04/08

3. 將原本陣列中的元素一個一個搬到新的陣列中

4. 最後將原本的陣列刪除,指標指向新陣列,並將「5」新增到新陣列中

動態陣列的操作複雜度將在作業中請大家證明,以下連結是一份只能從尾端插入元 素的動態陣列程式碼。

Link: dynamic_array.c , dynamic_array.cpp

順帶一提,C++ STL 中的 vector 容器即是動態陣列,是個好用的資料結構。以下 簡單介紹一些 std::vector 的常用函數:

• size():容器內的元素個數

• empty():容器是否是空的

• clear():清除所有容器內的元素

• push_back(x):新增元素 x 至容器末端

• pop_back():刪除容器末端的元素

2

(3)

2017 資訊之芽 作業 6 Deadline: 2017/04/08

習題

1. 了解動態陣列的原理之後,請推導新增元素時的複雜度。(malloc/new 的時間可以 不考慮)

(a) (20 pts) 請證明一個動態陣列若從初始狀態開始進行了 n 次的新增元素操作,

總時間複雜度為 O(n),空間複雜度也是 O(n)。

(b) (20 pts) 請證明一個動態陣列若從初始狀態開始進行了 n 次的新增元素操作,

但擴張陣列時,大小不是增加到 capacity× 2,而是 capacity + 1,則總時間 複雜度為 O(n2),空間複雜度是 O(n)。

2. 有 n 個節點,每個節點內只存著它的下一個節點。已知從 start 開始,每次走到當 前節點的下一個節點,最終可以走過所有的節點,並且進入一個環。也就是說,這 些節點形成的形狀就像字母 ρ 一樣。

1 struct Node {

2 struct Node* next;

3 };

4 struct Node* start;

請在不知道 n 的確切數值之下,找出這個環的長度。(意即,不能有如「從 start 開 始走 n 步」的敘述,因為你並不知道 n 是多少)

(a) (10 pts) 請給出時間複雜度 O(n),不限制空間大小的做法。(額外空間宣告在 Node 裡面或外面皆可)

(b) (20 pts) 給定 k,且假設環和起點的距離小於 k,請描述如何判斷環的大小是否 小於 k,如果小於 k 的話還要給出環的大小。另外,限制時間複雜度為 O(k),

空間複雜度為 O(1)。(環和起點的距離為 d 表示從起點開始至少走 d 步之後會 進入環)

(c) (20 pts) 令 k 從 1 開始,檢查 (b) 中提到的判斷是否成立,如果成立則可以得 到環的長度,否則將 k 變成 2 倍,繼續判斷直到得到環的長度。請證明這個演 算法一定會停止,並證明該演算法的時間複雜度為 O(n),空間複雜度為 O(1)。

3. 以下為 2015 年資訊之芽入芽考的其中一題。

3

(4)

2017 資訊之芽 作業 6 Deadline: 2017/04/08

Description

由於円円嚴重缺乏運動,他的好朋友們幫他設計了一個好玩的跳格子遊戲,讓他在 娛樂之餘還能順便活動身體,希望能讓他再長高一點 (雖然應該希望渺茫了)。

這個跳格子的遊戲是這樣的:一開始在地上畫出一條 1× N 的方格圖,並且大小依 序標上編號 0 ~ (N − 1)。接著在每個格子裡面寫上一個數字 ai,表示當円円跳到 第 i 格之後,下一次就要跳到第 ai 格。由於在格子內轉身不太方便,因此円円的 好朋友們十分好心,填上數字時一定保證 ai ≥ i,也就是說,円円只會往前跳或是 待在原地,而不會往後跳。

現在已知円円一開始在第 x 格,請問他跳了 k 步之後會停在哪格呢?

Input

第一行為兩個正整數 N, Q,表示共有 N 個格子,且有 Q 次詢問。

第二行為 N 個整數,依序為 a0, a1,· · · , aN−1

接著 Q 行,每行為兩個正整數 x, k,表示円円一開始在第 x 個格子,並且接著要 跳 k 步。

• 0 ≤ x, k ≤ (N − 1)

(a) (5 pts) 如果對於每筆詢問 (x, k),都一步一步的跳 (所謂的一步一腳印) 得到最 後的答案,時間複雜度為何?(請以 N, Q 表示)

(b) (10 pts) 若以 a[i] 表示從第 i 格跳 1 步之後所到達的格子編號 (即題目中的 ai),

請以 (a, i) 表示從第 i 格跳 2 步之後所到達的格子編號。

(c) (10 pts) 承上題,若以 b[i][j] 表示從第 i 格跳 2j 步之後所到達的格子編號,請 以 (b, i, j− 1) 表示 b[i][j] 在 j > 0 時的遞迴關係。

b[i][j] =

{a[i] , if j = 0

??? , if j > 0

(d) (10 pts) 承上題,假設已經建好了 b[i][j] 陣列,給定 x, k,請在 O(log k)(或是 O(log N ))的時間內得到從 x 開始跳 k 步之後所在的格子編號。

(e) (5 pts) 承上題,請問上述演算法的時間複雜度為何?(請以 N, Q 表示)

4

參考文獻

相關文件

門得列夫當時所提出的未知 元素

 當化合物是由兩種元素組成時,則稱為二元化 合物(binary compound)。二元離子化合物的實

原則上維持九年一貫課程的主題課程書寫方 式,在評量方式之前新增一個欄位為學習表 現,就每一主題的單元活動擬訂達成學習目

氧化 氧化 氧化: 氧化 : : :凡在化學反應中,元素之氧化數增加,或失去電子時稱為氧化 氧化 氧化(Oxidation)反應;反之,反應後 氧化 反應後 反應後元素之 反應後

雖然 Gal(L/K) 中的元素 不只是 L 到 L 的函數, 還必須是 ring homomorphism 且是 1-1 and onto, 不過兩個 ring homomorphisms 相加有可能不再是 ring homomorphism, 而兩個 1-1 and onto

不過 vector space 不 只是一個 group, 它的元素還可以和一個 field 的元素 “作用” (action), 這是所謂的 scaler multiplication... 注意這 8

這是因為依 basis 的定義, basis 中的元素都應是 open set, 再加上 open sets 的聯集仍為 open set, 因此若兩個 topology 有相同的 basis, 會造成這兩個 topology 有 相同的 open sets,

一個 vector space V , 若給定一組 basis 後, 由於 V 中的元素用這組 basis 都只有唯一 的表法, 所以我們可以將 V 中的元素用熟悉的向量坐標來表示.. 另一方面, 一個 linear transformation,