6、快速排序

快速排序是一种二叉树结构的交换排序方法。
思想:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

递归方法

 

 1、hoare版本

 思想:取数组第一个元素为key,right指针从最右端开始向左移动(必须是与key反方向指针先走),right寻找比key小的数(升序),如果找到比key小的数,停止移动。

之后left从最左端开始向右移动,left寻找比key大的数,找到后停止,right的数字和left的数字进行交换,right继续寻找...

直到left与right相遇,相遇位置的数字与key交换,这样以后相遇位置的数为key,左边是比key小的数,右边是比key大的数

然后将key左边和右边再分别进行该操作递归,直到只剩一个数字。

 纵向展开图:

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[right], &a[left]);
	}
	swap(&a[left], &a[keyi]);
	return left;
}
void QuickSort(int* a,int left,int right)     //排序递归
{
	if (left >= right)
	{
		return;
	}
	int new = PartSort1(a, left, right);
	QuickSort(a, 0, left);
	QuickSort(a, left+1, right);
}

2、挖坑版本

思想:将数组第一个元素拷贝,命名key

现在可视数组第一个元素是个坑,下标为hole=0(因为第一个元素已经拷贝,原数据可被覆盖,不会损失数据)

right指针从最右端向左移动,找到比key小的数,将该数放入坑位下标为hole的位置,并更新坑位,hole=right

之后left指针从最左端向右移动,找到比key大的数将该数放入坑位下标为hole的位置,并更新坑位,hole=left

直到left与right相遇,此时坑位也在它们相遇的地方,将key数据放入坑位,这样以后相遇位置的数为key,左边是比key小的数,右边是比key大的数

然后将key左边和右边再分别进行该操作递归,直到只剩一个数字。

 过程图:

 

 

 

 

 

 

 

 

 

int PartSort2(int* a, int left, int right)
{
	int key = a[left];
	int hole = 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;
}
void QuickSort(int* a,int left,int right)
{
	if (left >= right)
	{
		return;
	}
	int new = PartSort2(a, left, right);
	QuickSort(a, 0, left);
	QuickSort(a, left+1, right);
}

 3、前后指针法

 思想:初识位置key,prev在数组最左端,cur为prev+1的位置

之后cur和key的数进行比较,如果cur小于key,则prev++后与cur交换数据

不断重复该过程,直到cur>=n后key和prev交换数据

这样以后相遇位置的数为key,左边是比key小的数,右边是比key大的数

然后将key左边和右边再分别进行该操作递归,直到只剩一个数字。

 过程图:

 前期prev++==cur,等于自己和自己交换数据,没有改变数组

 

 

 

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++;
			int tmp = a[prev];
			a[prev] = a[cur];
			a[cur] = tmp;
		}
			cur++;
	}
	int tmp = a[left];
	a[left] = a[prev];
	a[prev] = tmp;
	return prev;
}
void QuickSort(int* a,int left,int right)
{
	if (left >= right)
	{
		return;
	}
	int new = PartSort3(a, left, right);
	QuickSort(a, 0, left);
	QuickSort(a, left+1, right);
}

 优化——三数取中(中间)

 当排升序时,原数组为降序,快排的速度会非常慢,上面的三种版本第一趟最终的结果都是:左边是比key小的数,右边是比key大的数,而降序直接导致key在边缘位置,每趟只会向内挪动一个位置,时间复杂度直接是O(N^2),和冒泡排序一样

所以我们可以控制取key的大小,在最左端、最右端和中间的数值进行比较,取中间大的数为key

 

int GetMidIndex(int* a, int left, int right)
{
	int mid = (right + left) / 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 right;
		else
			return left;
	}
}
int PartSort1(int* a, int left, int right)
{
	int midi = GetMidIndex(a, left, right);
	swap(&a[left], &a[midi]);                    //最左端的值和三数取中的值交换后再排序
	int keyi = left;
	while(left<right)
	{
		while (left < right && a[right] >= a[keyi])
		{
			right--;
		}
		while (left < right && a[left] <= a[keyi])
		{
			left++;
		}
		swap(&a[right], &a[left]);
	}
	swap(&a[left], &a[keyi]);
	return left;
}
void QuickSort(int* a,int left,int right)
{
	if (left >= right)
	{
		return;
	}
	int new = PartSort1(a, left, right);
	QuickSort(a, 0, left);
	QuickSort(a, left+1, right);
}

优化——三数取中(随机) 

传统的三数取中比较数值已被定死,容易被针对,所以三数中,左右数不变,把中间的数的取值给为随机下标取值可以解决该问题

int GetMidIndex(int* a, int left, int right)
{
	//int mid = (right + left) / 2;
	int mid = left + rand() % (right - left);
	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 right;
		else
			return left;
	}
}

 优化——三路划分

当待排序数组全是一个值时(如,全是2),效率也会非常慢,之前三个思路都是寻找比key大的和小的值,二路划分,在这种情况会十分棘手,未解决这一问题,我们可以使用三路划分的思路去解决

二路划分

 三路划分

 

思想(假设数组名为a):起始位置(下标):left最左端,right最右端,cur=left+1(以key=a[left]为例)

当a[cur]<key:交换cur和left的值,cur++,left++;

当a[cur]>key:交换cur和right的值,right--;

当a[cur]==key:cur++;

直到cur>right为止为一趟

 

 

 

 

 

 

 

 

 

[left,right]为等于key的数

 

void QuickSort_ThreeWay(int* a, int begin, int end)
{
	if (begin >= end)
		return;
	int left = begin;
	int right = end;
	int cur = left + 1;
	int key = a[begin];
	while (cur <= right)
	{
		if (a[cur] > key)
		{
			swap(&a[cur], &a[right]);
			right--;
		}
		else if (a[cur] < key)
		{
			swap(&a[cur], &a[left]);
			cur++;
			left++;
		}
		else
		{
			cur++;
		}
	}
	QuickSort_ThreeWay(a, begin, left-1);
	QuickSort_ThreeWay(a, right + 1, end);
}

 

非递归方法

 用非递归可以用栈(先进后出)去实现,传入开始下标和结束坐标,然后写一个函数去接受它们去排序

 一周期:push 0和9,pop 0和9 同时push 6和9 0和4,pop0和4 同时push 3和4 0和1,pop 0和1

注意:栈push是从栈底开始,pop是从栈顶开始

 首先,先写一个栈

typedef int STDataType;
typedef struct Stack
{
	STDataType* _a;
	int _top;		// 栈顶
	int _capacity;  // 容量 
}Stack;
// 初始化栈 
void StackInit(Stack* ps)
{
	ps->_a = NULL;
	ps->_capacity = 0;
	ps->_top = 0;
}
// 入栈 
void StackPush(Stack* ps, STDataType data)
{
	int tmp = ps->_capacity == 0 ? 4 : ps->_capacity * 2;
	if (ps->_top == ps->_capacity)
	{
		STDataType* p = (STDataType*)realloc(ps->_a, sizeof(STDataType)*tmp);
		ps->_a = p;
		ps->_capacity = tmp;
	}
	ps->_a[ps->_top] = data;
	ps->_top++;
}
// 出栈 
void StackPop(Stack* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));
	ps->_top--;
}
// 获取栈顶元素 
STDataType StackTop(Stack* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));
	return ps->_a[ps->_top-1];
}
// 获取栈中有效元素个数 
int StackSize(Stack* ps)
{
	assert(ps);
	return ps->_top;
}
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0 
int StackEmpty(Stack* ps)
{
	return ps->_top == 0;
}
// 销毁栈 
void StackDestroy(Stack* ps)
{
	assert(ps);
	free(ps->_a);
	ps->_a = NULL;
	ps->_capacity = 0;
	ps->_top = 0;
}
void QuickSortNonR(int* a, int begin, int end)
{
	Stack st;
	StackInit(&st);
	StackPush(&st, end);                //入栈原数组的尾下标
	StackPush(&st, begin);              //入栈原数组的头下标

	while (!StackEmpty(&st))            //栈不为空的话,出栈 入栈
	{
		STDataType left = StackTop(&st);    //left相当于begin
		StackPop(&st);
		STDataType right = StackTop(&st);   //right相当于end
		StackPop(&st);

		int keyi = PartSort3(a,left,right); //在该区间段进行排序(任取上边三种排序思路)
		if(keyi + 1 < right)                //压栈right
		{
			StackPush(&st, right);
			StackPush(&st, keyi + 1);
		}
		if (keyi - 1 > left)                //压栈left
		{
			StackPush(&st, keyi - 1);
			StackPush(&st, left);
		}

	}

	StackDestroy(&st);
}

时间复杂度:O(nlogn)

空间复杂度:O(logn) 

稳定性:不稳定

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值