快速排序-前后指针法和左右指针法

1.快速排序的原理:

快速排序是先任取数组中的一个元素作为基准值(一般是首或者尾),遍历一遍数组,让比大的在其右边,比他小的在其左边,这样就完成了基准值的定位,再将其右边和左边的分别重复上操作,完成排序。

2.前后指针法:

先定义数组开头是begin,结尾是end。end向左找比key小的数,找到就停止,begin向右找比key大的数找到就停止,最后交换end和begin的数据。当end=begin时就和key交换数据。

 3.左右指针法

定义头数据位置为prev,第二个数据位置为cur,头数据为key。cur向右找比key小的数据,找到后停止,与++prev位置交换数据,然后++cur。循环上述操作,当cur跳出循环时,key与prev位置交换数据.

 4.快排的优化

4.1三数取中

当数组中的数据是有序的时候,快排的效率最低,因为每次取的key都是最大或者最小值,这样他排出的位置是边缘位置,效率极低,我们只需要取出数组左位置,右位置,中间位置中数据的中值,与左位置处交换,这样我们的key值就不会排到边缘位置

4.2小区间优化

这里我们介绍的快排都是递归形式的,其实递归越到后面占据的时间越多,在最后的几个小区间就要占据大部分时间来递归。我们这里拿500个数据举例子,其实后面的小区间递归占据了大部分时间

如果数据是1w或者100w个,那么最后的小区间如果还用递归的话,就大幅度增加了运行时间,其实最后的小区间我们可以直接采用简单的不需要递归的插入排序替我们完成,这样就可以大幅度减少时间

5.代码 

void InsertSort(int* a, int n)
{
	//假设【0-end】有序,将第end+1的数插排入【0-end+1】     0-(n-1)
	for (int i = 0; i < n - 1; i++)
	{
		int end = i;
		int tmp = a[end + 1];
		while (end >= 0)
		{
			if (a[end] > tmp)
			{
				a[end + 1] = a[end];
				--end;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = tmp;
	}

}
void Swap(int* a1, int* a2)
{
	int tmp = *a1;
	*a1 = *a2;
	*a2 = tmp;
}
int MidIndex(int* arr, int left, int right)
{
	int mid = (left + right) / 2;
	if (arr[left] < arr[mid])
	{
		if (arr[mid] < arr[right])
		{
			return mid;
		}
		else
		{
			return arr[left] < arr[right] ? right : left;
		}
	}
	else//left > mid
	{
		if (arr[right] < arr[mid])
		{
			return mid;
		}
		else
		{

			return arr[left] < arr[right] ? left : right;
		}

	}


}

int _PartSort(int* arr, int left, int right)//挖坑法
{
	int begin = left;
	int end = right;
	int key = arr[begin];
	int pivot = begin;
	while (begin < end)
	{
		while (begin < end && arr[end] >= key)
		{
			end--;
		}
		arr[pivot] = arr[end];
		pivot = end;
		while (begin < end && arr[begin] <= key)
		{
			begin++;
		}
		arr[pivot] = arr[begin];
		pivot = begin;
	}
	pivot = begin;
	arr[pivot] = key;
	return pivot;
}

int _PartSort1(int* arr, int left, int right)//左右指针法
{
	int keyi = left;
	int begin = left;
	int end = right;
	while (begin < end)
	{
		while (begin < end && arr[end] >= arr[keyi])
		{
			end--;
		}
		while (begin < end && arr[begin] <= arr[keyi])
		{
			begin++;
		}
		Swap(&arr[begin], &arr[end]);
	}
	Swap(&arr[begin], &arr[keyi]);
	return begin;
}
//前后指针法
int _PartSort2(int* arr, int left, int right)
{
	int keyi = left;
	int rev = left;
	int cur = left + 1;
	while (cur <= right)
	{
		if (arr[cur] < arr[keyi] && cur != (rev + 1))
		{
			Swap(&arr[cur], &arr[++rev]);
		}
		++cur;
	}
	Swap(&arr[keyi], &arr[rev]);
	return rev;
}
void QuickSort(int* arr, int left, int right)
{
	if (left > right)
	{
		return;
	}
	int  index = MidIndex(arr, left, right); // 三数取中
	Swap(&arr[index], &arr[left]);

	int pivot = _PartSort2(arr, left, right);

	if (pivot - 1 - left + 1 > 10)//进行小区间分出去排序
	{
		QuickSort(arr, left, pivot - 1);
	}
	else
	{
		InsertSort(arr + left, pivot - 1 - left + 1);
	}
	if (right - pivot - 1 + 1 > 10)
	{
		QuickSort(arr, pivot + 1, right);
	}
	else
	{
		InsertSort(arr + pivot + 1, right - pivot - 1 + 1);
	}
}

6.扩展:

用数据结构的栈模拟实现快排的递归

<think>嗯,用户想了解快速排序霍尔法的实现原理。首先,我需要确认霍尔法是什么。根据引用[2]提到的,霍尔版本就是左右指针法,也就是快速排序的原始实现,由Tony Hoare发明的。所以,霍尔法应该就是最初的快速排序方法。 接下来要解释霍尔法的原理。快速排序的核心是分治,选基准,然后划分数组。霍尔法的具体步骤应该是通过左右指针向中间移动,交换不符合条件的元素,直到指针相遇,从而将数组分为两部分。 需要详细描述步骤,比如基准的选择,左右指针的初始化,移动规则,以及交换的条件。可能还需要举例说明,比如引用[3]中的例子,但用户没有给出具体数值,可能需要自己构造一个例子。比如用数组[5, 3, 8, 4, 2],基准选第一个元素5,左右指针分别从左右两端开始移动,左指针找大于等于基准的元素,右指针找小于等于基准的,然后交换,直到相遇。这样分区后递归处理。 代码实现部分,根据引用[2]提到的霍尔版本容易出错,需要注意边界条件。比如循环条件是否包括等于,指针移动的顺序,以及递归调用的范围。例如,分区后的左右部分是否需要包含基准点,或者排除基准点。霍尔法的实现可能挖坑法不同,需要确保分区正确。 可能还需要比较霍尔法其他方法的不同,比如挖坑法前后指针法。霍尔法作为原始方法,虽然容易理解,但在处理重复元素或特定情况下可能有性能问题,或者容易出错,如引用[3]提到的稳定性问题。 另外,引用[4]提到的小区间优化,可能在实现时需要考虑,当子数组较小时切换到插入排序,但用户的问题可能不涉及优化部分,但可以提及作为扩展。 最后,生成相关问题时要围绕霍尔法的实现细节、优缺点、与其他方法的对比,以及应用场景。比如,霍尔法的时间复杂度分析,如何避免错误,优化方法等。 现在需要将以上思路整理成结构化的回答,分点阐述霍尔法的原理实现步骤,并附上代码示例。注意使用正确的中文术语,确保LaTeX格式正确,变量用$...$,独立公式用$$...$$。同时,检查引用是否正确,比如引用[2]提到霍尔版本容易出错,引用[3]说明不稳定性移动过程。 可能还需要注意霍尔法中的指针移动顺序交换条件,确保代码正确性。例如,在循环中先移动右指针还是左指针,是否会导致死循环或错误分区。例如,在某些实现中,必须先从右向左扫描,以避免错误的分区点。 总结霍尔法的步骤: 1. 选择基准元素(通常为左端或右端,或三数取中)。 2. 初始化左右指针在数组两端。 3. 移动右指针向左,找第一个小于基准的元素。 4. 移动左指针向右,找第一个大于基准的元素。 5. 交换这两个元素,重复直到指针相遇。 6. 将基准与相遇点交换,完成分区。 7. 递归处理左右子数组。 在代码实现中,需要注意循环条件交换后的指针移动,避免无限循环。例如,在交换后是否需要移动指针,或者基准的位置是否正确放置。 现在编写Python代码示例,可能需要注意递归调用的范围。例如,分区后的左部分是小于基准的部分,右部分是大于基准的部分,而基准的位置可能在中间,因此递归处理左边右边,不包含基准点。 最后,验证示例是否正确。例如,使用示例数组[5,3,8,4,2],基准选5。右指针从2开始向左找小于5的元素,找到2,左指针从5开始向右找大于5的元素,找到8,交换后数组变为[5,3,2,4,8]。继续移动右指针到4,左指针到4相遇。交换基准5与相遇点元素4,得到数组[4,3,2,5,8]。此时基准5的位置正确,左边是小于5的元素,右边是大于5的元素。递归处理左边[4,3,2]右边[8]。 可能代码中的分区逻辑需要调整,比如霍尔法的实现可能不需要交换基准到最后,而是在分区结束后将基准放到正确位置。或者,在代码中基准是第一个元素,通过左右指针移动找到相遇点,然后交换基准相遇点的元素。 需要确保代码的正确性,可能需要参考标准实现。例如,在霍尔法中,左右指针移动的条件交换的步骤,以及最终的基准位置处理。</think>### 快速排序霍尔法的原理与实现 #### 一、霍尔法的原理 **霍尔法**(Hoare Partition Scheme)是快速排序的原始实现方法,由东尼·霍尔(Tony Hoare)于1960年提出。其核心思想是通过**左右指针双向扫描**将数组划分为两个子区间,并递归排序。具体步骤如下: 1. **选择基准元素(pivot)**:通常选择数组首元素或尾元素(也可优化为三数取中,见引用[2])。 2. **初始化左右指针**:左指针 $left$ 从数组左端开始,右指针 $right$ 从右端开始。 3. **双向扫描与交换**: - 右指针向左移动,找到第一个**小于等于**基准的元素。 - 左指针向右移动,找到第一个**大于等于**基准的元素。 - 若 $left < right$,交换这两个元素;否则退出循环。 4. **划分区间**:最终左右指针相遇,将基准元素与相遇点交换(或直接以相遇点为分界),将数组分为两部分。 5. **递归排序**:对左右子数组重复上述过程。 **关键点**: - 指针相遇位置即为分界点,左边元素 $\leq$ 基准,右边元素 $\geq$ 基准[^3]。 - 时间复杂度为 $O(n \log n)$,但若基准选择不当可能退化为 $O(n^2)$[^1]。 - 快速排序不稳定,可能改变相同元素的相对顺序[^3]。 --- #### 二、霍尔法的代码实现(Python示例) ```python def hoare_partition(arr, low, high): pivot = arr[low] # 选择首元素为基准(可优化为三数取中) left = low - 1 # 初始化为左指针前一位 right = high + 1 # 初始化为右指针后一位 while True: right -= 1 while arr[right] > pivot: # 右指针找<=pivot的元素 right -= 1 left += 1 while arr[left] < pivot: # 左指针找>=pivot的元素 left += 1 if left < right: arr[left], arr[right] = arr[right], arr[left] # 交换元素 else: return right # 返回分界点 def quick_sort_hoare(arr, low, high): if low < high: pi = hoare_partition(arr, low, high) # 获取分界点 quick_sort_hoare(arr, low, pi) # 递归处理左子数组 quick_sort_hoare(arr, pi + 1, high) # 递归处理右子数组 # 示例 arr = [5, 3, 8, 4, 2] quick_sort_hoare(arr, 0, len(arr)-1) print(arr) # 输出:[2, 3, 4, 5, 8] ``` **代码解析**: 1. `hoare_partition` 函数完成双向扫描与交换,返回分界点 `right`。 2. 递归调用时,子数组范围为 `[low, pi]` `[pi+1, high]`,避免遗漏元素[^2]。 3. 基准选择首元素,实际应用建议结合**三数取中法**优化(见引用[2])。 --- #### 三、与其他方法的对比 | 方法 | 特点 | |-------------|----------------------------------------------------------------------| | **霍尔法** | 原始实现,逻辑简单但易出错(如指针越界)[^2] | | 挖坑法 | 通过“填坑”减少交换次数,更易理解 | | 前后指针法 | 单方向扫描,适合链表结构 | ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值