freeCodeCamp/guide/arabic/algorithms/binary-search-trees/index.md

14 KiB

title localeTitle
Binary Search Trees أشجار البحث الثنائي

أشجار البحث الثنائي

شجرة البحث الثنائية

الشجرة هي بنية بيانات تتكون من عقد لها الخصائص التالية:

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

تضيف شجرة البحث الثنائية (BST) هاتين الخاصيتين:

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

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

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

العمليات الأساسية على BST

  • إنشاء: ينشئ شجرة فارغة.
  • إدراج: إدراج عقدة في الشجرة.
  • بحث: يبحث عن عقدة في الشجرة.
  • حذف: لحذف عقدة من الشجرة.

خلق

في البداية يتم إنشاء شجرة فارغة بدون أي عقد. تتم تهيئة المتغير / المعرف الذي يجب أن يشير إلى عقدة الجذر بقيمة NULL .

بحث

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

إدراج

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

حذف

هناك 3 حالات يمكن أن تحدث عندما تحاول حذف عقدة. إذا كان لديه،

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

وقت تعقيد إنشاء الشجرة هو O(1) . يعتمد تعقيد وقت البحث عن عقدة أو إدخالها أو حذفها على ارتفاع الشجرة h ، لذا فإن الحالة الأسوأ هي O(h) .

سلف عقدة

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

خلف العقدة

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

أنواع خاصة من BT

  • كومة
  • شجرة حمراء سوداء
  • B-شجرة
  • شجرة سبلاي
  • شجرة N-ary
  • تري (شجرة الجذر)

وقت التشغيل

هيكل البيانات: صفيف

  • أسوأ حالة أداء: O(log n)
  • أداء أفضل أداء: O(1)
  • متوسط ​​الأداء: O(log n)
  • أسوأ تعقيد في الفضاء: O(1)

حيث n هو عدد العقد في BST.

تنفيذ BST

وإليك تعريفًا لعقدة BST التي تحتوي على بعض البيانات ، مع الإشارة إلى عقد الطفل اليمنى واليسرى.

struct node { int data; struct node *leftChild; struct node *rightChild; };

عملية البحث

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

`struct node* search(int data){ struct node *current = root; printf("Visiting elements: ");

while(current->data != data){

  if(current != NULL) { 
     printf("%d ",current->data); 

     //go to left tree 
     if(current->data > data){ 
        current = current->leftChild; 
     }//else go to right tree 
     else { 
        current = current->rightChild; 
     } 

     //not found 
     if(current == NULL){ 
        return NULL; 
     } 
  } 

} return current; } `

أدخل العملية

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

`void insert(int data) { struct node tempNode = (struct node) malloc(sizeof(struct node)); struct node *current; struct node *parent;

tempNode->data = data; tempNode->leftChild = NULL; tempNode->rightChild = NULL;

//if tree is empty if(root == NULL) { root = tempNode; } else { current = root; parent = NULL;

  while(1) { 
     parent = current; 

     //go to left of the tree 
     if(data < parent->data) { 
        current = current->leftChild; 
        //insert to the left 

        if(current == NULL) { 
           parent->leftChild = tempNode; 
           return; 
        } 
     }//go to right of the tree 
     else { 
        current = current->rightChild; 

        //insert to the right 
        if(current == NULL) { 
           parent->rightChild = tempNode; 
           return; 
        } 
     } 
  } 

} } `

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

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

دعونا ننظر في اثنين من الإجراءات التي تعمل على الأشجار.

بما أن الأشجار يتم تعريفها بشكل متكرر ، فمن الشائع جدًا كتابة الإجراءات الروتينية التي تعمل على أشجار متكررة.

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

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

خوارزمية الارتفاع (شجرة)

if tree = nil: return 0 return 1 + Max(Height(tree.left),Height(tree.right))

هنا هو رمز في C ++

`int maxDepth(struct node* node) { if (node==NULL) return 0; else { int rDepth = maxDepth(node->right); int lDepth = maxDepth(node->left);

   if (lDepth > rDepth) 
   { 
       return(lDepth+1); 
   } 
   else 
   { 
        return(rDepth+1); 
   } 

} } `

يمكن أن ننظر أيضا في حساب حجم الشجرة التي هي عدد العقد.

  • مرة أخرى ، إذا كان لدينا شجرة لا شيء ، لدينا عقد الصفر.
  • خلاف ذلك ، لدينا عدد العقد في الطفل الأيسر بالإضافة إلى 1 لأنفسنا بالإضافة إلى عدد العقد في الطفل الصحيح. 1 بالإضافة إلى حجم الشجرة اليسرى بالإضافة إلى حجم الشجرة الصحيحة.

خوارزمية الحجم (شجرة)

if tree = nil return 0 return 1 + Size(tree.left) + Size(tree.right)

هنا هو رمز في C ++

int treeSize(struct node* node) { if (node==NULL) return 0; else return 1+(treeSize(node->left) + treeSize(node->right)); }

مقاطع الفيديو ذات الصلة على قناة YouTube freeCodeCamp

فيما يلي الأنواع الشائعة من الأشجار الثنائية:

Full Binary Tree / Strict Binary Tree: شجرة ثنائية ممتلئة أو صارمة إذا كان لكل عقدة 0 أو 2 أطفال.

18 / \ 15 30 / \ / \ 40 50 100 40

في شجرة الثنائي الكاملة ، عدد العقد الورقية يساوي عدد العقد الداخلية زائد واحد.

إكمال Binary Tree: A Binary Tree اكتمال Binary Tree إذا تمت تعبئة كافة المستويات تمامًا باستثناء المستوى الأخير ، بينما يحتوي المستوى الأخير على كافة المفاتيح التي تم تركها قدر الإمكان

18 / \ 15 30 / \ / \ 40 50 100 40 / \ / 8 7 9