CodeGym /مدونة جافا /Random-AR /فرز الخوارزميات من الناحية النظرية والعملية
John Squirrels
مستوى
San Francisco

فرز الخوارزميات من الناحية النظرية والعملية

نشرت في المجموعة
يعد الفرز إحدى العمليات الأساسية التي نقوم بها على الكائنات. حتى في مرحلة الطفولة، يتم تعليم الأطفال الفرز أثناء تطوير مهارات التفكير لديهم. أجهزة الكمبيوتر والبرمجيات ليست استثناء. هناك مجموعة كبيرة ومتنوعة من خوارزميات الفرز في Java . أقترح عليك التحقق من ما هي وكيف تعمل. ماذا لو سُئلت عن أحدهم في مقابلة يومًا ما؟

مقدمة

يعد فرز العناصر إحدى فئات الخوارزميات التي يجب على المطور معرفتها. إذا لم يتم أخذ علوم الكمبيوتر على محمل الجد عندما كنت في المدرسة، فيجب أن يكون طلاب اليوم قادرين على تنفيذ وفهم خوارزميات الفرز. يتم تنفيذ الخوارزميات الأساسية، وهي الأبسط، باستخدام حلقة for . بطبيعة الحال، لفرز مجموعة من العناصر، مثل المصفوفة، تحتاج إلى المرور عبر المجموعة بطريقة أو بأخرى. على سبيل المثال:
int[] array = {10, 2, 10, 3, 1, 2, 5};
for (int i = 0; i < array.length; i++) {
	System.out.println(array[i]);
}
ماذا يمكن أن يقال عن هذا الجزء من التعليمات البرمجية؟ لدينا حلقة نقوم فيها بتغيير الفهرس ( int i) من 0 إلى العنصر الأخير في المصفوفة. في الواقع، نحن ببساطة نأخذ كل عنصر في المصفوفة ونطبع محتوياته. كلما زاد عدد العناصر في المصفوفة، كلما استغرقت التعليمات البرمجية وقتًا أطول حتى تنتهي. وهذا يعني أنه إذا nكان عدد العناصر، n = 10فسيستغرق تشغيل البرنامج ضعف الوقت الذي يستغرقه تشغيله n = 5. إذا كان برنامجنا يحتوي على حلقة واحدة، فإن وقت التنفيذ ينمو خطيًا: كلما زاد عدد العناصر، زاد وقت التنفيذ. اتضح أن الخوارزمية المذكورة أعلاه تعمل في الزمن الخطي (دالة خطية لـ n). في مثل هذه الحالات، نقول أن تعقيد الخوارزمية هو "O(n)". يُطلق على هذا الترميز أيضًا اسم "big O" أو "السلوك المقارب". ولكن يمكنك فقط أن تتذكر "تعقيد الخوارزمية".

أبسط خوارزمية الفرز (الفرز الفقاعي)

لنفترض أن لدينا مصفوفة ويمكننا التكرار من خلالها. عظيم. الآن دعونا نحاول فرزها بترتيب تصاعدي. ماذا يعني هذا؟ وهذا يعني أنه بالنظر إلى عنصرين (على سبيل المثال a = 6، b = 5)، يجب علينا إعادة ترتيب aإذا bكان aأكبر من b(إذا a > b). ماذا يعني هذا عندما نستخدم فهرسًا للعمل مع مجموعة (كما هو الحال مع المصفوفة)؟ وهذا يعني أنه إذا كان العنصر ذو الفهرس a أكبر من العنصر ذو الفهرس b( array[a] > array[b])، فيجب تبديل العناصر. هناك طرق مختلفة لتغيير الأماكن. لكننا سنستخدم أسلوبًا بسيطًا ومفهومًا وسهل التذكر:
private void swap(int[] array, int ind1, int ind2) {
    int tmp = array[ind1];
    array[ind1] = array[ind2];
    array[ind2] = tmp;
}
والآن يمكننا أن نكتب ما يلي:
int[] array = {10, 2, 10, 3, 1, 2, 5};
System.out.println(Arrays.toString(array));
for (int i = 1; i < array.length; i++) {
	if (array[i] < array[i - 1]) {
		swap(array, i, i-1);
	}
}
System.out.println(Arrays.toString(array));
كما ترون، تم بالفعل تبديل العناصر. لقد بدأنا بالفهرس 1، لأنه إذا كان المصفوفة تحتوي على عنصر واحد فقط، array[i] < array[i-1]فسيكون التعبير غير صالح للفهرس 0. القيام بذلك يحمينا أيضًا من الحالات التي لا تحتوي فيها المصفوفة على عناصر أو عنصر واحد فقط، ويجعل الكود يبدو أفضل. لكن المصفوفة الناتجة لم يتم فرزها بعد، لأن تمريرة واحدة لا تكفي لإجراء الفرز. سيتعين علينا إضافة حلقة أخرى سنقوم من خلالها بإجراء التمريرات مرارًا وتكرارًا حتى نحصل على مصفوفة مرتبة:
int[] array = {10, 2, 10, 3, 1, 2, 5};
System.out.println(Arrays.toString(array));
boolean needIteration = true;
while (needIteration) {
	needIteration = false;
	for (int i = 1; i < array.length; i++) {
		if (array[i] < array[i - 1]) {
			swap(array, i, i-1);
			needIteration = true;
		}
	}
}
System.out.println(Arrays.toString(array));
وبذلك انتهينا من خوارزمية الفرز الأولى. نكرر الحلقة الخارجية ( while) حتى نقرر عدم الحاجة إلى المزيد من التكرار. افتراضيًا، قبل كل تكرار جديد، نفترض أن المصفوفة الخاصة بنا مرتبة ولم نعد بحاجة إلى التكرار. وبناء على ذلك، نتحرك بالتسلسل عبر العناصر ونتحقق من هذا الافتراض. لكن إذا لم تكن العناصر مرتبة، فإننا نقوم بتبديل العناصر ونفهم أنه ليس لدينا الآن أي ضمان بأن العناصر مرتبة بشكل صحيح. هذا يعني أننا بحاجة إلى إجراء تكرار آخر. على سبيل المثال، لنفترض أن لدينا [3, 5, 2]. 5أكثر من 3— كل شيء على ما يرام. ولكنها 2أقل من 5. ومع ذلك، [3, 2, 5]يتطلب تمريرة أخرى، حيث 3 > 2يجب تبديلهما. نظرًا لأننا نستخدم حلقة داخل حلقة، يزداد تعقيد الخوارزمية لدينا. بالنظر إلى nالعناصر، يصبح n * n، أي، O(n^2). وهذا ما يسمى التعقيد التربيعي. بشكل عام، لا يمكننا أن نعرف بالضبط عدد التكرارات المطلوبة. يوضح التعبير عن تعقيد الخوارزمية كيف يزداد التعقيد في أسوأ الحالات. أي أنه يشير إلى مقدار زيادة وقت التنفيذ مع تغير عدد العناصر n. يعد فرز الفقاعات أحد أبسط خوارزميات الفرز وأكثرها كفاءة. ويطلق عليه أيضًا أحيانًا "النوع الغبي". المواد حول هذا الموضوع:

اختيار نوع

خوارزمية فرز أخرى هي الفرز بالاختيار. كما أن لديها تعقيدًا من الدرجة الثانية، ولكن المزيد عن ذلك لاحقًا. لذا فإن الفكرة بسيطة. في كل تمريرة، نختار أصغر عنصر وننقله إلى البداية. بالإضافة إلى ذلك، تبدأ كل تمريرة بخطوة واحدة إلى اليمين. بمعنى آخر، التمريرة الأولى تبدأ من العنصر الصفري، والتمريرة الثانية من العنصر الأول، وهكذا. سيبدو الأمر كما يلي:
int[] array = {10, 2, 10, 3, 1, 2, 5};
System.out.println(Arrays.toString(array));
for (int left = 0; left < array.length; left++) {
	int minInd = left;
	for (int i = left; i < array.length; i++) {
		if (array[i] < array[minInd]) {
			minInd = i;
		}
	}
	swap(array, left, minInd);
}
System.out.println(Arrays.toString(array));
هذا النوع غير مستقر، لأن العناصر المتطابقة (من حيث أي خاصية نستخدمها لفرز العناصر) يمكن أن تغير مواقعها النسبية. يوجد مثال جيد في مقالة ويكيبيديا حول نوع التحديد . المواد حول هذا الموضوع:

ترتيب بالإدراج

يتميز فرز الإدراج أيضًا بتعقيد تربيعي، نظرًا لأنه لدينا مرة أخرى حلقة داخل حلقة. ما الذي يجعل فرز الإدراج مختلفًا؟ خوارزمية الفرز هذه "مستقرة". وهذا يعني أن العناصر المتطابقة لن تغير ترتيبها النسبي. مرة أخرى، نعني متطابقًا من حيث الخصائص التي نقوم بالفرز وفقًا لها.
int[] array = {10, 2, 10, 3, 1, 2, 5};
System.out.println(Arrays.toString(array));
for (int left = 0; left < array.length; left++) {
	// Get an element
	int value = array[left];
	// Iterate through the elements that are in front of this element
	int i = left - 1;
	for (; i >= 0; i--) {
		// If the current element is smaller, then move the larger element to the right.
		if (value < array[i]) {
			array[i + 1] = array[i];
		} else {
			// If the current element is larger, we stop
			break;
		}
	}
	// Insert the current value in the empty space
	array[i + 1] = value;
}
System.out.println(Arrays.toString(array));

نوع المكوك

هناك خوارزمية فرز بسيطة أخرى: الفرز المكوكي. يُعرف هذا أيضًا بنوع الفقاعة ثنائي الاتجاه أو نوع شاكر الكوكتيل. تخبرنا هذه الأسماء البديلة أن نوع المكوك لا يتعلق بالمكوك الفضائي. يتعلق الأمر بشيء يتحرك ذهابًا وإيابًا. الآن يمكنك التفكير في ذلك عندما تفكر في هذه الخوارزمية. ما هو جوهر الخوارزمية؟ جوهر الخوارزمية هو أننا نكرر من اليسار إلى اليمين، ونتبادل العناصر ونتحقق مما إذا كان أي من العناصر المتبقية في الاتجاه الآخر بحاجة إلى التبديل.
int[] array = {10, 2, 10, 3, 1, 2, 5};
System.out.println(Arrays.toString(array));
for (int i = 1; i < array.length; i++) {
	if (array[i] < array[i - 1]) {
		swap(array, i, i - 1);
		for (int z = i - 1; (z - 1) >= 0; z--) {
			if (array[z] < array[z - 1]) {
				swap(array, z, z - 1);
			} else {
				break;
			}
		}
	}
}
System.out.println(Arrays.toString(array));
المواد حول هذا الموضوع:

نوع شل

خوارزمية فرز بسيطة أخرى هي فرز الصدفة. جوهرها مشابه لفرز الفقاعات، ولكن في كل تكرار لدينا فجوة مختلفة بين العناصر المقارنة. يتم قطعه إلى النصف مع كل تكرار. وهنا التنفيذ:
int[] array = {10, 2, 10, 3, 1, 2, 5};
System.out.println(Arrays.toString(array));
// Calculate the gap between the checked elements
int gap = array.length / 2;
// As long as there is a gap between the elements
while (gap >= 1) {
    for (int right = 0; right < array.length; right++) {
        // Shift the right index until we find one for which
        // there is the necessary gap between it and the element before it
       for (int c = right - gap; c >= 0; c -= gap) {
           if (array[c] > array[c + gap]) {
               swap(array, c, c + gap);
           }
        }
    }
    // Recalculate the gap
    gap = gap / 2;
}
System.out.println(Arrays.toString(array));
المواد حول هذا الموضوع:

دمج الفرز

بالإضافة إلى خوارزميات الفرز البسيطة هذه، هناك أيضًا خوارزميات فرز أكثر تعقيدًا. على سبيل المثال، دمج الفرز. هناك شيئان يجب ملاحظتهما. أولا، العودية تأتي لإنقاذنا هنا. ثانيًا، لم يعد تعقيد الخوارزمية تربيعيًا، كما اعتدنا عليه. تعقيد هذه الخوارزمية لوغاريتمي. هذا مكتوب ك O(n log n). لذلك دعونا ننفذها. أولاً، سنكتب استدعاءً متكررًا لطريقة الفرز:
public static void mergeSort(int[] source, int left, int right) {
        // Select the delimiter, i.e. split the input array in half
        int delimiter = left + ((right - left) / 2) + 1;
        // Recursively execute this function on the two halves (if we can split the input array)
        if (delimiter > 0 && right > (left + 1)) {
            mergeSort(source, left, delimiter - 1);
            mergeSort(source, delimiter, right);
        }
}
الآن، دعونا نضيف الإجراء الرئيسي إلى تنفيذنا. إليك طريقتنا الفائقة:
public static void mergeSort(int[] source, int left, int right) {
        // Select the delimiter, i.e. split the input array in half
        int delimiter = left + ((right - left) / 2) + 1;
        // Recursively execute this function on the two halves (if we can split the input array)
        if (delimiter > 0 && right > (left + 1)) {
            mergeSort(source, left, delimiter - 1);
            mergeSort(source, delimiter, right);
        }
        // Create a temporary array with the required size
        int[] buffer = new int[right - left + 1];
        // Starting from the specified left index, go through each element.
        int cursor = left;
        for (int i = 0; i < buffer.length; i++) {
            // We use delimeter to point to the element on the right half
            // If delimeter> right, then the right half has no elements that haven't been added
            if (delimiter > right || source[cursor] > source[delimiter]) {
                buffer[i] = source[cursor];
                cursor++;
            } else {
                buffer[i] = source[delimiter];
                delimiter++;
            }
        }
        System.arraycopy(buffer, 0, source, left, buffer.length);
}
يمكننا تنفيذ مثالنا عن طريق الاتصال mergeSort(array, 0, array.length-1). كما ترون، تتلخص العملية في قبول مصفوفة إدخال مع مؤشرات لبداية ونهاية القسم الذي يحتاج إلى الفرز. عندما يبدأ الفرز، فهذه هي بداية المصفوفة ونهايتها. ثم نحسب المحدد، وهو الفهرس الذي سنقسم فيه المصفوفة. إذا كان من الممكن تقسيم المصفوفة إلى جزأين، فإننا نسمي طريقة الفرز بشكل متكرر لنصفي المصفوفة. نقوم بإعداد مخزن مؤقت مساعد حيث سنقوم بإدراج القسم الذي تم فرزه. بعد ذلك، قم بتعيين الفهرس في بداية القسم المراد فرزه وابدأ في التنقل عبر كل عنصر من عناصر المخزن المؤقت الفارغ، واملأه بأصغر العناصر. إذا كان العنصر الذي يشير إليه الفهرس أقل من العنصر الذي يشير إليه المحدد، فإننا نضع العنصر في المخزن المؤقت ونغير الفهرس. بخلاف ذلك، فإننا نضع العنصر الذي يشير إليه المحدد في المخزن المؤقت ونغير المحدد. بمجرد أن يتجاوز المحدد حدود القسم الذي يتم فرزه أو نملأ المصفوفة بأكملها، يعتبر النطاق المحدد مصنفا. المواد حول هذا الموضوع:

عد الفرز وفرز الجذر

خوارزمية فرز أخرى مثيرة للاهتمام هي فرز الفرز. التعقيد الخوارزمي هنا هو O(n+k)عدد nالعناصر والحد kالأقصى لقيمة العنصر. هذه الخوارزمية لديها عيب واحد: نحن بحاجة إلى معرفة الحد الأدنى والحد الأقصى للقيم في المصفوفة. فيما يلي مثال على فرز العد:
public static int[] countingSort(int[] theArray, int maxValue) {
        // An array of "counters", ranging from 0 to the maximum value
        int numCounts[] = new int[maxValue + 1];
        // We increase the counter in the corresponding cell (index = value)
        for (int num : theArray) {
            numCounts[num]++;
        }
        // Create an array to hold the sorted result
        int[] sortedArray = new int[theArray.length];
        int currentSortedIndex = 0;
        // Run through the array of "counters"
        for (int n = 0; n < numCounts.length; n++) {
            int count = numCounts[n];
            // Run through the number of values
            for (int k = 0; k < count; k++) {
                sortedArray[currentSortedIndex] = n;
                currentSortedIndex++;
            }
        }
        return sortedArray;
    }
يمكنك أن تفهم أنه من غير الملائم للغاية أن نحتاج إلى معرفة الحد الأدنى والحد الأقصى للقيم مسبقًا. ولدينا خوارزمية أخرى هنا: فرز الجذر. سأقدم الخوارزمية هنا بشكل مرئي فقط. انظر المواد التكميلية للتنفيذ: المواد:

فرز سريع

حسنًا، حان وقت الحلوى - الفرز السريع، إحدى خوارزميات الفرز الأكثر شهرة. لديها تعقيد لوغاريتمي: O(n log n). تم تطوير خوارزمية الفرز هذه بواسطة توني هور. ومن المثير للاهتمام أنه اخترعها أثناء إقامته في الاتحاد السوفيتي، حيث درس الترجمة الآلية في جامعة موسكو وقام بتطوير كتاب العبارات باللغتين الروسية والإنجليزية. علاوة على ذلك، تستخدم Java إصدارًا أكثر تعقيدًا من هذه الخوارزمية في Arrays.sort. ماذا عن Collections.sort؟ لماذا لا تلقي نظرة على كيفية فرز الأشياء "تحت الغطاء"؟ إليك الكود:
public static void quickSort(int[] source, int leftBorder, int rightBorder) {
        int leftMarker = leftBorder;
        int rightMarker = rightBorder;
        int pivot = source[(leftMarker + rightMarker) / 2];
        do {
            // Move the left marker from left to right as long as the element is less than pivot
            while (source[leftMarker] < pivot) {
                leftMarker++;
            }
            // Move the right marker as long as the element is greater than pivot
            while (source[rightMarker] > pivot) {
                rightMarker--;
            }
            // Check whether we need to swap the elements pointed to by the markers
            if (leftMarker <= rightMarker) {
                // The left marker will be less than the right one only if we need to do a swap
                if (leftMarker < rightMarker) {
                    int tmp = source[leftMarker];
                    source[leftMarker] = source[rightMarker];
                    source[rightMarker] = tmp;
                }
                // Shift the markers to get new borders
                leftMarker++;
                rightMarker--;
            }
        } while (leftMarker <= rightMarker);

        // Execute recursively on the parts
        if (leftMarker < rightBorder) {
            quickSort(source, leftMarker, rightBorder);
        }
        if (leftBorder < rightMarker) {
            quickSort(source, leftBorder, rightMarker);
        }
}
هذه كلها أشياء مخيفة للغاية، لذلك دعونا نتعمق فيها. بالنسبة لمصفوفة الإدخال ( int[]المصدر)، نقوم بإنشاء علامتين: اليسار ( L) واليمين ( R). أثناء استدعاء الأسلوب الأول، فإنها تتوافق مع بداية المصفوفة ونهايتها. ثم نحدد العنصر المحوري، والذي يدعى على نحو مناسب pivot. بعد ذلك، مهمتنا هي نقل القيم الأصغر من pivotإلى اليسار pivot، والقيم الأكبر إلى اليمين. للقيام بذلك، نقوم أولاً بتحريك Lالعلامة حتى نجد قيمة أكبر من pivot. إذا لم يتم العثور على قيمة أصغر، ثم L يصبح مساوياً لـ pivot. ثم نحرك Rالعلامة حتى نجد قيمة أصغر من pivot. إذا لم يتم العثور على قيمة أكبر، Rتصبح مساوية لـ pivot. بعد ذلك، إذا Lكانت العلامة قبل Rالعلامة أو تتزامن معها، فإننا نحاول تبديل العناصر إذا Lكان العنصر أقل من Rالعنصر. ثم ننتقل Lإلى اليمين بمقدار 1، وننتقل Rإلى اليسار بمقدار 1. عندما Lتتحرك العلامة خارج Rالعلامة، فهذا يعني أن المبادلة قد اكتملت: القيم الأصغر على يسار pivot، والقيم الأكبر على يمين pivot. بعد ذلك، نطلق نفس طريقة الفرز بشكل متكرر على المصفوفات الفرعية - من بداية القسم المراد فرزه إلى العلامة اليمنى، ومن العلامة اليسرى إلى نهاية القسم المراد فرزه. لماذا من البداية إلى العلامة الصحيحة؟ لأنه في نهاية التكرار، اتضح أن العلامة اليمنى تتحرك بما يكفي لتصبح حدود الجزء الأيسر. هذه الخوارزمية أكثر تعقيدًا من الفرز البسيط، لذا من الأفضل رسمها. خذ ورقة بيضاء واكتب فيها: 4 2 6 7 3. ثم اكتب pivotفي المنتصف، أي تحت الرقم 6. ضع دائرة حولها. أقل من 4 اكتب L، وأقل من 3 اكتب R. 4 أقل من 6، 2 أقل من 6. انتهى بنا الأمر بالانتقال Lإلى pivotالموضع، لأنه Lلا يمكننا تجاوزه pivot، وفقًا للشرط. اكتب مرة أخرى 4 2 6 7 3. ضع دائرة حول 6 ( pivot) ثم ضع Lتحتها. الآن حرك Rالعلامة. 3 أقل من 6، لذا ضع Rالعلامة على 3. بما أن 3 أقل من 6 ( pivot)، فإننا نقوم بإجراء swap. اكتب النتيجة: 4 2 3 7 6. ضع دائرة حول 6، لأنه لا يزال pivot. العلامة Lعلى 3. Rالعلامة على 6. تذكر أننا نحرك العلامات حتى Lتتجاوز R. انتقل Lإلى الرقم التالي. أود هنا استكشاف احتمالين: 1) الحالة التي يكون فيها الرقم قبل الأخير 7 و2) الحالة التي يكون فيها الرقم 1 وليس 7. إذا كان الرقم قبل الأخير هو 1: حرك Lالعلامة إلى 1، لأننا نستطيع التحرك Lطالما أن Lالعلامة تشير إلى رقم أصغر من pivot. لكن لا يمكننا التحرك Rمن 6، لأننا لا نستطيع تحريك R إلا إذا كانت Rالعلامة تشير إلى رقم أكبر من pivot. نحن لا نقوم بإجراء swap، لأن 1 أقل من 6. اكتب الوضع الحالي: 4 2 3 1 6. ضع دائرة حول 6 ( pivot). Lيتحول pivotويتوقف عن الحركة. Rلا يتحرك. نحن لا نقوم بإجراء مبادلة. التحول Lوبموضع Rواحد. اكتب Rالعلامة تحت الرقم 1. Lالعلامة خارج الحدود. لأن Lهو خارج الحدود، ونحن لا نفعل شيئا. لكننا نكتب 4 2 3 1 مرة أخرى، لأن هذا هو الجانب الأيسر، وهو أقل من pivot(6). حدد الجديد pivotوابدأ كل شيء مرة أخرى :) إذا كان الرقم قبل الأخير هو 7: انقل Lالعلامة إلى 7. لا يمكننا تحريك العلامة الصحيحة، لأنها تشير بالفعل إلى المحور. 7 أكبر من pivot، لذلك نقوم بإجراء swap. اكتب النتيجة: 4 2 3 6 7. ضع دائرة حول 6، لأنه pivot. تم الآن إزاحة العلامة Lإلى 7، وتم Rنقل العلامة إلى 3. ليس من المنطقي فرز الجزء من Lالنهاية، لأنه يوجد عنصر واحد فقط. ومع ذلك، نرسل الجزء من 4 إلى Rالعلامة للفرز. نختار a pivotونبدأ من جديد :) للوهلة الأولى، قد يبدو أنه إذا أضفت الكثير من القيم المساوية لـ pivot، فسوف تكسر الخوارزمية. ولكن هذا ليس هو الحال. يمكنك ابتكار مجموعات صعبة وإقناع نفسك على الورق بأن كل شيء صحيح، وتتعجب من أن مثل هذه الإجراءات البسيطة تنفذ آلية الفرز الموثوقة هذه. الجانب السلبي الوحيد هو أن خوارزمية الفرز هذه غير مستقرة. لأن المبادلة قد تغير الترتيب النسبي للعناصر المتطابقة إذا تمت مواجهة أحدها قبل pivotأن يتم تبديل العنصر الآخر إلى الجزء السابق pivot.

الخط السفلي

أعلاه، نظرنا إلى مجموعة "الجنتلمان" من خوارزميات الفرز المطبقة في Java. الخوارزميات مفيدة بشكل عام، سواء من الناحية التطبيقية أو من حيث تعلم كيفية التفكير. بعضها أبسط، وبعضها أكثر تعقيدا. دافع الأشخاص الأذكياء عن أطروحاتهم بالنسبة للبعض. وبالنسبة للآخرين، فقد كتبوا كتبًا سميكة جدًا. آمل أن تساعدك المواد التكميلية على تعلم المزيد، لأن هذه المقالة هي مجرد نظرة عامة تبين أنها ذات ثقل بالفعل. والغرض منه هو تقديم مقدمة صغيرة. يمكنك أيضًا العثور على مقدمة للخوارزميات إذا قرأت "Grokking Algorithms ". تعجبني أيضًا قائمة التشغيل من Jack Brown: AQA Decision 1 1.01 Tracing an Algorithm . من أجل المتعة، يمكنك مشاهدة تصورات الخوارزمية على sorting.at و visualgo.net .
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION