数据结构-快速排序

这里写目录标题

  • 前言:学完排序算法的希尔排序我们接着学一种优秀的算法,快速排序
    • 简介:快速排序的实现方式有很多种,这里我们介绍三种递归实现的方式,一种迭代实现的方式。
    • 排序思想:
      • 方法一:霍尔
      • 挖坑法
      • 前后指针法
    • 此时使用递归实现快排我们已经基本学会了,下边还有种情况,就是使用非递归如何实现快排呢?
      • 非递归实现思路讲解:
    • END

前言:学完排序算法的希尔排序我们接着学一种优秀的算法,快速排序

简介:快速排序的实现方式有很多种,这里我们介绍三种递归实现的方式,一种迭代实现的方式。

排序思想:

方法一:霍尔

在这里插入图片描述
在这里插入图片描述
讲解:我们可以设定一个数组内存在的值为key,令数组内的一侧全部小于这个值,另一边全部大于这个值。
那么这样我们是不是可以确定这个key值在升序数组内应该存放的位置呢?
以上是我们单趟排序的核心思想,现在来思考如何让整个数组有序。
我们对第一趟分成的两部分分别再进行一次单趟排序, 这样是不是又可以确定一个唯一数字的位置,
这样又分为了两部分,接着对每部分进行排序,直到只剩一个数字我们就结束。
这样,数组就完成了排序

下边写出代码的实现:

void printArrary(int* arr, int n) {
	for (int i = 0; i < n; i++) {
		printf("%d ", arr[i]);
	}
	printf("\n");
}
void test(int* arr, int begin, int end) {
	//递归结束条件
	if (begin >= end)return;
	int keyi = begin;//keyi 来保存key的下标,用作比较和交换
	int left = begin, right = end;
	while (right > left) {//没有相遇则一直走,碰面或者错过为结束
		//右边先走,可以保证最后的值是始终为相遇点
		while (right > left && arr[right] >= arr[keyi]) right--;
		while (right > left && arr[left] <= arr[keyi])left++;
		swap(arr + left, arr + right);
	}
	swap(arr + keyi, arr + right);//right即为相遇点
	test(arr, begin, right - 1);
	test(arr, right + 1, end);
}

int main(){
	int arr[] = { 0,4,3,2,1};
	test(arr, 0, 4);
	printArrary(arr,5);
}

在这里插入图片描述
运行结果符合预期,数组按升序排列了,但是这种是最简陋的实现,我们设想一下,存在这么一个数组 1 4 3 2 5 9 。
那么他第一趟右边是不是直接要遍历到最左边才能结束,而左边一次不走。在这样的极端情况下,它的时间复杂度是等于O(N2)的,这样的复杂度是我们万万不想见到的。针对这种情况处理也是比较简单的——三数取中,简单来讲就是,在每次拿到数组的时候在中间位置和头和尾进行一次比较,将中间值跟头的位置互换,这样使得效率大大提高。

下边我将代码进行一次优化:

int GetMiDe(int* arr, int begin, int end) {
	int midi = (begin + end) / 2;
	if (arr[begin] < arr[midi])
	{
		if (arr[midi] < arr[end])
			return midi;
		else if (arr[begin] > arr[end])
			return begin;
		else
			return end;
	}
	else
	{
		if (arr[midi] > arr[end])
			return midi;
		else if (arr[begin] < arr[end])
			return begin;
		else
			return end;
	}
}

void QuickSort(int* arr, int begin,int end) {
	if (begin >= end)return;
	int keyi = partsort1(arr, begin, end);
	//SORT:[begin,keyi)+(keyi,end]
	QuickSort(arr, begin, keyi - 1);//keyi左边都是小的keyi,右边全是大的
	QuickSort(arr, keyi + 1, end);
}

下边放出有三数取中和没有三数取中有序数组排序速度的对比:
在这里插入图片描述
其实还有个优化的点,那就是对小区间的处理,处理方法就是在分成的小数字达到一定规模时候我们直接选择插入排序,这样可以极大的节省栈的资源,通过递归实现的快排是需要重复创建函数栈帧的,而栈空间是非常小的空间,在debug模式下还需要在栈帧里打入调试信息,就导致非常容易爆栈,但是在release模式在,其优化已经非常不错了,有没有小区间优化几乎可以忽略不计。
下边我放出实现代码,测试有兴趣的同学可以自己做一下测试:

void QuickSort(int* arr, int begin,int end) {
	if (begin >= end)return;
	if (end - begin+1 <= 10) {//小区间优化,在DEBUG模式下可一定程度防止爆栈,但是和Relase模式拉不开差距
		InsertSort(arr+begin,end-begin+1);	
		return;
	}
	int keyi = partsort1(arr, begin, end);
	//SORT:[begin,keyi)+(keyi,end]
	QuickSort(arr, begin, keyi - 1);//keyi左边都是小的keyi,右边全是大的
	QuickSort(arr, keyi + 1, end);
}

挖坑法

下边放出动图可以很好理解:
在这里插入图片描述
这个方法其实就是霍尔的变种,他的优点呢在于容易理解,写起来的坑少,不容易犯错,核心思想和效率还是一样的。我简单讲一下:
初识位置作为坑,我们直接把他的下标记作hole,值保存为key,然后还是右边找小,找到以后将找到的值填入hole内,hole指向新空出来的位置。然后轮到左边找大,找到以后再填坑,hole改变位置。直到他们相遇结束,将key放入最后的hole。此时又完成了key左边小右边大的功能。然后再递归即可。

下边直接给出实现代码:


int GetMiDe(int* arr, int begin, int end) {
	int midi = (begin + end) / 2;
	if (arr[begin] < arr[midi])
	{
		if (arr[midi] < arr[end])
			return midi;
		else if (arr[begin] > arr[end])
			return begin;
		else
			return end;
	}
	else
	{
		if (arr[midi] > arr[end])
			return midi;
		else if (arr[begin] < arr[end])
			return begin;
		else
			return end;
	}
}
int partsort2(int* arr, int begin, int end) {//挖洞法
	int mid = GetMiDe(arr, begin, end);
	swap(arr + mid, arr+begin);

	int key = arr[begin];
	int holei = begin;
	int left = begin, right = end;
	while (left < right) {
		while (arr[right] >= key && left < right) {
			right--;
		}
		arr[holei] = arr[right];
		holei = right;
		while (arr[left] <= key && left < right) {
			left++;
		}
		arr[holei] = arr[left];
		holei = left;
	}
	arr[holei] = key;
	return holei;
}
void QuickSort(int* arr, int begin,int end) {
	if (begin >= end)return;
	if (end - begin+1 <= 10) {//小区间优化,在DEBUG模式下可一定程度防止爆栈,但是和Relase模式拉不开差距
		InsertSort(arr+begin,end-begin+1);	
		return;
	}
	int keyi = partsort2(arr, begin, end);
	//SORT:[begin,keyi)+(keyi,end]
	QuickSort(arr, begin, keyi - 1);//keyi左边都是小的keyi,右边全是大的
	QuickSort(arr, keyi + 1, end);
}

前后指针法

在这里插入图片描述
核心思想:
这个方法的实现跟霍尔的就有点区别了,但是他们的目的都是让key的左边小右边大而已,所以也不会太难,他实现的代码相对是比较少的。
开始key指向最开始(一个栈帧内不会变),prev指向开始,cur指向prev的下一个,
第一趟开始,cur与key指向的值比较,若小于key则prev自增,再与cur的值交换,换毕,cur再自增再进行比较,符合条件交换,不符合cur继续自增。直到cur越界,此时prev指向的必定是最后一个小于key的值,再将key与prev交换。我们则又可以完成key左小右大的操作了。废话不多说,直接放出源码:

int GetMiDe(int* arr, int begin, int end) {
	int midi = (begin + end) / 2;
	if (arr[begin] < arr[midi])
	{
		if (arr[midi] < arr[end])
			return midi;
		else if (arr[begin] > arr[end])
			return begin;
		else
			return end;
	}
	else
	{
		if (arr[midi] > arr[end])
			return midi;
		else if (arr[begin] < arr[end])
			return begin;
		else
			return end;
	}
}
int partsort3(int* arr, int begin, int end) {//前后指针法
	int mid = GetMiDe(arr, begin, end);
	swap(arr + mid, arr + begin);

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


void QuickSort(int* arr, int begin,int end) {
	if (begin >= end)return;
	if (end - begin+1 <= 10) {//小区间优化,在DEBUG模式下可一定程度防止爆栈,但是和Relase模式拉不开差距
		InsertSort(arr+begin,end-begin+1);	
		return;
	}
	int keyi = partsort2(arr, begin, end);
	//SORT:[begin,keyi)+(keyi,end]
	QuickSort(arr, begin, keyi - 1);//keyi左边都是小的keyi,右边全是大的
	QuickSort(arr, keyi + 1, end);
}

此时使用递归实现快排我们已经基本学会了,下边还有种情况,就是使用非递归如何实现快排呢?

非递归实现思路讲解:

我也不再设置悬念了,直接告诉大家方法,我们可以利用栈结构来模拟实现递归。
递归每次都会分出两个子区间,再对子区间进行划分。我们先将区间的头和尾压入栈内,设置循环,停止条件为空栈,
循环内设计:
先读入begin和end的下标,并弹栈
tip:[begin,keyi-1][keyi+1,end]就是分开的左右两个区间。
接受最后key的下标keyi,若区间合理继续压入栈内新的两个下标。
在这样的循环下,他可以模拟实现递归的操作,记得一个重要的点,栈的操作时先入后出,后入先出。


这样简单的逻辑已经实现,最后记得不要忘记释放栈空间。
实现代码:

typedef int STDataType;
typedef struct Stack {
	STDataType* val;
	int top;
	int capacity;
}Stack;
void InitStack(Stack* ps) {
	assert(ps);
	ps->capacity = 0;
	ps->top = -1;	//Stack is Empty when Top is -1
	ps->val = NULL;
}
void STPush(Stack* ps, STDataType val) {
	assert(ps);
	if (ps->top ==ps->capacity-1) {
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		ps->capacity = newcapacity;
	}	
	STDataType* temp = (STDataType*)realloc(ps->val, sizeof(STDataType) * ps->capacity);
	if (temp == NULL) {
		perror("Realloc faild");
		return;
	}
	ps->val = temp;
	ps->top++;
	ps->val[ps->top] = val;
}
STDataType STTop(Stack* ps) {
	assert(ps);
	assert(ps->top != -1);
	return ps->val[ps->top];
}

void STPop(Stack* ps) {
	assert(ps);
	assert(ps->top != -1);
	ps->top--;
}
bool STEmpty(Stack* ps) {
	return ps->top == -1;
}

void STDestory(Stack* ps) {
	STDataType* temp = ps->val;
	ps->val = NULL;
	free(temp);
	//printf("Stack had free\n");
}


void QuickSortNonR(int arr, int begin, int end) {
	Stack stack;
	Stack* s = &stack;
	InitStack(s);
	STPush(s,begin);
	STPush(s, end);
	while (!STEmpty(s)) {//Stack来控制区间下标,
		int right = STTop(s);
		STPop(s);
		int left = STTop(s);
		STPop(s);

		int keyi = partsort1(arr, left, right);
		if (left < keyi) {
			STPush(s, left);
			STPush(s, keyi - 1);
		}
		if (right > keyi) {
			STPush(s, keyi+1);
			STPush(s, right);
		}
	}
	STDestory(s);
}

END

  • 20
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

栗悟饭&龟波气功

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值