快速排序的挖坑法与双指针法

目录

序列文章

前言

快速排序(挖坑法)

实现步骤

代码实现

错误写法

正确写法

 时空复杂度

快速排序(伪双指针法)

基本思想

实现步骤

代码实现

时空复杂度


序列文章

非递归实现的快速排序:http://t.csdnimg.cn/UEcL6

快速排序的挖坑法与双指针法:http://t.csdnimg.cn/I1L7Q

快速排序的hoare法:http://t.csdnimg.cn/SV0nA

前言

        学习完hoare版本的快速排序方法后,今天我们来学习挖坑法和伪双指针法实现快速排序,它们的实现思想相比于hoare版本的快速排序更简单一些,需要注意的细节问题也没有hoare版本的快速排序多,相对而言还是比较好理解的,只要你足够细心~~~🥰

快速排序(挖坑法)

注意事项:key一经确定则不能再更改,直到一轮排序结束后才会将该元素放入队列中

实现步骤

1、将数组首元素放在临时变量key中,key = 6,形成一个坑位

2、令right向左走查找比6小的元素,left在right未找到之前一直停留在坑位

3、当right找到比key小的元素5,将5存放在坑位中,并将当前位置变为坑位

4、此时left开始移动,向右找比6大的元素,right在left未找到之前一直停留在坑位

5、当left找到比6大的元素7,将7存放在坑位中,并将当前位置变为坑位 

6、接着right开始移动,向左走找比6小的元素,left在right未找到之前一直停留在坑位

7、当right找到比6小的元素4,将4存放在坑位中,并将当前位置变为空位

8、然后left开始移动,向右走找比6大的元素,right在left未找到之前一直停留在坑位

9、当left找到比6大的元素9,将9存放在坑位中,并将当前位置变为坑位

10、接着right开始移动,向左找比6小的元素,left在right未找到之前一直停留在坑位

11、当right找到比6大的元素3,将3存放在坑位中,并将当前位置变为坑位

12、然后left开始移动,向右找比6大的元素,right在left未找到之前一直停留在坑位

13、 left继续向前移动就会与right相遇,此时将key放入坑位

代码实现

错误写法

//三数取中
int GetMidi(int* a, int begin, int end)
{
	int midi = (begin + end) / 2;
	// begin midi end 三个数选中位数
	if (a[begin] < a[midi])
	{
		if (a[midi] < a[end])
			return midi;   //返回a[midi] < a[midi] < a[end]

		else if (a[begin] > a[end])
			return begin;  //返回a[end] < a[begin] < a[midi]

		else
			return end;    //返回a[begin] < a[end] < a[midi]
	}
	else // a[begin] > a[midi]
	{
		if (a[midi] > a[end])
			return midi;   //返回a[end] < a[mid] < a[begin]
		else if (a[begin] < a[end])
			return begin;  //返回a[midi] < a[begin] < a[end]
		else
			return end;    //返回a[midi] < a[end] < a[begin]
	}
}

//方法二:挖坑法
void QuickHoleSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;

	int midi = GetMidi(a, begin, end);
	Swap(&a[midi], &a[begin]);

	int key = a[begin];
	int hole = begin;
	while (begin < end)
	{
		//right向左移动找小于key的元素,找到了就将该数字放入坑(左边的)
		while (begin < end && a[end] >= key)
		{
			--end;
		}
		//填坑
		a[hole] = a[end];
		hole = end;

		//left向右移动找大于key的元素,找到了就将该数字放入坑(右边的)
		while (begin < end && a[begin] <= key)
		{
			++begin;
		}
		//填坑
		a[hole] = a[begin];
		hole = begin;
	}

	a[hole] = key;
	QuickSort(a, begin, hole - 1);
	QuickSort(a, hole + 1, end);
}

原因分析:在hoare版本的快排中,begin和end的位置在每一轮比较和交换的过程中是没有改变的,改变的是left和right,而在这里当我们第一次递归开始时的begin的值是改变后的值

正确写法

//三数取中
int GetMidi(int* a, int begin, int end)
{
	int midi = (begin + end) / 2;
	// begin midi end 三个数选中位数
	if (a[begin] < a[midi])
	{
		if (a[midi] < a[end])
			return midi;   //返回a[midi] < a[midi] < a[end]

		else if (a[begin] > a[end])
			return begin;  //返回a[end] < a[begin] < a[midi]

		else
			return end;    //返回a[begin] < a[end] < a[midi]
	}
	else // a[begin] > a[midi]
	{
		if (a[midi] > a[end])
			return midi;   //返回a[end] < a[mid] < a[begin]
		else if (a[begin] < a[end])
			return begin;  //返回a[midi] < a[begin] < a[end]
		else
			return end;    //返回a[midi] < a[end] < a[begin]
	}
}

//方法二:挖坑法
int PartSort2(int* a, int begin, int end)
{
	int midi = GetMidi(a, begin, end);
	Swap(&a[midi], &a[begin]);

	int key = a[begin];
	int hole = begin;
	while (begin < end)
	{
		//right向左移动找小于key的元素,找到了就将该数字放入坑(左边的)
		while (begin < end && a[end] >= key)
		{
			--end;
		}
		//填坑
		a[hole] = a[end];
		hole = end;

		//left向右移动找大于key的元素,找到了就将该数字放入坑(右边的)
		while (begin < end && a[begin] <= key)
		{
			++begin;
		}
		//填坑
		a[hole] = a[begin];
		hole = begin;
	}

	a[hole] = key;
    return hole;
}

//快速排序
void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;

	int keyi = PartSort2(a, begin, end);
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi+1, end);
}

 时空复杂度

最坏时间复杂度:O(N^2)(当数组已经降序有序或升序有序时,此时基准元素一直位于首元素或尾元素,n个元素要进行n次快速排序才能将当前的顺序改变,n*n,三数取中的办法的确可以优化这种情况,但还是可能会出现O(N^2)的情况)

最好时间复杂度:O(N*logN)(每次划分都能将数组均匀地分成两个接近子数组,N个元素要进行logN次的排序,N*logN)

空间复杂度:O(logN)或O(N)(在递归过程中需要使用栈来保存函数调用信息,所以快速排序的空间复杂度取决于递归调用的层数。在最坏情况下,递归调用栈可能达到O(n)的空间复杂度,最好的空间复杂度为O(logn))

快速排序(伪双指针法)

基本思想

1、cur遇到比key大的值,++cur

2、cur遇到比key小的值,++prev,交换prev和cur位置的值,++cur

3、当cur走到头时一轮排序结束

注意事项:

1、++prev != cur时才可以交换prev和cur位置的值

2、if语句中的++prev也会导致每次prev的++ 

3、注意判断语句中的&&的短路效应,当a[cur] < a[keyi]为假时,&&后面的++prev != cur的判断不会执行,prev也就不会同时跟着向前移动

实现步骤

1、初始化时,选取数组首元素6作为key,将prev指向数组首元素,cur指向首元素的下一个元素

2、cur负责找到比key小的,2小于key,此时++prev,并交换a[prev]与a[cur]的值,但是如果就像图中所示的情况,++prev == cur 二者重合了,如果此时还要求二者交换位置,这是不合理的,所以我们除了基本思想外还需要额外的加上一条注意事项:当++prev != cur时才能交换二者位置 

3、 无论是交换还是不交换,cur都需要向前走,此时prev也往前走,在cur未找到比key大的值之前,二者的间隔总是相差1,当cur走到7时,由于&&的短路,prev不会向前移动,只有cur移动,此时二者之间的距离会不断拉大,直到cur找到了比key小的数字,即a[cur] < a[keyi],且此时由于二者之间已经有了间隔++prev != cur 的判断结果为真,故交换a[cur]与a[prev]位置上的值

(可以将整个过程抽象的理解为推箱子游戏的过程) 

 4、剩余的步骤如下图所示:

代码实现

//方法三:伪双指针法
int PartSort3(int* a, int begin, int end)
{
	int midi = GetMidi(a, begin, end);
	Swap(&a[midi], &a[begin]);
	int keyi = begin;

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

        关于各种排序性能的测试代码以及全部排序的代码内容会放在最后一篇排序的内容中,这两天更新...... 

时空复杂度

 与挖坑法的复杂度一致~ 

~over~

  • 92
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当我们在使用快速排序算法实现时,经常会遇到一些常见的问题。下面是一些填坑的建议: 1. 选择合适的基准元素:快速排序的关键是选择一个基准元素,并将数组分为两个子数组。一个常见的选择是将第一个元素作为基准,但在某些情况下,这可能会导致性能下降。为了避免最坏情况的出现,可以考虑使用随机选择或选取中间值作为基准。 2. 处理相等元素:快速排序通常使用双指针将元素分为两个子数组。但如果数组中存在相等的元素,可能会导致分割不均衡,从而影响排序的效率。一种解决方是使用三路快速排序,在基准元素的基础上再增加一个指针,将数组分为小于、等于和大于基准的三个部分。 3. 处理递归边界:快速排序使用递归来排序子数组。在实现递归函数时,需要确保处理边界情况,避免无限递归或越界错误。通常情况下,当子数组只有一个元素或为空时,可以作为递归的终止条件。 4. 优化递归次数:在某些情况下,快速排序可能会导致递归层数过深,消耗大量的栈空间。为了避免这种情况,可以考虑使用迭代的方式来实现快速排序,使用栈来保存待处理的子数组。 下面是一个使用填坑实现快速排序的Java示例代码: ```java public class QuickSort { public void quickSort(int[] nums, int low, int high) { if (low < high) { int pivotIndex = partition(nums, low, high); // 获取基准元素的位置 quickSort(nums, low, pivotIndex - 1); // 对基准元素的左边子数组进行排序 quickSort(nums, pivotIndex + 1, high); // 对基准元素的右边子数组进行排序 } } private int partition(int[] nums, int low, int high) { int pivot = nums[low]; // 选择第一个元素作为基准 int left = low + 1; int right = high; while (true) { while (left <= right && nums[left] < pivot) { left++; } while (left <= right && nums[right] > pivot) { right--; } if (left > right) { break; } // 交换左指针和右指针所指向的元素 int temp = nums[left]; nums[left] = nums[right]; nums[right] = temp; } // 将基准元素放到正确的位置上 int temp = nums[low]; nums[low] = nums[right]; nums[right] = temp; return right; // 返回基准元素的位置 } public static void main(String[] args) { QuickSort quickSort = new QuickSort(); int[] nums = {4, 2, 6, 1, 3, 5}; quickSort.quickSort(nums, 0, nums.length - 1); for (int num : nums) { System.out.print(num + " "); } } } ``` 希望以上的建议和示例代码可以帮助你在Java中填坑实现快速排序算法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值