[Data structure] Exchange sort (details)


1. Bubble sort

  1. Idea
    Sort in ascending order: compare the two elements before and after each pass, exchange according to "small in front and large in back", and put the largest element at the end.
    Sort in descending order: compare the two elements before and after each pass, exchange according to "big front and small back", and put the smallest element at the end.
  2. Example (take ascending order as an example)
    insert image description here
  3. Code
void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
void BubbleSort(int* a, int n)
{
	int flag = 1;
	for (int j = 0; j < n-1; j++)
	{
		//一趟排序
		for (int i = 0; i < n - 1 - j; i++)
		{
			if (a[i] > a[i + 1])
			{
				flag = 0;
				Swap(&a[i], &a[i + 1]);
			}
		}
		if (flag == 1)//如果一趟排序下来,发现根本没发生交换,说明数据本身有序,直接跳出
		{
			break;
		}
	}
}
  1. Algorithm analysis
    时间复杂度
    In the best case, it is ordered, and the time complexity is O(N), and in the worst case, it is in reverse order, and the time complexity is O(N^2).
    空间复杂度
    Without additional space, the space complexity is O(1).
    稳定性
    When sorting in ascending order, the same is not exchanged, and only the elements in the front that are greater than the latter are exchanged. is a stable sort.

2. Quick Sort

2.1 hall version

  1. The idea is
    to find a key value (key) in the data, such as finding the first element on the left, and then put it in the correct position in the data through some operations (taking ascending order as an example, put the element smaller than the key on the left, Place elements greater than key on the right). In this way, the position of the key after sorting is determined. Then take the key as the boundary and follow the above steps to find the correct position of other data after sorting.

  2. example
    insert image description here

insert image description here

  1. Code
//快速排序
void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}
	int keyi = begin ;//keyi是关键值的下标
	int left = begin ;
	int right = end;
	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]);
	QuickSort(a, begin, left - 1);
	QuickSort(a, left + 1, end);
}
  1. In the best case of algorithm analysis
    时间复杂度
    , the key is in the middle of the data, and the lengths of the left and right sequences are equal. In this way, if there are N data, there will be logN layers. The first layer needs to traverse N-1, and the second layer needs to traverse N-3 , so the time complexity of single-pass sorting is O(N), plus a total of logN layers, the time complexity is O(NlogN).
    In the worst case, the data itself is ordered (sequential or reversed). The time complexity of single-pass sorting is O(N), there are N layers in total, and the time complexity is O(N^2).
    空间复杂度
    O(logN) in the best case, O(N) in the worst case.
    稳定性
    There is a number equal to the key in the data, but the key may be exchanged with the elements behind it, so it is an unstable sort.

  2. Optimization
    What to do if there are sequential and reverse cases? Or use the quick sort method. The main problem is the choice of key. If the value of the key is the middle value of the data, the closer to the center, the more the traversal is like a binary tree, and the depth is more like logN, then the efficiency of quick sorting is the highest. So how to choose a good key?
    There are two methods:
    method one: randomly select keyi (subscript of key)

void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}

	//修改
	int keyi = begin;
	int randi = begin + rand() % (end - begin);
	Swap(&a[randi], &a[keyi]);
	
	int left = begin;
	int right = end;
	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]);
	QuickSort(a, begin, left - 1);
	QuickSort(a, left + 1, end);
}

Method 2: Take the middle of three numbers

void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}

	//修改
	int keyi = begin;
	int mid = GetMidNumi(a, begin, end);
	Swap(&a[mid], &a[keyi]);

	int left = begin;
	int right = end;


	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]);
	QuickSort(a, begin, left - 1);
	QuickSort(a, left + 1, end);
}

2.2 Digging method

  1. The idea
    is to put the key out first, and its position becomes a pit. Look for the small one on the right, put the small one into the pit when you find it, look for it on the left, and put the big one into the pit after you find it. Then repeat the above operation. Eventually left and right meet, or meet in a pit (because at least one of them is a pit).
  2. example
    insert image description here
  3. Code
void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}
	int mid = GetMidNumi(a, begin, end);
	Swap(&a[begin], &a[mid]);
	int key = a[begin];
	int hole = begin;

	int left = begin;
	int right = end;
	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;
	QuickSort(a, begin, hole - 1);
	QuickSort(a, hole + 1, end);
}

2.3 Back and forth pointer method (optimal)

  1. Thought
    There are two pointers, prev and cur. When cur finds a value smaller than key, ++prev, the value of cur and prev is exchanged, ++cur; cur finds a value larger than key, ++cur.

  2. example
    insert image description here

  3. Code

void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}
	int mid = GetMidNumi(a, begin, end);
	Swap(&a[mid], &a[begin]);
	int key = a[begin];

	int prev = begin;
	int cur = prev + 1;
	while (cur <= end)
	{
		if (a[cur] < key && ++prev != cur)
		{
			Swap(&a[cur], &a[prev]);
		}
		++cur;
	}
	Swap(&a[begin], &a[prev]);
	QuickSort(a, begin, prev - 1);
	QuickSort(a, prev + 1, end);
}

2.4 Inter-cell optimization

When the interval is smaller than a certain limit, recursion is no longer used, and direct insertion is used. If the last layer is not recursive, the number of recursions can be reduced by half (assuming a total of h layers of recursion, the last layer needs to be recursed 2^(h-1) times, and the total number of recursions is 2^h-1, so the last layer half of the number of recursions).
When the interval is small, recursion is no longer used, and direct insertion is used instead. This is inter-small optimization.
代码实现

void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}
	if ((end - begin ) + 1 > 10)//意思是区间元素个数大于10就递归,小于10就直接插入排序
	{
		int mid = GetMidNumi(a, begin, end);
		Swap(&a[mid], &a[begin]);
		int key = a[begin];

		int prev = begin;
		int cur = prev + 1;
		while (cur <= end)
		{
			if (a[cur] < key && ++prev != cur)
			{
				Swap(&a[cur], &a[prev]);
			}
			++cur;
		}
		Swap(&a[begin], &a[prev]);
		QuickSort(a, begin, prev - 1);
		QuickSort(a, prev + 1, end);
	}
	else
	{
		InsertSort(a + begin, end - begin + 1);
	}
}

2.5 Non-recursive quick sort

When the recursion level is too deep, the stack will overflow. At this time, you have to change recursive to non-recursive. There are generally two ways to change recursion to non-recursion: one is to directly change to a loop; the other is to indirectly change to a loop (using stack assistance). Quick sort is changed to non-recursive with stack assistance.

  1. The idea is
    to first put the two boundary points of the data into the stack, and then take out two boundary points (a section) from the stack each time, and then obtain the subscript of the key by single-pass sorting. Re-stack. When the interval has only one value or does not exist, it does not need to be pushed into the stack.

  2. example
    insert image description here

  3. Code

int QSort(int* a, int begin, int end)
{
	int mid = GetMidNumi(a, begin, end);
	int keyi = begin;
	Swap(&a[begin], &a[mid]);
	int prev = begin;
	int cur = prev + 1;
	while (cur <= end)
	{
		if (a[cur] < a[keyi] && ++prev != cur)
		{
			Swap(&a[cur], &a[prev]);
		}
		++cur;
	}
	Swap(&a[keyi], &a[prev]);
	return prev;
}
void QuickSortNonR(int* a, int begin, int end)
{
	ST st;
	STInit(&st);
	//将区间入栈
	STPush(&st, end);//注意入栈的顺序,这里统一先用右边界点入栈
	STPush(&st, begin);//出栈时统一先用左边界点接收
	while (!STEmpty(&st))
	{
		int left = STTop(&st);
		STPop(&st);
		int right = STTop(&st);
		STPop(&st);
		//单趟排序
		int keyi = QSort(a, left, right);//将前后指针法得到key封装成一个函数
		//现在有两段子区间,[left,keyi-1][keyi+1,right]
		//判断是否达到入栈条件:子区间元素个数>1
		if (keyi + 1 < right)
		{
			STPush(&st, right);
			STPush(&st, keyi+1);
		}
		if (left < keyi - 1)
		{
			STPush(&st, keyi - 1);
			STPush(&st, left);
		}
	}
	STDestroy(&st);
}

Guess you like

Origin blog.csdn.net/Zhuang_N/article/details/130542546