[Exchange sort] Bubble sort and quick sort

The basic idea of ​​exchange sorting:

The so-called exchange is to exchange the positions of the two records in the sequence based on the comparison results of the key values ​​​​of the two records in the sequence. The characteristics of exchange sorting are: move the record with a larger key value to the end of the sequence, and move the record with a larger key value to the end of the sequence. Smaller records are moved toward the front of the sequence.

Table of contents

1. Bubble sort

2.Quick sort

2.1 Recursive implementation 

2.2 Quick sort optimization

2.3 Non-recursive implementation


1. Bubble sort

Assume ascending order. Each time it is traversed, compare them in pairs, and swap the larger elements backward until the largest element is selected and placed at the end. At this time, the last element in the ascending order has been determined, and then the previous unordered elements are traversed multiple times, each time you can Determine a maximum number until sorting is complete.

Dynamic illustration:

Code:

//交换函数
void Swap(int* p1, int* p2)
{
	int t = *p1;
	*p1 = *p2;
	*p2 = t;
}

void BubbleSort(int* arr, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		for (int j = 0; j < n - 1 - i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				Swap(&arr[j], &arr[j + 1]);
			}
		}
	}
}

If we are sorting ordered data, we can also optimize it and improve efficiency. The code is as follows

void BubbleSort(int* arr, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
        int flag = 1;//假设已经有序
		for (int j = 0; j < n - 1 - i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				Swap(&arr[j], &arr[j + 1]);
                flag = 0;//发生交换,说明无序
			}
		}
        if(flag == 1)//已经有序,不在继续排序
        {
            break;
        }
	}
}

Summary of features of bubble sort:

  1. Bubble sorting is a very easy to understand sorting
  2. Time complexity: O(N^2)
  3. Space complexity: O(1)
  4. Stability: stable
     

2.Quick sort

2.1 Recursive implementation 

Quick sort is an exchange sorting method with a binary tree structure proposed by Hoare in 1962. Its basic idea is to take any element in the sequence of elements to be sorted as the reference value key, and divide the set to be sorted into two parts according to the sorting code. Sequence , all elements in the left subsequence are less than the reference value key, all elements in the right subsequence are greater than the reference value key, and then the process is repeated in the left and right subsequences until all elements are arranged in the corresponding positions .

Illustration:

Code:

Assume that the elements in the array [left, right] in the closed interval are sorted in ascending order.

void QuickSort(int* a, int left, int right)
{
	if (left >= right)
	{
		return;
	}

    //按照基准值(中间位置)对array数组的[left, right]区间中的元素进行划分
	int keyi = PartSort(a, left, right);

    //划分成功后以div为边界形成了左右两部分[left, keyi-1]和[key+1,right]
    //递归排 [left, keyi-1]
	QuickSort(a, left, keyi - 1);

    //递归排[key+1,right]
	QuickSort(a, keyi + 1, right);
}

The above is the main framework of the recursive implementation of quick sort. It is found that it is very similar to the binary tree pre-order traversal rules . When writing the recursive framework, you can think of the binary tree pre-order traversal rules and write them quickly. The post-order only needs to analyze how to follow the benchmark value. Just divide the data in the interval.

Common ways to divide the interval into left and right halves (PartSort) based on the base value are:

1. hoare version

Let’s take a look at the animation first to facilitate understanding. 

The clever thing: 

  1. Make the key on the left, and go first on the right; ensuring that the value of the encounter position is smaller than the key, or is the position of the key
  2. Make the key on the right, and go first on the left; this ensures that the value of the encounter position is larger than the key, or is the position of the key.

We use the first method here:

When L and R meet, there are nothing more than two situations: L meets R and R meets L:

  1. Situation 1: L meets R, R stops, L is walking, R goes first, the position where R stops must be smaller than the key , the position where they meet is the position where R stops, and it must be smaller than the key
  2. Scenario 2: R meets L. In the round of encounter, L does not move. R is moving and meets L. The meeting position is the position of L. The position of L is the position of the key or this position has been exchanged and replaced by If the key is small , the L position must be smaller than the key.

Code:

int PartSort1(int* a, int left, int right)
{
	int keyi = left;
	while (left < right)
	{
		//右边找小 与key相等的数据放在左边右边都可以
		while (left < right && a[right] >= a[keyi])
		{
			right--;
		}
		//左边找大 与key相等的数据放在左边右边都可以
		while (left < right && a[left] <= a[keyi])
		{
			left++;
		}
		Swap(&a[left], &a[right]);
	}
	Swap(&a[keyi], &a[right]);
	return right;
}

2. Digging method

 One of left and right must be a pit position. Find the smaller one on the right and the larger one on the left. If you find it, put the value into the original pit position, and the position becomes the new pit position.

Code:

int PartSort2(int* a, int left, int right)
{
	int hole = left;
	int key = a[left];
	while (left < right)
	{
		//右边找小
		while (left < right && a[right] >= key)
		{
			right--;
		}
		a[hole] = a[right];
		hole = right;
		//左边找大
		while (left < right && a[left] <= key)
		{
			left++;
		}
		a[hole] = a[left];
		hole = left;
	}
	a[hole] = key;
	return hole;
}

3. Front and back pointer version

  1. At the beginning, prev and cur are adjacent
  2. When cur encounters a value larger than key, the values ​​between them are all larger than key.
  3. Cur searches for the small one. After finding the small one, it exchanges it with the value of the ++prev position, which is equivalent to pushing the large roll to the right and changing the small one to the left.
     

 Code:

int PartSort3(int* a, int left, int right)
{
	int keyi = left;
	int prev = left;
	int cur = left + 1;
	while (cur <= right)
	{
		if (a[cur] <= a[keyi]&&++prev!=cur)//自己不与自己交换
		{
			Swap(&a[cur], &a[prev]);
		}
		cur++;
		
	}
	Swap(&a[prev], &a[keyi]);
	return prev;
}

2.2 Quick sort optimization

When we encounter ordered data, since our key is the first element selected, the time complexity will become O(N^2). There are two optimization methods:

  1. Three-number method to select key
  2. Random number selection key
  3. Inter-cell optimization

To find the middle of three numbers, the code is implemented:

int GetMidIndex(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 left;
		}
		else
		{
			return right;
		}
	}
	else//a[left]>a[mid]
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[left] > a[right])
		{
			return right;
		}
		else
		{
			return left;
		}
	}
}

Then we need to add the following code at the beginning of the above three division methods to achieve the purpose of optimization .

int midi = GetMidIndex(a, left, right);
Swap(&a[midi], &a[left]);

2. Select key by random number

int GetRandIndex(int* arr, int left, int right)
{
    int i = rand()%(right-left+1)+left;
    return i;
}

Of course we also need to use srand((unsigned int)time(NULL)) random number seed.

3. Inter-cell optimization

//小区间优化
if (end - begin +1<10)
{
    //使用插入排序
	InsertSort(arr + begin, end - begin + 1);
	return;
}

 The essence of optimization is to reduce the number of recursive calls due to the nature of binary trees. We can conclude that the last three levels of the full binary tree account for approximately 85% of the total number. In order to reduce the recursive overhead, we can change the recursive call between small cells to direct insertion sort , which can improve the performance of the sort a little, but it will not improve a lot .

2.3 Non-recursive implementation

We use a stack to implement it here. The stack stores the endpoint values ​​of the intervals that need to be divided . We use the first-in-last-out feature of the stack to simulate recursion.

void QuickSortNonR(int* a, int left, int right)
{
	Stack st;
	StackInit(&st);
	//入栈
	StackPush(&st, right);
	StackPush(&st, left);

	while (!StackEmpty(&st))
	{
		left = StackTop(&st);
		StackPop(&st);
		right = StackTop(&st);
		StackPop(&st);

		int keyi = PartSort1(a, left, right);
		//想先处理左边,就先右边区间先入栈

        //以基准值为分割点,形成左右两部分:[left, keyi-1]和[keyi+1,right)
		if(right > keyi + 1)
		{
			StackPush(&st, right);
			StackPush(&st, keyi + 1);
		}
		if (left < keyi - 1)
		{
			StackPush(&st, keyi - 1);
			StackPush(&st, left);
		}
	}

	StackDestroy(&st);
}

Of course, you can also use queues to simulate it. The queue is equivalent to breadth-first, level-order traversal in the binary tree, and the stack is depth-first, and the pre-order traversal of the binary tree.

Summary of quick sort features:

  1. The overall comprehensive performance and usage scenarios of quick sort are relatively good, so it is called quick sort.
  2. Time complexity: O(N*logN)
  3. Space complexity: O(logN)
  4. Stability: Unstable

2.4 There are a large number of identical elements

When the array to be sorted contains a large number of identical elements, the time complexity of quick sort will drop to O(N^2).

Three-way division:

 

 step:

  1. a[c] < key, exchange the values ​​of c and l positions, ++l, ++c
  2. a[c] > key, exchange the values ​​of c and r positions, --r
  3. a[c] == key,++cu

The essence of three-way division:

  1. Throw the small one to the left and the big one to the right
  2. The value equal to the key is pushed to the middle
  3. l always points to the first data equal to key

Code:

int RandIndex(int begin, int end)
{
	int i = rand() % (end - begin + 1) + begin;
	return i;
}

void QuickSortThree(int* arr, int begin, int end)
{
	//1.区间只有一个值
	//2.区间不存在
	if (begin >= end)
	{
		return;
	}
	//三路划分 效率有一定的下降

	int left = begin;
	int cur = begin + 1;
	int right = end;
	int Index = RandIndex(begin, end);
	Swap(&arr[Index], &arr[left]);
	int key = arr[left];
	while (cur <= right)
	{
		if (arr[cur] < key)
		{
			Swap(&arr[cur++], &arr[left++]);
		}
		else if (arr[cur] == key)
		{
			cur++;
		}
		else if (arr[cur] > key)
		{
			Swap(&arr[cur], &arr[right--]);
		}
	}
	QuickSortThree(arr, begin, left - 1);
	QuickSortThree(arr, cur, end);

}

Note: The efficiency of three-way division is lower than that of two-way division.

This article is over! In our next article, we will learn the fourth lesson of sorting [Merge Sort].

Guess you like

Origin blog.csdn.net/qq_72916130/article/details/132183666