التقنية الجشعة
نظرة عامة على التقنية الجشعة وتطبيقاتها في خوارزميات Prim و Kruskal و Dijkstra وترميز Huffman.
Greedy Technique
Overview of the Greedy Technique and its applications in Prim's, Kruskal's, Dijkstra's algorithms, and Huffman coding.
أهداف التعلم
- وصف التقنية الجشعة كمفهوم.
- تطبيق خوارزمية Prim وخوارزمية Kruskal وخوارزمية Dijkstra في سياقات مختلفة.
- شرح أشجار وأكواد Huffman.
- تطبيق شجرة Huffman في مجال المشكلات.
- Describe Greedy Technique as a concept.
- Apply Prim’s Algorithm, Kruskal’s Algorithm and Dijkstra’s Algorithm in various context.
- Explain Huffman Trees and Codes.
- Apply Huffman Tree in problem domain.
1 نظرة عامة على التقنية الجشعة
1 Greedy Technique Overview
استراتيجية تبني الحل خطوة بخطوة باختيار الأفضل محلياً في كل مرحلة، مثل شخص يجمع أكبر الفئات النقدية أولاً.
A strategy that builds a solution piece by piece by choosing the locally optimal option at each step.
تقوم التقنية الجشعة ببناء حل لمشكلة تحسين (optimization problem) قطعة بقطعة من خلال سلسلة من الخيارات التي يجب أن تكون:
- ممكنة (feasible)،
- مثالية محلياً (locally optimal)،
- لا رجعة فيها (irrevocable).
بالنسبة لبعض المشاكل، تعطي هذه التقنية حلاً مثالياً دائماً، ولكن في معظم المشاكل لا تفعل ذلك، بل تُستخدم للحصول على تقريبات سريعة (fast approximations).
Constructs a solution to an optimization problem piece by piece through a sequence of choices that are: feasible, locally optimal, and irrevocable.
For some problems, it yields an optimal solution for every instance. For most, it does not but can be useful for fast approximations.
الخاصية 'لا رجعة فيها' (irrevocable) هي ما يجعل الخوارزمية الجشعة سريعة جداً، حيث لا تتراجع (no backtracking) لتصحيح قرارات سابقة.
هذا يعني أنها قد تعلق في 'أمثلية محلية' (local optimum) وتفشل في الوصول للحل الأمثل الشامل إذا لم تكن المشكلة تمتلك خاصية الاختيار الجشع (greedy-choice property).
The 'irrevocable' nature is what makes greedy algorithms highly efficient, as they do not backtrack.
However, this is also why they fail to find the global optimum in problems lacking the greedy-choice property, getting stuck in local optima instead.
لماذا لا تعطي الخوارزمية الجشعة الحل الأمثل دائماً في مشكلة البائع المتجول (TSP)؟ Why doesn't the greedy algorithm always yield the optimal solution for the Traveling Salesman Problem (TSP)?
لأن اختيار أقرب مدينة في كل خطوة (أمثل محلياً) قد يجبر البائع في النهاية على اتخاذ مسار طويل جداً للعودة، مما يجعل المجموع الكلي للمسار غير أمثل.
Because choosing the nearest unvisited city at each step (locally optimal) might force the salesman to take a very long edge at the end to complete the tour, resulting in a suboptimal total distance.
2 مشكلة الصرافة (إرجاع الباقي)
2 Change-Making Problem
إعطاء الباقي بأقل عدد ممكن من العملات المعدنية.
Giving change for a specific amount using the least number of coins.
بافتراض وجود كميات غير محدودة من العملات بفئات معينة، الهدف هو إعطاء الباقي لمبلغ n بأقل عدد من العملات.
الحل الجشع يعمل بشكل مثالي مع الفئات 'التقليدية' (مثل 25، 10، 5، 1). لكنه يفشل مع بعض الفئات غير التقليدية.
على سبيل المثال، إذا كانت الفئات هي 1، 3، 4 والمبلغ المطلوب هو 6، الخوارزمية الجشعة ستختار 4، 1، 1 (3 عملات)، بينما الحل الأمثل هو 3، 3 (عملتان فقط).
Given unlimited amounts of coins of denominations d1 > ... > dm, give change for amount n with the least number of coins.
The greedy solution is optimal for 'typical' sets of denominations (e.g., 25c, 10c, 5c, 1c). However, it is not optimal for all coin denominations.
For example, with denominations 1, 3, 4 and n=6, greedy gives 4, 1, 1 (3 coins) while the optimal is 3, 3 (2 coins).
فشل الخوارزمية الجشعة في بعض الفئات يوضح أهمية البرمجة الديناميكية (Dynamic Programming) كبديل يضمن الحل الأمثل لجميع حالات مشكلة الصرافة، حيث تقوم البرمجة الديناميكية بتجربة جميع التركيبات الممكنة بشكل منهجي.
The failure of the greedy approach for arbitrary denominations highlights the need for Dynamic Programming, which guarantees an optimal solution for the change-making problem by systematically evaluating all possible combinations.
ما هو أصغر مبلغ تفشل فيه الخوارزمية الجشعة مع الفئات 1، 3، 4؟ For what value does the greedy algorithm fail with denominations 1, 3, 4?
المبلغ 6. الجشع يعطي 4+1+1 (3 عملات)، والأمثل هو 3+3 (عملتان).
Value 6. Greedy gives 4+1+1 (3 coins), optimal is 3+3 (2 coins).
3 شجرة الامتداد الصغرى (MST)
3 Minimum Spanning Tree (MST)
ربط جميع النقاط في شبكة بأقل تكلفة ممكنة وبدون تكوين أي حلقات.
Connecting all vertices in a graph with the minimum total edge weight and no cycles.
شجرة الامتداد (Spanning tree) لرسم بياني متصل هي رسم بياني فرعي متصل وخالٍ من الحلقات (acyclic) يتضمن جميع رؤوس الرسم البياني الأصلي.
شجرة الامتداد الصغرى (MST) هي شجرة امتداد لرسم بياني متصل وموزون بحيث يكون مجموع أوزان حوافها هو الأقل الممكن.
البحث الشامل (Exhaustive search) لإيجادها غير فعال بسبب العدد الهائل للأشجار الممكنة.
A spanning tree of a connected graph G is a connected acyclic subgraph that includes all of G's vertices.
A Minimum Spanning Tree (MST) of a weighted, connected graph G is a spanning tree of G with the minimum total weight.
Constructing an MST with exhaustive search is not efficient due to the exponential number of possible spanning trees.
تطبيقات MST تشمل تصميم شبكات الاتصالات، وتمديد الكابلات الكهربائية، وتصميم الدوائر المطبوعة، حيث يكون الهدف هو تقليل التكلفة الإجمالية لربط جميع العقد.
MST applications include telecommunication network design, electrical wiring, and printed circuit board design, where the goal is to minimize the total cost of connecting all nodes.
هل يمكن أن يكون لرسم بياني أكثر من شجرة امتداد صغرى (MST) واحدة؟ Can a graph have more than one Minimum Spanning Tree (MST)?
نعم، إذا كانت هناك حواف لها نفس الوزن، فقد يكون هناك عدة أشجار امتداد صغرى مختلفة ولكن جميعها ستكون بنفس الوزن الإجمالي الأصغر.
Yes, if there are edges with the same weight, there can be multiple distinct MSTs, though all will have the exact same minimum total weight.
4 خوارزمية برايم (Prim's Algorithm)
4 Prim’s Algorithm
تنمو الشجرة عقدة تلو الأخرى باختيار أقرب عقدة متصلة بالشجرة الحالية.
Grows an MST one vertex at a time by always picking the closest connected vertex.
تبدأ الخوارزمية بشجرة تتكون من رأس واحد (أي رأس)، وتنمو الشجرة بإضافة رأس واحد في كل مرة لإنتاج MST.
في كل تكرار، يتم إضافة الرأس غير الموجود في الشجرة والذي يكون أقرب (بأقل وزن حافة) إلى الرؤوس الموجودة بالفعل في الشجرة (هذه هي الخطوة الجشعة!).
تتوقف الخوارزمية عند تضمين جميع الرؤوس. تتطلب طابور أولوية (priority queue / min-heap) لتحديد أقرب رأس بكفاءة.
Starts with a tree consisting of one (any) vertex and 'grows' the tree one vertex at a time to produce an MST.
On each iteration, it constructs T_{i+1} from T_i by adding a vertex not in T_i that is closest to those already in T_i (the greedy step).
Stops when all vertices are included. Needs a priority queue (min-heap) for locating the closest fringe vertex.
كفاءة الخوارزمية تعتمد على بنية البيانات المستخدمة: O(|V|^2) إذا استخدمنا مصفوفة الأوزان ومصفوفة عادية لطابور الأولوية.
وتصبح O(|E| log |V|) إذا استخدمنا قوائم التجاور (adjacency lists) وكومة صغرى (min-heap) لطابور الأولوية، مما يجعلها ممتازة للرسوم البيانية المتناثرة (sparse graphs).
Efficiency depends on data structures: O(|V|^2) for weight matrix and array implementation of priority queue.
O(|E| log |V|) for adjacency list representation and min-heap implementation, making it highly efficient for sparse graphs.
ALGORITHM Prim(G)
//Input: A weighted connected graph G = (V, E)
//Output: ET, the set of edges composing a minimum spanning tree of G
VT <- {v0} // initialize with any vertex
ET <- empty set
for i <- 1 to |V| - 1 do
find a minimum-weight edge e* = (v*, u*) among all edges (v, u)
such that v is in VT and u is in V - VT
VT <- VT U {u*}
ET <- ET U {e*}
return ET
لماذا نستخدم طابور الأولوية (Priority Queue) في خوارزمية Prim؟ Why do we use a Priority Queue in Prim's algorithm?
للعثور بسرعة على العقدة المجاورة ذات الحافة الأقل وزناً (أقرب عقدة) دون الحاجة للبحث في جميع الحواف في كل مرة.
To efficiently find and extract the adjacent vertex with the minimum edge weight (closest fringe vertex) without scanning all edges every time.
5 خوارزمية كروسكال (Kruskal's Algorithm)
5 Kruskal’s Algorithm
ترتب جميع الحواف وتختار الأقصر دائماً، بشرط ألا تصنع حلقة مغلقة.
Sorts all edges and picks the shortest ones globally, as long as they don't form a cycle.
تنظر خوارزمية Kruskal إلى شجرة الامتداد الصغرى كرسم بياني فرعي خالٍ من الحلقات يحتوي على |V|-1 من الحواف بأقل مجموع أوزان. الخطوات:
- ترتيب الحواف ترتيباً تصاعدياً حسب الطول.
- بناء الشجرة حافة تلو الأخرى من خلال سلسلة من الغابات (forests).
- في كل تكرار، إضافة الحافة التالية في القائمة المرتبة ما لم تؤدِ إلى تكوين حلقة (إذا كانت ستكون حلقة، يتم تخطيها).
Kruskal’s algorithm looks at an MST as an acyclic subgraph with |V|− 1 edges for which the sum of the edge weights is the smallest. Steps:
- Sort edges in nondecreasing order of lengths.
- 'Grow' tree one edge at a time through a series of expanding forests.
- On each iteration, add the next edge on the sorted list unless this would create a cycle (skip if it does).
تبدو الخوارزمية أسهل من Prim نظرياً، لكن تنفيذها أصعب بسبب الحاجة للتحقق من الحلقات (Cycle checking).
يتم إنشاء حلقة إذا وفقط إذا كانت الحافة المضافة تربط بين عقدتين في نفس المكون المتصل.
للقيام بذلك بكفاءة، نستخدم خوارزميات الاتحاد والبحث (Union-find algorithms).
The algorithm looks easier than Prim’s but is harder to implement due to cycle checking.
A cycle is created iff the added edge connects vertices in the same connected component.
Efficient implementation requires the use of Union-find algorithms to track disjoint sets of vertices.
ALGORITHM Kruskal(G)
//Input: A weighted connected graph G = (V, E)
//Output: ET, the set of edges composing an MST
sort E in nondecreasing order of the edge weights
ET <- empty set; ecounter <- 0
k <- 0
while ecounter < |V| - 1 do
k <- k + 1
if ET U {e_k} is acyclic
ET <- ET U {e_k}; ecounter <- ecounter + 1
return ET
متى نفضل استخدام Kruskal على Prim؟ When is it preferable to use Kruskal's algorithm over Prim's?
نفضل Kruskal عندما يكون الرسم البياني متناثراً (Sparse Graph) أي يحتوي على عدد قليل من الحواف، لأن ترتيب الحواف سيكون سريعاً جداً.
Kruskal's is generally preferred for sparse graphs (few edges) because sorting a small number of edges is very fast.
6 خوارزمية ديكسترا (Dijkstra's Algorithm)
6 Dijkstra’s Algorithm
تجد أقصر طريق من نقطة بداية واحدة إلى جميع النقاط الأخرى، مثل تطبيق خرائط جوجل.
Finds the shortest paths from a single source vertex to all other vertices in a graph.
تحل مشكلة أقصر مسار من مصدر واحد (Single Source Shortest Paths Problem).
مشابهة لخوارزمية Prim، ولكن بطريقة مختلفة لحساب التسميات الرقمية: من بين الرؤوس غير الموجودة في الشجرة، تجد الرأس u الذي يمتلك أصغر مجموع (d_v + w(v,u))، حيث d_v هو طول أقصر مسار تم إيجاده من المصدر إلى v، و w(v,u) هو وزن الحافة من v إلى u.
لا تعمل الخوارزمية مع الرسوم البيانية ذات الأوزان السالبة.
Solves the Single Source Shortest Paths Problem.
Similar to Prim’s MST algorithm, but computes numerical labels differently: Among vertices not already in the tree, it finds vertex u with the smallest sum d_v + w(v,u), where d_v is the length of the shortest path from the source to v, and w(v,u) is the weight of the edge from v to u.
It does not work for graphs with negative weights.
كفاءة Dijkstra مطابقة لـ Prim: O(|V|^2) باستخدام مصفوفة الأوزان، و O(|E| log |V|) باستخدام قوائم التجاور والكومة الصغرى.
سبب فشلها مع الأوزان السالبة هو طبيعتها الجشعة؛ فهي تفترض بمجرد إضافة عقدة إلى الشجرة أن أقصر مسار لها قد تم إيجاده نهائياً (irrevocable)، والوزن السالب قد يكسر هذا الافتراض لاحقاً.
Efficiency is identical to Prim's: O(|V|^2) for weight matrix, O(|E| log |V|) for adj. lists and min-heap.
It fails with negative weights because its greedy choice assumes that once a vertex is added to the resolved set, its shortest path is finalized (irrevocable). A negative edge discovered later could provide a shorter path, violating this assumption.
ALGORITHM Dijkstra(G, s)
//Input: Weighted connected graph G with nonnegative weights and source s
Initialize(Q) // priority queue
for every vertex v in V:
dv <- infinity; pv <- null
Insert(Q, v, dv)
ds <- 0; Decrease(Q, s, ds)
VT <- empty set
for i <- 0 to |V| - 1 do
u* <- DeleteMin(Q)
VT <- VT U {u*}
for every vertex u in V - VT adjacent to u* do
if du* + w(u*, u) < du
du <- du* + w(u*, u); pu <- u*
Decrease(Q, u, du)
| Prim's Algorithm | Kruskal's Algorithm | Dijkstra's Algorithm | |
|---|---|---|---|
| الهدف (المشكلة التي يحلها) Goal (Problem Solved) | شجرة الامتداد الصغرى (MST) Minimum Spanning Tree (MST) | شجرة الامتداد الصغرى (MST) Minimum Spanning Tree (MST) | أقصر مسار من مصدر واحد Single-Source Shortest Path |
| طريقة الاختيار الجشع Greedy Choice Method | أقرب عقدة للشجرة الحالية (وزن الحافة فقط) Closest vertex to current tree (edge weight only) | أقصر حافة في الرسم البياني بأكمله (لا تكون حلقة) Shortest edge globally (that doesn't form a cycle) | العقدة ذات أقل مسافة متراكمة من المصدر Vertex with minimum accumulated distance from source |
| بنية البيانات الأساسية Key Data Structure | طابور أولوية (Min-Heap) Priority Queue (Min-Heap) | الاتحاد والبحث (Union-Find) Union-Find | طابور أولوية (Min-Heap) Priority Queue (Min-Heap) |
ما هو الفرق الأساسي بين المعيار الجشع في Prim والمعيار الجشع في Dijkstra؟ What is the main difference between the greedy criterion in Prim's and Dijkstra's algorithms?
Prim تختار الحافة ذات الوزن الأقل التي تربط الشجرة بعقدة جديدة. Dijkstra تختار العقدة التي لها أقل مسافة إجمالية متراكمة من نقطة البداية (المصدر).
Prim chooses the edge with the smallest weight connecting the tree to a new vertex. Dijkstra chooses the vertex with the smallest total accumulated distance from the source vertex.
7 أشجار وأكواد هوفمان (Huffman Trees and Codes)
7 Huffman Trees and Codes
طريقة لضغط البيانات بإعطاء الحروف المتكررة أكواداً قصيرة، والحروف النادرة أكواداً طويلة.
A data compression method that assigns shorter bit strings to frequent characters and longer ones to rare characters.
الترميز (Coding) هو تعيين سلاسل بتات (bit strings) لحروف الأبجدية. كود Huffman هو مخطط ترميز متغير الطول (variable-length) وخالٍ من البادئة (prefix-free)، مما يعني أنه لا يوجد كود يمثل بادئة لكود آخر. يتم بناء شجرة Huffman جشعياً:
- تهيئة n أشجار ذات عقدة واحدة بأوزان تساوي تكرار الحروف.
- دمج الشجرتين ذواتي الأوزان الأصغر في شجرة واحدة وزنها مجموع الوزنين، وتكرار ذلك n-1 مرة.
يتم تمييز الحواف بـ 0 و 1.
Coding is the assignment of bit strings to alphabet characters. A Huffman code is an optimal prefix-free variable-length encoding scheme. Prefix-free means no codeword is a prefix of another. Huffman's algorithm steps:
- Initialize n one-node trees with alphabet characters and frequencies as weights.
- Repeat n-1 times: join two binary trees with smallest weights into one, making its weight the sum of the two.
Mark edges with 0s and 1s. This minimizes the expected length of a codeword.
خاصية 'خالٍ من البادئة' (Prefix-free) ضرورية جداً في الأكواد متغيرة الطول لضمان فك التشفير (decoding) بشكل لا لبس فيه وبدون الحاجة لفواصل بين الحروف.
نسبة الضغط (Compression ratio) تُحسب بمقارنة متوسط البتات لكل حرف في ترميز Huffman مع عدد البتات في الترميز ثابت الطول (مثل ASCII).
The 'prefix-free' property is crucial for variable-length codes to ensure unambiguous decoding without needing separator bits.
The compression ratio is calculated by comparing the average bits per character in Huffman coding against a fixed-length encoding baseline.
لماذا ندمج العقدتين ذواتي التكرار الأقل أولاً في خوارزمية Huffman؟ Why do we merge the two nodes with the lowest frequencies first in Huffman's algorithm?
لأن العقد التي تدمج أولاً ستكون في أسفل الشجرة، مما يعني أن مسارها من الجذر سيكون الأطول. نحن نريد أن تكون الحروف الأقل تكراراً هي الأطول في الكود لتقليل الحجم الإجمالي.
Because nodes merged first end up deepest in the tree, resulting in the longest codewords. We want the least frequent characters to have the longest codes to minimize the overall average codeword length.