ก่อนที่จะวิเคราะห์อัลกอริทึม เราควรต้องทำความเข้าใจกับการทำงานแบบซ้ำๆของโปรแกรม เพื่อให้เข้าใจหลักการทำงานและเวลาที่ใช้ในการประมวลผล ตัวอย่างที่เห็นเด่นชัดคือฟังก์ชันเรียกซ้ำหรือรีเคอชัน ซึ่งเป็นฟังก์ชันที่เรียกตัวเองซ้ำๆจนกว่าจะทำงานเสร็จ โดยหลักการของรีเคอชันคือการแยกปัญหาที่ซับซ้อนให้เป็นปัญหาที่ง่ายขึ้นในทุกๆครั้งที่เรียกใช้ฟังก์ชันจนกระทั่งถึงจุดที่ง่ายที่สุดหรือจุดสุดท้ายในการทำงานของรีเคอชันที่เราเรียกว่าจุดสิ้นสุดการเรียกฟังก์ชันหรือ base case หลังจากนั้นฟังก์ชันก็จะทะยอยส่งผลลัพธ์กลับคืนไปยังการเรียกใช้ฟังก์ชันก่อนหน้า จนไปถึงการเรียกใช้ฟังก์ชันในครั้งแรก ก็จะได้ผลลัพธ์สุดท้ายที่สมบูรณ์ ตัวอย่างการทำงานแบบรีเคอชัน เช่นการคำนวณหาค่าของแฟคทอเรียล N! หรือ fac(N) ซึ่งเป็นการคูณเลขเริ่มจาก N และลดทีละ 1 จนถึง 1 คือ N*(N-1)*(N-2)*…*3*2*1 ซึ่งจะเห็นว่าการคูณด้วย 1 เป็นขั้นตอนสุดท้ายในการเรียกใช้ฟังก์ชันเมื่อตัวคูณเป็น 1 หรือเป็น base case ซึ่งไม่มีการเรียกซ้ำอีก และจะส่งคืนผลลัพธ์เป็น 1 ไปยังการเรียกใช้ฟังก์ชันก่อนหน้า จนย้อนกลับไปถึง N ที่มีการเรียกใช้ฟังก์ชันครั้งแรก เช่นถ้า N = 5 จะมีการเรียกใช้ฟังก์ชันซ้ำเป็น fac(5)*fac(4)*fac(3)*fac(2)*fac(1) จะได้ผลลัพธ์ที่ส่งคืนมาเป็น 1*2*3*4*5 = 120 เป็นต้น ตัวอย่างฟังก์ชันรีเคอชันในภาษา C# แสดงได้ตามรูปที่ 1.1 โดยที่จำนวนครั้งในการเรียกใช้ฟังก์ชันจนทำงานเสร็จได้ผลลัพธ์คือ N ครั้ง หรือเราอาจจเรียกว่าฟังก์ชันที่มีประสิทธิภาพหรือเวลาการทำงานแบบเชิงเส้น (Linear) ระดับ N (Order N) คือเท่ากับจำนวนอินพุท N นั่นเอง

เมื่อเราเข้าใจเรื่องเวลาในการทำงานของฟังก์ชันหรือต่อไปเราจะเรียกว่าอัลกอริทึม เราก็สามารถวิเคราะห์การทำงานของฟังก์ชันหรืออัลกอริทึมว่ามีประสิทธิภาพดีหรือไม่ดีอย่างไร โดยวัดเวลาในการทำงาน (Running Time) ของแต่ละอัลกอริทึมหรือการวิเคราะห์อัลกอริทึม (Algorithm Analysis)
อัลกอริทึมคือขั้นตอนการทำงานที่ชัดเจนของโปรแกรมเพื่อใช้ในการแก้ปัญหาใดๆ เช่นการค้นหาข้อมูล การเรียงข้อมูล ก็ต้องใช้อัลกอริทึมในการแก้ปัญหา โดยปกติเราสามารถคิดค้นอัลกอริทึมได้หลายแบบในการแก้ปัญหาเดียวกัน และเมื่อเราคิดค้นอัลกอริทึมหรือฟังก์ชัน (F) ที่ทำงานได้ถูกต้องแล้ว เราก็ต้องมาวิเคราะห์ต่อว่าอัลกอริทึมที่เราคิดได้นั้น แบบไหนดีหรือไม่ดีอย่างไร ซึ่งโดยปกติจะวัดเวลา (T) ในการทำงานเป็นหลัก โดยที่เวลาที่ใช้ก็จะขึ้นอยู่กับปริมาณข้อมูลหรือจำนวนอินพุท N นั่นเอง หรือ T (N) = F (N), โดย T คือเวลา และ F คือฟังก์ชันหรืออัลกอริทึม
มีปัจจัยหลายอย่างที่ช่วยให้โปรแกรมหรืออัลกอริทึมทำงานได้รวดเร็ว 1. เช่นความเร็วของเครื่องคอมพิวเตอร์ (เช่น CPU, RAM, DISK) 2. ประสิทธิภาพของตัวแปลภาษา (Compiler) 3. ประสิทธิภาพโปรแกรมที่ใช้สร้างอัลกอริทึม (Programmig Language) และ 4. หลักการทำงานพื้นฐานของอัลกอริทึม ซึ่งปัจจัยที่ 1,2,3 เราสามารถกำหนดให้เหมือนกันได้ ดังนั้นปัจจัยที่ 4 จึงเป็นปัจจัยหลักที่จะบอกว่าอัลกอรึทึมแบบไหนดีกว่ากัน ในการแก้ปัญหาเดียวกัน ซึ่งเราจะใช้ฟังก์ชัน Big O ในการประมาณหรือวัดการทำงานของอัลกอริทึม
Big O Notation คือเวลาในการทำงานที่ไม่เกินเวลาที่นานที่สุดที่คอมพิวเตอร์ต้องใช้ในการทำงานของอัลกอริทึมใดๆ (less that or equal to upper bound)
ยกตัวอย่าง เช่นการค้นหาข้อมูลประชากรแบบลำดับ (Sequential) จากจำนวนประชากร 70 ล้านคน เวลานานที่สุดที่ใช้ในการค้นหาคือ 70 ล้านครั้ง หรือ N ถ้าคนที่เราต้องการค้นหาอยู่ที่ลำดับสุดท้าย แต่ถ้าบังเอิญคนนั้นอยู่ที่ลำดับแรก เวลาก็จะเป็น 1 ครั้ง หรือถ้าอยู่ตรงกลาง เวลาก็จะเป็น 35 ล้านครั้ง ซึ่งจะเห็นว่าเวลาที่ใช้จะน้อยกว่าหรือเท่ากับ N เสมอ อันนี้คือตัวอย่างของฟังก์ชัน Big O ของ N หรือที่เราเรียกว่า O(N) เวลาที่มากที่สุดที่ใช้จะเกิดในกรณีที่เรียกว่า Worst Case ส่วนเวลาที่น้อยที่สุดเราเรียกว่า Best Case และเวลาเฉลี่ยเราเรียกว่า Average Case แต่โดยรวมๆ สรุปได้ว่าเวลาที่ใช้ก็คือ O(N) ซึ่งจะไม่เกิน N นั่นเอง อันนี้คือเราเรียกว่าการวิเคราะห์อัลกอริทึมด้วย Big O ซึ่งสามารถบอกได้โดยประมาณว่าอัลกอริทึมแบบไหนดีกว่ากัน เช่นการค้นหาข้อมูลแบบลำดับ ใช้เวลา O(N) ส่วนการค้นหาข้อมูลแบบไบนารี (Binary Search) ใช้เวลา O(log N) ซึ่งจะเร็วกว่าและดีกว่าแบบลำดับ


ขั้นตอน 1-3 แสดงได้ตามรูปที่ 1.3 ส่วนรายละเอียดขั้นตอนทั้งหมดในการย้ายจาน 3 ใบ แสดงได้ตามรูปที่ 1.4 Pseudocode ในการย้ายจาน แสดงได้ตามรูปที่ 1.5 ฟังก์ชันรีเคอชัน Hanoi( ) แสดงได้ตามรูปที่ 1.6 รายละเอียดฟังก์ชัน Hanoi( ) และการเรียกใช้งาน แสดงได้ตามรูปที่ 1.7 โปรแกรมทดสอบการแก้ปัญหาหอคอยฮานอย แสดงได้ตามรูปที่ 1.8 โปรแกรมกราฟิกส์จำลองการทำงานของการแก้ปัญหาหอคอยฮานอย แสดงได้ตามรูปที่ 1.9













ฟังก์ชัน Big O จะเลือกเฉพาะเทอมที่มีความสำคัญสูงสุดเท่านั้น และตัดตัวคูณที่เป็นค่าคงที่ออกไป เช่น 12N2 + 3N คือ O(N2) หรือ (N3 + 3N2 + 2N)/6 คือ O(N3) เป็นต้น
กราฟด้านล่างในรูปที่ 1.10 และ 1.11 แสดงอัตราการเพิ่มขึ้นของระยะเวลาในการทำงาน เทียบกับจำนวนข้อมูลอินพุทที่ใช้ของฟังก์ชัน Big O แต่ละแบบ โดยแกน x คือจำนวนข้อมูลอินพุท และแกน y คือ ระยะเวลาที่ใช้

จากกราฟในรูปที่ 1.10 จะเห็นว่าถ้าจำนวนข้อมูลอินพุท N มีน้อย หลายๆฟังก์ชันยังคำนวณเวลาได้ แต่บางฟังก์ชันเช่น O(N3) ไม่สามารถคำนวณเวลาการทำงานได้เมื่อ N มากขึ้น

จากกราฟในรูปที่ 1.11 จะเห็นว่าถ้าจำนวนข้อมูลอินพุท N มากขึ้น หลายๆฟังก์ชันจะคำนวณเวลาไม่ได้ แต่บางฟังก์ชันเช่น O(N) ยังสามารถคำนวณเวลาในการทำงานได้เสมอ
public class MaxSumTest
{
private static int seqStart = 0;
private static int seqEnd = -1;
static int MAX_DATA_SIZE = 1000;
// Cubic maximum contiguous subsequence sum algorithm.
// seqStart and seqEnd represent the actual best sequence.
public static int MaxSubSum1( int[ ] a )
{
int maxSum = 0;
for( int i = 0; i < a.Length; i++ )
for( int j = i; j < a.Length; j++ )
{
int thisSum = 0;
for( int k = i; k <= j; k++ )
thisSum += a[ k ];
if( thisSum > maxSum )
{
maxSum = thisSum;
seqStart = i;
seqEnd = j;
}
}
return maxSum;
}
// Quadratic maximum contiguous subsequence sum algorithm.
// seqStart and seqEnd represent the actual best sequence.
public static int MaxSubSum2( int[ ] a )
{
int maxSum = 0;
for( int i = 0; i < a.Length; i++ )
{
int thisSum = 0;
for( int j = i; j < a.Length; j++ )
{
thisSum += a[ j ];
if( thisSum > maxSum )
{
maxSum = thisSum;
seqStart = i;
seqEnd = j;
}
}
}
return maxSum;
}
// Linear-time maximum contiguous subsequence sum algorithm.
// seqStart and seqEnd represent the actual best sequence.
public static int MaxSubSum4( int[ ] a )
{
int maxSum = 0;
int thisSum = 0;
for( int i = 0, j = 0; j < a.Length; j++ )
{
thisSum += a[ j ];
if( thisSum > maxSum )
{
maxSum = thisSum;
seqStart = i;
seqEnd = j;
}
else if( thisSum < 0 )
{
i = j + 1;
thisSum = 0;
}
}
return maxSum;
}
// Recursive maximum contiguous subsequence sum algorithm.
// Finds maximum sum in subarray spanning a[left..right].
// Does not attempt to maintain actual best sequence.
private static int MaxSumRec( int[ ] a, int left, int right )
{
int maxLeftBorderSum = 0, maxRightBorderSum = 0;
int leftBorderSum = 0, rightBorderSum = 0;
int center = ( left + right ) / 2;
if( left == right ) // Base case
return a[ left ] > 0 ? a[ left ] : 0;
int maxLeftSum = MaxSumRec( a, left, center );
int maxRightSum = MaxSumRec( a, center + 1, right );
for( int i = center; i >= left; i-- )
{
leftBorderSum += a[ i ];
if( leftBorderSum > maxLeftBorderSum )
maxLeftBorderSum = leftBorderSum;
}
for( int i = center + 1; i <= right; i++ )
{
rightBorderSum += a[ i ];
if( rightBorderSum > maxRightBorderSum )
maxRightBorderSum = rightBorderSum;
}
return Max3( maxLeftSum, maxRightSum,
maxLeftBorderSum + maxRightBorderSum );
}
// Return maximum of three integers.
private static int Max3( int a, int b, int c )
{
return a > b ? a > c ? a : c : b > c ? b : c;
}
// Driver for divide-and-conquer maximum contiguous
// subsequence sum algorithm.
public static int MaxSubSum3( int[ ] a )
{
return a.Length > 0 ? MaxSumRec( a, 0, a.Length - 1 ) : 0;
}
// Simple test program.
public static void Main( string[ ] args )
{
int[ ] b = { -2, 11, -4, 13, -4, 2 };
int[] c = { 1, -3, 4, -2, -1, 6 };
int[] d = { 3, 2, -4, 3, -5, 8, -1, 7 };
int[] e = { 4, -2, 5, -7,-3, 8,-1, 1, 3, -2, 5, -4 };
int[] a = new int[MAX_DATA_SIZE];
for (int k = 0; k < 4; k++)
{
switch(k)
{
case 0:
for (int i = 0; i < b.Length; i++)
a[i] = b[i];
break;
case 1:
for (int i = 0; i < c.Length; i++)
a[i] = c[i];
break;
case 2:
for (int i = 0; i < d.Length; i++)
a[i] = d[i];
break;
case 3:
for (int i = 0; i < e.Length; i++)
a[i] = e[i];
break;
}
int maxSum;
var begin = DateTime.Now;
var end = DateTime.Now;
begin = DateTime.Now;
maxSum = MaxSubSum1(a); // cubic O(n*n*n)
end = DateTime.Now;
Console.WriteLine("Max sum1 is " + maxSum + "; it goes"
+ " from " + seqStart + " to " + seqEnd);
Console.WriteLine("Time elapsed: {0} ms\n",
(end - begin).TotalMilliseconds);
begin = DateTime.Now;
maxSum = MaxSubSum2(a); // quadratic O(n*n)
end = DateTime.Now;
Console.WriteLine("Max sum2 is " + maxSum + "; it goes"
+ " from " + seqStart + " to " + seqEnd);
Console.WriteLine("Time elapsed: {0} ms\n",
(end - begin).TotalMilliseconds);
begin = DateTime.Now;
maxSum = MaxSubSum3(a); // logarithm O(n log n)
end = DateTime.Now;
Console.WriteLine("Max sum3 is " + maxSum + "; it goes"
+ " from " + seqStart + " to " + seqEnd);
Console.WriteLine("Time elapsed: {0} ms\n",
(end - begin).TotalMilliseconds);
begin = DateTime.Now;
maxSum = MaxSubSum4(a); // Linear O(n)
end = DateTime.Now;
Console.WriteLine("Max sum4 is " + maxSum + "; it goes"
+ " from " + seqStart + " to " + seqEnd);
Console.WriteLine("Time elapsed: {0} ms\n",
(end - begin).TotalMilliseconds);
}
Console.ReadKey();
}
}
สังเกตว่าเวลาในการทำงาน Qubic > Qudratic > n log n > n ตรงตามค่าของฟังก์ชัน Big O สำหรับเวลาในการทำงานของ O(n) จะเป็น 0 เพราะอินพุทมีจำนวนน้อย คอมพิวเตอร์ทำงานได้เร็วมากกว่าอินพุทที่มีอยู่จึงวัดเวลาได้เป็น 0 จะต้องทำการเพิ่มอินพุทเป็นจำนวนมากๆ O(n) ก็จะวัดเวลาได้ แต่จะทำให้ Qubic กับ Quadratic ใช้เวลานานมากๆ เมื่ออินพุทมีจำนวนมาก
2020. mnet50.com