【C】排序算法之快速排序

1、算法基本思想

  找一个哨兵数,让小的到左边,大的到右边(不断执行),直到区间只剩下0个或者1个元素,排序完成。

2、源代码

交换数据

/*
*	函数名称:Swap
*
*	函数功能:交换数据
*
*	入口参数:a, b
*
*	出口参数:void
*
*	返回类型:void
*/

void Swap(int * a, int * b)
{
	int tmp = 0;
	
	assert(NULL != a);
	assert(NULL != b);

	tmp = *a;
	*a = *b;
	*b = tmp;

	return;
}

a.左右下标寻找法

  设置最右边的数字是哨兵数,如果最左边的数字小于等于哨兵数,那么左边的指针向右走,注意:是左边的数先走。左边的指针走完后,右边的指针开始向左边走,(假如左边的数字大于哨兵数,那么左边的指针不动。小于等于哨兵数后,向右走)。右边的数如果大于等于哨兵数,那么就向左走,直到找到一个小于哨兵数的数字,然后交换此时右边指向的数字和左边的数字,此时的情况应该是指针指向的左边的数字大于哨兵数,指针指向的右边的数字小于哨兵数。意思就是要保证左右两边指向的数字指向相同(同时大于或者同时小于或者同时等于),不能出现异号的情况,一旦出现就交换数字。当最后区间内只剩下一个数字时,交换哨兵数和该位置的数字,排序完成。

/*
*	函数名称:CutPart
*
*	函数功能:快排之切分(左右下标寻找法)
*
*	入口参数:array, left, right
*
*	出口参数:begin
*
*	返回类型:int
*/

int CutPart(int array[], int left, int right)
{
	int key = array[right];
	int begin = left; // 都是下标
	int end = right; // 为什么不是right - 1?   1 2 3 4 5出现问题 begin走到4的时候停下来,因为begin=end,交换4和5

	//从两头往中间扫描
	while (begin < end)
	{
		// 为什么不是小于? 5 5 5 5 5 时begin需要往右边走,最后走到begin=end时就停下来
		while ((begin < end) && (array[begin] <= key))
		{
			begin++;
		}

		while ((begin < end) && (array[end] >= key))
		{
			end--;
		}

		//begin指向的左边的数字大于哨兵数,end指向的右边的数字小于哨兵数
		if (begin < end)
		{
			Swap(array + begin, array + end);
		}
	}

	// begin只会有两个位置停下来 可能1:array[begin] > key  可能2:begin就是right
	// 错误1 Swap(array + begin, key);  key是局部变量 错误2 Swap(array + begin, array + end); end会发生变化
    // 最后交换哨兵数和 begin等于end时指向的数字即可
	Swap(array + begin, array + right);

	return begin; // 返回的begin就是之前设置的哨兵数的下标
}

b.挖坑法

  设置最右边的数字是哨兵数,然后从左边开始,如果左边的数字大于等于哨兵数,那么就覆盖掉哨兵数(或者说填上哨兵的坑)。接着end向左走,如果此时end指向的数字小于哨兵数,继续覆盖掉begin指向数字,直到begin和end指向同一个数字的时候,之前保存的哨兵数覆盖掉这个位置,排序完成。

/*
*	函数名称:CutPart
*
*	函数功能:快排之切分(挖坑法)
*
*	入口参数:array, left, right
*
*	出口参数:begin
*
*	返回类型:int
*/

int CutPart(int array[], int left, int right)
{
	int key = array[right];
	int begin = left;
	int end = right;

	while (begin < end)
	{
		while (begin < end && array[begin] <= key)
		{
			begin++;
		}

		array[end] = array[begin];		

		while (begin < end && array[end] >= key)
		{
			end--;
		}

		array[begin] = array[end];
	}

	array[begin] = key;

	return begin;
}

c.前后下标寻找法

  设置最右边的数字是哨兵数,一开始cur和firstBigger(第一个比哨兵数大的数,这意味着它前面的数都比哨兵数小,cur和firstBigger之间的数字都比哨兵数大)都指向左边,cur指向的数字如果比哨兵数大,那么cur向右走,当cur指向的数字小于哨兵数时,这时候交换cur指向的数字和firstBigger指向的数字,交换后,firstBigger向右走,cur继续向右走,重复上述过程,最后当cur走到哨兵数时,交换cur指向的数字(哨兵数)和firstBigger指向的数字。

/*
*	函数名称:CutPart
*
*	函数功能:快排之切分(前后下标法)
*
*	入口参数:array, left, right
*
*	出口参数:begin
*
*	返回类型:int
*/

int CutPart(int array[], int left, int right)
{
	int key = array[right];
	int cur = left;
	int firstBigger = left;

	while (cur < right)
	{
		if (array[cur] < key)
		{
			Swap(array + firstBigger, array + cur);
			firstBigger++;
		}
		else
		{
			;
		}

		cur++;
	}

	Swap(array + firstBigger, array + right);

	return firstBigger;
}

优化哨兵数

  三选一法:按照左中右下标找到对应的数字,然后比较大小找到大小在中间的数,这时候把这个数字和最右边的数字交换即可。

/*
*	函数名称:GetMiddle
*
*	函数功能:求中间大小的数
*
*	入口参数:array, left, right
*
*	出口参数:left or mid or right
*
*	返回类型:int
*/

int GetMiddle(int array[], int left, int right)
{
	int mid = (left & right) + ((left ^ right) >> 1);

	if (array[left] < array[mid]) 
	{
		if (array[mid] < array[right]) 
		{
			// left < mid < right
			return mid;
		}
		else if (array[left] > array[right]) 
			{
				// right < left < mid
				return left;
			}
			else 
			{
				// left < right < mid;
				return right;
			}
	}
	else 
	{
		if (array[mid] < array[right]) 
		{
			// mid < right < left
			return right;
		}
		else if (array[left] < array[right]) 
			{
				// mid < left < right
				return left;
			}
			else
			{
				// right < mid < left
				return mid;
			}
	}
}

快排之递归

/*
*	函数名称:QuickSortRecursion
*
*	函数功能:分组(递归)
*
*	入口参数:array, left, right
*
*	出口参数:void
*
*	返回类型:void
*/

void QuickSortRecursion(int array[], int left, int right)
{
	int div = 0;
	int pivot = GetMiddle(array, left, right);

	Swap(array + pivot, array + right);

	//left == right区间[left, right]剩下1个,left > right 区间剩下0个(1 2 3 4 5)
	if (left >= right)
	{
		return;
	}
	else
	{
		div = CutPart(array, left, right);
		
		QuickSortRecursion(array, left, div - 1);
		QuickSortRecursion(array, div + 1, right);
	}

	return;
}

/*
*	函数名称:QuickSort
*
*	函数功能:快速排序
*
*	入口参数:array, size
*
*	出口参数:void
*
*	返回类型:void
*/

void QuickSort(int array[], int size)
{
	//0代表最小下标值,size - 1代表最大下标值
	QuickSortRecursion(array, 0, size - 1);

	return;
}

快排之迭代

/*
*	函数名称:QuickSortLoop
*
*	函数功能:分组(迭代)  使用栈来实现
*
*	入口参数:array, size
*
*	出口参数:void
*
*	返回类型:void
*/

void QuickSortLoop(int array[], int size)
{
	int stack[100];
	int top = 0;
	int left = 0;
	int right = 0;
	int div = 0;

	// 先入左边界,再入右边界
	stack[top++] = 0;
	stack[top++] = size - 1;

	while (top > 0) 
	{
		// 先取右,再取左
		right = stack[--top];
		left = stack[--top];

		if (left >= right) 
		{
			continue;
		}
		else
		{
			;
		}

		div = CutPart(array, left, right);
		// 先入左边区间
		stack[top++] = left;
		stack[top++] = div - 1;

		// 再入右边区间
		stack[top++] = div + 1;
		stack[top++] = right;
	}

	return;
}

main

#define _CRT_SECURE_NO_WARNINGS 1

/*
* Copyright (c) 2018, code farmer from sust
* All rights reserved.
*
* 文件名称:QuickSort.c
* 功能:快速排序
*
* 当前版本:V1.0
* 作者:sustzc
* 完成日期:2018年7月10日16:32:46
*/

# include <stdio.h>
# include <assert.h>

/*
*	函数名称:main
*
*	函数功能:测试主程序
*
*	入口参数:void
*
*	出口参数:0
*
*	返回类型:int 
*/

int main(void)
{
	int i = 0;
	int numbers[] = {3, 1, 6, 4, 5, 2};
	int len = sizeof(numbers) / sizeof(int);

	printf("排序之前:\n");

	for (i = 0; i < len; i++)
	{
		printf("%d ", numbers[i]);
	}

	QuickSort(numbers, len);
	//QuickSortLoop(numbers, len);

	printf("\n排序之后:\n");

	for (i = 0; i < len; i++)
	{
		printf("%d ", numbers[i]);
	}

	printf("\n");

	return 0;
}

3、输出结果


猜你喜欢

转载自blog.csdn.net/sustzc/article/details/81055752