【排序】快速排序的n种写法(含非递归)

快速排序的n种写法

  • 前言
  • hoare版本
  • 挖坑法
  • 前后指针版本
  • 非递归版

前言

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

 `void QuickSort(int arr[], int left, int right)
{
	if (left >= right)
		return;
	//按照基准值对数组的[left,right]区间进行划分
	int ret = partion(arr, left, right);
	//递归后将数组排成了[left,ret-1]和[ret+1,right]
	//递归排[left,ret-1]
	QuickSort(arr, left, ret - 1);
	//递归排[ret+1,right]
	QuickSort(arr, ret + 1, right);
}

以上就是快速排序的主框架,这与二叉树的前序遍历有些相似,接下来就只用思考如何实现partion过程就可以了

hoare版本

【排序】快速排序的n种写法(含非递归)_第1张图片

  1. 首先将数组最左边的数作为划分值,并定义两个指针分别指向数组最左边和最右边两个元素
  2. R先走,当找到比key小的值的时候就停下来
  3. R停下来后,L开始向右边移动,当L遇到比key大的值的时候停下来
  4. 交换R和L所指向的元素,并从第二步开始重复以上行为,直到两指针相遇,就执行第五步
  5. 两指针相遇后,此时的相遇点是一定比key要小的,将key所指向的的值与相遇点的值进行交换

为什么相遇的位置要比key要小?
这里要分为两种情况

  1. R主动去和L相遇
    R再次移动的前提是L停下来时所指向的元素(>key)和R停下来所指向的元素(
  2. L主动去和R相遇
    L移动的前提是R已经停下来了,而R停下来的前提是R已经已经指向了比key小的元素位置
void swap(int arr[], int x, int y)
{
	int tmp = arr[x];
	arr[x] = arr[y];
	arr[y] = tmp;
}
int partion(int arr[], int left, int right)
{
//将基准值的下标记录下来是为了方便最后进行交换
	int key = left;
	while (right > left)
	{
	//前置条件right>left是为了防止越界
		while (right > left && arr[right] >= arr[key])
		{
			right--;
		}
		while (right > left && arr[left] <= arr[key])
		{
			left++;
		}
		swap(arr, right, left);
	}
	swap(arr, key, left);
	return left;
}

void QuickSort(int arr[], int left, int right)
{
	if (left >= right)
		return;
	//按照基准值对数组的[left,right]区间进行划分
	int ret = partion(arr, left, right);
	//递归后将数组排成了[left,ret-1]和[ret+1,right]
	//递归排[left,ret-1]
	QuickSort(arr, left, ret - 1);
	//递归排[ret+1,right]
	QuickSort(arr, ret + 1, right);
}

挖坑法

【排序】快速排序的n种写法(含非递归)_第2张图片

  1. 将左边第一个数作为key,并将此位置作为一个’坑’
  2. R先走,找到小于key的值停下,并将该值放到‘坑’中,此时R所指位置为新的坑
  3. L再走,找到大于key的值停下,并将该值放到‘坑’中,此时L所指位置为新的坑,并从第二步开始重复,直到两指针相遇
  4. 当它们相遇后,将key的值放到相遇点
int partion(int arr[], int left, int right)//挖坑法
{
	int k = arr[left];
	//此时left所指向位置为'坑'
	while (right > left)
	{
		while (right > left && arr[right] >= k)
		{
			right--;
		}
		arr[left] = arr[right];
		//此时right所指向位置更新为'坑'
		while (right > left && arr[left] <= k)
		{
			left++;
		}
		arr[right] = arr[left];
		//此时left所指向位置更新为'坑'
	}
	arr[left] = k;
	return left;
}

void QuickSort(int arr[], int left, int right)
{
	if (left >= right)
		return;
	//按照基准值对数组的[left,right]区间进行划分
	int ret = partion(arr, left, right);
	//递归后将数组排成了[left,ret-1]和[ret+1,right]
	//递归排[left,ret-1]
	QuickSort(arr, left, ret - 1);
	//递归排[ret+1,right]
	QuickSort(arr, ret + 1, right);
}

前后指针版本

【排序】快速排序的n种写法(含非递归)_第3张图片

  1. 初始时,prev指向数组的开头,cur指向prev的后一个位置,key指向基准值(此时以最左边的元素作为基准值)
  2. 判断cur位置的值是否小于基准值,若小于则prev指针向后移动一位,并将cur指向的内容与prev指向的内容交换,cur继续向后移动一位
  3. 若cur指向位置的值大于key,cur直接向后移动一位
  4. 重复执行第二、三步,直到cur指针越界
  5. 当cur越界时,交换key和prev所指向的内容
void swap(int arr[], int x, int y)
{
	int tmp = arr[x];
	arr[x] = arr[y];
	arr[y] = tmp;
}
int partion(int arr[], int left, int right)
{
	int key = left;
	int prev = left;
	int cur = prev + 1;
	while (cur <= right)
	{
		if (arr[cur] < arr[key])
		{
			prev++;
			swap(arr, prev, cur);
		}
		cur++;
	}
	swap(arr, key, prev);
	return prev;
}

void QuickSort(int arr[], int left, int right)
{
	if (left >= right)
		return;
	//按照基准值对数组的[left,right]区间进行划分
	int ret = partion(arr, left, right);
	//递归后将数组排成了[left,ret-1]和[ret+1,right]
	//递归排[left,ret-1]
	QuickSort(arr, left, ret - 1);
	//递归排[ret+1,right]
	QuickSort(arr, ret + 1, right);
}

非递归版

快速排序的非递归实现使用了栈来模拟递归的过程。其基本思路如下:

  1. 首先,将要排序的序列的起始位置和结束位置入栈。
  2. 进入循环,判断栈是否为空,如果为空,则说明排序完成。
  3. 从栈中取出一个区间的起始位置和结束位置,并进行分区操作
  4. 分区操作将起始位置到结束位置的序列分为两部分,左边部分的元素小于等于基准值,右边部分的元素大于等于基准值,并返回基准值的位置。
  5. 如果左边部分有两个及以上的元素,将左边部分的起始位置和结束位置入栈。
  6. 如果右边部分有两个及以上的元素,将右边部分的起始位置和结束位置入栈。
  7. 重复步骤3-6,直到栈为空。
  8. 排序完成后,序列中的元素即按照从小到大(或从大到小)的顺序排列。
int partion(int arr[], int left, int right)//挖坑法
{
	int k = arr[left];
	while (right > left)
	{
		while (right > left && arr[right] >= k)
		{
			right--;
		}
		arr[left] = arr[right];
		while (right > left && arr[left] <= k)
		{
			left++;
		}
		arr[right] = arr[left];
	}
	arr[left] = k;
	return left;
}
void QuickSort(int arr[], int left, int right)
{
	stack<int>st;
	//将左右两个区间放入栈中
	st.push(left);
	st.push(right);
	while (!st.empty())
	{
		right = st.top();
		st.pop();
		left = st.top();
		st.pop();
		if (left >= right)
			continue;//筛选掉不合规范的区间
		int ret = partion(arr, left, right);
		//以基准值为分割点,形成两部分[left,ret-1]和[ret+1,right]
		st.push(left);
		st.push(ret - 1);

		st.push(ret + 1);
		st.push(right);
	}
}

通过使用栈来保存每个待排序子序列的起始位置和结束位置,非递归的快速排序可以避免递归调用带来的额外开销,提高排序的效率。

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