اجتياز الرسم البياني البسيط: البحث عن عمق وعمق باستخدام جافا سكريبت كمثال



يوم جيد.

أقدم لكم ترجمة مقالة "الخوارزميات على الرسوم البيانية: لنتحدث عن العمق - البحث الأول (DFS) والبحث الشامل الأول (BFS)" بواسطة Try Khov.

ما هو اجتياز الرسم البياني؟


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

الخوارزميتان الأساسيتان لاختراقتا الرسم البياني هما بحث العمق الأول ، و DFS ، والبحث الأول ، BFS.

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

بحث العمق


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

لنلقي نظرة على مثال. لنفترض أن لدينا رسمًا بيانيًا موجهًا يبدو كالتالي:



نحن في النقطة "s" ونحتاج إلى العثور على قمة "t". باستخدام DFS ، نحقق في أحد المسارات المحتملة ، وننتقل على طوله حتى النهاية ، وإذا لم نجد t ، فارجع واستكشف مسارًا آخر. إليك ما تبدو عليه العملية:



هنا ننتقل على طول المسار (p1) إلى أقرب قمة ونرى أن هذه ليست نهاية المسار. لذلك ، ننتقل إلى القمة التالية.



وصلنا إلى نهاية p1 ، لكننا لم نجد t ، لذلك نعود إلى s ونتحرك على المسار الثاني.



بعد الوصول إلى أعلى المسار "p2" الأقرب إلى النقطة "s" ، نرى ثلاثة اتجاهات محتملة لمزيد من الحركة. بما أننا قمنا بالفعل بزيارة القمة التي تتوج الاتجاه الأول ، فإننا نتحرك على طول الاتجاه الثاني.



وصلنا مرة أخرى إلى نهاية المسار ، لكننا لم نجد t ، لذلك نعود. نتبع المسار الثالث ، وأخيرًا ، نصل إلى الذروة المطلوبة "t".



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

نتبع هذه الخوارزمية لكل قمة تمت زيارتها.

تشير الحاجة إلى التكرار المتكرر للإجراء إلى الحاجة إلى استخدام العودية لتنفيذ الخوارزمية.

إليك كود JavaScript:

//  ,       
// , : adj = {A: [B,C], B:[D,F], ... }
function dfs(adj, v, t) {
	// adj -  
	// v -   ()
	// t -  

	//   
	//    ,    
	if(v === t) return true
	if(v.visited) return false

	//    
	v.visited = true
	//    (  ) v
	for(let neighbor of adj[v]) {
		//    
		if(!neighbor.visited) {
			//     ,      
			let reached = dfs(adj, neighbor, t)
			//  true,  
			if(reached) return true
		}
	}
	//   v  t  
	return false
}

ملاحظة: تسمح لك خوارزمية DFS الخاصة بالتحقق مما إذا كان من الممكن الانتقال من مكان إلى آخر. يمكن استخدام DFS لأغراض مختلفة. ستحدد هذه الأهداف كيف ستبدو الخوارزمية نفسها. ومع ذلك ، يبدو المفهوم العام تمامًا مثل ذلك.

تحليل DFS


دعونا نحلل هذه الخوارزمية. نظرًا لأننا نتجول حول كل "جار" لكل عقدة ، متجاهلين تلك التي قمنا بزيارتها سابقًا ، فإن لدينا وقت تشغيل يساوي O (V + E).

شرح موجز لما تعنيه V + E:

V هو إجمالي عدد القمم. E هو إجمالي عدد الوجوه (الحواف).

قد يبدو من الأنسب استخدام V * E ، ولكن دعونا نفكر في معنى V * E.

يعني V * E أنه فيما يتعلق بكل قمة ، يجب علينا التحقق من جميع وجوه الرسم البياني بغض النظر عما إذا كانت هذه الوجوه تنتمي إلى قمة معينة.

من ناحية أخرى ، تعني V + E أنه بالنسبة لكل قمة ، نقيم فقط الحواف المجاورة لها. بالعودة إلى المثال ، كل رأس لديه عدد معين من الوجوه ، وفي أسوأ الأحوال ، نلتف حول جميع القمم (O (V)) ونفحص جميع الوجوه (O (E)). لدينا رؤوس V ووجوه E ، لذا نحصل على V + E.

علاوة على ذلك ، نظرًا لأننا نستخدم العودية لاجتياز كل قمة ، فإن هذا يعني أنه يتم استخدام المكدس (يؤدي العودية اللانهائي إلى خطأ تجاوز سعة المكدس). لذلك ، التعقيد المكاني هو O (V).

الآن فكر في BFS.

بحث واسع


يتبع BFS مفهوم "التوسع على نطاق واسع ، والارتفاع إلى ارتفاع رحلة الطائر" ("اذهب على نطاق واسع ، رؤية عين الطائر"). بدلاً من التحرك على طول مسار معين حتى النهاية ، تتضمن BFS المضي قدمًا في جار واحد في كل مرة. وهذا يعني ما يلي:



بدلاً من اتباع المسار ، يعني BFS زيارة الجيران الأقرب إلى s في إجراء واحد (خطوة) ، ثم زيارة جيران الجيران وما إلى ذلك حتى يتم اكتشاف t.







كيف يختلف DFS عن BFS؟ أحب أن أعتقد أن DFS تمضي قدمًا ، وأن BFS ليست في عجلة من أمرها ، ولكنها تدرس كل شيء في خطوة واحدة.

ثم يطرح السؤال: كيف تعرف أي الجيران يجب زيارته أولاً؟

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

هذا هو الرمز:

//  ,       
// , : adj = {A:[B,C,D], B:[E,F], ... }
function bfs(adj, s, t) {
	// adj -  
	// s -  
	// t -  

	//  
	let queue = []
	//  s  
	queue.push(s)
	//  s         
	s.visited = true
	while(queue.length > 0) {
		//   ()   
		let v = queue.shift()
		// abj[v] -  v
		for(let neighbor of adj[v]) {
			//    
			if(!neighbor.visited) {
				//    
				queue.push(neighbor)
				//    
				neighbor.visited = true
				//     ,  
				if(neighbor === t) return true
			}
		} 
	}
	//  t  ,     
	return false
}

تحليل BFS


قد يبدو أن BFS أبطأ. ومع ذلك ، إذا نظرت عن كثب إلى التصورات ، يمكنك أن ترى أن لديهم نفس وقت التشغيل.

تتضمن قائمة الانتظار معالجة كل قمة قبل الوصول إلى وجهة. هذا يعني أنه ، في أسوأ الأحوال ، يستكشف BFS جميع القمم والوجوه.

على الرغم من حقيقة أن BFS قد تبدو أبطأ ، إلا أنها في الواقع أسرع ، لأنه عند العمل مع الرسوم البيانية الكبيرة ، تم العثور على أن DFS تقضي الكثير من الوقت في تتبع المسارات التي تتحول في النهاية إلى خطأ. غالبًا ما يتم استخدام BFS للعثور على أقصر مسار بين قمتين.

وبالتالي ، فإن وقت تشغيل BFS هو أيضًا O (V + E) ، وبما أننا نستخدم قائمة انتظار تحتوي على جميع القمم ، فإن تعقيدها المكاني هو O (V).

قياسات الحياة الحقيقية


إذا أعطيت قياسات من الحياة الواقعية ، فهذه هي الطريقة التي أتخيل بها عمل DFS و BFS.

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



تبدو النسخة المبسطة كما يلي:



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



تبدو النسخة المبسطة كما يلي:



الموجودات


  • يتم استخدام عمليات البحث عن العمق والعرض لاجتياز الرسم البياني.
  • يتحرك DFS على طول الحواف ذهابًا وإيابًا ، وينتشر BFS عبر الجيران بحثًا عن هدف.
  • يستخدم DFS المكدس و BFS قائمة الانتظار.
  • وقت التشغيل لكليهما هو O (V + E) ، والتعقيد المكاني هو O (V).
  • هذه الخوارزميات لها فلسفة مختلفة ، ولكنها مهمة بنفس القدر للعمل مع الرسوم البيانية.

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

شكرآ لك على أهتمامك.

All Articles