Data structure - sorting algorithm exchange sorting (bubble && quick sort)

Table of contents

1. Swap sort - bubble sort

1.1 Basic idea of ​​bubble sorting

1.2 Implementation of Bubble Sort

2. Exchange sort - quick sort

1.1 Basic idea of ​​quick sort

1.2 Benchmark value division—analysis

1. hoare version:

2. Digging method:

3. Front and rear pointer versions

1.3 The specific implementation of hoare fast sorting

1.4 The concrete realization of quick row by excavation method

1.5 The specific implementation of the quick sorting of the front and rear pointer versions

1.6 Optimization of Quick Sort

Optimization 1: Take the middle of three numbers (improve efficiency and prevent ordered sequence stack overflow)

Optimization 2: Optimizing between cells (greatly reducing the number of recursions)

1.7 Quick sort non-recursive

1.8 Summary of Quick Sort Features


1. Swap sort - bubble sort

1.1 Basic idea of ​​bubble sorting

Basic idea: The so-called exchange is to exchange the positions of the two records in the sequence according to the comparison result of the key values ​​of the two records in the sequence. Records with smaller key values ​​are moved to the front of the sequence.

1.2 Implementation of Bubble Sort

void BubbleSort(int*a, int n)
{
	int i = 0;
	for (i = 0; i < n - 1; i++)//控制趟数
	{
		int flag = 1;//假设已经有序
		int j = 0;
		for (j = 0; j < n - 1 - i; j++)//控制比较次数
		{
			if (a[j] > a[j + 1])
			{
				flag = 0;
				int temp = a[j];
				a[j] = a[j + 1];
				a[j + 1] = temp;
			}
		}
		if (1 == flag)
		{
			break;
		}
	}
}

Summary of the characteristics of bubble sort:

1. Bubble sort is a very easy-to-understand sort

2. Time complexity: O(N^2)

3. Space complexity: O(1)

4. Stability: Stable

2. Exchange sort - quick sort

1.1 Basic idea of ​​quick sort

Quick sorting is a binary tree structure exchange sorting method proposed by Hoare in 1962. Its basic idea is: any element in the sequence of elements to be sorted is taken as a reference value, and the set to be sorted is divided into two subsequences according to the sorting code , all elements in the left subsequence are less than the reference value, all elements in the right subsequence are greater than the reference value, and then the leftmost subsequence repeats the process until all elements are arranged in the corresponding position.

// 假设按照升序对array数组中[left, right)区间中的元素进行排序
void QuickSort(int array[], int left, int right)
{
	if (right - left <= 1)
		return;

	// 按照基准值对array数组的 [left, right)区间中的元素进行划分
	int div = partion(array, left, right);

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

	// 递归排[div+1, right)
	QuickSort(array, div + 1, right);
}

The above is the main framework for the recursive implementation of quicksort. It is found that it is very similar to the preorder traversal rules of the binary tree. When writing the recursive framework, you can think about the preorder traversal rules of the binary tree and write it out quickly. The postorder only needs to analyze how to follow the benchmark value. The way to divide the data in the interval is enough.

1.2 Benchmark value division—analysis

Common ways to divide the interval into left and right halves according to the base value are:  

1. hoare version:

Analysis chart:

hoare version—fast single-pass sorting implementation:

//hoare
int PartSort1(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[keyi], &a[left]);
	// [begin , keyi - 1] keyi [keyi + 1,end]
	keyi = left;
	return keyi;
}

2.  Digging method:

 

Analysis chart:

Pit digging method - fast sorting single-pass sorting implementation:

//挖坑法
int PartSort2(int* a, int left, int right) {
	//坑值
	int key = a[left];
	//坑位 - left 坑位是动态变化
	while (left < right) {
		while (left < right && a[right] >= key) {
			right--;
		}
		//此时将right,大于key的值,填坑到left坑位
		a[left] = a[right];
		while (left < right && a[left] <= key) {
			left++;
		}
		//此时left,小于key的值,填坑到right坑位
		a[right] = a[left];
	}
	a[left] = key;
	//返回最终填坑位置,再次分为[begin , keyi - 1] keyi [keyi + 1,end]
	return left;
}

3. Front and rear pointer versions

Back and forth pointer version—fast single-pass sorting implementation:

//前后指针版
int PartSort3(int* a, int left, int right) {
	int keyi = left;
	int prev = left, cur = left + 1;
	while (cur <= right) {
		if (a[cur] < a[keyi] && ++prev != cur) {
			Swap(&a[prev], &a[cur]);
		}
		cur++;
	}
	Swap(&a[keyi], &a[prev]);
	return prev;
}

1.3 The specific implementation of hoare fast sorting

When the first row of the above hoare is completed, the key value has reached the correct position of its order. At this time, it is divided into [begin, keyi - 1] keyi [keyi + 1, end], sort the left side of the keyi position again, and then keyi Sort on the right side of the position, and divide it into [begin, keyi - 1] keyi [keyi + 1, end] again, so the idea of ​​divide and conquer is adopted, and recursion is used to realize quick sorting.

void QuickSort(int* a, int begin, int end) {
	assert(a);
	//当区间不存在,或者只有一个时,则不需要再处理
	if (begin >= end) {
		return;
	}
	int left = begin,right = end;
	int key = a[left];
	//坑位 - left 坑位是动态变化
	while (left < right) {
		while (left < right && a[right] >= key) {
			right--;
		}
		//此时将right,大于key的值,填坑到left坑位
		a[left] = a[right];
		while (left < right && a[left] <= key) {
			left++;
		}
		//此时left,小于key的值,填坑到right坑位
		a[right] = a[left];
	}
	a[left] = key;
	QuickSort(a, begin, left - 1);
	QuickSort(a, left + 1, end);
}

1.4 The concrete realization of quick row by excavation method

void QuickSort(int* a, int begin,int end){
	assert(a);
	//当区间不存在,或者只有一个时,则不需要再处理
	if (begin >= end) {
		return;
	}
	int key = a[left];
	//坑位 - left 坑位是动态变化
	while (left < right) {
		while (left < right && a[right] >= key) {
			right--;
		}
		//此时将right,大于key的值,填坑到left坑位
		a[left] = a[right];
		while (left < right && a[left] <= key) {
			left++;
		}
		//此时left,小于key的值,填坑到right坑位
		a[right] = a[left];
	}
	a[left] = key;
	QuickSort(a, begin, key - 1);
	QuickSort(a, key + 1, end);
}

1.5 The specific implementation of the quick sorting of the front and rear pointer versions

void QuickSort(int* a, int begin, int end) {
	assert(a);
	//当区间不存在,或者只有一个时,则不需要再处理
	if (begin >= end) {
		return;
	}
	int left = begin, right = end;
	int keyi = left;
	int prev = left, cur = left + 1;
	while (cur <= right) {
		if (a[cur] < a[keyi] && ++prev != cur) {
			Swap(&a[prev], &a[cur]);
		}
		cur++;
	}
	Swap(&a[keyi], &a[prev]);
	keyi = prev;
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
}

1.6 Optimization of Quick Sort

Optimization 1: Take the middle of three numbers (improve efficiency and prevent ordered sequence stack overflow)

analyze:

  1. If the selected key value is the median each time, the efficiency is higher at this time, and the data will quickly tend to be in order.
  2. If a set of data is completely ordered, but quick sorting still needs to be detected recursively from the first key value, after the detection, there are N-1 data left after detection, and N-2 data are left after detection...final detection When there is one piece of data remaining, recursively return layer by layer. The efficiency is extremely low, and the time complexity is O(N^2).
  3. If the amount of data is too large, layers of recursive detection may cause stack overflow.

Solution:

  1. Randomly select the key, but the small probability of selection is still uncertain
  2. Take the middle of the three numbers, the first one, the middle one, and the last one, choose neither the largest nor the smallest
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 right;
		}
		else {
			return left;
		}
		
	}
	else {//a[left] > a[mid]  a[mid] < a[right]
		if (a[mid] > a[right]) {
			return mid;
		}
		else if (a[left] < a[right]) {
			return left;
		}
		else {
			return right;
		}
	}
}

//前后指针版
int PartSort3(int* a, int left, int right) {
	int mid = GetMidIndex(a, left, right);
	Swap(&a[left], &a[mid]);
	int keyi = left;
	int prev = left, cur = left + 1;
	while (cur <= right) {
		if (a[cur] < a[keyi] && ++prev != cur) {
			Swap(&a[prev], &a[cur]);
		}
		cur++;
	}
	Swap(&a[keyi], &a[prev]);
	return prev;
}

void QuickSort(int* a, int begin,int end){
	assert(a);
	//当区间不存在,或者只有一个时,则不需要再处理
	if (begin >= end) {
		return;
	}
	int keyi = PartSort3(a, begin, end);
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
}

Optimization 2: Optimizing between cells (greatly reducing the number of recursions)

analyze:

When a set of data, keep looking for the key value, divide the left and right intervals, assuming that when the interval division data only has 10 numbers, the recursion is shown in the figure below

 It still needs to recurse more than 10 times, and it can be optimized at this time.

in conclusion:

When recursively dividing the interval, when the interval is relatively small, it is no longer recursively divided to sort the interval. It can be considered to directly process the inter-cells with other sorts.

solution:

Still use three-number optimization, while recursive partitioning is used for insertion sorting between small areas

void InsertSort(int* a, int n) {
	
	for (int i = 0; i < n - 1; i++) {
		int end = i;
		int temp = a[end + 1];
		while (end >= 0) {
			if (temp < a[end]) {
				a[end + 1] = a[end];
				end--;
			}
			else {
				break;
			}
		}
		a[end + 1] = temp;
	}
}

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

//前后指针版
int PartSort3(int* a, int left, int right) {
	int mid = GetMidIndex(a, left, right);
	Swap(&a[left], &a[mid]);
	int keyi = left;
	int prev = left, cur = left + 1;
	while (cur <= right) {
		if (a[cur] < a[keyi] && ++prev != cur) {
			Swap(&a[prev], &a[cur]);
		}
		cur++;
	}
	Swap(&a[keyi], &a[prev]);
	return prev;
}

void QuickSort(int* a, int begin, int end) {
	assert(a);
	//当区间不存在,或者只有一个时,则不需要再处理
	if (begin >= end) {
		return;
	}
	if (end - begin > 10) {
		int keyi = PartSort3(a, begin, end);
		QuickSort(a, begin, keyi - 1);
		QuickSort(a, keyi + 1, end);
	}
	else {
		InsertSort(a + begin, end - begin + 1);
		//对小区间前 n 个数进行排序
	}
}

1.7 Quick sort non-recursive

It is necessary to master the recursive change to non-recursive for the following reasons:

Recursion is a big problem. In extreme scenarios, if the recursion depth is too deep, stack overflow will occur

Change method:

1. Directly change to a loop - such as Fibonacci sequence, merge sort

2. Use the data structure stack to simulate the recursive process

3. Use the data structure queue to simulate the recursive process

3. Other data structures can also be used to simulate the recursive process to achieve non-recursive quick sorting

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

//前后指针版
int PartSort3(int* a, int left, int right) {
	int mid = GetMidIndex(a, left, right);
	Swap(&a[left], &a[mid]);
	int keyi = left;
	int prev = left, cur = left + 1;
	while (cur <= right) {
		if (a[cur] < a[keyi] && ++prev != cur) {
			Swap(&a[prev], &a[cur]);
		}
		cur++;
	}
	Swap(&a[keyi], &a[prev]);
	return prev;
}
void QuickSortNonR(int* a, int begin, int end) {
	assert(a);
	ST st;
	StackInit(&st);
	StackPush(&st, end);
	StackPush(&st, begin);
	while (!StackEmpty(&st)) {
		int left = StackPop(&st);
		int right = StackPop(&st);
		if (right - left < 10) {
			InsertSort(a + left, right - left + 1);
			continue;
		}
		int keyi = PartSort3(a, left, right);
		if (keyi + 1 < right) {
			StackPush(&st, right);
			StackPush(&st, keyi + 1);
		}
		if (left < keyi - 1) {
			StackPush(&st, keyi - 1);
			StackPush(&st, left);
		}
	}
	StackDestory(&st);
}

1.8 Summary of Quick Sort Features

1. The overall comprehensive performance and usage scenarios of quick sort are relatively good, so it dares to be called quick sort

2. Time complexity: O(N*logN)

3. Space complexity: O(logN)

4. Stability: Unstable

Guess you like

Origin blog.csdn.net/IfYouHave/article/details/129653050