排序(快排和归并排序) AND 二分法



一、快速排序

模板

// 例如 int a[n]   n=9
//quick_sort(a,0,n-1) 
void quick_sort(int a[],int l,int r)  
{
   if(l>=r)  //如果传入的数组数据量为1(递归中止条件)或者一个数都没有则直接return
  {
    return;
  }
  int i,j,Q;
  i=l-1;
  j=r+1;
  Q= q[l + r >> 1] //相当于(l+r)/2 
  while(i<j)
  {
   do i++;while(a[i]<Q);
   do j--;while(a[j]>Q);
   if(i<j)
   {
       swap(a[i],a[j]);
   }
  }
  //结束while时下标小于等于j的均为值小于或等于基准数的数
   quick_sort(a,l,j);
   quick_sort(a,j+1,r);
}

原理与基本步骤

在一组数中选取一个基准数,以这个数为基准把小于等于和大于等于它的数分为大小两组,然后对这大小两组进行同样的操作,又选取基准数进行分组,直到一组只有一个数为止;在这个过程中数据就会被有序(从小到大)的排好;
注意一个点:被分成的两组数据中都可能有等于基准数的数,在小于组中该基准数是最大值,在大于组中这些基准数是最小值;那么经过递归对小组们排序后相同的基准数便被排到了一起;还有一件事写递归就必须要有递归结束的条件,这是最先要想好的事情;

基本步骤:选取基准数→ 分组 → 利用递归来再分组
借鉴文章:快排原理

特点

快速排序是一种基于二分思想的算法,平均时间复杂度为 O( n*log2(n) )

但是给你一个本身就有序(或者趋近于有序)的序列,那么你的快速排序就会退化成 O( n^2 )的复杂度,容易TLE

对快排的优化某大佬洛谷P1177快速排序模板题解

( 在做这道题发生了比较不解的事,就是基准数的选取在固定为l(最左边的数)时TLE了,选取为 l + r >> 1时过了。)

二、归并排序

模板

void merge_sort(int a[],int tmp[],int l,int r)
{
	if(l>=r) return;
	int mid=(l+r)/2;
	merge_sort(a,tmp,l,mid);
	merge_sort(a,tmp,mid+1,r);
	int i,j,k;
	k=0;
	i=l;
	j=mid+1;
	while(i<=mid && j<=r)
	{
		if(a[i]>a[j])
		{
		//要使用tmp来暂存排序好的小组数据是因为会有例如a[l++]=a[j++]会引起a[i]值的改变的现象发生;
			tmp[k++]=a[j++];
		}
		else
		{
			tmp[k++]=a[i++];
		}
	}
	while(i<=mid) 	tmp[k++]=a[i++];
	while(j<=r)     tmp[k++]=a[j++];
	
    for (i = l, j = 0; i <= r; i ++, j ++ ) a[i] = tmp[j];  //这里i=l很关键;
	
}

原理与基本步骤

对于一组数据先根据左右边界(L ,R)得mid((L+R)/2)将其分成(L到mid),(mid+1到R)两组数据,再对这两组数据进行重复性操作直到分组的数据量为1时停止。
例如: mid(Rn)由该数组的(左边界+右边界)/2得到
(L,R)
(L,R1) (R1+1,R)
(L,R2) (R2+1,R1) (R1+1,R3) (R3+1,R)

aL a2 …aR
然后后从下到上,从左到右开始对小组数据量不为1的数组进行排序,每次排序根据边界范围和从小到大的排法改变数据在原来数组中的位置。
例如排(L,R1)在此之前会对(L,mid(R2)),(mid+1(R2+1),R1)这两个小组进行了从小到大的排序。即(a[L],a[R2])和(a[R2+1],a[R1])是分别有序的两个区间在a数组中。
排(L,R1)就是在它分出来的两个区间都有序的基础上的合并使得(L,R1)变得有序。
排(L,R)也是同理这样最后就能得到一个有序数组。

在这里插入图片描述
图片来源:归并排序(看这个up主对原理的讲解)

特点

空间复杂度:O(n)
时间复杂度:每一层归并的时间复杂度为O(n),归并层数最多为O(logn+1)则总的时间复杂度为O(nlogn);
特点就是时间复杂度稳定,在洛谷解同样的题用它不用快排稳过,还快;

但是在空间上快排要更占优势。

三、二分法查找

模板

         //二分法找有序数组中k的起始位置和中止位置
        int k;
    	scanf("%d",&k);
    	int l=0;
		int r=n-1;
    	while(l<r)
    	{
    		int mid=(l+r)/2;
    		if(q[mid]>=k) r=mid;
    		else
    		l=mid+1;
		}
    	if(q[l]!=k)
    	{
    		cout<<-1<<-1<<endl; //数组中不存在k
		}
		else
		{
			cout<<l<<" ";
		int l=0;
		int r=n-1;
		while(l<r)
    	{
    		int mid=(l+r+1)/2;
    		if(q[mid]<=k) l=mid;
    		else
    		r=mid-1;
		}
		cout<<l<<endl;
		}
//利用二分法求平方根(浮点数!)
#include <iostream>
using namespace std;
int main()
{
	double l,r,n;
	n=10000;
	l=0;
	//如果n小于1则右边界需要取更大的值1因为例如如果n=0.01,那么二分法需要在0到0.01里找0.1这不可能找到;
	r=1>n?1:n;
	while(r-l>1e-8)
	{
	   double	mid =(l+r)/2;
		if(mid*mid>=n)
		{
			r=mid;
		}
		else
		{
			l=mid;
		}
	}
	//经验法如果精度要求为几位数则r-l值应为精度要求位数加2;
	//例如 保留小数点后6位  r-l值因为1e-8或小于它 
	printf("%lf",l);
	return 0;
}

原理和特点

看这篇文章就够了
要知道二分法是一定有解的;
文章中对于预防边界死循环问题的分析与解决截图;
在这里q插入图片描述

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

秋刀鱼_(:з」∠)_别急

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

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

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

打赏作者

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

抵扣说明:

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

余额充值