Divide & Conquer
หลักการ
- ประกอบด้วยขั้นตอนหลักสามขั้นตอน
- Divide แบ่งปัญหาให้เป็นปัญหาเดียวกันที่ขนาดเล็กลง
- Conquer แก้ปัญหาย่อยๆ ให้ได้
- Combine รวบรวมคำตอบของปัญหาย่อยที่ถูกแก้แล้วมารวมกันเพื่อแก้ปัญหาเดิมที่ใหญ่กว่า
- ปัญหาที่ยังสามารถย่อยต่อเพื่อแก้ปัญหาได้เรียกว่า recursive case
- ปัญหาที่ขนาดเล็กเพียงพอที่จะแก้ปัญหาได้โดยไม่ต้องย่อยต่อเรียกว่า base case
- ปัญหาส่วนใหญ่จะใช้เวลา โดย มาจากการแบ่งปัญหาเป็นครึ่งๆ ไปเรื่อยๆ
General Pseudocode
DAC(p):
if is_base_case(p):
solve(p)
else:
divide p into p_1, p_2, ..., p_k
apply DAC(p_1), DAC(p_2), ..., DAC(p_k)
combine(DAC(p_1), DAC(p_2), ..., DAC(p_k))
Binary Search
การหาจำนวน (หรือคำตอบที่ต้องการ) ในอารเรย์ที่มีการเรียงแล้ว โดยตัวอัลกอริธึมจะตรวจสอบกับตัวเลขตรงกลาง ก่อนที่จะแบ่งอาร์เรย์ออกเป็นสองฝั่งเพื่อหาตัวเลขนั้นอีกครั้ง หากตัวเลขที่กำลังหาอยู่มากกว่าตัวเลขตรงกลาง ก็จะไปหาจำนวนนั้นในอาร์เรย์ฝั่งขวา แต่ถ้าหากตัวเลขที่กำลังหาอยู่น้อยกว่าตัวเลขตรงกลาง ก็จะไปหาจำนวนนั้นในอาร์เรย์ฝั่งซ้าย
Pattern
int lo = 0, hi = N;
while (lo <= hi) {
int mi = (lo + hi) / 2; // ระวัง overflow ตอน lo+hi
if (A[mi] > target) {
hi = mi - 1;
} else {
lo = mi + 1;
}
}
STL
- upper_bound
- lower_bound
Binary Search คำตอบ
- ปัญหาบางอย่างสามารถสมมุติคำตอบแล้ว check ได้เร็ว
- หากคำตอบมี pattern เช่น ถ้าค่าน้อยสามารถทำได้แล้วค่าที่มากกว่าจะทำได้ด้วยเสมอ ก็จะสามารถ binary search ได้
More on Binary Search
Closest Pair
https://www.geeksforgeeks.org/closest-pair-of-points-using-divide-and-conquer-algorithm/
ปัญหาเลขยกกำลัง
พิจารณาวิธีหาค่าของ หากทำการคูณเลขตรงๆ จะเป็น
ถ้าเราแบ่ง
- Recursive case
- Base case
จำนวน Fibonacci
จำนวนฟิโบนัชชี (เขียนแทนว่า ) คือลำดับจำนวนที่ถูกนิยามจากผลบวกของจำนวนฟิโบนัชชีสองตัวก่อนหน้า โดยกำหนดให้จำนวนฟิโบนัชชีสองตัวแรกเป็น 0 และ 1 ตามลำดับ นั่นคือ
โดยจะสามารถเขียนลำดับได้เป็น
เพราะฉะนั้นแล้ว Base Case ก็คือ และ Recursive Case ก็คือ
Merge Sort
void merge(int array[], int const left, int const mid, int const right) {
auto const subArrayOne = mid - left + 1;
auto const subArrayTwo = right - mid;
// Create temp arrays
auto *leftArray = new int[subArrayOne], *rightArray = new int[subArrayTwo];
// Copy data to temp arrays leftArray[] and rightArray[]
for (auto i = 0; i < subArrayOne; i++)
leftArray[i] = array[left + i];
for (auto j = 0; j < subArrayTwo; j++)
rightArray[j] = array[mid + 1 + j];
auto indexOfSubArrayOne = 0, // Initial index of first sub-array
indexOfSubArrayTwo = 0; // Initial index of second sub-array
int indexOfMergedArray = left; // Initial index of merged array
// Merge the temp arrays back into array[left..right]
while (indexOfSubArrayOne < subArrayOne && indexOfSubArrayTwo < subArrayTwo) {
if (leftArray[indexOfSubArrayOne] <= rightArray[indexOfSubArrayTwo]) {
array[indexOfMergedArray] = leftArray[indexOfSubArrayOne];
indexOfSubArrayOne++;
} else {
array[indexOfMergedArray] = rightArray[indexOfSubArrayTwo];
indexOfSubArrayTwo++;
}
indexOfMergedArray++;
}
// Copy the remaining elements of
// left[], if there are any
while (indexOfSubArrayOne < subArrayOne) {
array[indexOfMergedArray] = leftArray[indexOfSubArrayOne];
indexOfSubArrayOne++;
indexOfMergedArray++;
}
// Copy the remaining elements of
// right[], if there are any
while (indexOfSubArrayTwo < subArrayTwo) {
array[indexOfMergedArray] = rightArray[indexOfSubArrayTwo];
indexOfSubArrayTwo++;
indexOfMergedArray++;
}
}
Square Root Decomposition
- https://usaco.guide/plat/sqrt?lang=cpp
- https://cp-algorithms.com/data_structures/sqrt_decomposition.html
เพิ่มเติม
- การบรรยายวิชา การออกแบบและวิเคราะห์อัลกอริทึม
โดย สมชาย ประสิทธิ์จูตระกูล
- อัลกอริทึม : 3.1 การแบ่งแยกและเอาชนะ - แนวคิด
- อัลกอริทึม : 3.2 การแบ่งแยกและเอาชนะ - การยกกำลัง
- อัลกอริทึม : 3.3 การแบ่งแยกและเอาชนะ - การคูณจำนวนเต็ม
- อัลกอริทึม : 3.4 การแบ่งแยกและเอาชนะ - การคูณเมทริกซ์
- อัลกอริทึม : 3.5 การแบ่งแยกและเอาชนะ - การเรียงลำดับแบบผสาน
- อัลกอริทึม : 3.6 การแบ่งแยกและเอาชนะ - การเรียงลำดับแบบเร็ว
- อัลกอริทึม : 3.7 การแบ่งแยกและเอาชนะ - ตัวน้อยสุดอันดับ k
- อัลกอริทึม : 3.8 การแบ่งแยกและเอาชนะ - คู่จุดที่ใกล้กันสุด
- อัลกอริทึม : 3.9 การแบ่งแยกและเอาชนะ - ค่ามากสุด-ค่าน้อยสุด
- อัลกอริทึม : 3.10 การแบ่งแยกและเอาชนะ - การหาดาวเด่น