qsort的实现(c语言版)

快排的原理讲解

快速排序是1962年由hoare提出的一种二叉树排序方法,其基本思想是:任取待排序元素中的某一值为基准值,通过基准值将元素序列划分为左右子序列,将大于基准值的元素放到右子序列,将小于基准值的元素放到左子序列,然后左右子序列重复上述过程,直到所有元素都排到相应的位置上为止。

将序列划分为左右子序列常用的方法有三种分别是:
假设按照对arr数组的元素进行升序排列;

int arr[] = { 6,1,2,7,9,3,4,5,10,8 };//定义数组arr

1.hoare版本

1.1原理

在数组中取数组的最后一个元素作为基准值,将基准值的保存起来,定义一个指针begin指向数组的起始位置,定义一个指针end,指向数组的最后,然后begin开始向前走,当begin所指的数组元素大于基准值,此时begin停止,end向前走,当end所指的元素小于基准值时,end停止,交换begin和end所指的元素,begin继续向前走,end继续向后走,当begin小于等于end时循环结束,然后交换begin所指的元素和基准值,返回此时数组的下标也就是基准值。
1.2代码实现
int PartQsort1(int * a,int begin,int end)
{
	int key = end;//记录基准值的位置
	while (begin < end)//循环结束的条件
	{
		while (a[begin] <= a[key] && begin <end)//防止begin在移动的过程中大于end
		{
			begin++;//如果从数组begin开始的位置找到大于等于基准值的值
		}
		while (a[end] >= a[key] && begin < end)
		{
			end--;//从end开始找到小于等于基准值的值
		}
		Swap(&a[begin], &a[end]);//将大于基准值的值放到右子序列,将小于基准值的值放到左子序列
	}
	Swap(&a[end], &a[key]);//将基准值放到左右子序列的中间
	return begin;//返回基准值的下标
}

1.3图解

在这里插入图片描述

2. 挖坑法

2.1原理

将数组的最后一个元素当做基准值,然后从数组第一个元素开始,begin指向数组的第一个元素,begin向后走,找到大于基准值的元素,填到end所指的数组位置,从end向前找到小于基准值的元素,填到begin所指的位置,当begin小于等于end时结束循环,将基准值填到begin所指的数组位置。

2.1代码实现

int PartQsort3(int* a, int begin, int end)//挖坑法
{
	int key = a[end];//找基准值,并保存
	while (begin < end)
	{
		while(a[begin] < key&&begin<end)//确保begin小于end并且找到大于基准值的数组元素
		{
			begin++;
		}
		a[end] = a[begin];//填坑,将大于基准值的元素填到坑中
		while (a[end] >= key && begin < end)//找到小于基准值的元素
		{
			end--;
		}
		a[begin] = a[end];//填坑
	}
	a[begin] = key;//将基准值填到最后一个坑中
	return begin;//返回基准值所在的数组下标
}

2.3图解

哈哈

3. 前后指针版本

3.1原理

定义指针cur和prev,cur指向begin(开始位置),prev指向begin-1;如果cur指向的值小于标准值,++prev然后交换cur和prev所指的值,cur继续向后走寻找小于基准值的元素,当cur>=end时循环结束,++prev交换prev所指的元素和基准值。

3.2代码实现

int PartQsort2(int* a, int begin, int end)//前后指针法
{
	int cur = begin;
	int prev = begin - 1;
	while (cur < end)
	{
		if (a[cur] < a[end] && ++prev != cur)//如果cur指向的元素小于标准值且与++prev所指向的元素不同则交换cur指向的元素和prev指向的元素
		{
			Swap(&a[prev], &a[cur]);
		}
		cur++;//否则cur向后走
	}
	Swap(&a[++prev], &a[end]);//循环结束后++prev,然后交换prev指向的值和标准值
	return prev;//返回标准值
}

3.3图解

冲冲冲

快排的代码实现

void Swap(int* p1, int* p2)//交换两个数
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
int PartQsort2(int* a, int begin, int end)//前后指针法
{
	int cur = begin;
	int prev = begin - 1;
	while (cur < end)
	{
		if (a[cur] < a[end] && ++prev != cur)//如果cur指向的元素小于标准值且与++prev所指向的元素不同则交换cur指向的元素和prev指向的元素
		{
			Swap(&a[prev], &a[cur]);
		}
		cur++;//否则cur向后走
	}
	Swap(&a[++prev], &a[end]);//循环结束后++prev,然后交换prev指向的值和标准值
	return prev;//返回标准值
}
void QuickSort(int* a, int left, int right)
{
	int div = PartQsort3(a, left, right);//求出分割下标
	if (left >= right)//如果左边序列大于右边序列循环结束
	{
		return;
	}
	//按照基准值堆数组a[left,right]进行划分
	//划分成功后以div为边界将数组分成左右两个子数组
	QuickSort(a, left, div - 1);//递归排列左右两个子区间
	if (div + 1 <= right)
	{
		QuickSort(a, div + 1, right);
	}
}

快排的图解

在这里插入图片描述

快排的优化

优化1

如果用快排进行处理的数据本来就是有序的,这时候再去用快排排序,时间复杂度就会很大,因为每次取的数据都是有序的。如果每次排序取的是最后一个数, 那么每次都只能让取出的最后一个数有序,这样会导致每趟快排就只能排好一个数据,这时候的时间复杂度就会很大,达不到我们预期的效果,因此需要来优化快排,避免出现上面情况,优化的方法是三数取中。具体操作就是每次取基准值时,将数组开始的值(begin所指向的值),数据中间的值(mid所指向的值),数据末尾的值(end所指向的值),进行比较取出中间值作为基准值。

优化1的代码

static int GetMidIndex(int *a, int left, int right)
{
	int mid = (left + right) / 2;
	if (a[left] <a[mid])//求出三个数的中间数
	{
		if (a[left] > a[right])
		{
			return left;
		}
		else if (a[left] < a[right] )
		{
			return right;
		}
	}
	else
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[mid] < a[right] )
		{
			return right;
		}
	}
	return left;
}

优化二

因为每次排序左右区间就要递归调用函数,这样会浪费大量的栈空间,因此在递归到小区间时可以使用插入排序,(因为插入排序在数据有序或者接近有序时,时间复杂度很低),来降低栈空间的消耗。

优化二代码实现

void InsertSort(int* a, int n)
{
	for (int i = 0; i < n - 1; ++i)
	{
		int end = i;
		// 单趟排序:[0, end]有序 end+1位置的值,插入进入,保持它依旧有序
		int tmp = a[end + 1];
		while (end >= 0)
		{
			if (tmp < a[end])
			{
				a[end + 1] = a[end];
				--end;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = tmp;
	}
}
void QuickSort(int* a, int left, int right)
{
	
	if (left >= right)//如果左边序列大于右边序列循环结束
			return;
	if (right - left + 1 > 10)
	{
		int div = PartQsort3(a, left, right);//求出分割下标
		QuickSort(a, left, div - 1);//递归排列左右两个子数组
		if (div + 1 <= right)
			QuickSort(a, div + 1, right);
	}
	else
	{
		InsertSort(*(a + left), right - left + 1);//小区间直接插入排序
	}
	
}

快排的时间复杂度

在没有优化时最好情况下时间复杂度度是O(N*logN),(这时数组是无序的),如果数组是有序的时间复杂度就会变大,如果数组完全有序,那么每次快排就只能将一个数字排成有序,此时的时间复杂度就是O(N*N);这种情况通过优化是可以避免的,因此可以将快排的时间复杂度看成O(N*logN)。

完整代码

void Swap(int* pa, int* pb)
{
	int tmp = *pa;
	*pa = *pb;
	*pb = tmp;
}

// 时间复杂度:O(N^2)  -- 逆序
// 最好 O(N) -- 顺序有序 或 接近顺序有序
void InsertSort(int* a, int n)
{
	for (int i = 0; i < n - 1; ++i)
	{
		int end = i;
		// 单趟排序:[0, end]有序 end+1位置的值,插入进入,保持它依旧有序
		int tmp = a[end + 1];
		while (end >= 0)
		{
			if (tmp < a[end])
			{
				a[end + 1] = a[end];
				--end;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = tmp;
	}
}
int PartQsort3(int* a, int begin, int end)//挖坑法
{
	int tmp = GetMidIndex(a, begin, end);//优化--避免在数组有序情况下进行的效率极低的排序
	Swap(&a[end], &a[tmp]);
	int key = a[end];//找基准值,并保存
	while (begin < end)
	{
		while(a[begin] < key&&begin<end)//确保begin小于end并且找到大于基准值的数组元素
		{
			begin++;
		}
		a[end] = a[begin];//填坑,将大于基准值的元素填到坑中
		while (a[end] >= key && begin < end)//找到小于基准值的元素
		{
			end--;
		}
		a[begin] = a[end];//填坑
	}
	a[begin] = key;//将基准值填到最后一个坑中
	return begin;//返回基准值所在的数组下标
}
void QuickSort(int* a, int left, int right)//快速排序--时间复杂度是(n*logN----N的平方)--优化可以避免出现n的平方
{
	int div = PartQsort3(a, left, right);//求出分割下标
	if (right - left-1 > 10)
	{
		if (left >= right)//如果左边序列大于右边序列循环结束
		{
			return;
		}
		//按照基准值堆数组a[left,right]进行划分
		//划分成功后以div为边界将数组分成左右两个子数组
		QuickSort(a, left, div - 1);//递归排列左右两个子数组
		if (div + 1 <= right)
		{
			QuickSort(a, div + 1, right);
		}
	}
	else
	{
		InsertSort(a + left, right - left + 1);//小区间直接插入排序
	}
}
  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值