C++: خوارزميات STL
في الدرس 34 تعلّمنا عن حاويات STL — ماذا نخزّن البيانات فيه.
لكن الحاويات وحدها لا تكفي — تحتاج أيضًا إلى معالجة البيانات: البحث، والفرز، والعد، والتحويل...
كتابتها بنفسك قد تستغرق عشرات الأسطر؛ مع خوارزميات STL، سطر واحد يكفي.
1. نظرة عامة على خوارزميات STL
(1) 1.1 ما هي خوارزميات STL؟
خوارزميات STL هي مجموعة من قوالب الدوال العامة التي توفرها مكتبة C++ القياسية لمعالجة البيانات في الحاويات.
لماذا نستخدم خوارزميات STL؟
| كتابتها بنفسك | خوارزميات STL |
|---|---|
| تحتاج لكتابة حلقات | سطر واحد من الكود |
| عرضة للأخطاء | مختبرة بدقة |
| الأداء قد يتفاوت | محسّنة بأعلى مستوى |
| كود مطوّل | كود موجز |
تشبيه من الحياة الواقعية:
- كتابة الخوارزميات بنفسك = غسل الملابس يدويًا (مضني ومتعب)
- خوارزميات STL = غسالة أوتوماتيكية (بضغطة زر تنتهي)
(2) 1.2 ملفات رأس الخوارزميات
معظم خوارزميات STL في الرأس algorithm، والخوارزميات العددية في numeric.
▶ مثال 2: تطبيق خوارزميات STL (الصعوبة ⭐)
#include <algorithm> // معظم الخوارزميات
#include <numeric> // الخوارزميات العددية (accumulate، إلخ)
المخرجات:
(مخرجات البرنامج)
(3) 1.3 فئات الخوارزميات
تُقسَّم خوارزميات STL إلى عدة فئات رئيسية حسب الوظيفة:
| الفئة | الخوارزميات الممثّلة | الوصف |
|---|---|---|
| غير معدِّلة | find، count، for_each |
لا تُعدّل محتوى الحاوية |
| معدِّلة | copy، transform، replace |
تُعدّل محتوى الحاوية |
| الفرز | sort، stable_sort، partial_sort |
متعلقة بالفرز |
| البحث الثنائي | binary_search، lower_bound |
البحث في نطاقات مرتّبة |
| الدمج | merge، inplace_merge |
دمج نطاقات مرتّبة |
| عددية | accumulate، inner_product |
حسابات عددية |
| المجموعات | set_union، set_intersection |
عمليات المجموعات |
2. الخوارزميات غير المعدِّلة
(1) 2.1 find — البحث عن عناصر
الوظيفة: البحث عن عنصر محدد في حاوية، وإرجاع مكرّر.
النموذج الأولي:
InputIt find(InputIt first, InputIt last, const T& value);
مثال: البحث عن درجة (الصعوبة ⭐)
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector`<int>` scores = {85, 92, 78, 90, 88};
// البحث عن الدرجة 90
auto it = std::find(scores.begin(), scores.end(), 90);
if (it != scores.end()) {
// حساب الموضع (الفهرس)
int index = std::distance(scores.begin(), it);
std::cout << "وُجدت الدرجة 90 في الموضع: " << index << std::endl;
} else {
std::cout << "الدرجة 90 غير موجودة" << std::endl;
}
return 0;
}
النتيجة:
وُجدت الدرجة 90 في الموضع: 3
💡 نصيحة:
- يُرجع
last(عادةًend()) عند عدم العثور - تعقيد الوقت: O(n)
(2) 2.2 count — العد
الوظيفة: عدّ عدد العناصر المساوية لقيمة محددة.
مثال: عدّ الدرجات الكاملة (الصعوبة ⭐)
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector`<int>` scores = {100, 85, 100, 92, 78, 100};
// عدّ الدرجات الكاملة (100)
int perfect = std::count(scores.begin(), scores.end(), 100);
std::cout << "عدد الدرجات الكاملة: " << perfect << std::endl; // المخرج: 3
return 0;
}
(3) 2.3 for_each — التكرار
الوظيفة: تنفيذ عملية محددة على كل عنصر في حاوية.
مثال: طباعة جميع الدرجات (الصعوبة ⭐)
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector`<int>` scores = {85, 92, 78, 90, 88};
// استخدام تعبير lambda لطباعة كل درجة
std::for_each(scores.begin(), scores.end(), [](int s) {
std::cout << s << " ";
});
std::cout << std::endl;
return 0;
}
النتيجة:
85 92 78 90 88
💡 نصيحة:
for_eachأكثر إيجازًا من كتابة الحلقات يدويًا- قوي جدًا عند دمجه مع تعبيرات lambda
3. الخوارزميات المعدِّلة
(1) 3.1 copy — النسخ
الوظيفة: نسخ العناصر من نطاق إلى آخر.
مثال: نسخ مصفوفة (الصعوبة ⭐)
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector`<int>` src = {1, 2, 3, 4, 5};
std::vector`<int>` dst(5); // الحاوية الوجهة، الحجم 5
// النسخ
std::copy(src.begin(), src.end(), dst.begin());
// طباعة النتيجة
for (int x : dst) {
std::cout << x << " ";
}
std::cout << std::endl;
return 0;
}
(2) 3.2 transform — التحويل
الوظيفة: تحويل العناصر من نطاق ونسخها إلى آخر.
مثال: درجات مُوزونة (الصعوبة ⭐⭐)
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector`<int>` scores = {85, 92, 78, 90, 88};
std::vector`<int>` adjusted(scores.size()); // الدرجات المعدّلة
// أعمال دورية 70%، امتحان 30%
std::transform(scores.begin(), scores.end(), adjusted.begin(),
[](int s) { return s * 0.7 + 90 * 0.3; });
std::cout << "الدرجات المعدّلة: ";
for (int x : adjusted) {
std::cout << x << " ";
}
std::cout << std::endl;
return 0;
}
النتيجة:
الدرجات المعدّلة: 86.5 91.9 81.6 90 88.6
(3) 3.3 replace — الاستبدال
الوظيفة: استبدال العناصر المساوية لقيمة معينة بقيمة أخرى.
مثال: معالجة درجات الامتحان التعويضي (الصعوبة ⭐)
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector`<int>` scores = {85, 92, 78, 90, 88};
// استبدال الدرجات الراسبة (<60) بـ 60 (حد النجاح في الامتحان التعويضي)
std::replace_if(scores.begin(), scores.end(),
[](int s) { return s < 60; },
60);
std::cout << "الدرجات المُعالجة: ";
for (int x : scores) {
std::cout << x << " ";
}
std::cout << std::endl;
return 0;
}
4. خوارزميات الفرز
(1) 4.1 sort — الفرز
الوظيفة: فرز نطاق في حاوية (تصاعديًا افتراضيًا).
مثال: فرز الدرجات (الصعوبة ⭐)
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector`<int>` scores = {85, 92, 78, 90, 88};
// فرز تصاعدي
std::sort(scores.begin(), scores.end());
std::cout << "تصاعدي: ";
for (int x : scores) {
std::cout << x << " ";
}
std::cout << std::endl;
// فرز تنازلي
std::sort(scores.begin(), scores.end(), std::greater`<int>`());
std::cout << "تنازلي: ";
for (int x : scores) {
std::cout << x << " ";
}
std::cout << std::endl;
return 0;
}
النتيجة:
تصاعدي: 78 85 88 90 92
تنازلي: 92 90 88 85 78
(2) 4.2 قواعد فرز مخصصة
مثال: فرز الطلاب حسب الدرجة (الصعوبة ⭐⭐)
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
struct Student {
std::string name;
int score;
};
int main() {
std::vector`<Student>` students = {
{"Zhang San", 85},
{"Li Si", 92},
{"Wang Wu", 78}
};
// فرز تنازلي حسب الدرجة
std::sort(students.begin(), students.end(),
[](const Student& a, const Student& b) {
return a.score > b.score;
});
std::cout << "ترتيب الدرجات:" << std::endl;
for (const auto& s : students) {
std::cout << s.name << ": " << s.score << std::endl;
}
return 0;
}
النتيجة:
ترتيب الدرجات:
Li Si: 92
Zhang San: 85
Wang Wu: 78
5. الخوارزميات العددية
(1) 5.1 accumulate — الجمع
الوظيفة: حساب المجموع التراكمي للعناصر في نطاق.
مثال: حساب المجموع الكلي للدرجات (الصعوبة ⭐)
#include <iostream>
#include <vector>
#include <numeric>
int main() {
std::vector`<int>` scores = {85, 92, 78, 90, 88};
// حساب المجموع الكلي للدرجات
int total = std::accumulate(scores.begin(), scores.end(), 0);
std::cout << "المجموع الكلي: " << total << std::endl; // المخرج: 433
std::cout << "المتوسط: " << total / 5.0 << std::endl; // المخرج: 86.6
return 0;
}
(2) 5.2 الجداء الداخلي
مثال: جداء متجه نقطي (الصعوبة ⭐⭐)
#include <iostream>
#include <vector>
#include <numeric>
int main() {
std::vector`<int>` v1 = {1, 2, 3};
std::vector`<int>` v2 = {4, 5, 6};
// حساب الجداء النقطي: 1*4 + 2*5 + 3*6 = 32
int dot_product = std::inner_product(v1.begin(), v1.end(), v2.begin(), 0);
std::cout << "الجداء النقطي للمتجه: " << dot_product << std::endl; // المخرج: 32
return 0;
}
6. مثال شامل
▶ مثال 1: نظام تحليل الدرجات (الصعوبة ⭐⭐⭐)
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
#include <string>
int main() {
std::vector`<int>` scores = {85, 92, 78, 90, 88, 76, 95, 83, 89, 91};
// 1. حساب العدد الإجمالي للطلاب
int count = scores.size();
std::cout << "إجمالي الطلاب: " << count << std::endl;
// 2. حساب المجموع والمتوسط
int total = std::accumulate(scores.begin(), scores.end(), 0);
double average = static_cast`<double>`(total) / count;
std::cout << "المجموع الكلي: " << total << "، المتوسط: " << average << std::endl;
// 3. إيجاد أعلى وأدنى درجة
int max_score = *std::max_element(scores.begin(), scores.end());
int min_score = *std::min_element(scores.begin(), scores.end());
std::cout << "الأعلى: " << max_score << "، الأدنى: " << min_score << std::endl;
// 4. عدّ الطلاب الناجحين
int passed = std::count_if(scores.begin(), scores.end(),
[](int s) { return s >= 60; });
std::cout << "الطلاب الناجحون: " << passed << std::endl;
// 5. الفرز وإخراج أفضل 3
std::vector`<int>` top3 = scores;
std::sort(top3.begin(), top3.end(), std::greater`<int>`());
std::cout << "أفضل 3: ";
for (int i = 0; i < 3; i++) {
std::cout << top3[i] << " ";
}
std::cout << std::endl;
return 0;
}
المخرجات:
85 92 78 90 88 76 95 83 89 91
النتيجة:
إجمالي الطلاب: 10
المجموع الكلي: 867، المتوسط: 86.7
الأعلى: 95، الأدنى: 76
الطلاب الناجحون: 10
أفضل 3: 95 92 91
7. نصائح استخدام الخوارزميات
(1) 7.1 دوال المساعدة للمكرّرات
| الدالة | الغرض |
|---|---|
std::distance(first, last) |
حساب المسافة بين مكرّرين |
std::advance(it, n) |
تقديم مكرّر بمقدار n خطوة |
std::next(it) |
إرجاع المكرّر التالي |
std::prev(it) |
إرجاع المكرّر السابق |
(2) 7.2 تعبيرات Lambda المتقدمة
تعبيرات lambda هي رفيق رائع لخوارزميات STL:
// الشكل الأساسي
[capture](parameters) -> return_type { body }
// مثال: الفرز بمعايير متعددة
std::sort(students.begin(), students.end(),
[](const Student& a, const Student& b) {
if (a.score != b.score)
return a.score > b.score; // أولًا حسب الدرجة
return a.name < b.name; // ثم حسب الاسم
});
❓ أسئلة شائعة
vector، deque): كفاءة عالية - الحاويات الترابطية (set، map): لديها دوال عضوية خاصة، وهي أسرعس: ما هي تعبيرات Lambda؟ ج: تعبيرات lambda هي دوال مجهولة أُدخلت في C++11 يمكن تعريفها مباشرة أينما تكون هناك حاجة لدالة.
الصياغة الأساسية:
[capture](params) -> return_type { body }
مثال:
auto add = [](int a, int b) { return a + b; };
std::cout << add(3, 5) << std::endl; // المخرج: 8
back_inserter 3. عدم تطابق نوع المكرّر → تحقق من نوع الحاوية▶ مثال 3: sort (الصعوبة ⭐)
#include <iostream>
#include <algorithm>
#include <vector>
int main() {
std::vector`<int>` v = {5, 2, 8, 1, 9};
std::sort(v.begin(), v.end());
for (int x : v) {
std::cout << x << " ";
}
std::cout << std::endl;
return 0;
}
المخرجات:
1 2 3 4 5
std::sort يفرز ترتيبًا تصاعديًا افتراضيًا، ويأخذ نطاق مكرّرات [begin, end).
📖 ملخص
- خوارزميات STL: قوالب دوال عامة لمعالجة بيانات الحاويات
- الخوارزميات غير المعدِّلة:
find،count،for_each - الخوارزميات المعدِّلة:
copy،transform،replace - خوارزميات الفرز:
sort+ دوال المقارنة المخصصة - الخوارزميات العددية:
accumulate(الجمع) - Lambda: دوال مجهولة، تُستخدم بالاقتران مع الخوارزميات
توصيات الدراسة:
- استخدم خوارزميات STL أكثر، واكتب حلقات يدوية أقل
- تعرّف على الخوارزميات الشائعة؛ وابحث في التوثيق عن الأقل شيوعًا
- ادمجها مع تعبيرات lambda للحصول على كود أكثر إيجازًا
📝 تمارين
-
أساسي (الصعوبة ⭐): أنشئ
vector<int>يحتوي على 10 أرقام عشوائية، ورتّبه بـ sort وأخرجه، ثم اقلبه بـ reverse وأخرجه. -
متوسط (الصعوبة ⭐⭐): استخدم find للبحث عن سلسلة محددة في
vector<string>، واستخدم count لعدّ مرات ظهور قيمة معينة. -
متقدم (الصعوبة ⭐⭐⭐): استخدم remove_if وlambda لتطبيق "إزالة جميع الأرقام الزوجية من vector". افهم أسلوب erase-remove.
- خوارزميات STL تعمل على نطاقات المكرّرات، منفصلة عن الحاويات
- الخوارزميات الشائعة: sort/find/count/copy/reverse
- فئات الخوارزميات: قراءة فقط (find/count)، كتابة (copy/fill)، فرز (sort)
- تعبيرات lambda كمعاملات خوارزمية أكثر إيجازًا
- الخوارزميات + lambda أكثر إيجازًا وأمانًا من الحلقات المكتوبة يدويًا
الدرس التالي: تطبيق: شامل OOP (#36) — إعادة هيكلة نظام إدارة الطلاب باستخدام التفكير كائني التوجه