การวัดอัลกอริทึม (Analysis of Algorithm) บทที่ 7 การวัดอัลกอริทึม (Analysis of Algorithm)
บทนำ ตัวอย่าง การสลับค่าของ x และ y โปรแกรม 1 ขั้นตอน 1: x = y โปรแกรม 2 ขั้นตอน 1: xold = x ขั้นตอน 2: yold = y ขั้นตอน 3: y = yold ขั้นตอน 4: x = xold โปรแกรม 3 ขั้นตอน 1: x = x + y ขั้นตอน 2: y = y - x ขั้นตอน 3: x = x + y ขั้นตอน 4: y = -y
บทนำ การทำงานของโปรแกรม 3 เมื่อเปรียบเทียบกับโปรแกรม 2 จะเห็นว่ามีขั้นตอนเท่ากัน แต่จะมีการใช้เนื้อที่ของหน่วยความจำที่ต่างกัน ในโปรแกรม 3 การใช้เนื้อที่หน่วยความจำ จะน้อยกว่าแบบโปรแกรม 2 ถ้าหากจะเปรียบเทียบความสำคัญของจำนวนขั้นตอน เมื่อเทียบกับเนื้อที่ของหน่วยความจำที่ใช้ จำนวนขั้นตอนที่น้อยจะมีความสำคัญกว่า ดังนั้นในการเขียนโปรแกรม ควรคำนึงถึงจำนวนขั้นตอนที่ใช้มากกว่า เพราะการมีขั้นตอนน้อยจะทำให้การทำงานของโปรแกรมเร็วขึ้น
Algorithms a tool for solving a well-specified computational problem a sequence of computational steps that transform the input into the output ประโยชน์ ช่วยทำให้การเขียนโปรแกรมง่ายขึ้น ไม่สับสน และสามารถแบ่งการทำงานเป็นโมดูลได้ สามารถใช้ในการทดสอบ และนำไปใช้ในการวัดประสิทธิภาพได้
an instance of the sorting problem Algorithms Sorting problem: Input: a sequence of n numbers <a1,a2,…,an> Output: a reordering <a’1,a’2,…,a’n> of the input sequence such that a’1 <= a’2 <=…<= a’n Example: <31,41,59,26,41,58> <26,31,41,41,58,59> an instance of the sorting problem An algorithms is correct if, for every input instance, it halts with the correct output.
Elementary operation Operation whose execution time can be bounded above by a constant depending only on the particular implementation used - machine - programming - compiler Ex. การบวกใช้เวลา ta s , การคูณใช้เวลา tm s , การลบใช้เวลา ts s t = (a x ta )+ (m x tm) + (s x ts) ≤ max(ta,tm, ts ) x (a+m+s) (a+m+s)
t #คำสั่ง(elementary) Elementary operation สรุป : การวิเคราะห์อัลกอริทึม(เชิงเวลา) เป็นการนับจำนวน elementary operation ที่ทำงาน t #คำสั่ง(elementary) x min { T[i] | i ≤ i ≤ n } ไม่เป็น elementary operation
การนับจำนวนครั้งของการใช้งานคำสั่ง วิธีหนึ่งที่ใช้ในการวิเคราะห์เชิงเวลา คือการหาผลรวมของเวลาการทำงานของคำสั่งต่างๆ ในโปรแกรมซึ่งถูกใช้งาน ดังตัวอย่าง (กำหนดให้เวลาการทำงานของคำสั่งในบรรทัดที่ i เป็น ti วินาที) 01: function sum(var x : vector; n : integer) : integer; 02: var i,s : integer; 03: begin 04: s := 0; 05: for i:=1 to n do 06: s:=s + x[i]; 07: sum:= s; 08: end;
t1+t2+t3+t4+ (t5+t6)+t5+t7+t8 การนับจำนวนครั้งของการใช้งานคำสั่ง จากตัวอย่าง sum ทำหน้าที่หาผลรวมของจำนวนในเวกเตอร์ X ตั้งแต่ช่องที่ 1 ถึง n โดยใช้เวลารวมทั้งสิ้นเท่ากับ t1+t2+t3+t4+ (t5+t6)+t5+t7+t8 n i=1 4c1 + ( 2c1) + 3c1 n i=1 c1(2n+7)
สรุปได้ว่า sum ใช้เวลาการทำงานเป็นฟังก์ชันเชิงเส้นของ n คำสั่งมาตรเวลา คำสั่งมาตรเวลา เป็นคำสั่งที่ถูกใช้งานมากที่สุดในโปรแกรม นั่นคือเวลาการทำงานโดยรวมจะแปรโดยตรงตามจำนวนครั้งที่คำสั่งมาตรเวลาถูกใช้งาน จากตัวอย่างที่แล้ว คำสั่งมาตรเวลาของโปรแกรมคือ for(บรรทัดที่ 5) ซึ่งจะถูกเรียกใช้งานเป็นจำนวน n+1 ครั้ง หรือจะใช้คำสั่งในบรรทัดที่ 6 ภายใน loop for ก็ได้ จำนวนครั้ง ที่ถูกเรียกใช้งาน เป็นจำนวน n ครั้ง สรุปได้ว่า sum ใช้เวลาการทำงานเป็นฟังก์ชันเชิงเส้นของ n
การวิ เคราะห์เชิงเส้นกำกับ การวิเคราะห์เชิงเส้นกำกับ เป็นการศึกษาพฤติกรรมของอัตราการเติบโตของฟังก์ชันเวลาการทำงานของโปรแกรม เมื่อ n มีค่ามาก มีค่ามากแบบเข้าใกล้อนันต์ ตัวอย่างเช่น ถ้าต้องการเปรียบเทียบฟังก์ชัน 1000n กับ 0.1n2 จากรูป จะเห็นว่า 1000n ให้ค่ามากกว่า 0.1n2 เมื่อ n<=10000 แต่เมื่อ n>10000 ฟังก์ชัน 0.1n2 จะให้ค่ามากกว่าเสมอ ทั้งนี้เพราะ n2 มีอัตราการเติบโตที่เร็วกว่ามาก
กราฟเส้นของฟังก์ชัน 1000n กับ 0.1n2 การวิ เคราะห์เชิงเส้นกำกับ กราฟเส้นของฟังก์ชัน 1000n กับ 0.1n2
การวิ เคราะห์เชิงเส้นกำกับ หรือจะใช้เรื่องของลิมิตเพื่อเปรียบเทียบการเติบโตของฟังก์ชันโดยไม่ต้องวาดกราฟเส้น ก็จะได้ว่า แสดงให้เห็นว่าเมื่อ n มีค่ามากเป็นอนันต์ สัดส่วนของ 1000n กับ 0.1n2 มีค่าเข้าใกล้ศูนย์ หมายความ 0.1n2 นั้นให้ค่าที่เพิ่มขึ้นเมื่อ n เพิ่มมากกว่าค่าของ 1000n มาก จึงมีอัตราการเติบโตที่เร็วกว่า
ตัวอย่างฟังก์ชันที่มีอัตราการเติบโตแตกต่างกัน การวิ เคราะห์เชิงเส้นกำกับ ตัวอย่างฟังก์ชันที่มีอัตราการเติบโตแตกต่างกัน
การวิ เคราะห์เชิงเส้นกำกับ ดังนั้นเมื่อต้องการเปรียบเทียบการเติบโตของฟังก์ชัน f(n) กับ g(n) (ต้องขอเน้นว่าในที่นี้เราสนใจเฉพาะ n และฟังก์ชันของ n ที่ให้ค่าไม่ติดลบ เพราะฟังก์ชันนี้แทนเวลาการทำงานของโปรแกรม) ก็เพียงแต่หาค่าลิมิตของ f(n) / g(n) เมื่อ n ซึ่งสามารถตีความผลที่ได้ดังนี้ ถ้า แสดงว่า f(n) โตช้ากว่า g(n) ถ้า แสดงว่า f(n) โตเร็วกว่า g(n) ถ้า โดยที่ c 0 และ c แสดงว่า f(n) โตในอัตราที่เท่ากับ g(n)
การวิ เคราะห์เชิงเส้นกำกับ สรุป ถ้า f1(n) และ f2(n) เป็นฟังก์ชันของเวลาการทำงานของโปรแกรมที่ 1 และโปรแกรมที่ 2 ตามลำดับ โดย n เป็นขนาดของข้อมูลปัญหาที่โปรแกรมทั้งสองรับเข้าไปประมวลผล ถ้า f1(n) เป็นฟังก์ชันที่โตเร็วกว่า f2(n) สรุปว่า โปรแกรมที่ 1 ทำงานช้ากว่าโปรแกรมที่ 2 เมื่อ n มีค่ามาก และในทางกลับกัน ถ้า f1(n) เป็นฟังก์ชันที่โตช้ากว่า f2(n) สรุปว่า โปรแกรมที่ 1 ทำงานเร็วกว่าโปรแกรมที่ 2 เมื่อ n มีค่ามาก
สัญกรณ์เชิงเส้นกำกับ (Asymptotic notation) สัญลักษณ์ที่ใช้อธิบายการเติบโตของฟังก์ชั่น โดยในการวิเคราะห์อัลกอริทึมจะนำสัญกรณ์นี้มาใช้ในการระบุประสิทธิภาพของอัลกอริทึม
สัญกรณ์โอใหญ่ (Big-O notation : O) นิยาม O(f(n)) = { t(n) โดยที่ 0 ≤ t(n) ≤ cf(n) } เป็นเซตของฟังก์ชั่นทั้งหมดที่โตเร็วกว่า t(n) เป็นขอบบนของฟังก์ชั่นนั้น ๆ ตัวอย่าง ความหมายของ O(n) คือ ฟังก์ชั่นนั้น ๆ ใช้เวลาทำงานช้าที่สุด ≤ n เช่น อัลกอริทึม a1 มีประสิทธิภาพเป็น O(n2) ถ้า n = 10 แล้ว a1 จะใช้เวลาทำงานช้าที่สุด 100 หน่วยเวลา (รับประกันว่าไม่ช้าไปกว่านี้ - แต่อาจจะเร็วกว่านี้ได้)
โอเมก้าใหญ่ (Big-Omega notation : Ω) นิยาม Ω(f(n)) = { t(n) โดยที่ 0 ≤ cf(n) ≤ t(n) } เป็นเซตของฟังก์ชั่นทั้งหมดที่โตช้ากว่า t(n) เป็นขอบล่างของฟังก์ชั่นนั้น ๆ ตัวอย่าง ความหมายของ Ω(n) คือ ฟังก์ชั่นนั้น ๆ ใช้เวลาทำงานเร็วที่สุด ≥ n เช่น อัลกอริทึม a1 มีประสิทธิภาพเป็น Ω(n) ถ้า n = 10 แล้ว a1 จะใช้เวลาทำงานเร็วที่สุด 10 หน่วยเวลา (รับประกันว่าไม่เร็วไปกว่านี้ - แต่อาจจะช้ากว่านี้ได้)
O vs Ω f(n) t(n) t(n) = O(f(n)) และ f(n) = Ω(t(n))
เตต้าใหญ่ (Big-Teta notation : Ө) นิยาม f(n) = Ө(g(n)) ก็ต่อเมื่อ f(n) = O(g(n)) และ f(n) = Ω(g(n)) ขอบบนและขอบล่างเป็นฟังก์ชั่นเดียวกัน สังเกตว่า ขอบบน กับ ขอบล่าง เป็นฟังก์ชั่นเดียวกัน สัมประสิทธิ์ต่างกัน ความหมายของเตต้าคือ ใช้เวลาทำงาน = n
โอเล็ก (Little-o : o) นิยาม o(f(n)) = { t(n) โดยที่ 0 ≤ t(n) < cf(n) } ต่างจาก Big-O ตรงที่ Little-o จะไม่แตะขอบบน นั่นคือ ฟังก์ชั่นนี้ ทำงานช้าที่สุดไม่ถึง เช่น หากเรามี t(n) = n0.98 + 0.05√n เราสามารถเขียนได้เป็น O(n) หรือ o(n) แต่หากระบุเป็น Little-o จะเน้นให้เห็นชัดว่าไม่ถึง n (เพราะค่ากำลังของ n คือ 1 แต่ในฟังก์ชั่น t(n) ค่ากำลังของ n คือ 0.98)
โอเมก้าเล็ก (Little-omega : ω) นิยาม ω(f(n)) = { t(n) โดยที่ 0 ≤ cf(n) < t(n) } ต่างจาก Big-omega ตรงที่ Little-omega จะไม่แตะขอบล่าง นั่นคือ ฟังก์ชั่นนี้ทำงานเร็วที่สุดมากกว่า n
สรุปสัญลักษณ์ O(n) o(n) ω(n) Ω(n)
การหาเทอมที่โตเร็วที่สุดในฟังก์ชั่น รูปแบบของฟังก์ชั่นที่มักพบบ่อยได้แก่ exponential อยู่ในรูป an polynomial อยู่ในรูป na Linear อยู่ในรูป n logarithmic อยู่ในรูป log a n ทั้ง 3 รูปแบบ จะมีอัตราการเติบโตเรียงจากมากไปหาน้อย
กราฟแสดงการเติบโตของฟังก์ชั่น
การนับเวลาการทำงานของอัลกอริทึม ละเอียด ได้ฟังก์ชั่นที่ซับซ้อน คร่าว ๆ ยุบฟังก์ชั่นที่ซับซ้อน โดยบรรยายด้วยสัญกรณ์เชิงเส้นกำกับ การแก้ปัญหาเดียวกัน ด้วยวิธีที่ต่างกัน จะทำให้ได้ประสิทธิภาพการทำงานที่ต่างกันมาก
การนับเวลาการทำงานของอัลกอริทึม ตัวอย่างการหาค่าของแต่ละเทอมในอนุกรม Fibonacci FibRec(n) if n<2 then return n else return(FibRec(n-1)+FibRec(n-2))
การนับเวลาการทำงานของอัลกอริทึม สมมติต้องการหาเทอมที่ 5 โดยใช้ recursive tree จะได้ว่า F5 F4 F3 F3 F2 F2 F2 F1 F1 F0 F1 F0 - ทำงานซ้ำซ้อน - เวลาทำงานช้า เป็น exponential
การนับเวลาการทำงานของอัลกอริทึม FibIter(n) i=1 ; j=0 for k = 1 to n do j = i+j i = j-i return j k J i 1 2 3 4 5 1 1 2 3 5 1 1 2 3
การนับเวลาการทำงานของอัลกอริทึม เปรียบเทียบเวลาการทำงาน n 10 20 30 40 50 แปรตาม FibRec 8 ms 1 s 2 min 21 days 109 yrs ค่าคงที่n FibIter 1/6 ms 1/3 ms 1/2 ms 3/4 ms 1.5 ms n
การนับจำนวนคำสั่ง ตัวอย่างการเรียงลำดับข้อมูลแบบ Insertion Sort 5 4 2 3 6 4 5 2 3 6 2 4 5 3 6 2 3 4 5 6
การนับจำนวนคำสั่ง Times n n-1 n-1 InsertionSort(A) 1 for j=2 to length(A) do 2 key=A[j] 3 i=j-1 4 while i>0 and A[i]>key do 5 A[i+1]=A[i] 6 i=i-1 7 A[i+1]=key n-1
การนับจำนวนคำสั่ง จะได้ว่า T(n) = c1n+c2(n-1)+c3(n-1)+c4 +c5 +c6 +c7(n-1) สรุป T(n) เป็นฟังก์ชั่นของ n คือจำนวนข้อมูล และ ลักษณะของข้อมูล
การนับจำนวนคำสั่ง while i>0 and A[i]>key 5 4 3 2 1 4 5 3 2 1 5 4 3 2 1 4 5 3 2 1 3 4 5 2 1 2 3 4 5 1 1 2 3 4 5 ในแต่ละรอบ จะมีการเปรียบเทียบมากครั้งขึ้นเรื่อย ๆ
การนับจำนวนคำสั่ง while i>0 and A[i]>key 1 2 3 4 5 1 2 3 4 5 ในแต่ละรอบ จะใช้การเปรียบเทียบรอบละ 1 ครั้ง
การนับจำนวนคำสั่ง ดังนั้น Best case : กรณีที่ข้อมูลเรียงลำดับจากน้อยไปมากอยู่แล้ว (tj = 1) T(n) = c1n+c2(n-1)+c3(n-1)+c4(n-1)+c7(n-1) = (c1+c2+c3+c4+c7)n – (c2+c3+c4+c7)
การนับจำนวนคำสั่ง และ = = Worst case : กรณีที่ข้อมูลเรียงกลับลำดับ (tj = j) = = และ ดังนั้น T(n) = c1n+c2(n-1)+c3(n-1)+c4( ) +c5( )+c6( )+c7(n-1) =( )n2 +( )n -(c2+c3+c4+c7)
การนับจำนวนคำสั่ง worst case (n2) best case (n)
การนับจำนวนคำสั่ง สรุป วัดประสิทธิภาพในรูปของเวลาการทำงาน แต่เนื่องจากเราไม่สามารถวัดเวลาที่ใช้จริงได้ จึงใช้วิธีนับจำนวนคำสั่งแทน โดยหาฟังก์ชั่นที่แทนความสัมพันธ์ระหว่างขนาดของข้อมูลกับเวลา เมื่อได้ฟังก์ชั่นแล้ว เราสามารถเปรียบเทียบประสิทธิภาพได้เมื่อเพิ่มขนาดของข้อมูล ฟังก์ชั่นที่ได้ไม่จำเป็นต้องเขียนในรูปที่ซับซ้อน สามารถลดรูปให้ดูง่ายลงโดยใช้ตัวแทนในการบรรยายคือสัญกรณ์เชิงเส้นกำกับ
Control Structure Sequencing ตัวอย่าง P1 P2 t1 t1+t2 = Ө(max(t1,t2)) เวลาในการทำงานทั้งหมดถูกกำหนดโดยส่วนที่ 1 เพราะมันโตเร็วกว่า Ө(n2) Ө(n log n)
Control Structure Selection if … then else … if else ตัวอย่าง พิจารณาเช่นเดียวกับ Sequencing นั่นคือส่วนที่โตเร็วสุดเป็นส่วนกำหนดเวลาการทำงานโดยรวม if … then P1 else … if P2 else P3 ตัวอย่าง ชุดคำสั่ง P1,P2 และ P3 ใช้เวลาทำงานเป็น O(n2), O(n log n) และ O(n) ตามลำดับ คำสั่ง if P1 then P2 else P3 จะใช้เวลาเป็น O(n2) เพราะเป็นส่วนที่โตเร็วที่สุด
Control Structure for i=1 to m do P(i) ถ้า P(i) ใช้เวลา ti -> For loop for i=1 to m do P(i) ถ้า P(i) ใช้เวลา ti -> ถ้า P(i) ใช้เวลา t -> = mt ตัวอย่าง 1 for i = 1 to n c+=A[i] หมุน n รอบ ใช้เวลาเป็น O(n) เป็น elementary operation ใช้เวลาคงที่เป็น O(1)
Control Structure ตัวอย่าง 2 for i = 1 to n for j = n downto 1 c+=x[i][j] O(n) O(n2)
Control Structure While loop i = 1, j = n while i < j i = i+3 j = j-5
Control Structure While loop BinarySearch(T[1..n],x) i=1; j=n while i<j do m=(i+j)/2 case x < T[m] : j=m-1 x > T[m] : i=m+1 x = T[m] : i=j=m return i
Control Structure สังเกตการเปลี่ยนแปลง n -> n/2 -> n/22 -> n/23 -> … -> n/2k n/2k = 1 n = 2k k = log2n = O(log n) ทำไป k รอบ แล้วผลลัพธ์เท่ากับ 1 นั่นคือ n/2k = 1
แบบฝึกหัด SequentialSearch(A[1..n],x) i = n while i>0 and a[i] <> x i=i-1 return i Exam(n) for i = 1 to n for j = 1 to n s=s+1 for x = 1 to 4 print x
แบบฝึกหัด i = 0, j = n, s = 0 while i < j i = i+2 s++ j = j-3
การนับจำนวนครั้งของการใช้งานคำสั่ง 01: procedure matrixMult(var x,y,z:matrix; n:integer); 02: var i,j,k : integer; 03: begin 04: for i:=1 to n do 05: for j:=1 to n do 06: z[i,j]:= 0.0; 07: for i:=1 to n do 08: for j :=1 to n do 09: for k:=1 to n do 10: z[i,j] := z[i,j] + x[i,k] * y[k,j] 11: end;