快速排序算法详解及实现(Hoare版本、挖坑法、前后指针)

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

  • Hoare版本
    在这里插入图片描述
  • 挖坑法
    在这里插入图片描述
  • 前后指针法
    在这里插入图片描述

  若待排序的序列较长,使用递归的方法一层一层调用极易造成栈溢出,由于当待排序的序列长度逐渐减小时,元素已经接近有序,使用插入排序会更加快捷。

代码实现:

#include<stdlib.h>
#include<stdio.h>
#include<string.h>


void Print(int *arr,int size);
//快速排序1
void quickSort(int *arr, int left, int right);



////////////////////////////////////////////////////////

void InsertSort(int *array, int size)
{
	int i = 0;
	//要插入的位置的前一个位置
	int pos = 0;
	int k = 0;
	//循环需要size-1次
	for (i = 1; i < size; i++)
	{
		//从被插入的元素的前一个元素开始比较
		pos = i - 1;
		//记录要插入的元素
		k = array[i];
		//如果被插入的元素比前一个元素小,则将元素右移,然后和前一个元素比较
		//如果被插入的元素比前一个元素大,则直接插入
		while (k < array[pos] && pos >= 0)
		{
			//说明该元素k要插在pos的前面,所以pos后移
			array[pos + 1] = array[pos];
			pos--;
		}
		//插
		array[pos + 1] = k;
	}
}
//打印函数
void Print(int *arr, int size)
{
	int i = 0;
	for (i = 0; i < size; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

void swap(int *x, int *y)

{
	/*int tmp = *x;
	*x = *y;
	*y = tmp;*/
	*x = ((*x) ^ (*y));
	*y = ((*x) ^ (*y));
	*x = ((*x) ^ (*y));
}
int MiddleNumber(int *arr, int begin, int end)
{
	int mid = begin + ((end - begin) >> 1);
	if (arr[begin] > arr[mid])
	{
		if (arr[begin] > arr[end])
		{
			if (arr[mid] > arr[end])
			{
				return mid;
			}
			else
				return end;
		}
		else
			return begin;
	}
	else
	{
		if (arr[mid] > arr[end])
		{
			if (arr[begin] > arr[end])
			{
				return begin;
			}
			else
			{
				return end;
			}
		}
		else
			return mid;
	}
	
}
/*
  Hoare方法
  快速排序1
  以数组最后一个元素作为基准key、设置两个指针bengin、end
  bengin从前往后移动遇到比基准key大的停止
  end从后往前移动遇到比基准key小的停止
  bengin和end所对应的数据交换
  重复直至bengin==end
*/
int qs1(int *arr, int begin, int end)
{
	//找基准
	/*
	由于每次规划取到最大值或最小值的概率
	都非常高,这样容易使树变成单支树,所以采用三数取中法
	来降低取到最值的概率
	*/
	int index = MiddleNumber(arr, begin, end);
	if (index != end)
	{
		swap(&arr[index], &arr[end]);
	}
	int key = arr[end];


	//基准值的下标
	int k = end;
	//两个指针、begin从0开始、end从size-1开始
	while (begin != end)
	{
		//begin向后移动,找比基准k大的元素,且begin不能大于end
		//如果arr[begin]比key小,则begin++
		while (arr[begin] <= key && (begin < end))
		{
			begin++;
		}

		//end向前移动,找比基准key小的元素,且end不能小于begin
		//如果arr[end]比key大,则end--
		while (arr[end] >= key && (begin < end))
		{
			end--;
		}

		//如果begin和end对应的值不相等,则交换
		if (begin != end)
		{
			//交换bengin和end所对应的值
			swap(&arr[begin], &arr[end]);
		}
	}
	//如果begin的最终位置就是基准的位置则不用交换
	if (begin != k)
	{
		//将基准值挪到相应的位置上
		swap(&arr[begin], &arr[k]);
	}
	return begin;
}
/*
  挖坑法
  先将最后一个元素标记设置为第一个坑
  设置两个指针begin、end
  begin从左边开始找比关键字大的元素将其入坑,begin所在位置变为坑
  end从右边开始找比关键字小的元素将其入坑,end所在位置变为坑
  知道begin==end将标记的第一个元素入坑
*/
int qs2(int *arr, int begin, int end)
{
	//找基准
	/*
	由于每次规划取到最大值或最小值的概率
	都非常高,这样容易使树变成单支树,所以采用三数取中法
	来降低取到最值的概率
	*/
	int index = MiddleNumber(arr, begin, end);
	if (index != end)
	{
		swap(&arr[index], &arr[end]);
	}

	//第一个坑
	int key = arr[end];
	int k = end;
	while (begin != end)
	{
		/*begin从左边开始找比关键字大的元素将其入坑
		,begin所在位置变为坑*/
		while (arr[begin] <= key&&begin < end)
		{
			begin++;
		}
		if (begin != end)
		{
			arr[end] = arr[begin];
			end--;
		}
		/*end从右开始找比关键字小的元素将其入begin坑*/
		while (arr[end] >= key&&begin < end)
		{
			end--;
		}
		if (begin != end)
		{
			arr[begin] = arr[end];
			begin++;
		}
	}
	if (begin != k)
	{
		arr[begin] = key;
	}
	return begin;
}
/*
	前后指针法
	cur在待排序序列的最左侧
	perv=cur-1
	如果arr[cur]<key,则prev一直跟在cur的后面
	如果arr[cur]>key,则prev停止,cur向前走,直至arr[cur]<key
	cur与prev对应的元素交换;重复,直至cur超过待排序序列的长度
	prev和cur中间是比基准值大的元素
*/
int qs3(int *arr, int begin, int end)
{
	//找基准
	/*
	由于每次规划取到最大值或最小值的概率
	都非常高,这样容易使树变成单支树,所以采用三数取中法
	来降低取到最值的概率
	*/
	int index = MiddleNumber(arr, begin, end);
	int cur = begin;
	int prev = begin - 1;
	if (index != end)
	{
		swap(&arr[index], &arr[end]);
	}
	int key = arr[end];
	//cur不能超过序列长度
	while (cur <= end)
	{
		if (arr[cur] <= key&&++prev != cur)
		{
			swap(&arr[cur], &arr[prev]);
		}
		cur++;
	}
		return prev;
}
void quickSort(int *arr, int left,int right)
{
	int position ;
	/*
	由于快速排序是递归调用,容易产生栈溢出
	但是快速排序排到最后元素也接近有序,则采用插入排序
	*/
	if (right - left<2)
	{
		InsertSort(arr + left, right - left);
	}
	else
	{
		//左右指针
	    //position = qs1(arr, left, right - 1);
		//挖坑法
		position = qs2(arr, left, right - 1);
		//快速排序基准值左侧
		quickSort(arr, 0, position);
		//快速排序基准值右侧
		quickSort(arr, position + 1, right);
	}
}

测试

#include"quickSort.h"

void TestquickSort()
{
	int array[] = {2,5,4,9,3,6,8,7,1,0};
	//打印
	printf("排序前: ");
	Print(array, sizeof(array) / sizeof(array[0]));
	//快速排序1
	quickSort(array,0, sizeof(array) / sizeof(array[0]));
	//quickSortNor(array, sizeof(array) / sizeof(array[0]));
	printf("排序后: ");
	Print(array, sizeof(array) / sizeof(array[0]));

}
int main()
{
	TestquickSort();
	system("pause");
	return 0;
}

在这里插入图片描述
算法的平均时间复杂度: o(nlogn)
算法的空间复杂度: o(1)
算法的稳定性: 稳定
使用场景: 数据量大,数据越随机越合适

猜你喜欢

转载自blog.csdn.net/zhao_miao/article/details/82828134
今日推荐