الفرق بين المراجعتين لصفحة: «Algorithms/graphs»

من موسوعة حسوب
لا ملخص تعديل
لا ملخص تعديل
سطر 1: سطر 1:
<noinclude>{{DISPLAYTITLE:معامل الحمولة وإعادة التقطيع}}</noinclude>
<noinclude>{{DISPLAYTITLE:الرسم البياني}}</noinclude>


تتطلب إضافة زوج مفتاح (K) - قيمة (V) إلى جدول التقطيع الخطوات التالية:
الرسم البياني هو من بنى المعطيات غير الخطية والتي تتضمّن عقدًا nodes وأضلاع edges. تُسمّى العقد في بعض الأحيان بالرؤوس vertices والأضلاع بالخطوط lines أو الأقواس arcs التي تربط بين عقدتين في الرسم البياني.


# يحوّل المفتاح إلى عدد صحيح ذي قيمة أصغر (يسمّى شيفرة التقطيع) باستخدام دالة التقطيع.
ويمكن وصف الرسم البياني بصورة أدق بأنّه يحتوي على مجموعة محدّدة من العقد (أو الرؤوس) ومجموعة محدّدة من الأضلاع التي تربط بين عقدتين.
# تستخدم شيفرة التقطيع لإيجاد موقع (<code>hashCode % arrSize</code>) ويجري البحث في القائمة المترابطة في ذلك الموقع (السلاسل المنفصلة Separate chaining) بحثًا عن المفتاح.
# إن عُثر على ذلك المفتاح فإنّ الدالة تحدّث قيمته، وإن لم يعثر عليه يُخزّن زوج K-V كعقدة جديدة في القائمة المترابطة.


== التعقيد ومعامل التحميل ==
[[File:https://media.geeksforgeeks.org/wp-content/cdn-uploads/undirectedgraph.png|frame|none|alt=|caption ]]


* يعتمد الوقت المستغرق لتنفيذ الخطوة الأولى على المفتاح وعلى دالة التقطيع. فعلى سبيل المثال إن كان المفتاح عبارة عن سلسلة نصية <code>&quot;abcd&quot;</code> فيمكن لدالة التقطيع أن تعتمد على طول السلسلة النصية، ولكن عند استخدام أعداد كبيرة من العناصر التي ستدخل إلى الجدول، فإنّ طول المفاتيح سيكون ضئيلًا جدًّا مقارنة بعدد العناصر؛ لذا يمكن القول أنّ العملية ستجري في زمن ثابت وسيبلغ التعقيد الزمني لها <code>O(1)‎</code>.
يتضمّن الرسم البياني أعلاه مجموعة الرؤوس V = {0,1,2,3,4}ومجموعة الأضلاع E = {01, 12, 23, 34, 04, 14, 13}‎.
* تتطلب الخطوة الثانية التنقل عبر قائمة أزواج K-V الموجودة في الموقع المحدد. أسوأ الاحتمالات هو أن جميع العناصر موجودة في الموقع ذاته وبيلغ التعقيد الزمني لهذه الخطوة عندها <code>O(n)</code>. ولكن أجريت الكثير من الأبحاث لجعل دوال التقطيع توزّع المفاتيح في المصفوفة بصورة نظامية؛ لذا هذا الاحتمال غير وارد إطلاقًا.
* لو كان هناك n من العناصر وكان حجم المصفوفة هو b فإن معدّل العناصر في كل موقع سيكون n/b، وتسمّى هذه القيمة بمعامل الحمولة load factor والذي يمثّل حمولة جدول التقطيع.
* يجب الإبقاء على قيمة منخفضة لمعامل الحمولة، وبذلك يصبح عدد العناصر في الموقع الواحد أقلّ ما يمكن، ويكون التعقيد الزمني ثابتًا تقريبًا أي يصبح <code>O(1)</code>.


== إعادة التقطيع ==
يجدر التنبيه إلى أن مجموعة الحواف مرتّبة وذلك لأنّ الزوج (u, v) يختلف عن الزوج (v, u) في حال استخدام الرسم البياني الموجّه directed graph (أو ما يعرف اختصارًا بـ di-graph). يشير الزوج (u, v) إلى أنّ هناك حافة من الرأس u إلى الرأس v، ويمكن للحافة أن تتضمّن الوزن/القيمة/الكلفة.


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


=== تنفيذ عملية إعادة التقطيع ===
يعرض المثال التالي صورة لرسم بياني غير موجّه يمتلك خمسة رؤوس. [[File:https://cdncontribute.geeksforgeeks.org/wp-content/uploads/undirectedgraph.png|fig:]]


تجرى عملية إعادة التقطيع باتباع الخطوات التالية:
== تمثيل الرسوم البيانية ==


* يجري التحقق من قيمة معامل الحمولة عند إضافة عنصر جديد إلى جدول التقطيع.
هناك طريقتان شائعتان لتمثيل الرسوم البيانية:
* إن كانت قيمة معامل الحمولة أكبر من القيمة المعرفة مسبقًا (أو القيمة الافتراضية 0.75 إن لم تكن هناك قيمة معرّفة) نجري عملية إعادة التقطيع.
* ننشئ مصفوفة جديدة حجمها ضعف حجم المصفوفة السابقة.
* نتنقل عبر جميع العناصر في المصفوفة القديمة ونستدعي الدالة <code>insert()‎</code> لكل عنصر حتى تدرج حميع العناصر في المصفوفة الجديدة ذات الحجم الأكبر.


== أمثلة ==
# مصفوفة المجاورة Adjacency Matrix.
# قائمة المجاورة Adjacency List.


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


<source lang="java">import java.util.ArrayList;
=== مصفوفة المجاورة ===


class Map<K, V> {
مصفوفة المجاورة هي مصفوفة ثنائية الأبعاد حجمها V x V وتمثل V عدد الرؤوس في الرسم البياني. ولو فرضنا أنّ المصفوفة ثنائية الأبعاد تحمل الاسم adj فإنّ الموقع <code>adj[i][j] = 1</code> يشير إلى أنّ هناك ضلعًا يربط الرأس <code>i</code> بالرأس <code>j</code>. تكون مصفوفات المجاورة التي تمثّل رسومًا بيانية غير موجّهةٍ متناظرةً دائمًا. تستخدم مصفوفات المجاورة كذلك في تمثيل الرسوم البيانية الموزونة، فلو كان الموقع <code>adj[i][j] = w</code> فإنّ هذا يعني أن هناك ضلعًا يربط الرأس <code>i</code> بالرأس <code>j</code> ولهذا الضلع الوزن <code>w</code>.


class MapNode<K, V> {
مصفوفة المجاورة للمثال أعلاه هي: [[File:https://cdncontribute.geeksforgeeks.org/wp-content/uploads/adjacencymatrix.png|fig:]]


K key;
'''مميزات هذه الطريقة:'''
V value;
MapNode<K, V> next;


public MapNode(K key, V value)  
# سهولة التنفيذ والمتابعة.
{
# يبلغ التعقيد الزمني لعملية حذف ضلع من الأضلاع <code>O(1)‎</code>.
this.key = key;
# يمكن لعمليات الاستعلام عن وجود ضلع يربط الرأس u بالرأس v أن تكون فعّالة وأن تنفّذ في <code>O(1)‎</code> من الوقت.
this.value = value;
 
next = null;
'''عيوب هذه الطريقة:'''
}
}


// المصفوفة التي تخزّن فيها أزواج مفتاح-قيمة
# تستهلك الكثير من المساحة <code>O(V^2)‎</code>، وحتى لو تضمّن الرسم البياني القليل من الرؤوس (أي أن عدد الأضلاع قليل) فإنّ هذه الطريقة تستهلك المساحة ذاتها.
    ArrayList<MapNode<K, V> > buckets;
# يبلغ التعقيد الزمني لعملية إضافة رأس إلى الرسم البياني <code>O(V^2)‎</code>.


// n - عدد الأزواج المخزّنة
==== أمثلة ====
int size;


    // b - حجم المصفوفة
يعرض المثال التالي طريقة تنفيذ مصفوفة المجاورة في لغة بايثون:
int numBuckets;


// القيمة الافتراضية لمعامل الحمولة
<source lang="python">class Graph:
final double DEFAULT_LOAD_FACTOR = 0.75;
    def __init__(self,numvertex):
        self.adjMatrix = [[-1]*numvertex for x in range(numvertex)]
        self.numvertex = numvertex
        self.vertices = {}
        self.verticeslist =[0]*numvertex


public Map()  
    def set_vertex(self,vtx,id):
{
        if 0<=vtx<=self.numvertex:
numBuckets = 5;
            self.vertices[id] = vtx
            self.verticeslist[vtx] = id


buckets = new ArrayList<>(numBuckets);
    def set_edge(self,frm,to,cost=0):
        frm = self.vertices[frm]
        to = self.vertices[to]
        self.adjMatrix[frm][to] = cost
        # لا تضف هذا السطر إن كنت تستخدم الرسوم البيانية الموجّهة
        self.adjMatrix[to][frm] = cost


for (int i = 0; i < numBuckets; i++) {
    def get_vertex(self):
// null  تهيئة المصفوفة بقيم
        return self.verticeslist
buckets.add(null);
}
System.out.println("HashMap created");
System.out.println("Number of pairs in the Map: " + size);
System.out.println("Size of Map: " + numBuckets);
System.out.println("Default Load Factor : " + DEFAULT_LOAD_FACTOR + "\n");
}


private int getBucketInd(K key)  
    def get_edges(self):
{
        edges=[]
        for i in range (self.numvertex):
        // استخدام دالة داخلية من صنف الكائن
            for j in range (self.numvertex):
int hashCode = key.hashCode();
                if (self.adjMatrix[i][j]!=-1):
                    edges.append((self.verticeslist[i],self.verticeslist[j],self.adjMatrix[i][j]))
        return edges
       
    def get_matrix(self):
        return self.adjMatrix


// array index = hashCode%numBuckets
G =Graph(6)
return (hashCode % numBuckets);
G.set_vertex(0,'a')
}
G.set_vertex(1,'b')
G.set_vertex(2,'c')
G.set_vertex(3,'d')
G.set_vertex(4,'e')
G.set_vertex(5,'f')
G.set_edge('a','e',10)
G.set_edge('a','c',20)
G.set_edge('c','b',30)
G.set_edge('b','e',40)
G.set_edge('e','d',50)
G.set_edge('f','e',60)
print("Vertices of Graph")
print(G.get_vertex())
print("Edges of Graph")
print(G.get_edges())
print("Adjacency Matrix of Graph")
print(G.get_matrix())</source>
=== قائمة المجاورة ===


public void insert(K key, V value)
تستخدم هذه الطريقة مصفوفة من القوائم، ويكون حجم المصفوفة مساويًا لعدد الرؤوس في الرسم البياني. ولو فرضنا أنّ المصفوفة تحمل الاسم <code>array[]‎</code> فإنّ العنصر <code>array[i]‎</code> يمثّل قائمة الرؤوس المجاورة للرأس i. يمكن استخدام طريقة التمثيل هذه مع الرسوم البيانية الموزونة، إذ يمكن تمثيل أوزان الأضلاع بواسطة قوائم من الأزواج.
{
// الحصول على الموقع الذي سيدرج العنصر فيه
int bucketInd = getBucketInd(key);


// العقدة الأولى في ذلك الموقع
يعرض الشكل التالي طريقة تمثيل الرسم البياني أعلاه باستخدام قائمة المجاورة:
MapNode<K, V> head = buckets.get(bucketInd);


// المرور على جميع العقد الموجودة في ذلك الموقع أولًا
        // وذلك للتحقق ممّا إذا كان المفتاح موجودًا بالفعل
while (head != null) {


// تُحدّث القيمة إن كان المفتاح موجودًا
            // If already present the value is updated
if (head.key.equals(key)) {
head.value = value;
return;
}
head = head.next;
}


// عقدة جديدة مع مفتاح وقيمة
[[File:https://cdncontribute.geeksforgeeks.org/wp-content/uploads/listadjacency.png|frame|none|alt=|caption ]]
MapNode<K, V> newElementNode = new MapNode<K, V>(key, value);


// العقدة الأولى في الموقع
==== أمثلة ====
head = buckets.get(bucketInd);
        // تُدرج العقدة الجديدة وذلك بجعلها عقدة الرأس
        // وتكون العقدة التالية لها هي عقدة الرأس السابقة
newElementNode.next = head;


buckets.set(bucketInd, newElementNode);
تعرض الأمثلة التالية طريقة تنفيذ قائمة المجاورة في عدد من لغات البرمجة.


System.out.println("Pair(" + key + ", " + value + ") inserted successfully.\n");
يجدر التنبيه إلى أنّ طرق التنفيذ أدناه تستخدم المصفوفات الديناميكية (المتجهات vector في C++‎ و <code>ArrayList</code> في جافا) عوضًا عن القوائم المترابطة لتمثيل قوائم المجاورة.


        // زيادة حجم المصفوفة عند إضافة زوج (مفتاح-قيمة) جديد إلى جدول التقطيع
* C++‎:
size++;


// حساب قيمة معامل الحمولة
<source lang="c++">#include<bits/stdc++.h>
double loadFactor = (1.0 * size) / numBuckets;  
using namespace std;  


System.out.println("Current Load factor = " + loadFactor);  
// دالة مساعدة لإضافة ضلع إلى رسم بياني غير موجّه
void addEdge(vector<int> adj[], int u, int v)
{
adj[u].push_back(v);
adj[v].push_back(u);  
}


// إن تجاوزت قيمة معامل الحمولة 0.75، تنفّذ عملية إعادة التقطيع
// دالة مساعدة لطباعة قائمة المجاورة التي تمثّل الرسم البياني
if (loadFactor > DEFAULT_LOAD_FACTOR) {  
void printGraph(vector<int> adj[], int V)  
System.out.println(loadFactor + " is greater than " + DEFAULT_LOAD_FACTOR);  
{  
System.out.println("Therefore Rehashing will be done.\n");  
for (int v = 0; v < V; ++v)
{
cout << "\n Adjacency list of vertex "
<< v << "\n head ";
for (auto x : adj[v])  
cout << "-> " << x;  
printf("\n");  
}
}


// إعادة التقطيع
// اختبار الدوال السابقة
rehash();  
int main()
{
int V = 5;
vector<int> adj[V];
addEdge(adj, 0, 1);
addEdge(adj, 0, 4);
addEdge(adj, 1, 2);
addEdge(adj, 1, 3);
addEdge(adj, 1, 4);
addEdge(adj, 2, 3);  
addEdge(adj, 3, 4);
printGraph(adj, V);
return 0;
}
</source>
* بايثون:
 
<source lang="python"># يستخدم هذا الصنف لتمثيل قائمة المجاورة الخاصّة بعقدة معينة
class AdjNode:
def __init__(self, data):
self.vertex = data
self.next = None


System.out.println("New Size of Map: " + numBuckets + "\n");
}


System.out.println("Number of pairs in the Map: " + size);
# يمثل هذا الصنف رسمًا بيانياً.  
System.out.println("Size of Map: " + numBuckets + "\n");
# الرسم البياني هو قائمة تتضمن قوائم المجاورة.
}
# عدد الرؤوس سيكون هو حجم المصفوفة.


private void rehash()  
class Graph:
{
def __init__(self, vertices):
self.V = vertices
self.graph = [None] * self.V


System.out.println("\n***Rehashing Started***\n");
# دالة لإضافة ضلع إلى الرسم البياني غير الموجّه
def add_edge(self, src, dest):
# إضافة العقدة إلى العقدة المصدرية
node = AdjNode(dest)
node.next = self.graph[src]
self.graph[src] = node


// تحوّل القائمة الحالية إلى قائمة مؤقتة
# إضافة العقدة المصدرية إلى الهدف الذي هو رسم بياني غير موجّه
ArrayList<MapNode<K, V> > temp = buckets;
node = AdjNode(src)
node.next = self.graph[dest]
self.graph[dest] = node


// إنشاء قائمة جديدة حجمها ضعف حجم القائمة القديمة
# دالة لطباعة الرسم البياني
buckets = new ArrayList<MapNode<K, V> >(2 * numBuckets);
def print_graph(self):
for i in range(self.V):
print("Adjacency list of vertex {}\n head".format(i), end="")
temp = self.graph[i]
while temp:
print(" -> {}".format(temp.vertex), end="")
temp = temp.next
print(" \n")  


for (int i = 0; i < 2 * numBuckets; i++) {
// null تهيئة المصفوفة بقيم
            // Initialised to null
buckets.add(null);
}
// نحوّل الحجم إلى القيمة 0
        // ثم نمرّ على جميع العقد في القائمة الأصلية
        // وندرجها كلّها في القائمة الجديدة
size = 0;
numBuckets *= 2;


for (int i = 0; i < temp.size(); i++) {
# اختبار الشيفرة السابقة
if __name__ == "__main__":
V = 5
graph = Graph(V)
graph.add_edge(0, 1)
graph.add_edge(0, 4)
graph.add_edge(1, 2)
graph.add_edge(1, 3)
graph.add_edge(1, 4)
graph.add_edge(2, 3)  
graph.add_edge(3, 4)  


// رأس السلسلة في هذا الموقع
graph.print_graph() </source>
MapNode<K, V> head = temp.get(i);
* جافا


while (head != null) {
<source lang="java">import java.util.LinkedList;  
K key = head.key;
V val = head.value;  


// استدعاء دالة الإدراج لكل عقدة في القائمة المؤقتة
public class GFG
                // فالقائمة الجديدة هي القائمة الأساسية
{
insert(key, val);
// صنف معرّف من قبل المستخدم لتمثيل الرسم البياني
head = head.next;  
    // الرسم البياني هو مصفوفة تتضمن قوائم المجاورة
    // عدد الرؤوس في الرسم البياني سيكون هو حجم المصفوفة
static class Graph
{
int V;
LinkedList<Integer> adjListArray[];
// دالة بانية
Graph(int V)
{
this.V = V;
// تعريف حجم المصفوفة ليكون مساويًا عدد الرؤوس
adjListArray = new LinkedList[V];
            // إنشاء قائمة جديدة لكل رأس
            // وذلك ليكون بالإمكان تخزين العقد المتجاورة
for(int i = 0; i < V ; i++){
adjListArray[i] = new LinkedList<>();  
}  
}  
}  
}  
System.out.println("\n***Rehashing Ended***\n");
}  
}  
 
public void printMap()  
// تضيف الدالة ضلعًا إلى الرسم البياني غير الموجّه
static void addEdge(Graph graph, int src, int dest)  
{  
{  
 
// إضافة الضلع من المصدر إلى الهدف
// تحوّل القائمة الحالية إلى قائمة مؤقتة
graph.adjListArray[src].add(dest);  
ArrayList<MapNode<K, V> > temp = buckets;  
 
// لما كان الرسم البياني غير موجّه، نضيف ضلعًا من الهدف إلى المصدر أيضًا
System.out.println("Current HashMap:");  
graph.adjListArray[dest].add(src);  
// المرور على جميع العقد وطباعة قيمها
}
for (int i = 0; i < temp.size(); i++) {  
 
// دالة مساعدة لطباعة قائمة المجاورة التي تمثل الرسم البياني
// رأس السلسلة في هذا الموقع
static void printGraph(Graph graph)
MapNode<K, V> head = temp.get(i);  
{
 
for(int v = 0; v < graph.V; v++)  
while (head != null) {  
{  
System.out.println("key = " + head.key + ", val = " + head.value);
System.out.println("Adjacency list of vertex "+ v);
 
System.out.print("head");  
head = head.next;  
for(Integer pCrawl: graph.adjListArray[v]){  
System.out.print(" -> "+pCrawl);  
}  
}  
System.out.println("\n");
}  
}  
System.out.println();
}  
}  
}
 
// اختبار الدوال السابقة
public class GFG {
public static void main(String args[])  
 
public static void main(String[] args)  
{  
{  
 
// إنشاء الرسم البياني في الشكل أعلاه
// إنشاء جدول التقطيع
int V = 5;  
Map<Integer, String> map = new Map<Integer, String>();  
Graph graph = new Graph(V);  
 
addEdge(graph, 0, 1);  
// إدراج العناصر
addEdge(graph, 0, 4);  
map.insert(1, "Geeks");  
addEdge(graph, 1, 2);  
map.printMap();  
addEdge(graph, 1, 3);  
 
addEdge(graph, 1, 4);  
map.insert(2, "forGeeks");  
addEdge(graph, 2, 3);  
map.printMap();  
addEdge(graph, 3, 4);  
 
map.insert(3, "A");  
// طباعة قائمة المجاورة التي تمثل الرسم البياني أعلاه
map.printMap();  
printGraph(graph);  
 
map.insert(4, "Computer");  
map.printMap();  
 
map.insert(5, "Portal");
map.printMap();  
}  
}  
} </source>
} </source>
تعطي الشيفرة السابقة المخرجات التالية:
'''مميزات هذه الطريقة:'''
 
<source lang="text">HashMap created
Number of pairs in the Map: 0
Size of Map: 5
Default Load Factor : 0.75
 
Pair(1, Geeks) inserted successfully.
 
Current Load factor = 0.2
Number of pairs in the Map: 1
Size of Map: 5
 
Current HashMap:
key = 1, val = Geeks
 
Pair(2, forGeeks) inserted successfully.
 
Current Load factor = 0.4
Number of pairs in the Map: 2
Size of Map: 5
 
Current HashMap:
key = 1, val = Geeks
key = 2, val = forGeeks
 
Pair(3, A) inserted successfully.
 
Current Load factor = 0.6
Number of pairs in the Map: 3
Size of Map: 5
 
Current HashMap:
key = 1, val = Geeks
key = 2, val = forGeeks
key = 3, val = A
 
Pair(4, Computer) inserted successfully.
 
Current Load factor = 0.8
0.8 is greater than 0.75
Therefore Rehashing will be done.
 
 
***Rehashing Started***
 
Pair(1, Geeks) inserted successfully.
 
Current Load factor = 0.1
Number of pairs in the Map: 1
Size of Map: 10
 
Pair(2, forGeeks) inserted successfully.
 
Current Load factor = 0.2
Number of pairs in the Map: 2
Size of Map: 10
 
Pair(3, A) inserted successfully.
 
Current Load factor = 0.3
Number of pairs in the Map: 3
Size of Map: 10
 
Pair(4, Computer) inserted successfully.
 
Current Load factor = 0.4
Number of pairs in the Map: 4
Size of Map: 10
 
 
***Rehashing Ended***
 
New Size of Map: 10
 
Number of pairs in the Map: 4
Size of Map: 10
 
Current HashMap:
key = 1, val = Geeks
key = 2, val = forGeeks
key = 3, val = A
key = 4, val = Computer


Pair(5, Portal) inserted successfully.
# تمتاز طريقة التمثيل هذه بتوفير المساحة <code>O(|V|+|E|)‎</code>. وفي أسوأ الاحتمالات يمكن أن يكون هناك <code>C(V, 2)‎</code> من الأضلاع في الرسم البياني وبهذا تكون المساحة المستهلكة <code>O(V^2)‎</code>.
# إضافة الرؤوس إلى الرسم البياني أكثر سهولة.


Current Load factor = 0.5
'''عيوب هذه الطريقة:'''
Number of pairs in the Map: 5
Size of Map: 10


Current HashMap:
عمليات الاستعلام عن وجود ضلع يربط الرأس u بالرأس v غير فعّالة وتنفّذ في <code>O(V)‎</code> من الوقت.
key = 1, val = Geeks
key = 2, val = forGeeks
key = 3, val = A
key = 4, val = Computer
key = 5, val = Portal</source>


== مصادر ==
== مصادر ==


* صفحة [https://www.geeksforgeeks.org/load-factor-and-rehashing/ Load Factor and Rehashing] في توثيق بنى المعطيات في موقع GeeksforGeeks.
* صفحة [https://www.geeksforgeeks.org/graph-data-structure-and-algorithms/ Graph Data Structure and Algorithms] في توثيق بنى المعطيات في موقع GeeksforGeeks.
* صفحة [https://www.geeksforgeeks.org/graph-and-its-representations/ Graph and its representations] في توثيق بنى المعطيات في موقع GeeksforGeeks.


[[تصنيف: الخوارزميات]]
[[تصنيف: الخوارزميات]]
[[تصنيف: بنى المعطيات]]
[[تصنيف: بنى المعطيات]]

مراجعة 20:43، 18 يونيو 2019


الرسم البياني هو من بنى المعطيات غير الخطية والتي تتضمّن عقدًا nodes وأضلاع edges. تُسمّى العقد في بعض الأحيان بالرؤوس vertices والأضلاع بالخطوط lines أو الأقواس arcs التي تربط بين عقدتين في الرسم البياني.

ويمكن وصف الرسم البياني بصورة أدق بأنّه يحتوي على مجموعة محدّدة من العقد (أو الرؤوس) ومجموعة محدّدة من الأضلاع التي تربط بين عقدتين.

ملف:https://media.geeksforgeeks.org/wp-content/cdn-uploads/undirectedgraph.png
caption

يتضمّن الرسم البياني أعلاه مجموعة الرؤوس V = {0,1,2,3,4}‎ ومجموعة الأضلاع E = {01, 12, 23, 34, 04, 14, 13}‎.

يجدر التنبيه إلى أن مجموعة الحواف مرتّبة وذلك لأنّ الزوج (u, v) يختلف عن الزوج (v, u) في حال استخدام الرسم البياني الموجّه directed graph (أو ما يعرف اختصارًا بـ di-graph). يشير الزوج (u, v) إلى أنّ هناك حافة من الرأس u إلى الرأس v، ويمكن للحافة أن تتضمّن الوزن/القيمة/الكلفة.

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

يعرض المثال التالي صورة لرسم بياني غير موجّه يمتلك خمسة رؤوس. fig:

تمثيل الرسوم البيانية

هناك طريقتان شائعتان لتمثيل الرسوم البيانية:

  1. مصفوفة المجاورة Adjacency Matrix.
  2. قائمة المجاورة Adjacency List.

وهناك طرق أخرى لتمثيل الرسوم البيانية مثل مصفوفة الوقوع Incidence Matrix أو قائمة الوقوع Incidence List. يعتمد اختيار طريقة التمثيل على الحالة التي يجري التعامل معها، وعلى العمليات المراد إجراؤها وسهولة الاستخدام.

مصفوفة المجاورة

مصفوفة المجاورة هي مصفوفة ثنائية الأبعاد حجمها V x V وتمثل V عدد الرؤوس في الرسم البياني. ولو فرضنا أنّ المصفوفة ثنائية الأبعاد تحمل الاسم adj فإنّ الموقع adj[i][j] = 1 يشير إلى أنّ هناك ضلعًا يربط الرأس i بالرأس j. تكون مصفوفات المجاورة التي تمثّل رسومًا بيانية غير موجّهةٍ متناظرةً دائمًا. تستخدم مصفوفات المجاورة كذلك في تمثيل الرسوم البيانية الموزونة، فلو كان الموقع adj[i][j] = w فإنّ هذا يعني أن هناك ضلعًا يربط الرأس i بالرأس j ولهذا الضلع الوزن w.

مصفوفة المجاورة للمثال أعلاه هي: fig:

مميزات هذه الطريقة:

  1. سهولة التنفيذ والمتابعة.
  2. يبلغ التعقيد الزمني لعملية حذف ضلع من الأضلاع O(1)‎.
  3. يمكن لعمليات الاستعلام عن وجود ضلع يربط الرأس u بالرأس v أن تكون فعّالة وأن تنفّذ في O(1)‎ من الوقت.

عيوب هذه الطريقة:

  1. تستهلك الكثير من المساحة O(V^2)‎، وحتى لو تضمّن الرسم البياني القليل من الرؤوس (أي أن عدد الأضلاع قليل) فإنّ هذه الطريقة تستهلك المساحة ذاتها.
  2. يبلغ التعقيد الزمني لعملية إضافة رأس إلى الرسم البياني O(V^2)‎.

أمثلة

يعرض المثال التالي طريقة تنفيذ مصفوفة المجاورة في لغة بايثون:

class Graph:
    def __init__(self,numvertex):
        self.adjMatrix = [[-1]*numvertex for x in range(numvertex)]
        self.numvertex = numvertex
        self.vertices = {}
        self.verticeslist =[0]*numvertex

    def set_vertex(self,vtx,id):
        if 0<=vtx<=self.numvertex:
            self.vertices[id] = vtx
            self.verticeslist[vtx] = id

    def set_edge(self,frm,to,cost=0):
        frm = self.vertices[frm]
        to = self.vertices[to]
        self.adjMatrix[frm][to] = cost
        # لا تضف هذا السطر إن كنت تستخدم الرسوم البيانية الموجّهة
        self.adjMatrix[to][frm] = cost

    def get_vertex(self):
        return self.verticeslist

    def get_edges(self):
        edges=[]
        for i in range (self.numvertex):
            for j in range (self.numvertex):
                if (self.adjMatrix[i][j]!=-1):
                    edges.append((self.verticeslist[i],self.verticeslist[j],self.adjMatrix[i][j]))
        return edges
        
    def get_matrix(self):
        return self.adjMatrix

G =Graph(6)
G.set_vertex(0,'a')
G.set_vertex(1,'b')
G.set_vertex(2,'c')
G.set_vertex(3,'d')
G.set_vertex(4,'e')
G.set_vertex(5,'f')
G.set_edge('a','e',10)
G.set_edge('a','c',20)
G.set_edge('c','b',30)
G.set_edge('b','e',40)
G.set_edge('e','d',50)
G.set_edge('f','e',60)
print("Vertices of Graph")
print(G.get_vertex())
print("Edges of Graph")
print(G.get_edges())
print("Adjacency Matrix of Graph")
print(G.get_matrix())

قائمة المجاورة

تستخدم هذه الطريقة مصفوفة من القوائم، ويكون حجم المصفوفة مساويًا لعدد الرؤوس في الرسم البياني. ولو فرضنا أنّ المصفوفة تحمل الاسم array[]‎ فإنّ العنصر array[i]‎ يمثّل قائمة الرؤوس المجاورة للرأس i. يمكن استخدام طريقة التمثيل هذه مع الرسوم البيانية الموزونة، إذ يمكن تمثيل أوزان الأضلاع بواسطة قوائم من الأزواج.

يعرض الشكل التالي طريقة تمثيل الرسم البياني أعلاه باستخدام قائمة المجاورة:


ملف:https://cdncontribute.geeksforgeeks.org/wp-content/uploads/listadjacency.png
caption

أمثلة

تعرض الأمثلة التالية طريقة تنفيذ قائمة المجاورة في عدد من لغات البرمجة.

يجدر التنبيه إلى أنّ طرق التنفيذ أدناه تستخدم المصفوفات الديناميكية (المتجهات vector في C++‎ و ArrayList في جافا) عوضًا عن القوائم المترابطة لتمثيل قوائم المجاورة.

  • C++‎:
#include<bits/stdc++.h> 
using namespace std; 

// دالة مساعدة لإضافة ضلع إلى رسم بياني غير موجّه
void addEdge(vector<int> adj[], int u, int v) 
{ 
	adj[u].push_back(v); 
	adj[v].push_back(u); 
} 

// دالة مساعدة لطباعة قائمة المجاورة التي تمثّل الرسم البياني
void printGraph(vector<int> adj[], int V) 
{ 
	for (int v = 0; v < V; ++v) 
	{ 
		cout << "\n Adjacency list of vertex "
			<< v << "\n head "; 
		for (auto x : adj[v]) 
		cout << "-> " << x; 
		printf("\n"); 
	} 
} 

// اختبار الدوال السابقة
int main() 
{ 
	int V = 5; 
	vector<int> adj[V]; 
	addEdge(adj, 0, 1); 
	addEdge(adj, 0, 4); 
	addEdge(adj, 1, 2); 
	addEdge(adj, 1, 3); 
	addEdge(adj, 1, 4); 
	addEdge(adj, 2, 3); 
	addEdge(adj, 3, 4); 
	printGraph(adj, V); 
	return 0; 
}
  • بايثون:
# يستخدم هذا الصنف لتمثيل قائمة المجاورة الخاصّة بعقدة معينة
class AdjNode: 
	def __init__(self, data): 
		self.vertex = data 
		self.next = None


# يمثل هذا الصنف رسمًا بيانياً. 
# الرسم البياني هو قائمة تتضمن قوائم المجاورة.
# عدد الرؤوس سيكون هو حجم المصفوفة.

class Graph: 
	def __init__(self, vertices): 
		self.V = vertices 
		self.graph = [None] * self.V 

	# دالة لإضافة ضلع إلى الرسم البياني غير الموجّه
	def add_edge(self, src, dest): 
		# إضافة العقدة إلى العقدة المصدرية
		node = AdjNode(dest) 
		node.next = self.graph[src] 
		self.graph[src] = node 

		# إضافة العقدة المصدرية إلى الهدف الذي هو رسم بياني غير موجّه
		node = AdjNode(src) 
		node.next = self.graph[dest] 
		self.graph[dest] = node 

	# دالة لطباعة الرسم البياني
	def print_graph(self): 
		for i in range(self.V): 
			print("Adjacency list of vertex {}\n head".format(i), end="") 
			temp = self.graph[i] 
			while temp: 
				print(" -> {}".format(temp.vertex), end="") 
				temp = temp.next
			print(" \n") 


# اختبار الشيفرة السابقة
if __name__ == "__main__": 
	V = 5
	graph = Graph(V) 
	graph.add_edge(0, 1) 
	graph.add_edge(0, 4) 
	graph.add_edge(1, 2) 
	graph.add_edge(1, 3) 
	graph.add_edge(1, 4) 
	graph.add_edge(2, 3) 
	graph.add_edge(3, 4) 

	graph.print_graph()
  • جافا
import java.util.LinkedList; 

public class GFG 
{ 
	// صنف معرّف من قبل المستخدم لتمثيل الرسم البياني
    // الرسم البياني هو مصفوفة تتضمن قوائم المجاورة
    // عدد الرؤوس في الرسم البياني سيكون هو حجم المصفوفة
	static class Graph 
	{ 
		int V; 
		LinkedList<Integer> adjListArray[]; 
		
		// دالة بانية
		Graph(int V) 
		{ 
			this.V = V; 
			
			// تعريف حجم المصفوفة ليكون مساويًا عدد الرؤوس
			adjListArray = new LinkedList[V]; 
			
            // إنشاء قائمة جديدة لكل رأس
            // وذلك ليكون بالإمكان تخزين العقد المتجاورة
			for(int i = 0; i < V ; i++){ 
				adjListArray[i] = new LinkedList<>(); 
			} 
		} 
	} 
	
	// تضيف الدالة ضلعًا إلى الرسم البياني غير الموجّه
	static void addEdge(Graph graph, int src, int dest) 
	{ 
		// إضافة الضلع من المصدر إلى الهدف
		graph.adjListArray[src].add(dest); 
		
		// لما كان الرسم البياني غير موجّه، نضيف ضلعًا من الهدف إلى المصدر أيضًا
		graph.adjListArray[dest].add(src); 
	} 
	
	// دالة مساعدة لطباعة قائمة المجاورة التي تمثل الرسم البياني
	static void printGraph(Graph graph) 
	{	 
		for(int v = 0; v < graph.V; v++) 
		{ 
			System.out.println("Adjacency list of vertex "+ v); 
			System.out.print("head"); 
			for(Integer pCrawl: graph.adjListArray[v]){ 
				System.out.print(" -> "+pCrawl); 
			} 
			System.out.println("\n"); 
		} 
	} 
	
	// اختبار الدوال السابقة
	public static void main(String args[]) 
	{ 
		// إنشاء الرسم البياني في الشكل أعلاه
		int V = 5; 
		Graph graph = new Graph(V); 
		addEdge(graph, 0, 1); 
		addEdge(graph, 0, 4); 
		addEdge(graph, 1, 2); 
		addEdge(graph, 1, 3); 
		addEdge(graph, 1, 4); 
		addEdge(graph, 2, 3); 
		addEdge(graph, 3, 4); 
	
		// طباعة قائمة المجاورة التي تمثل الرسم البياني أعلاه
		printGraph(graph); 
	} 
}

مميزات هذه الطريقة:

  1. تمتاز طريقة التمثيل هذه بتوفير المساحة O(|V|+|E|)‎. وفي أسوأ الاحتمالات يمكن أن يكون هناك C(V, 2)‎ من الأضلاع في الرسم البياني وبهذا تكون المساحة المستهلكة O(V^2)‎.
  2. إضافة الرؤوس إلى الرسم البياني أكثر سهولة.

عيوب هذه الطريقة:

عمليات الاستعلام عن وجود ضلع يربط الرأس u بالرأس v غير فعّالة وتنفّذ في O(V)‎ من الوقت.

مصادر