快速排序的三种实现方法及非递归实现快排

递归版快速排序

     Horae法

     挖坑法

     前后指针法

非递归实现快速排序


快速排序有递归和非递归的2种实现方法,一般我们说的快排都是指递归实现的,而快速排序在对它的左右区间进行调整时也有3种方法:①Horae法  ②挖坑法  ③前后指针法 。对于左右区间的调整就是通过选出一个基准值 (key) ,然后将key的左右两侧分成小于key的和大于key的。在快速排序中对于左右区间的划分一直都是快排的核心,接下来我们来挨个分析以上提到的3种方法及其实现的代码。【接下来我们以排升序为例】

Horae法

这种方法是由是Hoare在1962年提出的一种二叉树结构的交换排序方法。排序的主要思路: 这里以最右边 (end) 作为基准值 (key) 为例,此时左边要先走,左边先去找比key大的;然后右边再去找比key小的。找到后2者进行交换。直到左边begin和右边end相等(begin不小于end)的时候,停止循环。然后交换key和相遇点的值

快速排序的三种实现方法及非递归实现快排_第1张图片

//1. Horae法
int HoraeMethod(int* a, int begin, int end)
{
	//右边做key,“左边” 先移动!
	int key = end;

	while (begin < end)
	{
		//【因为end做key,左边先走】左边先走,找到比key大的
		while (begin < end && a[begin] <= a[key])
		{
			begin++;
		}
		//右边再走,找到比key小的
		while (begin < end && a[end] >= a[key])
		{
			end--;
		}

		if (begin < end)
		{
			swap(&a[begin], &a[end]);
		}
	}
	//交换key与相遇点的值,这样key的左边都是比key小的,key的右边都是比key大的了
	swap(&a[key], &a[begin]);
	return begin;//返回相遇点
}

挖坑法

挖坑法的核心就是通过基准值 (key) 去保存我们最开始选择的基准值,此时由于基准值的值已经被保存了,那么原来的位置的值就可以被其他值替换了,也就相当于原来的位置就变成了一个“坑”。排序的主要思路:这里以最左边 (begin) 作为key,同时它也作为个坑。推荐从右边开始查找,右边找比key小的,找到后将它的值 赋值给 坑的位置,这一步骤就相当于将原来位置的坑填上了,然后又对现在的位置进行了挖坑。然后左边再继续找比key大的,继续填坑,挖坑。。。直到左边begin和右边end相等(begin不小于end)的时候,停止循环。最后将key的值填到它们的相遇点。

​​​​​​​快速排序的三种实现方法及非递归实现快排_第2张图片

 

//2.挖坑法
int HoleMethod(int* a, int begin, int end)
{
	int key = a[begin];

	while (begin < end)
	{
		while (begin < end && a[end] >= key)
		{
			end--;
		}
		//把左边的坑填上,右边进行挖坑
		a[begin] = a[end];

		while (begin < end && a[begin] <= key)
		{
			begin++;
		}	
		//把右边的坑填上,左边进行挖坑
		a[end] = a[begin];
	}
	//相遇点填上key值
	a[begin] = key;
	return begin;
}

前后指针法

前后指针法顾名思义就是借助前后2个指针进行排序。排序的主要思路:我们这里以end作为key值,同时定义2个指针(下标),前指针cur和后指针prev。当cur对应的值小于key的时候,先让prev++,然后再交换prev和cur对应的值,最后cur再++;如果cur对应的值大于key,只让cur++。这样做可以让小于等于key的值永远在prev的前面prev和cur中间的值都是大于key的。最后当cur > end时循环。

快速排序的三种实现方法及非递归实现快排_第3张图片

//3.前后指针法
int PrevCurMethod(int* a, int begin, int end)
{
	int cur = begin;
	int prev = cur - 1;
	int key = a[end];

	while (cur <= end)
	{
		//cur的值小于key,那么prev先++,然后交换cur和prev的值,cur再++
		//if (a[cur] <= key) 
		if(a[cur] <= key && ++prev != cur)//小优化:如果++prev和cur相等,就不用换了
		{
			//prev++;
			swap(&a[prev], &a[cur]);
		}
		cur++;//如果cur的值比key大,那么只有cur++;
	}
	return prev;
}

非递归实现快速排序

我们用非递归的意义就在于,当数据量过大的时候,我们使用递归会导致栈溢出(Stack Overflow)的问题,这是由于递归太深,栈空间不足出现的。所以我们要使用非递归,在堆区去开辟空间从而实现快速排序。而实现非递归的快速排序的核心就在于如何去模拟实现递归。这里我们需要借助(数据结构中的)栈,借助栈的后进先出的性质,这一性质和我们递归调用函数的时候非常相似,所以我们才可以用栈区模拟实现递归。

 首先我们需要将整段需要快排的区间Push进去,然后再取出来,对这段区间进行单趟快排。接收返回值keyindex,然后再将它的左区间和右区间分别进行push,再分别对他们进行快排。。。(进行循环)当左区间元素只有1个时,不再push了。当栈中没有任何元素时(代表没有任何区间时),此时所有区间都已经经过快排了,完成快速排序。

//非递归版----快速排序
void quicksortNonR(int* a, int begin, int end)
{
	//非递归版要借用栈---利用栈的后进先出的特性!!!
	Stack st;
	StackInit(&st);
	StackPush(&st, begin);//左边先入
	StackPush(&st, end);
	//栈为空的时候就代表所有区间都是有序的了
	while (!StackEmpty(&st))
	{
		//因为是end后入的,所以出的时候就是end先出,要用right接收
		int right = StackTop(&st);
		StackPop(&st);
		int left = StackTop(&st);
		StackPop(&st);

		//这里要传left和right【代表传一个区间过去,对这个区间进行快排】
		int keyindex = PrevCurMethod(a, left, right);
		
		//不能取等【取等的时候代表这个区间就1个元素,那么这个元素就已经是有序的了】
		if (left < keyindex - 1)
		{
			//这里也必须是左边先入,右边后入;对应开头写的右边先Pop,左边后Pop
			StackPush(&st, left);
			StackPush(&st, keyindex - 1);
		}

		if (keyindex + 1 < right)
		{
			StackPush(&st, keyindex + 1);
			StackPush(&st, right);
		}

	}
	StackDestory(&st);
}

你可能感兴趣的:(数据结构算法,排序算法,算法,数据结构)