数据结构:选择排序,快速排序

1.选择排序

直接遍历数组,找出最大值和最小值,记录下标,将最大值和最小值分别与首位交换

但是由于当begin == maxi时,会导致出错,因此需要 if 特殊判断

void Swap(int* a, int* b)
{
	int temp = *a;
	*a = *b;
	*b = temp;
}

void SelectSort(int* a, int n)
{
	int begin = 0;
	int end = n - 1;
	while (begin < end)
	{
        //选出最大最小值的位置
		int maxi = end;
		int mini = begin;
		for (int i = begin; i <= end; i++)
		{
			if (a[end] < a[i])
			{
				maxi = i;
			}

			if (a[begin] > a[i])
			{
				mini = i;
			}
		}
		Swap(&a[begin], &a[mini]);
		if (maxi == begin)
		{
			maxi = mini;
		}
		Swap(&a[end], &a[maxi]);
		begin++;
		end--;
	}
	
}

2.快速排序

快速排序是一种基于二叉树结构的排序

首先将数组的开头作为关键字key,

并设置左右指针left 和 right 分别从头尾开始寻找比key的值大和小的数,(right先走,left后走)

并将二者交换,直到left >= right,

并将key与 left 和 right 所指向的值(比key小)交换,

注:相遇位置一定比key小

原因:

1.L遇到R:R先走,R在比key小的位置停下,L没有找到比key大的数,L与R就会相遇,相遇位置就比key小

2.R遇到L:

(1).第一轮交换过后先交换了则L位置的值一定小于key,R的位置一定大于key,R继续走,找比key小的数,没找到,与L相遇,相遇位置就是L停的位置,一定比key小

(2).第一轮就没找到比key大的数,R与L相遇,此时L还没有开始走,则L在key的位置,相遇位置就是key的位置

这样key的左边就全是比他小的数,key的右边就全是比key大的数,

即key的位置放对了,

之后将数组以已经放好的key为边界分割

进行递归,直到所有递归完成,每一部分都只剩一个节点,就完成了

void QuickSort(int* a, int left, int right)
{
	if (left >= right)
		return;
	
	int begin = left, end = right;

	int keyi = left;
	while (left < right)
	{
		while (left < right && a[right] > a[keyi])
		{
			right--;
		}

		while (left < right && a[left] < a[keyi])
		{
			left++;
		}

		Swap(&a[left], &a[right]);
	}

	Swap(&a[left], &a[keyi]);

	keyi = left;

	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
}

1.缺点

但是,这种快速排序存在一定的缺陷,

数组越接近有序,快速排序的速度越慢,

当数组为有序时,导致每次key都只会移动一位,如图

导致时间复杂度变为O(N^2)

2.时间复杂度优化

1.随机选key

如果将随机值作为key,就可以避免上述问题,

但是如果直接将随机数作为key的话,就需要重新完成单次快速排序(将数组分割为比key大或小)的代码

因此,可以在使用随机数作为key时,可以将选中的key与数组头部交换,就可以复用之前的代码了

void QuickSort(int* a, int left, int right)
{
	if (left >= right)
		return;
	
	int begin = left, end = right;

    //选区间中的随机数做key
	int randi = rand();

	randi %= (right - left + 1);//保证randi的值与区间长度一致
	randi += left;//让randi与区间对齐,保证randi一定在范围内
	Swap(&a[randi], &a[left]);

	int keyi = left;
	while (left < right)
	{
		while (left < right && a[right] > a[keyi])
		{
			right--;
		}

		while (a[left] < a[keyi] && left < right)
		{
			left++;
		}

		Swap(&a[left], &a[right]);
	}

	Swap(&a[left], &a[keyi]);

	keyi = left;

	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
}

2.三数取中选key
    

取随机值的方法不够稳定,也可以选择三数取中法

这是一种将有序和随机综合起来的方法

选出三个数left, mid, right,并从她们中取出大小居中的数

这样就可以尽可能避开较坏的情况

//获取三者中数值居中的
int GetMid(int* a, int left, int right)
{
	int mid = (left + right) / 2;
	if (a[left] > a[mid])
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[left] < a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
	else//a[left] > a[mid]
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[left] > a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
}

void QuickSort(int* a, int left, int right)
{
	if (left >= right)
		return;

	int begin = left, end = right;
	//三数取中
	int midi = GetMid(a, left, right);
	Swap(&midi, left);
	int keyi = left;
	while (left < right)
	{
		while (left < right && a[right] > a[keyi])
		{
			right--;
		}

		while (left < right && a[left] < a[keyi])
		{
			left++;
		}

		Swap(&a[left], &a[right]);
	}

	Swap(&a[left], &a[keyi]);

	keyi = left;

	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
}

3.递归次数优化

由于快速排序的实现依赖递归,而每一次递归都需要建立栈帧,.

而快速排序又有着类似于二叉树的结构,

这样会导致消耗最后一层递归占了所有空间的1/2,

最后的几层数据占据了快速排序的绝大多数空间

而较小的数组区间排序又比较容易且快捷

因此我们可以对较小的区间进行小区间优化

小区间优化可以选择插入排序,可以减少90%左右的递归

4.代码

void QuickSort(int* a, int left, int right)
{
	if (left >= right)
		return;

	//小区间优化
	if (right - left + 1 < 10)
	{
		InsertSort(a + left, right - left + 1);
	}
	else
	{ 
		int begin = left, end = right;
		//三数取中
		int midi = GetMid(a, left, right);
		Swap(&midi, left);
		int keyi = left;
		while (left < right)
		{
			while (left < right && a[right] > a[keyi])
			{
				right--;
			}
	
			while (left < right && a[left] < a[keyi])
			{
				left++;
			}

			Swap(&a[left], &a[right]);
		}

		Swap(&a[left], &a[keyi]);

		keyi = left;

		QuickSort(a, begin, keyi - 1);
		QuickSort(a, keyi + 1, end);
	}
	
}

3.另一种快排

1.优势

代码简单

2.思想

同样设置关键值key

设置双指针prev 和 cur,分别指向数组头和数组头后面的一个节点

如果a[cur] >= key ,++cur

如果a[cur] < key,++prev,交换a[prev]和a[cur],++cur

这样cur和prev之间就是比key大的值(如果还没有遇到比key大的值就是相邻)

直到cur走到最后,再将key和a[prev]交换

这样就将比key大的数和比key小的数分割了

key就到了正确的位置了

后续进行相同的递归就行了

3.代码

void QuickSort2(int* a, int left, int right)
{
	int keyi = left;
	int prev = left, cur = left + 1;

	while (cur <= right)
	{
		if (a[cur] < a[keyi] && ++prev != cur)//如果++prev == cur,则说明两个数都是小于keyi的,就没必要交换了
			Swap(&a[prev], &a[cur]);

		++cur;
	}

	Swap(&a[keyi], &a[prev]);
	keyi = prev;

	QuickSort(a, left, keyi - 1);
	QuickSort(a, keyi + 1, right);
}

4.非递归快速排序

如果递归深度过深,可能会出现问题,因此可以将递归改为非递归

将递归改为非递归可以利用循环或栈(栈符合递归的后进先出的特性)

1.核心

将递归改为非递归的核心是使用栈来存储原本每次递归的区间(left 和 right)

每次都取出栈顶的区间,单次排序

2.代码

void QuickSortNonR(int* a, int left, int right)
{
	ST st;//创建一个栈
	STInit(&st);
	STPush(&st, right);//先让right入栈,则right后出栈,left先出栈
	STPush(&st, left);//在栈中存储区间(left 和 right)
	while (!STEmpty(&st))
	{
		int begin = STTop(&st);// 取出left
		STPop(&st);

		int end = STTop(&st);//取出right
		STPop(&st);

		//单次
		int keyi = begin;
		int prev = begin, cur = end;
		
		while (cur <= end)
		{
			if (a[cur] < a[keyi] && ++prev != cur)
				Swap(&a[prev], &a[cur]);

			++cur;
		}
		Swap(&a[keyi], &a[left]);
		keyi = prev;

		if (keyi + 1 < end)//说明区间内还有两个及以上的值
		{
			STPush(&st, end);
			STPush(&st, begin + 1);
		}

		if (begin < keyi - 1)
		{
			STPush(&st, keyi - 1);
			STPush(&st, begin);
		}
	}

	STDestroy(&st);
}

  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 快速排序是一种高效的排序算法,它的基本思想是通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,然后再分别对这两部分记录继续进行排序,以达到整个序列有序的目的。快速排序的时间复杂度为O(nlogn),是一种比较快速的排序算法。 ### 回答2: 快速排序是一种基于分治法的高效的排序算法,被广泛应用于实际的排序问题中。其核心思想是选取一个枢轴元素将待排序序列分成两个子序列,一个子序列中的所有元素小于枢轴,另一个子序列中的所有元素大于枢轴,然后对这两个子序列分别进行递归排序,最终得到有序序列。 下面我们来分步讲解快速排序算法的实现过程: 1.选择枢轴 在实现快速排序算法时,一般采用三数取中法选取枢轴元素。即从待排序序列的第一个元素,中间元素和最后一个元素中选取中间值作为枢轴元素。 2.划分子序列 选取了枢轴元素后,接下来就需要根据枢轴元素将待排序序列划分成两个子序列。划分时需要从序列两端开始向中间扫描,将小于枢轴元素的元素放到左边的子序列中,大于枢轴元素的元素放到右边的子序列中。当左右两端的扫描相遇时结束扫描,最终得到两个子序列。 3.递归排序 接下来对划分后的两个子序列进行递归排序,重复上述过程,直到子序列中只包含一个元素为止。这时,所有子序列都已排序好,整个序列也就变成有序的了。 需要注意的是,在实现快速排序算法时,应该注意边界问题,尤其是递归过程中的边界条件的设置,否则可能会导致死循环或越界等错误。 快速排序算法的时间复杂度为O(nlogn),是一种十分高效的排序算法。其应用范围也非常广泛,几乎包括了所有需要排序的领域。在实际应用中,我们可以根据需要对快速排序算法进行一定的改进,以满足实际需求。例如,在排序大量数据时,我们可以采用并行化的方法,将待排序序列分成多个子序列并行地进行排序,以提高排序效率。 ### 回答3: 快速排序是一种基于比较的排序算法,它的基本思想是通过一趟排序将待排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据要小,然后再递归地对这两部分数据分别进行快速排序快速排序的时间复杂度为O(nlogn),是目前比较常用的排序算法之一。 快速排序的基本思想是选定一个基准元素,将数组中小于等于基准元素的元素放到其左边,大于基准元素的元素放到其右边,然后递归地对左、右子数组进行快速排序。具体实现时,我们通常选择待排序数组中的第一个元素作为基准元素,将数组从第二个元素开始扫描,把小于等于基准元素的放到左边,大于基准元素的放到右边,最后将基准元素插入到中间位置,这样排序之后基准元素就在它的最终位置上了。 快速排序的关键在于如何把小于等于基准元素的元素放到左边,大于基准元素的元素放到右边。这可以通过一趟扫描来实现,具体实现时可以采用双指针法,即设置一个指针i指向数组的第二个元素,一个指针j指向数组的最后一个元素,然后在数组中查找第一个小于等于基准元素的元素和第一个大于基准元素的元素,交换它们的位置,直到i>=j为止。这样扫描结束后,i所在的位置就是基准元素的位置。 快速排序的递归结束条件是子数组只包含一个元素或为空。这样,每次递归都会使待排序的数组规模减半,排序的复杂度就为O(nlogn)。 总结一下,快速排序是一种高效的排序算法,具有简单、快速、不需要额外的存储空间等优点,但是在最坏情况下的时间复杂度会达到O(n^2),因此需要注意基准元素的选择和算法的实现细节,以避免这种情况的出现。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值