C: تطبيق: المصفوفات والدوال
مهما تعلمت من تقنيات، تبقى نظرية حتى تدخل الحلبة. يدمج هذا الفصل المصفوفات والدوال والمؤشرات عبر أربعة مشاريع عملية لاختبار مهاراتك الفعلية.
1. المشروع 1: الترتيب بالفقاعات
الترتيب بالفقاعات هو أبسط خوارزمية ترتيب. الفكرة هي اجتياز المصفوفة بشكل متكرر، مقارنة العناصر المتجاورة ومبادلتها إذا كانت بترتيب خاطئ، حتى لا تحدث مبادلات. كل مسار يرفع أكبر قيمة حالية إلى النهاية.
(1) خطوات الخوارزمية
- ابدأ من العنصر الأول وقارن كل زوج من العناصر المتجاورة
- إذا كان العنصر الأيسر أكبر من الأيمن، بادلهما
- بعد مسار واحد، أكبر قيمة تكون في موضعها الصحيح
- كرر للجزء المتبقي حتى يكتمل الترتيب
(2) التنفيذ
#include <stdio.h>
void bubble_sort(int *arr, int len) {
int i, j;
int swapped;
for (i = 0; i < len - 1; i++) {
swapped = 0;
for (j = 0; j < len - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
swapped = 1;
}
}
if (!swapped) break;
}
}
void print_array(const int *arr, int len) {
int i;
for (i = 0; i < len; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main(void) {
int data[] = {64, 34, 25, 12, 22, 11, 90};
int len = sizeof(data) / sizeof(data[0]);
printf("Before: ");
print_array(data, len);
bubble_sort(data, len);
printf("After: ");
print_array(data, len);
return 0;
}
Before: 64 34 25 12 22 11 90
After: 11 12 22 25 34 64 90
تحسين علم swapped: إذا لم تحدث مبادلات خلال مسار، فالمصفوفة مرتّبة بالفعل وتُنهي الخوارزمية مبكرًا. في أفضل حالة (مرتّبة فعلًا)، ينخفض التعقيد الزمني إلى O(n).
qsort في المشاريع الفعلية.
▶ مثال
ترتيب الدرجات تنازليًا باستخدام الترتيب بالفقاعات:
#include <stdio.h>
void sort_desc(int *arr, int len) {
int i, j, swapped;
for (i = 0; i < len - 1; i++) {
swapped = 0;
for (j = 0; j < len - 1 - i; j++) {
if (arr[j] < arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
swapped = 1;
}
}
if (!swapped) break;
}
}
int main(void) {
int scores[] = {85, 92, 78, 95, 88};
int len = sizeof(scores) / sizeof(scores[0]);
int i;
sort_desc(scores, len);
printf("Ranking: ");
for (i = 0; i < len; i++) {
printf("%d ", scores[i]);
}
printf("\n");
return 0;
}
Ranking: 95 92 88 85 78
للترتيب التنازلي، غيّر المقارنة من > إلى < ببساطة.
2. المشروع 2: البحث الثنائي
يحدد البحث الثنائي بكفاءة قيمة هدف في مصفوفة مرتّبة بتقسيم نطاق البحث إلى نصفين في كل خطوة.
(1) خطوات الخوارزمية
- عيّن نطاق البحث إلى
[low, high] - احسب الموضع الأوسط:
mid = (low + high) / 2 - إذا كان
arr[mid]يساوي الهدف، وُجد - إذا كان
arr[mid]أقل من الهدف، ابحث في النصف الأيمن - إذا كان
arr[mid]أكبر من الهدف، ابحث في النصف الأيسر - إذا تقلص النطاق إلى فارغ دون إيجاد، الهدف غير موجود
(2) التنفيذ
#include <stdio.h>
int binary_search(const int *arr, int len, int target) {
int low = 0;
int high = len - 1;
while (low <= high) {
int mid = low + (high - low) / 2;
if (arr[mid] == target) {
return mid;
} else if (arr[mid] < target) {
low = mid + 1;
} else {
high = mid - 1;
}
}
return -1;
}
int main(void) {
int data[] = {2, 5, 8, 12, 16, 23, 38, 45, 56, 78};
int len = sizeof(data) / sizeof(data[0]);
int targets[] = {23, 7, 78, 1};
int num = sizeof(targets) / sizeof(targets[0]);
int i;
for (i = 0; i < num; i++) {
int pos = binary_search(data, len, targets[i]);
if (pos >= 0) {
printf("%d found at index %d\n", targets[i], pos);
} else {
printf("%d not found\n", targets[i]);
}
}
return 0;
}
23 found at index 5
7 not found
78 found at index 9
1 not found
low + (high - low) / 2 بدلًا من (low + high) / 2 لتجنب فيضان الأعداد الصحيحة. هذه تفصيلة كلاسيكية في البحث الثنائي.
التعقيد الزمني O(log n)؛ مليار عنصر تحتاج 30 مقارنة على الأكثر.
▶ مثال
إيجاد موضع أول عنصر أكبر من أو يساوي الهدف (بحث الحد الأدنى):
#include <stdio.h>
int lower_bound(const int *arr, int len, int target) {
int low = 0;
int high = len;
while (low < high) {
int mid = low + (high - low) / 2;
if (arr[mid] < target) {
low = mid + 1;
} else {
high = mid;
}
}
return low;
}
int main(void) {
int data[] = {1, 3, 3, 5, 7, 7, 7, 9, 11};
int len = sizeof(data) / sizeof(data[0]);
int pos = lower_bound(data, len, 7);
printf("First >= 7 at index %d, value=%d\n", pos, data[pos]);
return 0;
}
First >= 7 at index 4, value=7
بحث الحد الأدنى شائع جدًا في البرمجة التنافسية و STL.
3. المشروع 3: عكس السلسلة
مبادلة المحارف الأمامية والخلفية لسلسلة. استخدم تقنية المؤشر المزدوج للعمل في المكان، بدون مصفوفة إضافية.
(1) التنفيذ
#include <stdio.h>
#include <string.h>
void str_reverse(char *s) {
char *left = s;
char *right = s + strlen(s) - 1;
while (left < right) {
char temp = *left;
*left = *right;
*right = temp;
left++;
right--;
}
}
int main(void) {
char s1[] = "Hello World";
char s2[] = "ABCDE";
char s3[] = "racecar";
str_reverse(s1);
str_reverse(s2);
str_reverse(s3);
printf("%s\n", s1);
printf("%s\n", s2);
printf("%s\n", s3);
return 0;
}
dlroW olleH
EDCBA
racecar
ملاحظة: يجب استخدام مصفوفة محارف (قابلة للتعديل)، وليس مؤشرًا إلى قيمة حرفية للسلسلة.
▶ مثال
التحقق مما إذا كانت سلسلة متساوية القراءة (متخطيًا المحارف غير الأبجدية الرقمية، متجاهلًا حالة الأحرف):
#include <stdio.h>
#include <string.h>
#include <ctype.h>
int is_palindrome(const char *s) {
const char *left = s;
const char *right = s + strlen(s) - 1;
while (left < right) {
while (left < right && !isalnum((unsigned char)*left)) left++;
while (left < right && !isalnum((unsigned char)*right)) right--;
if (tolower((unsigned char)*left) != tolower((unsigned char)*right)) {
return 0;
}
left++;
right--;
}
return 1;
}
int main(void) {
printf("%d\n", is_palindrome("racecar"));
printf("%d\n", is_palindrome("hello"));
printf("%d\n", is_palindrome("A man a plan a canal Panama"));
return 0;
}
1
0
1
4. المشروع 4: تحليل تكرار المحارف
عدّ عدد مرات ظهور كل محرف في سلسلة — عملية أساسية في تحليل النصوص.
(1) التنفيذ
#include <stdio.h>
#include <string.h>
void char_freq(const char *s, int *freq, int size) {
int i;
for (i = 0; i < size; i++) freq[i] = 0;
while (*s != '\0') {
unsigned char ch = (unsigned char)*s;
if (ch < (unsigned)size) freq[ch]++;
s++;
}
}
int main(void) {
char text[] = "Hello World!";
int freq[256] = {0};
int i;
char_freq(text, freq, 256);
for (i = 32; i < 127; i++) {
if (freq[i] > 0) printf("'%c': %d ", i, freq[i]);
}
printf("\n");
return 0;
}
' ': 1 '!': 1 'H': 1 'W': 1 'd': 1 'e': 1 'l': 3 'o': 2 'r': 1
استخدام قيم ASCII كأدلة مصفوفة، مسار واحد يُنهي العد. 256 خانة تغطي جميع محارف 8 بت.
▶ مثال
عدّ تكرارات الحروف وأوجد أكثر حرف تكرارًا:
#include <stdio.h>
#include <ctype.h>
#include <string.h>
void letter_freq(const char *s, int *freq) {
int i;
for (i = 0; i < 26; i++) freq[i] = 0;
while (*s != '\0') {
if (isalpha((unsigned char)*s)) {
freq[tolower((unsigned char)*s) - 'a']++;
}
s++;
}
}
char find_max(const int *freq, int size) {
int max_idx = 0;
int i;
for (i = 1; i < size; i++) {
if (freq[i] > freq[max_idx]) max_idx = i;
}
return 'a' + max_idx;
}
int main(void) {
const char *text = "The quick brown fox jumps over the lazy dog";
int freq[26];
letter_freq(text, freq);
char max_ch = find_max(freq, 26);
printf("Most frequent letter: %c (%d times)\n", max_ch, freq[max_ch - 'a']);
return 0;
}
Most frequent letter: o (4 times)
❓ أسئلة شائعة
std::reverse في <algorithm>؛ في C، يجب تنفيذها بنفسك.📖 ملخص
- الترتيب بالفقاعات يرتّب بمبادلة العناصر المتجاورة؛ علم swapped يتيح الإنهاء المبكر
- البحث الثنائي يتطلب مصفوفة مرتّبة؛ كفاءة O(log n) تتفوق على البحث الخطي
- تقنية المؤشر المزدوج تعكس السلاسل في المكان بتعقيد مساحة O(1)
- تحليل تكرار المحارف يستخدم قيم ASCII كأدلة مصفوفة، يُنجزه مسار واحد
- هذه المشاريع توضح المهارات الأساسية الثلاث: تخزين المصفوفات وتغليف الدوال ومعالجة المؤشرات
📝 تمارين
- حسّن الترتيب بالفقاعات: نفّذ دالة تستطيع الترتيب تصاعديًا أو تنازليًا، يتحكم بها معامل مؤشر دالة.
- نفّذ نسخة عودية من البحث الثنائي:
int bin_search_rec(const int *arr, int low, int high, int target). - اكتب محلل تكرار محارف محسّنًا: اقرأ سطرًا من إدخال المستخدم، ثم أخرج أكثر 3 محارف تكرارًا وعدد مراتها.