تعلم المصفوفات في لغة C: الشرح الكامل مع أمثلة عملية

目次

1. لماذا نتعلم المصفوفات في لغة C

أهمية تعلم المصفوفات بلغة C

تعلم كيفية التعامل مع المصفوفات باستخدام لغة C لا يساهم فقط في تحسين مهارات البرمجة، بل يعد خطوة مهمة لتوسيع نطاق التطبيقات الممكنة.

ما هي المصفوفة؟

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

| 1  2  3 |
| 4  5  6 |
| 7  8  9 |

في هذا المثال، لدينا مصفوفة مكونة من 3 صفوف و3 أعمدة. تُستخدم المصفوفات على نطاق واسع في المجالات التالية:

  • الرسوميات: تدوير أو تكبير/تصغير الأجسام ثلاثية الأبعاد.
  • تعلم الآلة: عمليات المتجهات والحسابات المصفوفية للبيانات.
  • المحاكاة الفيزيائية: نمذجة حالة النظام.

أهمية استخدامها في لغة C

تتميز لغة C بالأداء العالي والمرونة، مما يجعلها مناسبة للتعامل مع البيانات الكبيرة والحسابات منخفضة المستوى. من خلال تعلم العمليات المصفوفية في C، يمكنك اكتساب المهارات التالية:

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

توفر هذه المقالة محتوى منظماً من الأساسيات حتى التطبيقات العملية.

2. أساسيات المصفوفات وتمثيلها في لغة C

المفاهيم الأساسية للمصفوفات

لفهم عمليات المصفوفة، من المهم أولاً معرفة بنيتها والعمليات الأساسية التي تدعمها، مثل:

  • الضرب القياسي (Scalar Multiplication): ضرب جميع عناصر المصفوفة بقيمة ثابتة.
  • الجمع والطرح: جمع أو طرح العناصر المناظرة بين مصفوفتين.
  • الضرب: حساب حاصل ضرب المصفوفات (مع وجود قواعد خاصة للحساب).
  • التحويل (Transpose): تبديل الصفوف بالأعمدة.

كيفية تمثيل المصفوفة في C

في لغة C، يتم تمثيل المصفوفات غالبًا باستخدام “المصفوفات ثنائية الأبعاد”.

تصريح مصفوفة ثابتة

عندما يكون حجم المصفوفة معروفاً مسبقاً، يمكن التصريح عنها باستخدام مصفوفة ثنائية الأبعاد كما يلي:

#include <stdio.h>

int main() {
    int matrix[3][3] = {
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9}
    };

    printf("matrix[1][1] = %d\n", matrix[1][1]);  // النتيجة: 5
    return 0;
}

في هذا المثال، قمنا بتصريح مصفوفة 3×3 وتهيئتها بالقيم.

استخدام تخصيص الذاكرة الديناميكي

عندما تحتاج إلى مصفوفة بحجم يتم تحديده أثناء التنفيذ، يمكنك استخدام malloc لتخصيص الذاكرة كما يلي:

#include <stdio.h>
#include <stdlib.h>

int main() {
    int rows = 3, cols = 3;
    int **matrix = (int **)malloc(rows * sizeof(int *));
    for (int i = 0; i < rows; i++) {
        matrix[i] = (int *)malloc(cols * sizeof(int));
    }

    // إدخال القيم في المصفوفة
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            matrix[i][j] = i * cols + j + 1;
        }
    }

    // عرض المصفوفة
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            printf("%d ", matrix[i][j]);
        }
        printf("\n");
    }

    // تحرير الذاكرة
    for (int i = 0; i < rows; i++) {
        free(matrix[i]);
    }
    free(matrix);

    return 0;
}

هذه الطريقة تمنحك مرونة أكبر عند التعامل مع المصفوفات متغيرة الحجم.

3. كيفية إجراء العمليات على المصفوفات في لغة C

جمع وطرح المصفوفات

جمع وطرح المصفوفات هي عمليات بسيطة تتم عن طريق جمع أو طرح العناصر المناظرة.

مثال على التنفيذ: جمع المصفوفات

الكود التالي يجمع بين مصفوفتين لهما نفس الأبعاد:

#include <stdio.h>

#define ROWS 2
#define COLS 3

void addMatrices(int matrix1[ROWS][COLS], int matrix2[ROWS][COLS], int result[ROWS][COLS]) {
    for (int i = 0; i < ROWS; i++) {
        for (int j = 0; j < COLS; j++) {
            result[i][j] = matrix1[i][j] + matrix2[i][j];
        }
    }
}

int main() {
    int matrix1[ROWS][COLS] = {{1, 2, 3}, {4, 5, 6}};
    int matrix2[ROWS][COLS] = {{6, 5, 4}, {3, 2, 1}};
    int result[ROWS][COLS];

    addMatrices(matrix1, matrix2, result);

    printf("نتيجة الجمع:\n");
    for (int i = 0; i < ROWS; i++) {
        for (int j = 0; j < COLS; j++) {
            printf("%d ", result[i][j]);
        }
        printf("\n");
    }

    return 0;
}

مثال على التنفيذ: طرح المصفوفات

يمكنك تنفيذ الطرح بنفس الطريقة مع استبدال + بـ -.

ضرب المصفوفات

عملية ضرب المصفوفات أكثر تعقيداً، حيث يتم ضرب الصفوف في المصفوفة الأولى بالأعمدة في المصفوفة الثانية.

مثال على التنفيذ: ضرب المصفوفات

الكود التالي يضرب مصفوفة 2×3 في مصفوفة 3×2 لإنتاج مصفوفة 2×2:

#include <stdio.h>

#define ROWS1 2
#define COLS1 3
#define ROWS2 3
#define COLS2 2

void multiplyMatrices(int matrix1[ROWS1][COLS1], int matrix2[ROWS2][COLS2], int result[ROWS1][COLS2]) {
    for (int i = 0; i < ROWS1; i++) {
        for (int j = 0; j < COLS2; j++) {
            result[i][j] = 0;
            for (int k = 0; k < COLS1; k++) {
                result[i][j] += matrix1[i][k] * matrix2[k][j];
            }
        }
    }
}

int main() {
    int matrix1[ROWS1][COLS1] = {{1, 2, 3}, {4, 5, 6}};
    int matrix2[ROWS2][COLS2] = {{1, 2}, {3, 4}, {5, 6}};
    int result[ROWS1][COLS2];

    multiplyMatrices(matrix1, matrix2, result);

    printf("نتيجة الضرب:\n");
    for (int i = 0; i < ROWS1; i++) {
        for (int j = 0; j < COLS2; j++) {
            printf("%d ", result[i][j]);
        }
        printf("\n");
    }

    return 0;
}

تحويل المصفوفة (Transpose)

التحويل يعني تبديل الصفوف بالأعمدة.

مثال على التنفيذ: تحويل المصفوفة

#include <stdio.h>

#define ROWS 2
#define COLS 3

void transposeMatrix(int matrix[ROWS][COLS], int transposed[COLS][ROWS]) {
    for (int i = 0; i < ROWS; i++) {
        for (int j = 0; j < COLS; j++) {
            transposed[j][i] = matrix[i][j];
        }
    }
}

int main() {
    int matrix[ROWS][COLS] = {{1, 2, 3}, {4, 5, 6}};
    int transposed[COLS][ROWS];

    transposeMatrix(matrix, transposed);

    printf("المصفوفة المحولة:\n");
    for (int i = 0; i < COLS; i++) {
        for (int j = 0; j < ROWS; j++) {
            printf("%d ", transposed[i][j]);
        }
        printf("\n");
    }

    return 0;
}

عملية التحويل تُنتج مصفوفة جديدة عن طريق تبديل الصفوف بالأعمدة.

4. عمليات مصفوفية متقدمة وأمثلة عملية

حساب المصفوفة العكسية

المصفوفة العكسية هي مصفوفة إذا ضربتها في المصفوفة الأصلية تعطي المصفوفة الوحدة. لا يمكن حساب العكس إلا إذا كانت المصفوفة “منتظمة” (قيمة المحدد ≠ 0).

مثال على التنفيذ: المصفوفة العكسية 2×2

#include <stdio.h>

void calculateInverse(int matrix[2][2], float inverse[2][2]) {
    int determinant = matrix[0][0] * matrix[1][1] - matrix[0][1] * matrix[1][0];
    if (determinant == 0) {
        printf("لا يوجد مصفوفة عكسية.\n");
        return;
    }

    inverse[0][0] = (float)matrix[1][1] / determinant;
    inverse[0][1] = (float)-matrix[0][1] / determinant;
    inverse[1][0] = (float)-matrix[1][0] / determinant;
    inverse[1][1] = (float)matrix[0][0] / determinant;
}

int main() {
    int matrix[2][2] = {{4, 7}, {2, 6}};
    float inverse[2][2];

    calculateInverse(matrix, inverse);

    printf("المصفوفة العكسية:\n");
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 2; j++) {
            printf("%.2f ", inverse[i][j]);
        }
        printf("\n");
    }

    return 0;
}

في هذا المثال، نحسب المحدد ثم نستخدمه لاستخراج المصفوفة العكسية.

الضرب القياسي (Scalar Multiplication) والتوسيع

الضرب القياسي يعني ضرب جميع عناصر المصفوفة بعدد ثابت، ويُستخدم في التطبيع وتغيير المقياس.

مثال على التنفيذ: الضرب القياسي

#include <stdio.h>

void scaleMatrix(int rows, int cols, int matrix[rows][cols], int scalar) {
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            matrix[i][j] *= scalar;
        }
    }
}

int main() {
    int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};
    int scalar = 3;

    scaleMatrix(2, 3, matrix, scalar);

    printf("نتيجة الضرب القياسي:\n");
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 3; j++) {
            printf("%d ", matrix[i][j]);
        }
        printf("\n");
    }

    return 0;
}

بهذه الطريقة يمكنك تكبير أو تصغير جميع عناصر المصفوفة بنفس النسبة.

مثال عملي: برنامج عمليات على المصفوفات باستخدام إدخال المستخدم

في هذا المثال سنستخدم تخصيص الذاكرة الديناميكي لتمكين المستخدم من تحديد حجم المصفوفة وتنفيذ عمليات عليها.

مثال على التنفيذ: برنامج يدعم إدخال المستخدم

#include <stdio.h>
#include <stdlib.h>

void addMatrices(int rows, int cols, int **matrix1, int **matrix2, int **result) {
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            result[i][j] = matrix1[i][j] + matrix2[i][j];
        }
    }
}

int main() {
    int rows, cols;
    printf("أدخل عدد الصفوف للمصفوفة: ");
    scanf("%d", &rows);
    printf("أدخل عدد الأعمدة للمصفوفة: ");
    scanf("%d", &cols);

    // تخصيص الذاكرة
    int **matrix1 = (int **)malloc(rows * sizeof(int *));
    int **matrix2 = (int **)malloc(rows * sizeof(int *));
    int **result = (int **)malloc(rows * sizeof(int *));
    for (int i = 0; i < rows; i++) {
        matrix1[i] = (int *)malloc(cols * sizeof(int));
        matrix2[i] = (int *)malloc(cols * sizeof(int));
        result[i] = (int *)malloc(cols * sizeof(int));
    }

    // إدخال المصفوفة الأولى
    printf("أدخل عناصر المصفوفة الأولى:\n");
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            scanf("%d", &matrix1[i][j]);
        }
    }

    // إدخال المصفوفة الثانية
    printf("أدخل عناصر المصفوفة الثانية:\n");
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            scanf("%d", &matrix2[i][j]);
        }
    }

    // جمع المصفوفات
    addMatrices(rows, cols, matrix1, matrix2, result);

    printf("نتيجة الجمع:\n");
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            printf("%d ", result[i][j]);
        }
        printf("\n");
    }

    // تحرير الذاكرة
    for (int i = 0; i < rows; i++) {
        free(matrix1[i]);
        free(matrix2[i]);
        free(result[i]);
    }
    free(matrix1);
    free(matrix2);
    free(result);

    return 0;
}

5. تطبيقات المصفوفات في لغة C

تطبيق في معالجة الصور: تحويل إلى التدرج الرمادي

في معالجة الصور، يتم التعامل مع بيانات البكسل كمصفوفات. المثال التالي يحول صورة RGB إلى صورة بتدرج رمادي.

مثال على التنفيذ: التحويل من RGB إلى تدرج رمادي

#include <stdio.h>

#define ROWS 3
#define COLS 3

void convertToGrayscale(int red[ROWS][COLS], int green[ROWS][COLS], int blue[ROWS][COLS], int grayscale[ROWS][COLS]) {
    for (int i = 0; i < ROWS; i++) {
        for (int j = 0; j < COLS; j++) {
            // التحويل باستخدام المتوسط
            grayscale[i][j] = (red[i][j] + green[i][j] + blue[i][j]) / 3;
        }
    }
}

int main() {
    int red[ROWS][COLS] = {{255, 128, 64}, {64, 128, 255}, {0, 0, 0}};
    int green[ROWS][COLS] = {{64, 128, 255}, {255, 128, 64}, {0, 0, 0}};
    int blue[ROWS][COLS] = {{0, 0, 0}, {64, 128, 255}, {255, 128, 64}};
    int grayscale[ROWS][COLS];

    convertToGrayscale(red, green, blue, grayscale);

    printf("صورة التدرج الرمادي:\n");
    for (int i = 0; i < ROWS; i++) {
        for (int j = 0; j < COLS; j++) {
            printf("%d ", grayscale[i][j]);
        }
        printf("\n");
    }

    return 0;
}

تطبيق في تحويل الإحداثيات: مصفوفة الدوران

تُستخدم مصفوفات الدوران في الرسوميات ثنائية وثلاثية الأبعاد لتحريك الأجسام.

مثال على التنفيذ: دوران نقطة في 2D

#include <stdio.h>
#include <math.h>

#define PI 3.14159265

void rotatePoint(float x, float y, float angle, float *newX, float *newY) {
    float radians = angle * PI / 180.0;
    *newX = x * cos(radians) - y * sin(radians);
    *newY = x * sin(radians) + y * cos(radians);
}

int main() {
    float x = 1.0, y = 0.0; // النقطة الأصلية
    float angle = 90.0; // زاوية الدوران
    float newX, newY;

    rotatePoint(x, y, angle, &newX, &newY);

    printf("قبل الدوران: (%.2f, %.2f)\n", x, y);
    printf("بعد الدوران: (%.2f, %.2f)\n", newX, newY);

    return 0;
}

تطبيق في تحليل البيانات: تطبيع المصفوفة

التطبيع يهدف إلى تحويل البيانات إلى نطاق معين (مثل 0 إلى 1) لتحسين التحليل.

مثال على التنفيذ: تطبيع المصفوفة

#include <stdio.h>

#define ROWS 2
#define COLS 3

void normalizeMatrix(int rows, int cols, int matrix[rows][cols], float normalized[rows][cols]) {
    int max = matrix[0][0], min = matrix[0][0];

    // إيجاد القيم القصوى والدنيا
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            if (matrix[i][j] > max) max = matrix[i][j];
            if (matrix[i][j] < min) min = matrix[i][j];
        }
    }

    // التطبيع
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            normalized[i][j] = (float)(matrix[i][j] - min) / (max - min);
        }
    }
}

int main() {
    int matrix[ROWS][COLS] = {{1, 2, 3}, {4, 5, 6}};
    float normalized[ROWS][COLS];

    normalizeMatrix(ROWS, COLS, matrix, normalized);

    printf("المصفوفة بعد التطبيع:\n");
    for (int i = 0; i < ROWS; i++) {
        for (int j = 0; j < COLS; j++) {
            printf("%.2f ", normalized[i][j]);
        }
        printf("\n");
    }

    return 0;
}

6. تعلم المزيد عن عمليات المصفوفات وتطبيقاتها

استخدام المكتبات المتخصصة

تنفيذ العمليات المصفوفية يدوياً مهم للتعلم، لكن في المشاريع الكبيرة يُفضل استخدام مكتبات جاهزة مثل:

مكتبة Eigen

  • الميزات: مكتبة عالية الأداء للجبر الخطي في C++.
  • الوظائف: جمع، طرح، ضرب، تحويل، حساب العكس، حساب القيم الذاتية.
  • الموقع الرسمي: Eigen

مكتبة GSL (GNU Scientific Library)

  • الميزات: مكتبة للحسابات العلمية في C.
  • الوظائف: عمليات على المصفوفات، إحصائيات، تحليل عددي.
  • الموقع الرسمي: GSL

مكتبة BLAS

  • الميزات: مكتبة عالية الأداء للجبر الخطي.
  • الوظائف: تحسين عمليات المصفوفات والمتجهات.
  • الموقع الرسمي: BLAS

مواضيع متقدمة

  • معالجة المصفوفات الضخمة بكفاءة.
  • الجبر الخطي العددي: القيم الذاتية والمتجهات الذاتية، حل المعادلات المصفوفية.
  • التطبيقات في تعلم الآلة وتحليل البيانات: تقليل الأبعاد باستخدام SVD، خوارزميات الانحدار.

خطة التعلم المقترحة

  1. مراجعة الأساسيات.
  2. بناء برامج تطبيقية.
  3. المشاركة في مشاريع مفتوحة المصدر.
年収訴求