Recursive model summary II - merge sort

Keywords: Algorithm data structure recursion

1. Preface

For beginners of recursion, recursion is undoubtedly difficult to understand. This series aims to analyze the recursion used in common algorithms to a certain extent, and summarize its recursion model while learning these algorithms. In order to deepen the understanding of recursion, at the same time, when we master a variety of recursion models. Whenever we need to use recursion to solve problems, we can get some inspiration from the existing recursion model. Or when the models used in similar scenes are the same (similar to modeling in mathematics or physics, we can summarize the general laws through the analysis of things with certain characteristics, so as to apply them to the cases with the same characteristics), we can apply the existing models, which is believed to be very convenient.

2. Merge and sort

1. Algorithm understanding

From Baidu Encyclopedia

Merge Sort is an effective and stable sorting algorithm based on merge operation. It is a very typical application of Divide and Conquer. The ordered subsequences are combined to obtain a completely ordered sequence; That is, each subsequence is ordered first, and then the subsequence segments are ordered. If two ordered tables are merged into one, it is called two-way merging.

In short, it is to divide a sequence into two sequences, arrange the two sequences in order, and then merge them back in size order. In merging and sorting, we will recursively divide a sequence until a subsequence is divided into two subsequences containing only one element. Because it contains only one element, we can think of it as orderly, so we don't have to sort. The recursion ends and the merge begins.

For example, it is easy to understand merging and sorting:
The following data needs to be sorted:

{1,4,2,65,76,15,9,0,54,32};

Firstly, we use recursive depth to divide the above data into indivisible subsequences, that is, each sequence has only one data. The segmentation process is:

First split {1, 4, 2, 65, 76, 15, 9, 0, 54, 32} into:
{1,4,2,65,76} {15,9,0,54,32};
{1, 4, 2, 65, 76} continues to be recursively divided into {1, 4, 2} and {65, 76};
{1, 4, 2} continues to be divided into {1, 4} and {2};
{1, 4} continues to be divided into {1} and {4};

At this point, the {1,4} sequence becomes the smallest non separable sequence. Recursion starts to pick up and return, and starts to merge {1}, {4} into an ordered sequence. After merging, it is {1,4}. This function returns and processes another sequence {2} split with {1,4}. As the minimum sequence, it does not need to be split. It returns and merges with {1,4} into an ordered sequence, which is {1,2,4}. This layer function returns and recursively processes {65,76} split with {1,4,2}, then {65,76} is divided into {65} and {76}. After returning, it is {65} and {76} {65, 76}, and then {65, 76} and {1, 2, 4} are merged into an ordered sequence {1, 2, 4, 65, 76}. The function returns {15, 9, 0, 54, 32} until {15, 9, 0, 54, 32} is processed into {0, 9, 15, 32, 54} and {1, 2, 4, 65, 76} is merged into {0, 1, 2, 4, 9, 15, 32, 54, 65, 76}. This function ends.

It may not be intuitive to say so. Look at a few pictures to understand:

Next, there is only one point left, that is, the method of merging ordered sequences. In fact, this step is very simple:
For example, merge the ordered sequences {1, 2, 4} and {65, 76}. First, take a number from each of the two sequences, which are 1 and 65 respectively. Compare it. If 1 is small, 1 will be stored in the new sequence to get {1}. Then take 2 from the first sequence, compare it with 2, and store it in the sequence to get {1, 2}. Take 4, compare it, and store it in the sequence to get {1, 2, 4}.
At this time, the first sequence has no data. And because the sequences are orderly, you only need to store the number of the second sequence in the new sequence in turn.

Figure 1 shows the process of recursive call, and Figure 2 shows the process of recursive pick-up merge sorting.

2. Code

#include <iostream>
#include <Windows.h>
#include <stdio.h>
#include <string.h>

using namespace std;

int Sort(int* nums,int left,int mid,int right,int* tmp) {    //Merge function, create a temporary array, compare the element sizes of the two arrays, and enter the small one into the temporary array until both arrays are empty

	int left_tmp = left;
	int mid_tmp = mid + 1;
	int count = 0;
	while (left_tmp <= mid && mid_tmp <= right) {      //Store all the elements of one of the arrays into the temporary array

		if (nums[left_tmp] < nums[mid_tmp]) {
			tmp[count++] = nums[left_tmp++];
		}
		else {
			tmp[count++] = nums[mid_tmp++];
		}
	}

	while (left_tmp <= mid) {                    //Then all the elements of another non empty array are stored in the array  
		tmp[count++] = nums[left_tmp++];
	}

	while (mid_tmp <= right) {                  //Then all the elements of another non empty array are stored in the array  
		tmp[count++] = nums[mid_tmp++];
	}

	memcpy(nums + left, tmp, sizeof(int) * (right - left + 1));       //Store all the elements in the temporary array into the nums array

	return 1;
}


int mergeSort(int* nums, int left, int right, int* tmp) {

	if (left < right) {                      

		int mid = left + (right - left) / 2;
		mergeSort(nums, left, mid, tmp);            //Recursively split the array until there is only one element in the array
		mergeSort(nums, mid + 1, right, tmp);
		Sort(nums, left, mid, right, tmp);          //Merge array
	}

	return 1;
}


int main(void) {

	int nums[10] = { 1,4,2,65,76,15,9,0,54,32 };
	//int nums[10] = { 1,4,2,6,7,15,9,0,5,33 };
	//int nums[10] = { 1,2,3,4,5,6,7,8,9,10 };

	int count = sizeof(nums) / sizeof(nums[0]);   //Calculate temporary array size

	int* tmp = new int[count];                    //Allocate temporary array space

	mergeSort(nums, 0, 9, tmp);

	for (auto& art : nums) {                      //Print num elements
		cout << art << " ";
	}

	system("pause");

	return 0;
}

3. Recursive model summary

        mergeSort(nums, left, mid, tmp);            
		mergeSort(nums, mid + 1, right, tmp);
		Sort(nums, left, mid, right, tmp); 

Understanding recursion requires some abstract thinking. If only from the current point of view, it does not go deep into the bottom of recursion. The mergesort (Num, left, mid, TMP) function can be regarded as a sorting function, which will order the elements of the num array from left to mid from small to large. Mergesort (Num, mid+1, right, TMP) Similarly, the function arranges the data from mid+1 to right in the num array. After both functions are completed, execute sort (Num, left, mid, right, TMP), that is, merge and sort the two ordered arrays from small to large. In fact, each layer of recursion is implemented in this way.

Merge sort is actually a very typical application of divide and conquer. Divide and conquer is a kind of recursive model. Its feature is to divide the original problem into two until it is divided into the smallest problem we want (the code is embodied as mergesort (Num, left, mid, TMP) and mergesort (Num, mid + 1, right, TMP)) In merge sort, that is, the original array is divided into an array containing only one element. Then recursively pick up and start processing the separated subsequences as we want (the code is embodied as sort (Num, left, mid, right, TMP)).

The recursive model is to recurse first until the two recursions pick up, and then deal with them.

A little more abstract, combined with the code understanding, is that the divide and conquer method is divided into two, so there are two recursions in the recursive function. Because some processing is often carried out after recursion, there is another step to process. It is also worth noting that the processing step may not be written after recursion. It may also be like this:

        mergeSort(nums, left, mid, tmp);  
        Sort(nums, left, mid, right, tmp);          
		mergeSort(nums, mid + 1, right, tmp);
		

It may also be:

        Sort(nums, left, mid, right, tmp);         
        mergeSort(nums, left, mid, tmp);   
		mergeSort(nums, mid + 1, right, tmp);

4. Enterprise practice

1. Question: if a 100G file needs to be sorted, but the maximum memory can only hold 1G files, how can the data in the file be sorted?

2. Solution: we can use a method similar to merging and sorting to divide 100G files into two 50G files, divide 50G files into two 25G files, and then continue to subdivide them until they are divided into sizes that can be operated in memory, arrange them in memory and write them back to the file. For example, we finally subdivide a 2G file into two 1g files. After the two 1g files are sorted in memory, they are written back to the file to get two sorted 1g files. Then we can arrange two 1g files in 1G memory to get a 2G file by using the sorting method we used in merging and sorting (in fact, 10MB of memory is enough for sorting at this time, because I only need to take out two numbers at a time, compare the size and save them into the file. It only needs two numbers of space at a time). And so on, the largest files can be sorted.

Posted by niki on Sun, 21 Nov 2021 12:33:37 -0800