排序篇——递归实现快速排序(hoare版-挖坑法-前后指针版)

目录

前言

一、key?

二、思路及代码实现

1.hoare版

2.挖坑法

3.前后指针版本

总结


前言

        快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法。它会选出一个基准值(key),把它放到正确的位置(排序之后的位置)。


提示:以下是本篇文章正文内容,下面案例可供参考

以下所有排序均以升序为例 

一、key?

        快速排序中有一个基准值,一般在待排序元素的最左边或最右边,看个人喜好。此次以最左边为例做key。把key位置代表的元素放到正确的位置。这是排序的第一步。并且这个位置的将左边比5小,右边比5大。

二、思路及代码实现

1.hoare版

        看这个动图很容易就能看出这个思路,右边先走,找比key小的位置,找到比key小的位置停止,之后左边再走找比key大的位置,找到后两者交换,右边继续走重复上述操作。当两者相遇那么就找到key的位置了,将key的值放到key位置里。(左边找大,右边找小 降序降序)。这是这个排序的单趟排序。这样这个数组比6小的都在左边,比6大的都在右边.

  结论: 左边做key右边先走,右边做key左边先走。  相遇位置一定比key小

        下面就是排序的单趟,这里有一些坑,需要大家注意。

       如果是 1 2 3 4 5 右边找小的情况下right会一种减直到越界访问,所以要带上 left < right。如果是 5 1 2 3 4 左边找小的情况下left会一种加,直到越界访问,所以要带上left < right。同时左边找小的情况下如果写成a[left] == a[key]的那么left再还未走到下一个位置,就和right位置进行了交换,那么key的位置直接被打乱了。左边的位置一定是要走到下一个位置之后再找小。

void quick_sort(int* a, int left, int right)
{
	int key = left;
	while (left < right)
	{
		//右边找小
		while (left < right && a[right] >= a[key])
		{
			right--;
		}
		//左边找大
		while (left < right && a[left] <= a[key])
		{
			left++;
		}
		swap(&a[left], &a[right]);	//	一轮过后进行交换
	}
	swap(&a[key], &a[left]);	//这里,left和right相遇了,进行交换。代码写key和right交换也可以
}

         进行完成单趟之后,key的位置就确定了,后面我们以key分为界限分左右两个部分,根据这两个部分继续把key放在正确的位置,左右再分左右,用递归的方法进行找key,直到排序完成。

        实现递归要满足的两个条件:1.递归有一个界限,当满足这个界限停止递归,进行返回。2.每次向后递归都会接近这个界限。 

        看到单趟排序,和递归实现图,一个就有了一个思路了。那么代码怎么实现?返回递归的界限是什么?怎么递归?

        这个图是不是有点像二叉树。我们递归需要套两个自身的函数。一个左,一个右。先走左边再走右。有点前序遍历的味道。那么递归结束的条件是什么呢?当左右相等,那么只有一个数或者左边的下标大于右边的下标也代表这个数以及放在固定的位置上了,递归结束返回。

void quick_sort(int* a, int left, int right)
{
	if (left >= right)
	{
		return 0;
	}
	int begin = left, end = right;//	储存最左边和右边的值
	int key = left;
	while (left < right)
	{
		//右边找小
		while (left < right && a[right] > a[key])
		{
			right--;
		}
		//左边找大
		while (left < right && a[left] <= a[key])
		{
			left++;
		}
		swap(&a[left], &a[right]);	//	一轮过后进行交换
	}
	swap(&a[key], &a[left]);	//这里,left和right相遇了,进行交换。代码写key和right交换也可以
	key = left;	//left的值代表key的下标,但key现在依然为0所以将left现在的值交给key。
	quick_sort(a, begin, key - 1);//先走左边
	quick_sort(a, key + 1, end);//再走右边
}

        这里的排序还有一个点,key的正确位置越接近中间,越接近满二叉树,越接近满二叉树,左右两边的深度越均匀,越均匀,这个排序的时间越少。假如一串数越接近有序,那么当key 再靠左或靠右的位置上,那么它的深度越深。当这串数越多越明显。所有就有大佬想出了一种方法,我们随机一个位置,让这个位置的值与key的值相交换,那么就可以让排序的深度可能性的更好一些,有的大佬感觉随机选key这个不好,有概率。所以又有了一个叫三数取中。从最左边,最右边,中间去不是最小也不是最大的值。下面是一些优化。

int get_(int* a, int left, int right)//三目取中判断
{
	int middle = (left + right) / 2;
	if (a[left] < a[middle])
	{
		if (a[middle] < a[right])
		{
			return middle;
		}
		else if (a[left] > a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
	else
	{
		if (a[middle] > a[right])
		{
			return middle;
		}
		else if (a[left] < a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
}
void quick_sort(int* a, int left, int right)
{
	if (left >= right)
	{
		return 0;
	}
	int begin = left, end = right;//	储存最左边和右边的值
	int key = left;
	//随机选key
	int randl = left + (rand() % (right - left));
	swap(&a[left], &a[randl]);
	//三目取中
	//int middle = get_(a, left, right);
	//swap(&a[left], &a[middle]);
	while (left < right)
	{
		//右边找小
		while (left < right && a[right] > a[key])
		{
			right--;
		}
		//左边找大
		while (left < right && a[left] <= a[key])
		{
			left++;
		}
		swap(&a[left], &a[right]);	//	一轮过后进行交换
	}
	swap(&a[key], &a[left]);	//这里,left和right相遇了,进行交换。代码写key和right交换也可以
	key = left;	//left的值代表key的下标,但key现在依然为0所以将left现在的值交给key。
	quick_sort(a, begin, key - 1);//先走左边
	quick_sort(a, key + 1, end);//再走右边
}

2.挖坑法

         挖坑法也很好懂。将key值先取出来,形成一个坑位,之后左边找大,右边找小,与上面的交换不同,这里是找到之后将当前位置的值放到坑位中,自己的位置就形成了一个新的坑位。当两者相遇那么把key放到相遇位置的坑(它们相遇一定在坑上相遇)。

        大方向和hoare是一样的,都是比key小的在左边,比key大的在右边。

         这里要主要key代表的是key的值,而hole才代表坑的位置。并且这里的key值也可以像hoare版的那些进行优化,这里就不作过多说明,hoare中有详细讲解。

//挖坑法
void gouge_pit(int* a, int left, int right)
{
	if (left >= right)
	{
		return;
	}
	int begin = left, end = right;

	int hole = left;	//坑的位置值
	int key = a[left];	//保存key的值

	while (left < right )
	{
		while (left < right && a[right] >= key)
		{
			right--;
		}
		a[hole] = a[right];	//将值放进坑里面
		hole = right;	//更新坑
		while (left < right && a[left] <= key)
		{
			left++;
		}
		a[hole] = a[left];
		hole = left;
	}
	a[hole] = key;	//将key放到正确的位置
	gouge_pit(a, begin, hole - 1);
	gouge_pit(a, hole + 1, end);
}

3.前后指针版本

        放置两个指针,可以称之为下标。一前一后,这里我们让cur在前,prev在后。

        cur的作用是找小,比key值大,++cur向前走,走完再次比较,如果cur位置的值比key小那么,++prev之后,cur和prev位置的值进行交换 ,cur在向前走,依此类推,直到cur越界,此时prev位置的值就是key的正确位置。两者进行交换。单趟排序完成。

//前后指针排序
void ahead_after(int* a, int left, int right)
{
	if (left >= right)
	{
		return;
	}
	//随机选key
	int randl = left + (rand() % (right - left));
	swap(&a[left], &a[randl]);

	int key = left;
	int prev = left;
	int cur = left + 1;


	while (cur <= right)
	{
		//r如果满足第一个条件,注意这是前置++prev,把prev加过的值与cur进行比较。
		// 如果我们在同一个位置那么为假跳过if,代码是从右向左走的,这样prev已经加过了,之后在比较。
		if (a[cur] < a[key] && ++prev != cur)
		{
			swap(&a[cur], &a[prev]);
		}
		cur++;
	}
	swap(&a[prev], &a[key]);
	ahead_after(a, left, prev - 1);
	ahead_after(a, prev + 1, right);
}

总结

        1.排序左边做key右边先走,右边做key左边先走。  相遇位置一定比key小

        2.key的正确位置越接近中间,越接近满二叉树,越接近满二叉树,左右两边的深度越均匀,越均匀,这个排序的时间越少。

        3.key的值可以优化,有两种方法。

  • 7
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值