Acwing基础算法1.1

目录

    • 第一章
    • 排序
      • 快速排序
      • 快速排序题目
      • 归并排序
    • **二分**
      • 整数二分
      • 浮点数二分

第一章

1.排序
快速: 任意一个分节点:左边数字都比它小,右边都比它大,递归处理
归并:类似于二叉树,先递归,分到最小,然后从最小开始,排序,归并,成为大数组
2.二分(整数,浮点)

排序

快速排序

边界问题:
取数:最左边 最后递归时候不能(l,i-1) (i,r)
右边 (l,j) (j+1,r)
特例:1 2
模板:
很好的推导
运用了递归双指针二分的思想
Acwing基础算法1.1_第1张图片

Acwing基础算法1.1_第2张图片

题目
Acwing基础算法1.1_第3张图片

适用例子:
2
1 2

#include 

using namespace std;

const int N = 1e6 +10;

int n;
int q[N];

void quick_sort(int q[],int l,int r)
{
	if(l>=r) return ;
	
	int x = q[l],i = l-1,j = r+1;//x = q[r]
	//最好x=q[l+r>>1
	while(i<j)//如果是i<=j,例子1 2陷入死循环
	{
		do i++; while(q[i] < x);
		do j--; while(q[j] > x);
		//while(q[i] < x) i++;
        //while(q[j] > x) j–;
		//当q[i]和q[j]都为 x 时, i 和 j 都不会更新,导致 while 陷入死循环
		if(i < j) swap(q[i],q[j]);
	}
	
	quick_sort(q,l,j);//q,l,i-1
	quick_sort(q,j+1,r);//q,i,r
}
int main()
{
	scanf("%d",&n);
	
	for (int i=0;i<n;i++) scanf("%d",&q[i]);
	
	quick_sort(q,0,n-1);
	
	for (int i=0;i<n;i++) printf("%d ",q[i]);
	return 0;	
} 

自己写了一遍

#include 

using namespace std;

const int N = 100000;

int n;
int q[N];

void quick_sort(int q[],int l,int r)
{
    if(l>=r) return;
    int x=q[(l+r)>>1],i=l-1,j=r+1;//取中间节点作为分界点
    while(i<j)
    {
        do i++;while(q[i]<x);//找到大于x的数字,停止右移
        do j--;while(q[j]>x);//找到小于x的数字,停止左移
        if(i<j) swap(q[i],q[j]);//交换
    }
    quick_sort(q,l,j);//递归排序x左右侧的数字
    quick_sort(q,j+1,r);
}
int main()
{
    scanf("%d",&n);
    for(int i=0;i<n;i++) scanf("%d",&q[i]);
    
    quick_sort(q,0,n-1);
    
    for(int i=0;i<n;i++) printf("%d ",q[i]);
    return 0;
}

错误代码错因解析:

void quick_sort(int l, int r){
    if(l >= r)  return;

    int mid = l + r >> 1;
    int i = l - 1, j = r + 1;
    while(i < j){
        do i++; while(a[i] < a[mid]);
        do j--; while(a[j] > a[mid]);
        if(i < j) swap(a[i], a[j]);
    }
    quick_sort(l, j), quick_sort(j + 1, r);
}

mid位置所在的值(比较参考值)会改变
Acwing基础算法1.1_第4张图片
Acwing基础算法1.1_第5张图片
题解1:排序+print

#include 
using namespace std;
const int N = 1e6+10;

int n,k;
int q[N];

void quick_sort(int l,int r)
{
    //特殊情况
    if(l>=r) return;
    //分出左右
    int x=q[l+r>>1],i=l-1,j=r+1;
    while(i<j)
    {
        do i++;while(q[i]<x);
        do j--;while(q[j]>x);
        if(i<j) swap(q[i],q[j]);
    }
    //递归处理左右
    quick_sort(l,j);
    quick_sort(j+1,r);
}
int main()
{
    scanf("%d %d",&n,&k);
    for(int i=0;i<n;i++) scanf("%d",&q[i]);
    
    quick_sort(0,n-1);
    
    printf("%d",q[k-1]);
    return 0;
}

题解2:快排+二分

#include 
using namespace std;
const int N = 1e6+10;

int n,k;
int q[N];

void quick_sort(int l,int r)
{
    //特殊情况
    if(l>=r) return;
    //分出左右
    int x=q[l+r>>1],i=l-1,j=r+1;
    while(i<j)
    {
        do i++;while(q[i]<x);
        do j--;while(q[j]>x);
        if(i<j) swap(q[i],q[j]);
    }
    //递归处理左右
    if(k<=j) quick_sort(l,j);//if判断
    else quick_sort(j+1,r);
}
int main()
{
    scanf("%d %d",&n,&k);
    k--;//位置减去
    for(int i=0;i<n;i++) scanf("%d",&q[i]);
    
    quick_sort(0,n-1);
    
    printf("%d",q[k]);
    return 0;
}
void swap(int *a,int i,int j)
{
	int t = a[i];
	a[i] = a[j];
	a[j] = t;
}
void quicksort(int *a,int begin,int end)
{
    if(begin >= end)
	return ;
	int flag = begin;//注意:flag在左边,则从右边开始,反之亦然
	int l = begin + 1;
	int r = end;
	while(l < r)
	{
		while(l<r&&a[r]>a[flag])//找到比flag小的数,然后停下来
		r--;
		while(l<r&&a[l]<a[flag])//找到比flag大的数,然后停下来
		l++;
		if(l<r)//交换两个位置上的数
		swap(a,l,r);
	}
	swap(a,flag,r);//l = r,(注意的原因),此时此位置上的数字一定小于flag,
	//因为是从右边开始先走的
	quicksort(a,begin,r-1);
	quicksort(a,r+1,end);	//递归
} 

快速排序题目

在著名的快速排序中,有一个经典的过程叫做划分。
在此过程中,我们通常选取其中一个元素作为分界值。
将小于分界值的元素移到其左侧,将大于分界值的元素移到其右侧。

给定 N个不同的正整数进行过一次划分后的排列情况。

请你判断,共有多少元素可能是此次划分的分界值。

例如,N=5
,各元素排列为 1,3,2,4,5
,则:
1 可能是分界值,因为它的左侧没有元素,而右侧的元素都比它大。
3一定不是分界值,因为尽管它的左侧的元素都比它小,但是它右侧的 2
也小于它。
2一定不是分界值,因为尽管它的右侧的元素都比它大,但是它左侧的 3
也大于它。
出于类似判断可知 4,5也可能是分界值。
因此,在此样例中,共有 3 个可能的分界值。

输入格式

第一行包含整数 N
第二行包含 N 个不同的正整数。

输出格式

第一行输出可能的分界值数量。

第二行按升序顺序输出所有可能的分界值。

如果分界值数量为 0
,则在输出分界值数量后,输出一个空行即可。

输入样例:

5
1 3 2 4 5

输出样例:

3
1 4 5

最开始的做法:

int sort(int *a,int *b,int n)
{
	int i,l,r,k = 0,p = 0;
	for(i = 0;i < n;i++)
    {
    	k = 0;
    	for(l = i - 1;k == 0&&l >= 0 && l < i;l++)
    	{
    		if(a[l] > a[i])
    		{
    			k = 1;
			}
		}
		for(r = i + 1;k == 0&& r < n;r ++)
		{
			if(a[r] < a[i])
    		{
    			k = 1;
			}
		}
		if(k == 0)
		b[p ++] = a[i];
	}
	return p;
}

超时了,然后

void sort(int *a,int n)
{
    int i;
	int LeftMax[n],RightMin[n];
	LeftMax[0] = 0;
	RightMin[n-1] = 1000000;
	for(i = 1;i < n;i++)	
	    LeftMax[i] = LeftMax[i - 1] > a[i - 1]?LeftMax[i - 1]:a[i - 1];
	for(i = n - 2;i >= 0;i --)
	    RightMin[i] = RightMin[i + 1] < a[i + 1]?RightMin[i + 1]:a[i + 1];
	vector<int> b;
	for(i = 0;i < n;i ++)
	{
		if(a[i] > LeftMax[i]&&a[i] < RightMin[i])
		b.push_back(a[i]);
	}
	int n1 = b.size();
	cout << n1 << endl;
	if(n1 == 0)
	puts(" ");
	else
	{
	  	for(i = 0;i < n1;i ++)
        	cout << b[i] << " ";	
	}
} 

这个题解有点懵:

#include 
#include 
#include 
using namespace std;
const int N=100010;
int a[N],b[N];
int n;
int main()
{
    cin>>n;
    for(int i=0;i<n;i++)
    {
        cin>>a[i];
        b[i]=a[i];
    }
    sort(a,a+n);
    int lmax=0;
    vector<int> res;
    for(int i=0;i<n;i++)
    {
        if(a[i]==b[i]&&a[i]>lmax) res.push_back(a[i]);
        if(b[i]>lmax) lmax=b[i];
    }

    cout<<res.size()<<endl;
    if(res.size()==0) puts("");
    else
    {
        for(int i=0;i<res.size();i++) cout<<res[i]<<" ";
    }
    return 0;
}

//作者:陌若安生
//链接:https://www.acwing.com/solution/content/15523/
//来源:AcWing
//著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

归并排序

相关推导
类似于二叉树
1.递归拆分
2.双指针排序回到原来
运用了递归双指针二分的思想
Acwing基础算法1.1_第6张图片
Acwing基础算法1.1_第7张图片

Acwing基础算法1.1_第8张图片

#include 
using namespace std;
const int N=1e6+10;

int n;
int q[N],res[N];

void merge_sort(int l,int r)
{
    if(l>=r) return;
    int mid = l+r >> 1;
    //递归拆分
    merge_sort(l,mid);
    merge_sort(mid+1,r);
    //双指针排序
    //(1)双指针走
    int i=l,j=mid+1,k=0;
    while(i<=mid&&j<=r)
    {
        if(q[i]<q[j]) res[k++]=q[i++];
        else res[k++] = q[j++];
    }
    //(2)剩余部分不上
    while(i<=mid) res[k++] = q[i++];
    while(j<=r) res[k++] = q[j++];
    //(3)复制到原数组
    for(i=l,j=0;i<=r;i++,j++) q[i] = res[j];
}
int main()
{
    scanf("%d",&n);
    for(int i=0;i<n;i++) scanf("%d",&q[i]);
    
    merge_sort(0,n-1);
    
    for(int i=0;i<n;i++) printf("%d ",q[i]);
    return 0;
}

Acwing基础算法1.1_第9张图片

#include 
using namespace std;
const int N=1e5+10;

int n;
long long cnt=0;
int q[N];

void merge_sort(int l,int r)
{
    //边界条件
    if(l>=r) return ;
    int mid = l+r >> 1;
    //递归分开
    //wrong(1)忘记写递归
    merge_sort(l,mid);
    merge_sort(mid+1,r);
    int i=l,j=mid+1,k=0,res[r-l+1],h;//wrong(2):l-r+1
    //双指针对比
    while(i<=mid&&j<=r)
    {
        if(q[i]<=q[j]) {res[k++] = q[i++];}
        else {res[k++] = q[j++]; cnt+=mid-i+1;}
    }
    //0-mid还剩下没有复制完
    while(i<=mid)
    {
        res[k++] = q[i++];
    }
    //mid+1~r还剩下
    //错误(3):改变了i,j
    while(j<=r)
    {
        res[k++] = q[j++];
    }
    for(i=l,j=0;i<=r;i++,j++) q[i]=res[j];
}
int main()
{
    scanf("%d",&n);
    for(int i=0;i<n;i++) scanf("%d",&q[i]);
    
    merge_sort(0,n-1);
    
    printf("%lld",cnt);
    return 0;
}

二分

写的特别好的优质文章

整数二分

前提:单调
(1)防止溢出:
int mid = l + ((r - l) / 2);
Acwing基础算法1.1_第10张图片

Acwing基础算法1.1_第11张图片
Acwing基础算法1.1_第12张图片
题目:
Acwing基础算法1.1_第13张图片
模板:

#include 
using namespace std;
const int N=1e6+10;
int n;
int q[N];

void binary_sort(int x)
{
    //找到左边界
    int mid,l=0,r=n-1;
    while(l<r)
    {
        mid = l+r >>1;
        // if(q[mid]
        // else r=mid;
        if(q[mid]>=x) r=mid;
        else l=mid+1;
    }
    
    if(q[r] != x ) 
    {
        cout<<"-1 -1"<<endl;
        return ;
    }
    cout<<r<<" ";
    
    //找到右边界
    l=0,r=n-1;
    while(l<r)
    {
        mid = l+r+1 >>1;
        if(q[mid]<=x) l=mid;
        else r=mid-1;
    }
    cout<<r<<endl;
}
int main()
{
    int m,x;
    scanf("%d %d",&n,&m);
    for(int i=0;i<n;i++) scanf("%d",&q[i]);
    while(m--)
    {
        scanf("%d",&x);
        binary_sort(x);
    }
    return 0;
}

解析:
二分:

(1) while(l (2)mid = l+r >>1还是mid = l+r+1 >>1?
(3)q[mid] 模板中:2.1配3.1 2.2配3.2
(4)谁是左边界谁是右边界?

找到边界 :
例如找数列1 2 2 3 3 4中左边的2

while(l<r)
    {
        mid = l+r>>1;
        //错误情况1:
        //改成l+r+1:超时无限循环 例如 1 2 2 3 3 4
        //例如:当mid =l+r+1,l=r-1
        //mid=r
        //若满足q[mid]>=x -> r=mid =r
        //r 保持不变,l也是,无限循环
        
        if(q[mid]<x) l=mid+1;
         else r=mid;
         //if-else 内容可以根据判断条件推导
         //因为if语句的条件可以推导出后面的语句
         //然而后面的语句,在l+r+1的时候超时
         //所以L+r+1不能和q[mid]
         //l+r 可以,下面的模板同理
         
         // 上面两句等价于
        //if(q[mid]>=x) r=mid;
        //else l=mid+1;
        
        //讨论第二个:q[mid]
        //q[mid]<=x 时,成立:l=mid 否则:r=mid-1
        //q[mid]<=x时此时情况就成为了错误情况2(解释看下面)
    }

找到右边界 :
例如找数列1 2 2 3 3 4中右边的2

l=0,r=n-1;
    while(l<r)
    {
        mid = l+r+1 >>1;
        //错误情况2
        //改成l+r超时无限循环
        //例如:当mid =l+r,l=r-1
        //mid=l(向下取整)
        //若满足q[mid]<=x -> l=mid =l
        //l 保持不变,r也是,无限循环
        if(q[mid]<=x) l=mid;
        else r=mid-1;
    }

浮点数二分

Acwing基础算法1.1_第14张图片
Acwing基础算法1.1_第15张图片

#include 
#include 
using namespace std;

int main()
{
    double n;
    scanf("%lf",&n);
    double ans=1e-9,mid=0,flag=1;
    //负数
    if(n<0)
    {
        n=fabs(n);
        flag=0;
    }
    double l=0,r;
    //小于1的数字
    n>1.0?r=n:r=1;
    
    //while(fabs(ans-n)>1e-6)
    for(int i=0;i<1000;i++)//改进:while(r-l>1e-6) r=-22 l=22(解决了负数和小数的问题)
    {
        mid = (l+r)/2;
        ans = mid*mid*mid;
        if(ans<n) l=mid;
        else r=mid;
    }
    if(flag==1)
    {
        printf("%.6lf",l);
    }
    else
    {
        printf("-%.6lf",l);
    }
    return 0;
}

你可能感兴趣的:(AcWing算法学习,算法,数据结构)