Detailed explanation of common sorting algorithms

  • Bubble sorting

Bubble sorting is basically the first sorting method we come into contact with in the learning process. The idea is also relatively simple, that is, after pairwise comparison, if the large number is in front of the small number, we will exchange the positions of the two, then go back and compare the two numbers. When you get to the end of the team, you will complete a bubble, move the maximum number to the last, and then bubble from the beginning. At this time, the captain degree is n-1, and the length of a trip will be reduced by one, and finally complete the ascending arrangement.

//When taking a bubble
for (int i = 0; i < n-1; i++)    This is the first trip n The number should be compared n-1 second
	{
		if (a[i] > a[i + 1])
		{
			swap(&a[i], &a[i + 1]);
		}
	}
//Complete bubble sorting
void bubblesort(int* a, int n)  
{
   int end = n - 1;              //The number of n is in order as long as it takes n-1 times         
while (end)
{
	int flat = 0;                  //      Use a flag to record whether there is exchange data in a bubble, such as
	                                   // If no data is exchanged, it indicates that it is in order and can jump out in advance.
	for (int i = 0; i < end; i++)    
	{
		if (a[i] > a[i + 1])
		{
			swap(&a[i], &a[i + 1]);
			flat = 1;
		}
	}
	if (flat == 0)
		break;
		end--;
}
}

Analysis of space complexity and time complexity of bubble sorting

  • The space complexity is O (1), and there is no extra multivariable space

  • Time complexity: compare the number of times n-1, n-2 ` ` ` ` ` ` ` ` ` ` 1 equal difference sequence each time, sum the highest term n2, ignore the coefficient, so the time complexity is O (n2). The best situation is that it is orderly at the beginning. The comparison trip is not exchanged once, and the time complexity is O(n).

  • Quick sort

Quick sort is a relatively complex sort, and its performance is very superior in various sorts. To quickly sort a group of data, first break it down into several small parts for explanation. First, use a step to select one of the numbers as the benchmark value. After a comparison, make the number to a position so that the number on the left is not greater than it and the number on the right is not less than it. The position of this number will not change after the sorting is finally completed.
Method 1 hoare version


The rules here are: if you select the first value on the left as the key, let the right go first. If you select the first value on the right as the key, let the left go first. When the key is on the left, the last time the key and the meeting position value exchange, the value of that position must be less than the key, then the left l must meet R. at this time, R must have found a value smaller than the key, otherwise it will not stop. If it does not find it, it will directly meet L. the position of L at this time is the position where the two exchanged last time, After each exchange between two people, the value of L is less than the key and the value of R is greater than the key. According to this rule, we can ensure that when we exchange the key to the meeting position, we can also ensure the final effect. The same is true when the first one on the right is elected as the key.

int parrtion1(int* a, int left, int right)     
{
	int keyi = left;
	while (left < right)
	{
		while (left < right && a[right] >= a[keyi])
			right--;
		while (left < right && a[left] <= a[keyi])
			left++;
		swap(&a[left], &a[right]);
	}
	swap(&a[left], &a[keyi]);
	return left;         //Returns the coordinates that determine the final position of a value, and then recursively processes the number on the left and the number on the right                            
}

After completing the above step, the position of a number in the array has been determined. We divide the interval according to the same idea to process [0,key-1],[key+1,n-1], and continue to process it with recursive method. The condition for the end of recursion is that there is only one number in the interval, and its position cannot be adjusted, or the interval does not exist.

void quicksort(int* a, int left, int right)
{
	if (left >= right)
		return;
		int keyi = parrtion1(a, left, right);
		quicksort(a, left, keyi - 1);
		quicksort(a, keyi + 1, right);
}

We use the hoare version method to determine the final location of the selected key after a trip. Two other methods play the same role as this method.

  • Method 2 pit excavation method

First, select a value as the reference value and save it with the variable key. The position coordinate of the reference value is saved with the pivot variable. Let r on the right of the pivot go first to find a value smaller than the key, stop when it finds it, and put the value of this position into the pivot recording position. The reference value stored in the original recording position of the pivot has been saved with the key, and then pivot records the position of R at this time, Let the left go, find a value larger than the key, and put it in the place where the pivot is recorded. Let the pivot record the position of L at this time, and let r go. Repeat until RL meets. Put the value of the key in this position and return to this position.

The data after the excavation method shown above is 5 1 2 4 3 6 9 7 10 8, so that the reference value 6 is in the correct position.

int parrtion2(int* a, int left, int right)
{
	int key = a[left];
	int pivot = left;
	while (left < right)
	{
		while (left < right && a[right] >= key)
			right--;
		a[pivot] = a[right];
		pivot = right;
		while (left < right && a[left] <= key)
			left++;
		a[pivot] = a[left];
		pivot = left;
	}
	a[left] = key;
	return left;
}

Method 3 front and back pointer method
Select a reference value at the left or right end of the array and record its position with the key. When the left end is selected as the reference value, record the position of the next number with cur. Prev records the position of the first number. Let cur go to the right and find a value smaller than the key. When it is found, let prev move one bit, and then exchange the values at cur and prev positions. Continue to let cur go. Repeat the above steps until it reaches the right end, Then exchange the values at the prev and key positions.
Cur is used to traverse other numbers except the key position. When the right end value is selected as the reference value, cur records the position of the first number. prev is the next bit after cur. Finally, prve+1 should be exchanged at the key position.

int parrtion3(int* a, int left, int right)
{
	int key = left;
	int prev = left;
	int cur = left + 1;
	while (cur <= right)
	{
		if (a[cur] < a[key]&&++prev!=cur)    //When prev=cur, there is no need to exchange with yourself
		{
			swap(&a[cur], &a[prev]);
		}
		cur++;
	}
	swap(&a[key], &a[prev]);
	return prev;
}
  • Analyze the defects of the recursive version of quick sort.

    When a reference value is finally determined in the middle of the array each time, the left and right intervals are recursed, which is logically similar to a complete binary tree. At this time, the recursion depth is approximately logn. When the array to be sorted itself is very close to order, or both are a number, such as 2 2 2 ··, 2 3 2 3 ····, in these cases, we choose the value at one end as the reference value, After a run, the last position of the benchmark value is still at this end, so that the case of layer by layer recursion is shown in the figure below.

    This depth is similar to N. when the number n is very large, the recursion depth is too deep and it is easy to overflow the stack. To improve these situations, we should first know that quick sort is not suitable for sorting a group of numbers that are approximately ordered at the beginning. Then, you can improve the method when selecting the key, so that the last position of each benchmark value is relatively in the middle, which needs to be recursive to reduce the depth.
int getmid(int* a, int left, int right)
{
	int mid = (left + right) / 2;
	if (a[left] < a[mid])
	{
		if (a[mid] < a[right])
			return mid;
		else if (a[left] < a[right])
			return right;
		else
			return left;
	}
	else
	{
		if (a[mid] > a[right])
			return mid;
		else if (a[left] < a[right])
			return left;
		else
			return right;
	}
}
int parrtion3(int* a, int left, int right)
{
	int tem = getmid(a, left, right);
	swap(&a[left], &a[tem]);
	int key = left;
	int prev = left;
	int cur = left + 1;
	while (cur <= right)
	{
		if (a[cur] < a[key]&&++prev!=cur)
		{
			swap(&a[cur], &a[prev]);
		}
		cur++;
	}
	swap(&a[key], &a[prev]);
	return prev;
}

Like the above code, when you want to use one of the above three methods to put the key in the correct position for the number of an interval each time, select the number of the middle size from the number at the left and right ends and the number at the middle position as the benchmark value, so that the benchmark value is finally in the middle position, which is conducive to re delimiting the molecular interval. The above method constructs a function to take the middle number to complete the task. If the others remain unchanged, take the number of the middle size to the first position, and then run according to the original method.

There is an inter cell optimization method that can also be added to the quick sort, that is, the length of the interval is judged during each recursion. If the interval is already very small, the sorting of this interval can be sorted by other sorting methods, which can greatly reduce the depth of recursion.

void quicksort(int* a, int left, int right)
{
 if (left >= right)
 {
 	return;
 }
 if (right - left + 1 < 10)          //When the number of recursion layers is deep and the interval is less than 10, other sorting methods between cells can be used
 {                                                    //To reduce the problem of fast row recursion too deep
 	insertsort(a + left, right - left + 1);
 }
 else
 {
 	int keyi = parrtion3(a, left, right);
 	quicksort(a, left, keyi - 1);
 	quicksort(a, keyi + 1, right);
 }
}
  • Quick sort non recursive version

The idea of quick sorting is to continuously determine the position of the benchmark value until the position of all the last numbers is determined. The sorting is completed. The parameters to be accepted for each recursion are the interval boundary value and the address of the first element of the array. Because recursion may lead to the problem of stack overflow due to too deep recursion, non recursive methods can be used to solve the problem of stack overflow, Non recursion also simulates the execution process of recursion. The parameters of recursion are interval boundary values. Then we can use the data structure of stack to save the interval boundary values to go every time. In this way, the simulation can also realize fast sorting.

void quicksortnonR(int* a, int left, int right)
{
	Sta st;                        //Build a stack
	stackinit(&st);             //Stack initialization
	stackpush(&st, left);     //  Stack the interval boundary of the original array
	stackpush(&st, right);
	while (!stackEmpty(&st))      //As long as the stack is not empty, it means that there are intervals to go according to the above method
	{
		int end = stacktop(&st);            //Take out two boundary values and pay attention to the principle of stack last in first out
		stackpop(&st);
		int begin = stacktop(&st);
		stackpop(&st);
		int key = parrtion3(a,begin, end);          //Just sort it out here
		if (key + 1 < end)                              //If there are still numbers to the right of the benchmark value, the right sub interval boundary value is stacked
		{
			stackpush(&st, key + 1);
			stackpush(&st, end);
		}
		if (begin < key-1)                        //Here, the left subinterval is judged by the same principle
		{
			stackpush(&st, begin);
			stackpush(&st, key-1);
		}
	}
	stackdestory(&st);
}

Analysis of space complexity and time complexity of quick sort

  • Spatial assistance degree: each time a benchmark value is determined, it needs to recurse one layer. Each single trip is O (1) recursive average depth logn, so the spatial assistance degree is O (logn).

  • Time assistance: the total number of layers to be compared in each recursive layer is n, the number of layers is logn, the best time assistance is O(nlogn), and the worst case is that the number of groups to be sorted is the same number. At this time, the depth of recursion is n, the number of data compared in each layer is n-1, n-2 ` ` ` ` ` ` 1, and the time complexity is O (n2).

  • Merge sort

The idea of merging is to merge two groups of ordered arrays into a group of ordered arrays.

As shown in the dynamic diagram, at the beginning, one number in a group of numbers is considered to be ordered, so that the two numbers can be merged into a group of ordered arrays. When we get the array to be sorted, we first dynamically open up n data size spaces to store the merged numbers, and then copy back to the original array to get a group of numbers. Initially, it is disordered. Divide the interval in half and recurse the left and right intervals, Recurse until there is only one number or no number in the interval, return to the previous layer, recurse back to the ordered left and right intervals, and the two groups of numbers can be merged into a group. Continue to return to the previous layer and merge again, and finally order.

void _mergesort(int* a, int left, int right, int* tem)
{
	if (left >= right)                             //If the interval has only one number or none, it is considered to be ordered. Return to the previous layer for merging.
		return;
	int mid = (left + right) / 2;               //Here is the constant recursion between partitions.
	_mergesort(a, left, mid, tem);           //Recursive left interval
	_mergesort(a, mid + 1, right, tem);   //Recursive right interval
	int begin1 = left;                               //When you return to this layer, the left and right groups of numbers have been ordered, merged into the tem array, and then copied back to the original array
	int end1 = mid;
	int begin2 = mid + 1;
	int end2 = right;
	int j = left;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] < a[begin2])
		{
			tem[j++] = a[begin1++];
		}
		else
		{
			tem[j++] = a[begin2++];
		}
	}
	while(begin1<=end1)
		tem[j++] = a[begin1++];
	while(begin2<=end2)
		tem[j++] = a[begin2++];
	for (int i = left; i <= right; i++)
		a[i] = tem[i];
}
void mergesort(int* a, int n)
{
	int* tem = (int*)malloc(sizeof(int) * n);    //Dynamic tem temporary array
	if (tem == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	_mergesort(a, 0, n - 1,tem);    //Pass the original interval recursion
	free(tem);
	tem = NULL;
}
  • Merge sort non recursive version

Merge sort: when merging starts, the two groups are merged. When there is only one number in each group, two original data can be grouped into one group first. In this way, you can traverse for one trip. If there is no matching merging in the end, the number can be returned to the original position intact, and then the next cycle is an ordered sequence of two groups into a group of four numbers, If you don't get four hours, the return is still orderly. Repeat this way and finally return to the final ordered sequence.

void mergesortnonR(int* a, int n)
{
   int* tem = (int*)malloc(sizeof(int) * n);
   if (tem == NULL)
   {
   	printf("malloc fail\n");
   	exit(-1);
   }
   int gap = 1;
   while (gap < n)
   {
   	for (int i = 0; i < n; i += 2 * gap)   //One step goes through two groups, that is, merge the two groups, and then merge the next two groups
   	{
   		int begin1 = i, end1 = i + gap - 1;  //  The boundary of the group on the left
   		int begin2 = i + gap, end2 = i + 2 * gap - 1;   //The boundary of the group on the right
   		if (end1 >= n || begin2 >= n)    //Go to the far right. Maybe the group on the right doesn't exist, so you don't have to go back
   			break;
   		if (end2 >= n)    //When the right group at the rightmost end does not have the same number as the left group, correct the right boundary of the right group to prevent cross-border access
   		{
   			end2 = n - 1;
   		}
   		int j = i;
   		while (begin1 <= end1 && begin2 <= end2)   //Below is the normal merge, and then copy back to the original array
   		{
   			if (a[begin1] < a[begin2])
   			{
   				tem[j++] = a[begin1++];
   			}
   			else
   			{
   				tem[j++] = a[begin2++];
   			}
   		}
   		while (begin1 <= end1)
   			tem[j++] = a[begin1++];
   		while (begin2 <= end2)
   			tem[j++] = a[begin2++];
   		for (j = i; j <= end2; j++)
   			a[j] = tem[j];
   	}
   	gap *= 2;    //After pairing into a group, group the ordered group just merged into a group,
   	                   // Until this set is the complete ordered sequence of the original array
   }
   free(tem);
   tem = NULL;
}

Analysis of space complexity and time complexity of merging and sorting

  • Spatial assistance: the temporary array tem is opened up, and the spatial complexity is O (n).
  • Time assistance: the recursion depth is logn, and each layer is merged into o (n), so the time complexity is O (nlogn).

Tags: C Algorithm data structure Binary tree

Posted on Fri, 26 Nov 2021 14:40:59 -0500 by mlvnjm