كيف تعمل الخوارزميات
Chapter 5 Graphs

الفصل 5: الرسوم البيانية في الخوارزميات

الرسوم البيانية هي بنية بيانات أساسية تنمذج الاتصالات والعلاقات بين الكائنات. لها تطبيقات واسعة النطاق في علوم الحاسوب وما وراءها، من نمذجة الشبكات الاجتماعية وروابط صفحات الويب، إلى حل المشكلات في النقل والجدولة وتخصيص الموارد. في هذا الفصل، نستكشف الخصائص الأساسية والخوارزميات للعمل مع الرسوم البيانية، مع التركيز على الرسوم البيانية غير الموجهة، والبحث بعمق أولاً والبحث بعرض أولاً، والحد الأدنى من الأشجار الناشرة، والمسارات الأقصر.

الرسوم البيانية غير الموجهة

الرسم البياني غير الموجه هو مجموعة من الرؤوس (أو العقد) متصلة بحواف. رسميًا، نعرف الرسم البياني غير الموجه G على أنه زوج (V، E)، حيث V هو مجموعة من الرؤوس و E هو مجموعة من الأزواج غير المرتبة من الرؤوس، تُسمى حواف. تربط الحافة (v، w) الرؤوس v و w. نقول إن v و w متجاوران أو جيران. درجة رأس هي عدد الحواف المتصلة به.

فيما يلي مثال بسيط على رسم بياني غير موجه:

   A --- B
  /     / \
 /     /   \
C --- D --- E

في هذا الرسم البياني، مجموعة الرؤوس V = {A، B، C، D، E} ومجموعة الحواف E = {(A، B)، (A، C)، (B، D)، (B، E)، (C، D)، (D، E)}.

هناك عدة طرق لتمثيل رسم بياني في برنامج. اثنان من التمثيلات الشائعة هما:

  1. مصفوفة الجوار: مصفوفة بولينية n x n A، حيث n هو عدد الرؤوس. تكون القيمة A[i][j] صحيحة إذا كان هناك حافة من الرأس i إلى الرأس j، وخاطئة خلاف ذلك.

  2. قوائم الجوار: مصفوفة Adj بحجم n، حيث n هو عدد الرؤوس. تحتوي القيمة Adj[v] على قائمة تحتوي على جيران v.

تعتمد خيارات التمثيل على كثافة الرسم البياني (نسبة الحواف إلى الرؤوس) والعمليات التي نحتاج إلى إجرائها. مصفوفات الجوار بسيطة ولكن قد تكون غير فعالة للرسوم البيانية الخفيفة. قوائم الجوار أكثر كفاءة في استخدام المساحة للرسوم البيانية الخفيفة وتوفر وصولاً أسرع إلى جيران رأس.

فيما يلي مثال على كيفية تمثيل الرسم البياني المذكور أعلاه باستخدام قوائم الجوار في Java:

List<Integer>[] graph = (List<Integer>[]) new List[5];
graph[0] = Arrays.asList(1, 2);        // A -> B, C
graph[1] = Arrays.asList(0, 3, 4);     // B -> A, D, E
graph[2] = Arrays.asList(0, 3);        // C -> A, D
graph[3] = Arrays.asList(1, 2, 4);     // D -> B, C, E
graph[4] = Arrays.asList(1, 3);        // E -> B, D

البحث العميق الأول (DFS)

البحث العميق الأول (DFS) هو خوارزمية أساسية لتجوال الرسوم البيانية التي تستكشف أبعد ما يمكن على طول كل فرع قبل الرجوع إلى الخلف. ويمكن استخدامها لحل العديد من مشاكل الرسوم البيانية، مثل إيجاد المكونات المترابطة، والفرز الهرمي، واكتشاف الدورات.

تعمل خوارزمية DFS على النحو التالي:

  1. ابدأ من عقدة المصدر s.
  2. ضع العقدة الحالية على أنها تم زيارتها.
  3. قم بزيارة جميع العقد غير المزارة w المجاورة للعقدة الحالية بشكل متكرر.
  4. إذا تم زيارة جميع العقد المجاورة للعقدة الحالية، فارجع إلى العقدة التي تم استكشاف العقدة الحالية منها.
  5. إذا بقيت أي عقد غير مزارة، فاختر واحدة منها وكرر من الخطوة 1.

فيما يلي تنفيذ بسيط باستخدام لغة Java لـ DFS باستخدام قوائم الجوار:

boolean[] visited;
 
void dfs(List<Integer>[] graph, int v) {
    visited[v] = true;
    for (int w : graph[v]) {
        if (!visited[w]) {
            dfs(graph, w);
        }
    }
}

لإجراء عملية DFS كاملة، نقوم بالاتصال بـ dfs(graph, s) لكل عقدة s في الرسم البياني، حيث يتم تهيئة visited إلى false لجميع العقد.

يوجد للـ DFS العديد من التطبيقات. على سبيل المثال، يمكننا استخدامه لإيجاد المكونات المترابطة في رسم بياني غير موجه من خلال تشغيل DFS من كل عقدة غير مزارة وتعيين كل عقدة إلى مكون بناءً على شجرة DFS.

البحث العرضي الأول (BFS)

البحث العرضي الأول (BFS) هو خوارزمية أخرى أساسية لتجوال الرسوم البيانية التي تستكشف العقد على طبقات. إنه يزور جميع العقد على العمق الحالي قبل الانتقال إلى العقد على مستوى العمق التالي.

تعمل خوارزمية BFS على النحو التالي:

  1. ابدأ من عقدة المصدر s وضعها على أنها تم زيارتها.
  2. أضف s إلى طابور FIFO.
  3. و.بينما لا تكون القائمة فارغة:
    • إزالة عقدة v من الطابور.
    • لكل عقدة w غير مؤشرة متجاورة مع v:
      • ضع علامة على w كزائر.
      • أضف w إلى الطابور.

هذا تنفيذ Java لـ BFS باستخدام قوائم الجوار:

boolean[] visited;
 
void bfs(List<Integer>[] graph, int s) {
    Queue<Integer> queue = new LinkedList<>();
    visited[s] = true;
    queue.offer(s);
 
    while (!queue.isEmpty()) {
        int v = queue.poll();
        for (int w : graph[v]) {
            if (!visited[w]) {
                visited[w] = true;
                queue.offer(w);
            }
        }
    }
}

BFS مفيد بشكل خاص لإيجاد أقصر المسارات في الرسوم البيانية غير المرجحة. المسافة من العقدة المصدر إلى أي عقدة أخرى هي الحد الأدنى لعدد الحواف في المسار بينهما. BFS يضمن إيجاد أقصر مسار.

أشجار الحد الأدنى

شجرة الحد الأدنى (MST) هي مجموعة فرعية من حواف رسم بياني موجه غير موزون متصل، والتي تربط جميع العقد، دون أي دورات، وبأقل وزن إجمالي ممكن للحواف.

هناك خوارزميتان كلاسيكيتان لإيجاد MSTs هما خوارزمية Kruskal وخوارزمية Prim.

تعمل خوارزمية Kruskal على النحو التالي:

  1. إنشاء غابة F حيث تكون كل عقدة شجرة منفصلة.
  2. إنشاء مجموعة S تحتوي على جميع الحواف في الرسم البياني.
  3. بينما S ليست فارغة وF ليست بعد شجرة ممتدة:
    • إزالة حافة ذات أقل وزن من S.
    • إذا كانت الحافة المزالة تربط شجرتين مختلفتين، أضفها إلى F، مما يؤدي إلى دمج شجرتين في شجرة واحدة.

تعمل خوارزمية Prim على النحو التالي:

  1. تهيئة شجرة بعقدة واحدة، تم اختيارها بشكل اعتباطي من الرسم البياني.
  2. توسيع الشجرة بحافة واحدة: من بين جميع الحواف التي تربط الشجرة بعقد ليست بعد في الشجرة، ابحث عن الحافة ذات الوزن الأدنى، ونقلها إلى الشجرة.
  3. كرر الخطوة 2 حتى تكون جميع العقد في الشجرة.

هذا تنفيذ Java لخوارزمية Prim:

int minKey(int[] key, boolean[] mstSet, int V) {
    int min = Integer.MAX_VALUE, min_index = -1;
for (int v = 0; v < V; v++) {
        if (!mstSet[v] && key[v] < min) {
            min = key[v];
            min_index = v;
        }
    }
    return min_index;
}
 
void primMST(int[][] graph, int V) {
    int[] parent = new int[V];
    int[] key = new int[V];
    boolean[] mstSet = new boolean[V];
 
    // قم بتهيئة جميع المفاتيح إلى أقصى قيمة ممكنة
    for (int i = 0; i < V; i++) {
        key[i] = Integer.MAX_VALUE;
        mstSet[i] = false;
    }
 
    // تعيين المفتاح الأول إلى 0 والأب إلى -1
    key[0] = 0;
    parent[0] = -1;
 
    // بناء الشجرة الممتدة الأدنى
    for (int count = 0; count < V - 1; count++) {
        int u = minKey(key, mstSet, V);
        mstSet[u] = true;
 
        // تحديث المفاتيح والآباء للعقد المجاورة
        for (int v = 0; v < V; v++) {
            if (graph[u][v] != 0 && !mstSet[v] && graph[u][v] < key[v]) {
                parent[v] = u;
                key[v] = graph[u][v];
            }
        }
    }
 
    printMST(parent, graph, V);
}

MSTs لها العديد من التطبيقات، مثل تصميم الشبكات (اتصالات، كهرباء، هيدروليكية، كمبيوتر) وتقريب مشاكل البائع المتجول.

المسارات الأقصر

مشكلة المسار الأقصر هي إيجاد مسار بين عقدتين في رسم بياني بحيث يكون مجموع أوزان حواف هذا المسار هو الأدنى. لهذه المشكلة العديد من الاختلافات، مثل المسارات الأقصر من مصدر واحد، وجميع المسارات الأقصر، والمسارات الأقصر إلى وجهة واحدة.

خوارزمية ديجكسترا هي خوارزمية طماعة تحل مشكلة المسارات الأقصر من مصدر واحد لرسم بياني ذي أوزان حواف غير سالبة. وتعمل على النحو التالي:

  1. إنشاء مجموعة sptSet (مجموعة شجرة المسار الأقصر) التي تتتبع العقد المضمنة في شجرة المسار الأقصر.
  2. تعيين قيمة مسافة لجميع العقد في الرسم البياني. قم بتهيئة جميع قيم المسافة على أنها لا نهائية. عيّن قيمة المسافة على 0 للعقدة المصدر.
  3. بينما sptSet لا تشمل جميع العقد، اختر عقدة v التي ليست موجودة في sptSet وتمتلك أقل قيمة مسافة. أضف v إلى sptSet.

قم بتحديث قيم المسافة لجميع العقد المجاورة لـ v. لتحديث قيم المسافة، قم بالتكرار عبر جميع العقد المجاورة. بالنسبة لكل عقدة مجاورة w، إذا كان مجموع المسافة v.هنا ترجمة الملف إلى اللغة العربية:

قيمة v (من المصدر) ووزن حافة v-w، أقل من قيمة المسافة لـ w، ثم قم بتحديث قيمة المسافة لـ w.

هذا تنفيذ Java لخوارزمية ديجكسترا:

public void dijkstra(int[][] graph, int src) {
    int V = graph.length;
    int[] dist = new int[V];
    boolean[] sptSet = new boolean[V];
 
    // قم بتهيئة المسافات إلى قيمة عظمى وتعيين كل العقد إلى خارج مجموعة أقصر المسارات
    for (int i = 0; i < V; i++) {
        dist[i] = Integer.MAX_VALUE;
        sptSet[i] = false;
    }
 
    // المسافة من المصدر إلى المصدر هي 0
    dist[src] = 0;
 
    // كرر V-1 مرة
    for (int count = 0; count < V - 1; count++) {
        // اختر العقدة ذات أقل مسافة من بين العقد غير المضافة إلى مجموعة أقصر المسارات
        int u = minDistance(dist, sptSet);
        sptSet[u] = true;
 
        // حدث المسافات إلى جميع العقد غير المضافة إلى مجموعة أقصر المسارات
        for (int v = 0; v < V; v++) {
            if (!sptSet[v] && graph[u][v] != 0 && dist[u] != Integer.MAX_VALUE
                    && dist[u] + graph[u][v] < dist[v]) {
                dist[v] = dist[u] + graph[u][v];
            }
        }
    }
 
    printSolution(dist);
}

خوارزمية بيلمان-فورد هي خوارزمية أخرى لإيجاد أقصر المسارات من عقدة مصدر واحدة إلى جميع العقد الأخرى في رسم موجه مزن. على عكس خوارزمية ديجكسترا، يمكن لخوارزمية بيلمان-فورد التعامل مع الرسوم ذات أوزان حافة سالبة، طالما أنه لا توجد دورات سالبة.

تعمل الخوارزمية على النحو التالي:

  1. قم بتهيئة المسافات من المصدر إلى جميع العقد إلى قيمة لا نهائية والمسافة إلى المصدر نفسه إلى 0.
  2. قم بتخفيف جميع الحواف |V| - 1 مرة. لكل حافة u-v، إذا كانت المسافة إلى v يمكن تقصيرها عن طريق أخذ الحافة u-v، قم بتحديث المسافة إلى v.
  3. تحقق من وجود دورات ذات أوزان سالبة. قم بتشغيل خطوة من التخفيف لجميع الحواف. إذا تغيرت أي مسافة، فهناك دورة ذات وزن سالب.

هنا تنفيذ Java لخوارزمية بيلمان-فورد:

public void bellmanFord(int[][] graph, int src) {
    int V = graph.length;
    int[] dist = new int[V];
 
    // قم بتهيئة المسافات إلى قيمة عظمى
    for (int i = 0; i < V; i++)
        dist[i] = Integer.MAX_VALUE;
    dist[src] = 0;
 
    // كرر V-1 مرة
    for (int i = 1; i < V; i++) {
        for (int u = 0; u < V; u++) {
            for (int v = 0; v < V; v++) {
                if (graph[u][v] != 0 && dist[u] != Integer.MAX_VALUE
                        && dist[u] + graph[u][v] < dist[v]) {
                    dist[v] = dist[u] + graph[u][v];
                }
            }
        }
    }
 
    for (int u = 0; u < V; u++) {
        for (int v = 0; v < V; v++) {
            if (graph[u][v] != 0 && dist[u] != Integer.MAX_VALUE
                    و `dist[u] + graph[u][v] < dist[v]`) {
                System.out.println("الرسم البياني يحتوي على دورة ذات وزن سلبي");
                return;
            }
        }
    }
 
    printSolution(dist);
}

خوارزميات المسارات القصيرة لها العديد من التطبيقات ، مثل أنظمة الملاحة ، وبروتوكولات توجيه الشبكة ، وتخطيط النقل. إنها أدوات أساسية في نظرية الرسوم البيانية وضرورية في العديد من مهام معالجة الرسوم البيانية.

الخاتمة

الرسوم البيانية هي هياكل بيانات متنوعة وقوية يمكن أن تنمذج مجموعة واسعة من المشاكل. في هذا الفصل ، استكشفنا الخصائص الأساسية وأنواع الرسوم البيانية ، ودرسنا الخوارزميات الأساسية للرسوم البيانية بما في ذلك البحث العمق أولاً ، والبحث العرض أولاً ، والحد الأدنى من الأشجار الناشرة ، والمسارات القصيرة.

يوفر البحث العمق أولاً والبحث العرض أولاً طرقًا منهجية لاستكشاف الرسم البياني ، وتشكل أساس العديد من الخوارزميات المتقدمة للرسوم البيانية. تجد خوارزميات الحد الأدنى من الأشجار الناشرة مثل Kruskal و Prim شجرة تربط جميع الرؤوس بأقل وزن إجمالي للحافة. تجد خوارزميات المسار الأقصر مثل Dijkstra و Bellman-Ford مسارات ذات الحد الأدنى من الوزن بين الرؤوس.

فهم هذه المفاهيم والخوارزميات الأساسية أمر حاسم للعمل بفعالية مع الرسوم البيانية ومعالجة المشاكل المعقدة في مختلف المجالات. عند تقدمك في دراسة الخوارزميات ، ستواجه خوارزميات رسوم بيانية متقدمة أكثر تبني هذه التقنيات الأساسية.

توفر الرسوم البيانية لغة قوية لوصف وحل المشاكل في علوم الكمبيوتر وما وراءها. إتقان خوارزميات الرسوم البيانية سيزودك بمجموعة أدوات متنوعة لنمذجة وحل مجموعة واسعة من المشاكل الحسابية.# التحديات الرئيسية

التحديات الفنية

  1. إدارة البيانات والتخزين: التعامل مع كميات كبيرة من البيانات والتخزين الفعال لها.
  2. معالجة الصور والفيديو: تطوير خوارزميات فعالة لمعالجة الصور والفيديو.
  3. معالجة اللغة الطبيعية: تطوير نماذج لفهم اللغة الطبيعية والتواصل بها.
  4. الذكاء الاصطناعي والتعلم الآلي: تطوير نماذج ذكاء اصطناعي وتعلم آلي متقدمة.

التحديات التنظيمية

  1. إدارة الفرق: إنشاء وإدارة فرق عمل فعالة.
  2. إدارة المشاريع: التخطيط والتنفيذ الناجح للمشاريع.
  3. التطوير المستمر: الحفاظ على التطوير والتحسين المستمر للمنتجات.
  4. التكاليف والميزانية: إدارة التكاليف والميزانية بفعالية.

التحديات التقنية

  1. أمن المعلومات: ضمان أمن المعلومات والبيانات.
  2. التكامل والتوافق: ضمان التكامل والتوافق بين مختلف النظم والتقنيات.
  3. البنية التحتية والتوسع: بناء بنية تحتية قوية وقابلة للتوسع.
  4. الأداء والكفاءة: تحسين الأداء والكفاءة في النظم والتطبيقات.

التحديات الأخرى

  1. التعلم المستمر: الحفاظ على التعلم والتطوير المستمر.
  2. التكيف مع التغيير: القدرة على التكيف مع التغييرات السريعة في التقنية والسوق.
  3. الابتكار والإبداع: تشجيع الابتكار والإبداع في المنظمة.
  4. التواصل والتعاون: تعزيز التواصل والتعاون بين الفرق والأقسام.
# هذا هو تعليق باللغة العربية
def example_function(arg1, arg2):
    """
    هذا هو تعليق باللغة العربية للدالة
    """
    # هذا هو تعليق باللغة العربية داخل الدالة
    result = arg1 + arg2
    return result