อัลกอริธึมโลภพร้อมตัวอย่าง: คืออะไร วิธีการ และแนวทาง
อัลกอริทึม Greedy คืออะไร?
In อัลกอริธึมโลภ ชุดของทรัพยากรจะถูกแบ่งแบบวนซ้ำตามความพร้อมใช้งานสูงสุดในทันทีของทรัพยากรนั้นในขั้นตอนการดำเนินการที่กำหนด
การแก้ปัญหาตามแนวทางโลภมีสองขั้นตอน
- กำลังสแกนรายการสินค้า
- การเพิ่มประสิทธิภาพ
ขั้นตอนเหล่านี้ครอบคลุมแบบคู่ขนานในบทช่วยสอนอัลกอริทึม Greedy ในระหว่างการแบ่งอาร์เรย์
เพื่อทำความเข้าใจแนวทางโลภ คุณจะต้องมีความรู้ในการทำงานเกี่ยวกับการเรียกซ้ำและการสลับบริบท ซึ่งจะช่วยให้คุณเข้าใจวิธีการติดตามโค้ด คุณสามารถกำหนดกระบวนทัศน์แห่งความละโมบได้ในแง่ของข้อความที่จำเป็นและเพียงพอของคุณเอง
เงื่อนไขสองประการกำหนดกระบวนทัศน์แห่งความละโมบ
- วิธีแก้ปัญหาแต่ละขั้นตอนจะต้องสร้างโครงสร้างปัญหาให้สอดคล้องกับวิธีแก้ปัญหาที่ดีที่สุดที่ได้รับการยอมรับ
- ก็เพียงพอแล้วหากโครงสร้างของปัญหาสามารถหยุดได้ในขั้นตอนโลภจำนวนจำกัด
ในขณะที่การสร้างทฤษฎีดำเนินต่อไป เราจะอธิบายประวัติที่เกี่ยวข้องกับแนวทางการค้นหา Greedy
ประวัติศาสตร์แห่งความโลภ Algorithms
นี่คือจุดสังเกตสำคัญของอัลกอริทึมที่โลภ:
- อัลกอริธึมแบบโลภถูกคิดค้นขึ้นสำหรับอัลกอริธึมการเดินกราฟมากมายในช่วงทศวรรษปี 1950
- Esdger Djikstra วางแนวคิดเกี่ยวกับอัลกอริทึมเพื่อสร้างแผนผังที่ขยายน้อยที่สุด เขาตั้งเป้าที่จะย่นระยะเวลาเส้นทางภายในกรุงอัมสเตอร์ดัม เมืองหลวงของเนเธอร์แลนด์
- ในทศวรรษเดียวกัน Prim และ Kruskal บรรลุกลยุทธ์การปรับให้เหมาะสมซึ่งอิงจากการลดต้นทุนเส้นทางตามเส้นทางที่ชั่งน้ำหนักให้เหลือน้อยที่สุด
- ในทศวรรษ 70 นักวิจัยชาวอเมริกัน Cormen, Rivest และ Stein ได้เสนอโครงสร้างย่อยแบบเรียกซ้ำของโซลูชันที่โลภมากในหนังสือแนะนำอัลกอริทึมแบบคลาสสิกของพวกเขา
- กระบวนทัศน์การค้นหา Greedy ได้รับการบันทึกเป็นกลยุทธ์การปรับให้เหมาะสมประเภทอื่นในบันทึกของ NIST ในปี 2005
- จนถึงปัจจุบัน โปรโตคอลที่รันเว็บ เช่น open-shortest-path-first (OSPF) และโปรโตคอลการสลับแพ็กเก็ตเครือข่ายอื่นๆ จำนวนมากใช้กลยุทธ์ที่โลภเพื่อลดเวลาที่ใช้บนเครือข่าย
กลยุทธ์และการตัดสินใจที่โลภ
ตรรกะในรูปแบบที่ง่ายที่สุดถูกต้มจนเป็น "โลภ" หรือ "ไม่โลภ" ข้อความเหล่านี้ถูกกำหนดโดยแนวทางที่ใช้เพื่อความก้าวหน้าในแต่ละขั้นตอนของอัลกอริทึม
ตัวอย่างเช่น อัลกอริทึมของ Djikstra ใช้กลยุทธ์แบบทีละขั้นตอนในการระบุโฮสต์บนอินเทอร์เน็ตโดยคำนวณฟังก์ชันต้นทุน ค่าที่ส่งคืนโดยฟังก์ชันต้นทุนจะกำหนดว่าเส้นทางถัดไปจะเป็นแบบ "โลภ" หรือ "ไม่โลภ"
กล่าวโดยสรุป อัลกอริธึมจะไม่โลภหากขั้นตอนใดขั้นตอนหนึ่งที่ไม่โลภในท้องถิ่น ปัญหาความโลภยุติลงโดยไม่มีขอบเขตของความโลภอีกต่อไป
ลักษณะของอัลกอริทึม Greedy
ลักษณะสำคัญของอัลกอริทึม Greedy คือ:
- มีรายการทรัพยากรที่เรียงลำดับพร้อมการระบุต้นทุนหรือมูลค่า ข้อจำกัดเหล่านี้เป็นปริมาณในระบบ
- คุณจะใช้ทรัพยากรในปริมาณสูงสุดในเวลาที่มีข้อจำกัด
- ตัวอย่างเช่น ในปัญหาการกำหนดตารางกิจกรรม ต้นทุนทรัพยากรจะคิดเป็นชั่วโมง และกิจกรรมต่างๆ จะต้องดำเนินการตามลำดับ
ทำไมต้องใช้วิธีโลภ?
ต่อไปนี้เป็นเหตุผลในการใช้วิธีการโลภ:
- แนวทางที่ละโมบมีข้อแลกเปลี่ยนอยู่บ้าง ซึ่งอาจจะทำให้เหมาะสำหรับการเพิ่มประสิทธิภาพได้
- เหตุผลสำคัญประการหนึ่งคือการบรรลุแนวทางแก้ไขที่เป็นไปได้มากที่สุดทันที ในปัญหาการเลือกกิจกรรม (อธิบายด้านล่าง) หากสามารถทำกิจกรรมเพิ่มเติมได้ก่อนที่จะทำกิจกรรมปัจจุบันเสร็จสิ้น กิจกรรมเหล่านี้ก็สามารถดำเนินการได้ภายในเวลาเดียวกัน
- อีกเหตุผลหนึ่งคือการแบ่งปัญหาแบบวนซ้ำตามเงื่อนไข โดยไม่จำเป็นต้องรวมวิธีแก้ปัญหาทั้งหมด
- ในปัญหาการเลือกกิจกรรม ขั้นตอน "การแบ่งแบบเรียกซ้ำ" ทำได้โดยการสแกนรายการข้อมูลเพียงครั้งเดียวและพิจารณากิจกรรมบางอย่าง
วิธีแก้ปัญหาการเลือกกิจกรรม
ในตัวอย่างการจัดกำหนดการกิจกรรม มีเวลา "เริ่มต้น" และ "สิ้นสุด" สำหรับทุกกิจกรรม แต่ละกิจกรรมจะถูกจัดทำดัชนีด้วยตัวเลขเพื่อใช้อ้างอิง กิจกรรมมีสองประเภท
- ถือเป็นกิจกรรม: คือ Activity ซึ่งเป็นข้อมูลอ้างอิงที่ใช้วิเคราะห์ความสามารถในการทำกิจกรรมที่เหลือมากกว่าหนึ่งกิจกรรม
- กิจกรรมที่เหลือ: กิจกรรมในดัชนีตั้งแต่หนึ่งรายการขึ้นไปก่อนกิจกรรมที่พิจารณา
ระยะเวลาทั้งหมดจะเป็นค่าใช้จ่ายในการดำเนินกิจกรรม นั่นคือ (เสร็จสิ้น – เริ่มต้น) ให้ระยะเวลาเป็นต้นทุนของกิจกรรม
คุณจะได้เรียนรู้ว่าขอบเขตความโลภคือจำนวนกิจกรรมที่เหลือที่คุณสามารถทำในช่วงเวลาของกิจกรรมที่พิจารณาได้
Archiการสอนแนวทาง Gredy
ขั้นตอนที่ 1) สแกนรายการต้นทุนกิจกรรม โดยเริ่มจากดัชนี 0 เป็นดัชนีที่พิจารณา
ขั้นตอนที่ 2) เมื่อสามารถทำกิจกรรมอื่น ๆ เสร็จได้ทันเวลา กิจกรรมที่พิจารณาเสร็จสิ้นแล้ว จึงเริ่มค้นหากิจกรรมที่เหลือหนึ่งรายการหรือหลายรายการ
ขั้นตอนที่ 3) หากไม่มีกิจกรรมเหลืออยู่ กิจกรรมที่เหลืออยู่ในปัจจุบันจะกลายเป็นกิจกรรมที่พิจารณาถัดไป ทำซ้ำขั้นตอนที่ 1 และขั้นตอนที่ 2 โดยมีกิจกรรมที่พิจารณาใหม่ หากไม่มีกิจกรรมเหลืออยู่ ให้ไปที่ขั้นตอนที่ 4
ขั้นตอนที่ 4 ) ส่งกลับการรวมดัชนีที่พิจารณา สิ่งเหล่านี้คือดัชนีกิจกรรมที่จะใช้เพื่อเพิ่มปริมาณงานสูงสุด

คำอธิบายรหัส
#include<iostream> #include<stdio.h> #include<stdlib.h> #define MAX_ACTIVITIES 12
คำอธิบายของรหัส:
- รวมไฟล์ส่วนหัว/คลาส
- จำนวนกิจกรรมสูงสุดที่ผู้ใช้ให้ไว้
using namespace std; class TIME { public: int hours; public: TIME() { hours = 0; } };
คำอธิบายของรหัส:
- เนมสเปซสำหรับการดำเนินการสตรีมมิ่ง
- คำจำกัดความของคลาสสำหรับ TIME
- การประทับเวลาหนึ่งชั่วโมง
- ตัวสร้างเริ่มต้นของ TIME
- เวลามีการเปลี่ยนแปลง
class Activity { public: int index; TIME start; TIME finish; public: Activity() { start = finish = TIME(); } };
คำอธิบายของรหัส:
- คำจำกัดความของชั้นเรียนจากกิจกรรม
- การประทับเวลาซึ่งกำหนดระยะเวลา
- การประทับเวลาทั้งหมดจะเริ่มต้นเป็น 0 ในตัวสร้างเริ่มต้น
class Scheduler { public: int considered_index,init_index; Activity *current_activities = new Activity[MAX_ACTIVITIES]; Activity *scheduled;
คำอธิบายของรหัส:
- ส่วนที่ 1 ของคำจำกัดความคลาสตัวกำหนดเวลา
- ดัชนีที่พิจารณาเป็นจุดเริ่มต้นในการสแกน แถว.
- ดัชนีการเริ่มต้นใช้เพื่อกำหนดการประทับเวลาแบบสุ่ม
- อาร์เรย์ของวัตถุกิจกรรมจะถูกจัดสรรแบบไดนามิกโดยใช้ตัวดำเนินการใหม่
- ตัวชี้ที่กำหนดเวลาไว้จะกำหนดตำแหน่งฐานปัจจุบันสำหรับความโลภ
Scheduler() { considered_index = 0; scheduled = NULL; ... ...
คำอธิบายของรหัส:
- ตัวสร้างตัวจัดกำหนดการ - ส่วนที่ 2 ของคำจำกัดความคลาสตัวจัดกำหนดการ
- ดัชนีที่พิจารณาจะกำหนดการเริ่มการสแกนในปัจจุบัน
- ขอบเขตความโลภในปัจจุบันไม่ได้ถูกกำหนดไว้ตั้งแต่เริ่มต้น
for(init_index = 0; init_index < MAX_ACTIVITIES; init_index++) { current_activities[init_index].start.hours = rand() % 12; current_activities[init_index].finish.hours = current_activities[init_index].start.hours + (rand() % 2); printf("\nSTART:%d END %d\n", current_activities[init_index].start.hours ,current_activities[init_index].finish.hours); } … …
คำอธิบายของรหัส:
- วงจรสำหรับการเริ่มต้นชั่วโมงเริ่มต้นและชั่วโมงสิ้นสุดของกิจกรรมแต่ละกิจกรรมที่จัดกำหนดการไว้ในปัจจุบัน
- การเริ่มต้นเวลาเริ่มต้น
- การเริ่มต้นเวลาสิ้นสุดจะอยู่หลังหรือที่ชั่วโมงเริ่มต้นเสมอ
- คำสั่งแก้ไขข้อบกพร่องเพื่อพิมพ์ระยะเวลาที่จัดสรร
public: Activity * activity_select(int); };
คำอธิบายของรหัส:
- ส่วนที่ 4 – ส่วนสุดท้ายของคำจำกัดความคลาสตัวจัดกำหนดการ
- ฟังก์ชั่นการเลือกกิจกรรมจะใช้ดัชนีจุดเริ่มต้นเป็นฐานและแบ่งภารกิจที่โลภออกเป็นปัญหาย่อยที่โลภ
Activity * Scheduler :: activity_select(int considered_index) { this->considered_index = considered_index; int greedy_extent = this->considered_index + 1; … …
- การใช้ตัวดำเนินการการแก้ไขขอบเขต (::) จะทำให้สามารถกำหนดฟังก์ชันได้
- ดัชนีที่พิจารณาคือดัชนีที่เรียกตามค่า greedy_extent เป็นค่าเริ่มต้นเพียงดัชนีหลังจากดัชนีที่พิจารณา
Activity * Scheduler :: activity_select(int considered_index) { while( (greedy_extent < MAX_ACTIVITIES ) && ((this->current_activities[greedy_extent]).start.hours < (this->current_activities[considered_index]).finish.hours )) { printf("\nSchedule start:%d \nfinish%d\n activity:%d\n", (this->current_activities[greedy_extent]).start.hours, (this->current_activities[greedy_extent]).finish.hours, greedy_extent + 1); greedy_extent++; } … ...
คำอธิบายของรหัส:
- ตรรกะหลัก - ความโลภนั้นจำกัดอยู่ที่จำนวนกิจกรรม
- ชั่วโมงเริ่มต้นของกิจกรรมปัจจุบันจะถูกตรวจสอบว่าสามารถจัดกำหนดการได้ก่อนที่กิจกรรมที่พิจารณา (ให้โดยดัชนีที่พิจารณา) จะเสร็จสิ้น
- ตราบเท่าที่เป็นไปได้ คำสั่งดีบักเผื่อเลือกจะถูกพิมพ์
- เลื่อนไปยังดัชนีถัดไปในอาร์เรย์กิจกรรม
... if ( greedy_extent <= MAX_ACTIVITIES ) { return activity_select(greedy_extent); } else { return NULL; } }
คำอธิบายของรหัส:
- การตรวจสอบแบบมีเงื่อนไขว่ากิจกรรมทั้งหมดได้รับการคุ้มครองหรือไม่
- ถ้าไม่เช่นนั้น คุณสามารถเริ่มต้นความโลภของคุณใหม่โดยให้ดัชนีที่พิจารณาเป็นจุดปัจจุบัน นี่เป็นขั้นตอนการเรียกซ้ำที่แบ่งคำชี้แจงปัญหาอย่างตะกละตะกลาม
- ถ้าใช่ มันจะกลับไปหาผู้โทรโดยไม่มีขอบเขตที่จะขยายความโลภ
int main() { Scheduler *activity_sched = new Scheduler(); activity_sched->scheduled = activity_sched->activity_select( activity_sched->considered_index); return 0; }
คำอธิบายของรหัส:
- ฟังก์ชั่นหลักที่ใช้ในการเรียกใช้ตัวกำหนดตารางเวลา
- ตัวกำหนดเวลาใหม่จะถูกสร้างขึ้นทันที
- ฟังก์ชั่นเลือกกิจกรรมซึ่งส่งคืนพอยน์เตอร์ประเภทกิจกรรมจะกลับมาหาผู้เรียกหลังจากภารกิจโลภสิ้นสุดลง
Output:
START:7 END 7 START:9 END 10 START:5 END 6 START:10 END 10 START:9 END 10 Schedule start:5 finish6 activity:3 Schedule start:9 finish10 activity:5
ข้อจำกัดของเทคนิคโลภ
ไม่เหมาะกับปัญหา Greedy ที่จำเป็นต้องมีวิธีแก้ไขสำหรับทุกปัญหาย่อย เช่น การเรียงลำดับ
ในปัญหาการฝึกหัดอัลกอริทึม Greedy วิธี Greedy อาจผิดได้ ในกรณีที่เลวร้ายที่สุดอาจนำไปสู่วิธีแก้ปัญหาที่ไม่เหมาะที่สุดด้วยซ้ำ
ดังนั้นข้อเสียของอัลกอริทึมแบบโลภก็คือ การไม่รู้ว่าสถานะโลภในปัจจุบันจะเป็นอย่างไรต่อไป
ด้านล่างนี้เป็นภาพข้อเสียของวิธี Greedy:
ในการสแกนแบบโลภที่แสดงที่นี่เป็นต้นไม้ (ค่าที่สูงกว่าความโลภที่สูงกว่า) สถานะอัลกอริทึมที่ค่า: 40 มีแนวโน้มว่าจะใช้ 29 เป็นค่าถัดไป นอกจากนี้ ภารกิจสิ้นสุดที่ 12 ซึ่งมีค่าเท่ากับ 41
อย่างไรก็ตาม หากอัลกอริทึมใช้เส้นทางที่ไม่เหมาะสมหรือใช้กลยุทธ์ในการพิชิต จากนั้น 25 จะตามมาด้วย 40 และการปรับปรุงต้นทุนโดยรวมจะเป็น 65 ซึ่งมีมูลค่าสูงกว่า 24 คะแนนในฐานะการตัดสินใจที่ไม่ดีนัก
ตัวอย่างของ Gredy Algorithms
อัลกอริทึมเครือข่ายส่วนใหญ่ใช้แนวทางแบบโลภ นี่คือรายการตัวอย่างอัลกอริทึมแบบโลภบางส่วน:
- อัลกอริทึม Spanning Tree ขั้นต่ำของ Prim
- ปัญหาพนักงานขายในการเดินทาง
- กราฟ – การระบายสีแผนที่
- อัลกอริทึม Spanning Tree ขั้นต่ำของ Kruskal
- อัลกอริทึม Spanning Tree ขั้นต่ำของ Dijkstra
- กราฟ – ฝาครอบจุดยอด
- ปัญหากระเป๋าเป้สะพายหลัง
- ปัญหาการจัดตารางเวลางาน
สรุป
โดยสรุป บทความนี้ได้อธิบายถึงแนวคิดแบบโลภะ แสดงให้เห็นว่าการเพิ่มประสิทธิภาพแบบโลภะและการเรียกซ้ำสามารถช่วยให้คุณได้รับโซลูชันที่ดีที่สุดได้ในระดับหนึ่ง อัลกอริทึมแบบโลภะถูกนำไปใช้ในการแก้ปัญหาในหลายภาษาอย่างแพร่หลาย เช่น อัลกอริทึมแบบโลภะ Python, C, C#, PHP, Javaฯลฯ การเลือกกิจกรรมของตัวอย่างอัลกอริทึม Greedy ได้รับการอธิบายว่าเป็นปัญหาเชิงกลยุทธ์ที่สามารถบรรลุปริมาณงานสูงสุดโดยใช้แนวทาง greedy ในที่สุดก็มีการอธิบายข้อเสียของการใช้แนวทางโลภแล้ว