جاري التحميل...
جاري التحميل...
هياكل البيانات الشجرية والرسوم البيانية
الأشجار والرسوم البيانية هي هياكل بيانات شبكية وهرمية تُستخدم لتمثيل العلاقات. وهي ضرورية لحل الكثير من مشاكل العالم الحقيقي والخوارزميات.
💡 ما سنتعلمه:
تجذور الأشجار (DFS، BFS)، أشجار البحث الثنائية، تجذور الرسوم البيانية، ومشاكل الأشجار/الرسوم البيانية الشائعة.
الشجرة هي بنية هرمية لها عقدة جذر وعقد أبناء. لكل عقدة في الشجرة الثنائية ما يصل إلى ابنتين.
💡 المصطلحات الرئيسية:
الجذر، الأب، الابن، الورقة، الارتفاع، العمق. الشجرة الثنائية: ما يصل إلى 2 ابن لكل عقدة.
يبحث DFS بشكل عميق قدر الإمكان على كل فرع قبل العودة. يمكن تنفيذه باستخدام التكرار الذاتي أو المكدس.
💡 أوامر التجذور:
التقدمي: الجذر→اليسار→اليمين. الترددي: اليسار→الجذر→اليمين. التراجعي: اليسار→اليمين→الجذر.
يزور BFS جميع العقد في العمق الحالي قبل الانتقال إلى الأعمق. يستخدم هيكل بيانات القائمة.
💡 النمط:
أضف الجذر إلى القائمة. بينما القائمة ليست فارغة: أزل العقدة، عالجها، أضف الأبناء.
في BST، الابن الأيسر < الأب < الابن الأيمن. يعطي التجذور الترددي ترتيبًا مرتبًا.
💡 خاصية BST:
قيم الشجرة الفرعية اليسرى < قيمة العقدة < قيم الشجرة الفرعية اليمنى. يتيح بحث O(log n).
تتكون الرسم البياني من رؤوس (عقد) وحواف (اتصالات). يمكن أن تكون الرسوم البيانية موجهة/غير موجهة، موزونة/غير موزونة.
💡 التمثيلات:
قائمة الجوار (الأكثر شيوعًا)، مصفوفة الجوار. استخدم DFS/BFS للتجذور.
أكتب دالة تُعيد العمق الأقصى للشجرة الثنائية. العمق الأقصى هو عدد العقد في أطول مسار من الجذر إلى الورقة.
📋 أمثلة:
root = [3,9,20,null,null,15,7]
3
root = [1,null,2]
2
💡 تلميح:
function maxDepth(root) {
if (!root) return 0;
const leftDepth = maxDepth(root.left);
const rightDepth = maxDepth(root.right);
return Math.max(leftDepth, rightDepth) + 1;
}أكتب دالة تتحقق مما إذا كان شجرتان متماثلتين. شجرتان متماثلتان إذا كانا لهما نفس البنية ونفس القيم في العقد المقابلة.
📋 أمثلة:
p = [1,2,3], q = [1,2,3]
true
p = [1,2], q = [1,null,2]
false
💡 تلميح:
function isSameTree(p, q) {
if (!p && !q) return true;
if (!p || !q) return false;
return p.val === q.val &&
isSameTree(p.left, q.left) &&
isSameTree(p.right, q.right);
}أكتب دالة تقلّب الشجرة ثنائية (تعكسها أفقياً). أي أن كل عقدة تبدل أبنائها اليسار واليمين.
📋 أمثلة:
root = [4,2,7,1,3,6,9]
[4,7,2,9,6,3,1]
root = [2,1,3]
[2,3,1]
💡 تلميح:
function invertTree(root) {
if (!root) return null;
const temp = root.left;
root.left = root.right;
root.right = temp;
invertTree(root.left);
invertTree(root.right);
return root;
}أكتب دالة تتحقق مما إذا كان subRoot هو شجرة فرعية من شجرة root الرئيسية. شجرة فرعية تعني أن شجرة فرعية تبدأ من عقدة ما في الشجرة الرئيسية وتحتوي على جميع الأبناء.
📋 أمثلة:
root = [3,4,5,1,2], subRoot = [4,1,2]
true
root = [3,4,5,1,2,null,null,null,null,0], subRoot = [4,1,2]
false
💡 تلميح:
function isSubtree(root, subRoot) {
if (!root) return false;
if (isSameTree(root, subRoot)) return true;
return isSubtree(root.left, subRoot) || isSubtree(root.right, subRoot);
}
function isSameTree(p, q) {
if (!p && !q) return true;
if (!p || !q) return false;
return p.val === q.val &&
isSameTree(p.left, q.left) &&
isSameTree(p.right, q.right);
}أكتب دالة تُعيد ترتيب العرض المستوي للشجرة الثنائية (BFS). يجب أن تكون النتيجة متتالية من المجموعات، كل مجموعة تحتوي على عقد نفس المستوى.
📋 أمثلة:
root = [3,9,20,null,null,15,7]
[[3],[9,20],[15,7]]
root = [1]
[[1]]
💡 تلميح:
function levelOrder(root) {
if (!root) return [];
const result = [];
const queue = [root];
while (queue.length > 0) {
const levelSize = queue.length;
const currentLevel = [];
for (let i = 0; i < levelSize; i++) {
const node = queue.shift();
currentLevel.push(node.val);
if (node.left) queue.push(node.left);
if (node.right) queue.push(node.right);
}
result.push(currentLevel);
}
return result;
}أكتب دالة تتحقق مما إذا كانت الشجرة الثنائية هي شجرة بحثية ثنائية (BST) صالحة. في BST صالح، كل العقد في اليسار أصغر من الأب، وكل العقد في اليمين أكبر.
📋 أمثلة:
root = [2,1,3]
true
root = [5,1,4,null,null,3,6]
false
💡 تلميح:
function isValidBST(root) {
function validate(node, min, max) {
if (!node) return true;
if (node.val <= min || node.val >= max) return false;
return validate(node.left, min, node.val) &&
validate(node.right, node.val, max);
}
return validate(root, -Infinity, Infinity);
}أكتب دالة تُعيد أدنى سلف مشترك (Lowest Common Ancestor - LCA) لعقدتين معينتين في شجرة ثنائية. LCA هو أعمق عقدة لها كلا العقدتين كأبناء (أو أحفاد).
📋 أمثلة:
root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
3
root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
5
💡 تلميح:
function lowestCommonAncestor(root, p, q) {
if (!root || root === p || root === q) return root;
const left = lowestCommonAncestor(root.left, p, q);
const right = lowestCommonAncestor(root.right, p, q);
if (left && right) return root;
return left || right;
}أكتب دالة تُعيد عدد الجزر في شبكة 2D. الجزر تمثل بـ '1' والمياه تمثل بـ '0'. الجزيرة هي مجموعة من الأراضي ('1') المتصلة أفقياً وعمودياً.
📋 أمثلة:
grid = [["1","1","1","1","0"],["1","1","0","1","0"],["1","1","0","0","0"],["0","0","0","0","0"]]
1
grid = [["1","1","0","0","0"],["1","1","0","0","0"],["0","0","1","0","0"],["0","0","0","1","1"]]
3
💡 تلميح:
function numIslands(grid) {
if (!grid || grid.length === 0) return 0;
const rows = grid.length;
const cols = grid[0].length;
let count = 0;
function dfs(r, c) {
if (r < 0 || r >= rows || c < 0 || c >= cols || grid[r][c] === '0') return;
grid[r][c] = '0';
dfs(r + 1, c);
dfs(r - 1, c);
dfs(r, c + 1);
dfs(r, c - 1);
}
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
if (grid[r][c] === '1') {
count++;
dfs(r, c);
}
}
}
return count;
}أكتب دالة تُعيد نسخة من رسم بياني موجه. الرسم البياني ممثل بعقدة واحدة، كل عقدة تحتوي على قيمة وقائمة بالجيران. يجب أن تكون النسخة مستقلة عن الأصل.
📋 أمثلة:
adjList = [[2,4],[1,3],[2,4],[1,3]]
[[2,4],[1,3],[2,4],[1,3]]
adjList = [[]]
[[]]
💡 تلميح:
function cloneGraph(node) {
if (!node) return null;
const map = new Map();
function dfs(current) {
if (map.has(current)) return map.get(current);
const clone = new Node(current.val);
map.set(current, clone);
for (const neighbor of current.neighbors) {
clone.neighbors.push(dfs(neighbor));
}
return clone;
}
return dfs(node);
}أكتب دالة تُعيد أكبر مجموع مسار في شجرة ثنائية. المسار هو أي متتالية من العقد متصلة حيث لا يوجد عقدة متكررة. يمكن أن يبدأ المسار من أي عقدة وينتهي في أي عقدة.
📋 أمثلة:
root = [1,2,3]
6
root = [-10,9,20,null,null,15,7]
42
💡 تلميح:
function maxPathSum(root) {
let maxSum = -Infinity;
function dfs(node) {
if (!node) return 0;
const leftMax = Math.max(dfs(node.left), 0);
const rightMax = Math.max(dfs(node.right), 0);
const currentPathSum = node.val + leftMax + rightMax;
maxSum = Math.max(maxSum, currentPathSum);
return node.val + Math.max(leftMax, rightMax);
}
dfs(root);
return maxSum;
}أكتب دالتين: واحدة لتحويل الشجرة الثنائية إلى نص (serialize) وأخرى لتحويل النص إلى شجرة (deserialize). يجب أن تكون العملية قابلة للعكس.
📋 أمثلة:
root = [1,2,3,null,null,4,5]
serialize → deserialize تُعيد الشجرة الأصلية
root = []
serialize → deserialize تُعيد []
💡 تلميح:
function serialize(root) {
const result = [];
function dfs(node) {
if (!node) {
result.push('null');
return;
}
result.push(node.val.toString());
dfs(node.left);
dfs(node.right);
}
dfs(root);
return result.join(',');
}
function deserialize(data) {
const values = data.split(',');
let index = 0;
function dfs() {
if (values[index] === 'null') {
index++;
return null;
}
const node = new Node(parseInt(values[index]));
index++;
node.left = dfs();
node.right = dfs();
return node;
}
return dfs();
}معطى قائمة مرتبة من الكلمات بلغة غريبة، حدد ترتيب الأحرف في اللغة الغريبة. الكلمات مرتبة أبجدياً وفقاً لقواعد اللغة الغريبة.
📋 أمثلة:
words = ["wrt","wrf","er","ett","rftt"]
wertf
words = ["z","x"]
zx
💡 تلميح:
function alienOrder(words) {
const graph = new Map();
for (const word of words) {
for (const char of word) {
if (!graph.has(char)) graph.set(char, new Set());
}
}
for (let i = 0; i < words.length - 1; i++) {
const word1 = words[i];
const word2 = words[i + 1];
const minLen = Math.min(word1.length, word2.length);
if (word1.length > word2.length && word1.startsWith(word2)) {
return "";
}
for (let j = 0; j < minLen; j++) {
if (word1[j] !== word2[j]) {
graph.get(word1[j]).add(word2[j]);
break;
}
}
}
const visited = new Set();
const visiting = new Set();
const result = [];
function dfs(char) {
if (visited.has(char)) return true;
if (visiting.has(char)) return false;
visiting.add(char);
for (const neighbor of graph.get(char)) {
if (!dfs(neighbor)) return false;
}
visiting.delete(char);
visited.add(char);
result.unshift(char);
return true;
}
for (const char of graph.keys()) {
if (!dfs(char)) return "";
}
return result.join('');
}الأشجار (Trees):
DFS traversal
Pre-order: الجذر ← اليسار ← اليمين
In-order: اليسار ← الجذر ← اليمين
Post-order: اليسار ← اليمين ← الجذر
BFS traversal
عرض مستوي باستخدام Queue
BST
يسار < جذر < يمين
In-order يُعطي ترتيب تصاعدي
الرسوم البيانية (Graphs):
DFS
استخدام Stack أو استدعاء ذاتي
ممتاز للبحث العميق
BFS
استخدام Queue
أقصر مسار في رسم بياني غير مرجّح
Topological Sort
لترتيب المهام مع التبعيات
يعمل فقط مع رسم بياني أطياف (DAG)