Algorithm learning notes - merge sort

Keywords: C++ Algorithm

Merge sort

Merging is an algorithm based on divide and conquer, which is to split an array into small arrays in half, sort them in turn, and finally merge these sorted arrays.

(divide and Conquer: big problems are divided into small problems and solved one by one)

Stepwise analysis

Suppose we have an array of {5, 8, 1, 0, 2, 9, 7, 4}. We need to sort from small to large.

for the first time

We split it into two arrays, {5, 8, 1, 0}, {2, 9, 7, 4}.

The second time

Then split: {5, 8}, {1, 0}, {2, 9}, {7, 4} four arrays.

The third time it becomes eight separate numbers.

{5}, {8} , {1} , {0} , {2} , {9} , {7} , {4};

Finally, we will

5 and 8 sort, 1 and 0 sort, 2 and 9 sort, 7 and 4 sort (sort by the previously separated array)

You get these four arrays

{5 , 8} , {0, 1} { 2 , 9 } , { 4 , 7 }.

​ ↑ ↑ ↑ ↑

​ ① ② ③ ④

Note here:

How did we divide large arrays into small arrays before? When merging, we should merge them back in the original order.

After that, ① and ② are sorted and combined to obtain {0, 1, 5, 8}. Similarly, ③ and ④ are used to obtain {2, 4, 7, 9}.

The last step is to merge the two just obtained arrays to get the final sorted array.

Algorithm analysis and Implementation

In the analysis process just now, it can be seen that in each step after segmentation, the two arrays are combined and sorted at the same time to get a new array. The processing steps are the same. Therefore, in the actual code, we can use the recursive method to realize the above algorithm.

Merge sorting is a continuous half sorting of arrays, so the time complexity is O(nlogn). The best and worst cases are the same, because they have to traverse the entire array and cannot evolve (Evolution: become a sorting algorithm with higher time complexity). At the same time, merge sorting is also a stable sorting algorithm (it is assumed that there are multiple records with the same keyword in the record sequence to be sorted. If they are sorted, the relative order of these records remains unchanged, that is, in the original sequence, r[i]=r[j], and r[i] is before r[j], while in the sorted sequence, r[i] is still before r[j], then this sorting algorithm is called stable; otherwise, it is called unstable.) If you don't understand the stability, you can refer to relevant data.

The problem to be noticed now is how to merge and sort the two arrays to get a new array:

Here we use a very common algorithm idea - Double pointers.

Reference the example just mentioned: {0, 1, 5, 8}, {2, 4, 7, 9} two arrays, which are replaced by a and b.

First create a temporary array temp. Use two pointers (iterators, etc.) Pa and Pb to point to the beginning of a and b respectively, and then compare the sizes of PA and Pb.

Put the smaller number into temp, and then increment the pointer.

For example, pa and pb point to a [0] and B [0] at the beginning.

Compare a[0],b[0], that is, compare 0 and 2, put the value of a[0] into temp, and then pa++,pa points to a[1].

Then we can compare a[1] < b[0] with b[0], and put a[1] into temp, pa + +.

Comparing a[2] with b[0], b[0] < a[2], b[0] is put into temp, Pb + +, and Pb points to b[1]

Then we can use a[2] and b[1] to compare according to the above method.

And so on, we can get a sorted array.

But in fact, the double pointer algorithm mentioned so far has not been completely completed. You can consider where there are problems and give the answer in the subsequent code comments.

Here are the codes and comments:

(each algorithm has its own advantages in the implementation of merging and sorting. This paper only briefly describes the idea, and the code is for reference only. You can change it yourself to try to improve the efficiency)

//Merge sort
//The left and right subscripts of the array to be sorted are passed in, and 0 and vec.size() are passed in for the whole array sorting
//Default from small to large
void mergeSort(vector<int>& vec, int l, int r) {
	if (l >= r) return;   
    //Judge whether the segmentation is completed
	
    int mid = (l + r) / 2;
    
    //Recursive sort array
	mergeSort(vec, l, mid);
    //vec[l...mid] stands for the first half
	mergeSort(vec, mid+1, r);
    //vec[mid+1...r] stands for the second half
    
    
    /*
    Note that l and r after each recursion here represent the beginning and end of each small array after vec grouping
    l The meaning of and r must be understood clearly
    */
    
    
	//Let's start merging the two recursively sorted arrays in turn
	vector<int> temp(r - l + 1);
	for (int i = l;i <= r; i++) {
		temp[i-l] = vec[i];
	}
	//The first step is temporary space assignment
    //This is different from the explanation just now. We use the temporary array temp to store the values in the original array vec, and the sorted array is directly put back to vec

    //The second step is to merge the two divided arrays vec[l...mid],vec[mid+1...r] with two pointers.
	int i = l, j = mid + 1;
	for (int k = l;k <= r;k++) {
        /*Back to the questions mentioned before the code started:
        	Improve our double pointer algorithm.
        	When traversing two arrays with double pointers, if one of the arrays is traversed, you need to put all the data of the remaining array into temp,
        	Otherwise, incomplete data will appear.
        	For example, {1,3,5,9} {2,4,6,10,12,31};
        	When we put all 1, 2, 3, 4, 5, 6 and 9 in the array, 10, 12 and 31 have not been put in
        	We need to put these three numbers into the sorted array.
        	
        	The following ① ② is to judge whether there is any spare data.
        */
		if (i > mid) {//①
			vec[k] = temp[j - l];
			j++;
		}
		else if(j>r){//②
			vec[k] = temp[i - l];
			i++;
		}
        //The following is the comparison and merging of double pointers
		else if (temp[i - l] < temp[j - l]) {
			vec[k] = temp[i - l];
			i++;
		}else{
			vec[k] = temp[j - l];
			j++;
		}
	}
}

Screenshot of code test:
)

Posted by harinath on Fri, 08 Oct 2021 20:27:38 -0700