排序(三、快速排序)

3.1快速排序的思想基本框架

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:

任取待排序元素序列中 的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

// 假设按照升序对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);
}

上述为快速排序递归实现的主框架,发现与二叉树前序遍历规则非常像,在写递归框架时可想想二叉 树前序遍历规则即可快速写出来,后序只需分析如何按照基准值来对区间中数据进行划分的方式即可。

3.2快速排序的三种实现方法

区间按照基准值划分为左右两半部分的常见方式有:

1. hoare版本

文字解释


1、选出一个key,一般是最左边或是最右边的。
2、定义一个begin和一个end,begin从左向右走,end从右向左走。(需要注意的是:若选择最左边的数据作为key,则需要end先走;若选择最右边的数据作为key,则需要bengin先走)。
3、在走的过程中,若end遇到小于key的数,则停下,begin开始走,直到begin遇到一个大于key的数时,将begin和right的内容交换,end再次开始走,如此进行下去,直到begin和end最终相遇,此时将相遇点的内容与key交换即可。(选取最左边的值作为key)
4.此时key的左边都是小于key的数,key的右边都是大于key的数
5.将key的左序列和右序列再次进行这种单趟排序,如此反复操作下去,直到左右序列只有一个数据,或是左右序列不存在时,便停止操作,此时此部分已有

图解

 

代码实现 

void Swap(int* a, int* b)
{
	int c = *a;
	*a = *b;
	*b = c;
}
void Quick_Sort_Hoare(int* arr, int left, int right)
{
	if (left >= right)
{
	return;
}
	int key = arr[left];
	int a = left;
	int b = right;
	while (left < right)
	{
		while (arr[right] >= key&&left<right)
		{
			right--;
		}
		while (arr[left] <= key && left < right)
		{
			left++;
		}
		Swap(&arr[left], &arr[right]);
	}
	Swap(&arr[right], &arr[a]);
		Quick_Sort_Hoare(arr, right + 1, b);
		Quick_Sort_Hoare(arr, a, right - 1);
}

2.挖坑法

文字解释

1.选出一个数据(一般是最左边或是最右边的)存放在key变量中,在该数据位置形成一个坑
2、还是定义一个L和一个R,L从左向右走,R从右向左走。(若在最左边挖坑,则需要R先走;若在最右边挖坑,则需要L先走

图解

代码实现

 

void Swap(int* a, int* b)
{
	int c = *a;
	*a = *b;
	*b = c;
}
void Quick_Sort_Hole(int* arr, int left, int right)
{
	if (left >= right)
	{
		return;
	}
	int key = arr[left];
	while (left < right)
	{
		while (arr[right] >= key && left < right)
		{
			right--;
		}
		arr[hole] = arr[right];
		hole = right;
		while (arr[left] <= key && left < right)
		{
			left++;
		}
		arr[hole] = arr[left];
		hole = left;
	}
	arr[hole] = arr[right];
		Quick_Sort_Hole(arr, right + 1, b);
		Quick_Sort_Hole(arr, a, right - 1);
}

3 .前后指针法

文字解释

1、选出一个key,一般是最左边或是最右边的。
2、起始时,prev指针指向序列开头,cur指针指向prev+1。
3、若cur指向的内容小于key,则prev先向后移动一位,然后交换prev和cur指针指向的内容,然后cur指针++;若cur指向的内容大于key,则cur指针直接++。如此进行下去,直到cur到达end位置,此时将key和++prev指针指向的内容交换即可。

图解

代码实现 

void Swap(int* a, int* b)
{
	int c = *a;
	*a = *b;
	*b = c;
}
void Quick_Sort_Pointer(int* arr, int left,int right)
{
	if (left >= right)
{
	return;
}
	int cur = left + 1;
	int prev = left;
	int key = arr[left];
	while (cur <= right)
	{
		if (arr[cur] < key && ++prev != cur)
			Swap(&arr[cur], &arr[prev]);
		cur++;
	}
	Swap(&arr[prev],&arr[left]);
	Quick_Sort_Pointer(arr, prev + 1, right);
	Quick_Sort_Pointer(arr, left, prev - 1);
}

3.3快速排序优化

1. 选key

1.1三数取中法选key

由于快速排序选的key一般是最左边或者最右边,所以数据越有序效率就越低,还可能由于数据有序,不断递归,造成栈溢出

所以解决方法就是:

选取最左边、中间、最右边的数据,判断哪个数字不是最大也不是最小,然后与key下标所在的数据互换。

代码实现:

void Swap(int* a, int* b)
{
	int c = *a;
	*a = *b;
	*b = c;
}
int GetMidNumi(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 left;
		}
		else
		{
			return right;
		}
	}
}
void Quick_Sort_Hoare(int* arr, int left, int right)
{
	if (left >= right)
{
	return;
}
	int midi = GetMidNumi(arr, left, right);//优化方法:三数取中
	if (midi != left)
		Swap(&arr[midi], &arr[left]);
	int key = arr[left];
	int a = left;
	int b = right;
	while (left < right)
	{
		while (arr[right] >= key&&left<right)
		{
			right--;
		}
		while (arr[left] <= key && left < right)
		{
			left++;
		}
		Swap(&arr[left], &arr[right]);
	}
	Swap(&arr[right], &arr[a]);
		Quick_Sort_Hoare(arr, right + 1, b);
		Quick_Sort_Hoare(arr, a, right - 1);
}

2.随机数法选key 

void Swap(int* a, int* b)
{
	int c = *a;
	*a = *b;
	*b = c;
}
int GetMidNumi(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 left;
		}
		else
		{
			return right;
		}
	}
}
void Quick_Sort_Hoare(int* arr, int left, int right)
{
	if (left >= right)
{
	return;
}
	int  ramd = left + rand() % (right - left);/*取余数来固定随机数范围*/
/*优化方法:使最左边的数为范围内的随机数,这样就不怕越有序效率越低*/
	Swap(&arr[left], &arr[ramd]);
	int key = arr[left];
	int a = left;
	int b = right;
	while (left < right)
	{
		while (arr[right] >= key&&left<right)
		{
			right--;
		}
		while (arr[left] <= key && left < right)
		{
			left++;
		}
		Swap(&arr[left], &arr[right]);
	}
	Swap(&arr[right], &arr[a]);
		Quick_Sort_Hoare(arr, right + 1, b);
		Quick_Sort_Hoare(arr, a, right - 1);
}

 

2. 递归到小的子区间时,可以考虑使用插入排序 

 子区间小的时候直接用插入排序,能减少递归次数,可以减少栈的开辟空间避免栈溢出而且效率会更高

根据大量数据统计,区间在16的时候直接插入排序效率会更高一些。

void Swap(int* a, int* b)
{
	int c = *a;
	*a = *b;
	*b = c;
}
int GetMidNumi(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 left;
		}
		else
		{
			return right;
		}
	}
}
void Quick_Sort_Hoare(int* arr, int left, int right)
{
	if (right - left + 1 < 16)/*区间优化:区间小于十六的直接排序,少了四层递归*/
	{
		Insert_Sort(arr + left, right - left + 1);
		return;
	}
	int  ramd = left + rand() % (right - left);/*取余数来固定随机数范围*/
/*优化方法:使最左边的数为范围内的随机数,这样就不怕越有序效率越低*/
	Swap(&arr[left], &arr[ramd]);
	int key = arr[left];
	int a = left;
	int b = right;
	while (left < right)
	{
		while (arr[right] >= key&&left<right)
		{
			right--;
		}
		while (arr[left] <= key && left < right)
		{
			left++;
		}
		Swap(&arr[left], &arr[right]);
	}
	Swap(&arr[right], &arr[a]);
		Quick_Sort_Hoare(arr, right + 1, b);
		Quick_Sort_Hoare(arr, a, right - 1);
}

3.4快速排序的非递归实现

1.目的

递归的快排固然好,但是有时候递归的深度太深会造成栈溢出,这时候就要用非递归的方法

2.实现思想

1、栈里面取一段区间,单趟排序

2、单趟分割子区间入栈

3、子区间只有一个值或者不存在就不入栈

3.实现代码

typedef int*  STDataType;
typedef struct Stack
{
	STDataType* a;
	int top;
	int capacity;
}ST;
void STInit(ST* ps)
{
	assert(ps);

	ps->a = (STDataType*)malloc(sizeof(STDataType) * 4);
	if (ps->a == NULL)
	{
		perror("malloc fail");
		return;
	}

	ps->capacity = 4;
	ps->top = 0;   // top是栈顶元素的下一个位置

	//ps->top = -1;   // top是栈顶元素位置
}

void STDestroy(ST* ps)
{
	assert(ps);

	free(ps->a);
	ps->a = NULL;
	ps->top = 0;
	ps->capacity = 0;
}

void STPush(ST* ps, STDataType x)
{
	assert(ps);

	if (ps->top == ps->capacity)
	{
		STDataType* tmp = (STDataType*)realloc(ps->a,
			sizeof(STDataType) * ps->capacity * 2);
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}

		ps->a = tmp;
		ps->capacity *= 2;
	}

	ps->a[ps->top] = x;
	ps->top++;
}

void STPop(ST* ps)
{
	assert(ps);
	assert(!STEmpty(ps));

	ps->top--;
}

int STSize(ST* ps)
{
	assert(ps);

	return ps->top;
}

int STEmpty(ST* ps)
{
	assert(ps);
	if (ps->top  == 0)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}

STDataType STTop(ST* ps)
{
	assert(ps);
	assert(!STEmpty(ps));

	return ps->a[ps->top - 1];
}
void Swap(int* a, int* b)
{
	int c = *a;
	*a = *b;
	*b = c;
}
int Quick_Sort_Hoare_For_Stack_Sort(int* arr, int left, int right)
/*返回值为int是为了得到key*/
{
	//if (right - left + 1 < 16)/*区间优化:区间小于十六的直接排序,少了四层递归*/
	//{
	//	Insert_Sort(arr + left, right - left + 1);
	//	return;
	//}
		if (left >= right)
	{
		return;
	}
		//int  ramd = left + rand() % (right - left);/*优化方法:使最左边的数为范围内的随机数,这样就不怕越有序效率越低*/
		//Swap(&arr[left], &arr[ramd]);
	int midi = GetMidNumi(arr, left, right);//优化方法:三数取中
	if (midi != left)
		Swap(&arr[midi], &arr[left]);
	int key = arr[left];
	int a = left;
	int b = right;
	while (left < right)
	{
		while (arr[right] >= key && left < right)
		{
			right--;
		}
		while (arr[left] <= key && left < right)
		{
			left++;
		}
		Swap(&arr[left], &arr[right]);
	}
	Swap(&arr[right], &arr[a]);
	return right;
}
void Quick_Stack_Sort(int* arr, int left, int right)
{
	ST a;
	STInit(&a);
	STPush(&a, right);
	STPush(&a, left);
	while (!STEmpty(&a))
	{
		int begin = STTop(&a);
		STPop(&a);
		int end = STTop(&a);
		STPop(&a);
		int key=Quick_Sort_Hoare_For_Stack_Sort(arr, begin, end);
		if (key +1<end )
		{
			STPush(&a, end);
			STPush(&a, key + 1);
		}
		if (key - 1 > begin)
		{
			STPush(&a, key - 1);
			STPush(&a, begin);
		}
	}
	STDestroy(&a);
}

3.5快速排序时间复杂度

粗略计算 

 由于一趟快排需要比较n-1次

第一层粗略地计算,一趟快排比较了n次

第二次有两颗子树,每一棵子树有n/2个元素,所以一颗子树一趟快排比较了n/2次,两颗加起来总的比较次数为n次

剩下的同理

.......

深度为lg n   每层为n

时间复杂度为O(Nlg N)

仔细分析计算

 

 3.6快速排序的特性总结:

1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序

2. 时间复杂度 

 最好:O(N*logN)

最坏:O(N*N)

3. 空间复杂度:O(logN)

4. 稳定性:不稳定

  • 25
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值