C++常用排序模板代码及易错分析

例题链接:P1177 【模板】排序

1.选择排序
#include<bits/stdc++.h>
using namespace std;
int n,a[100009];
int main()
{  
    cin>>n;
    for (int i=1;i<=n;i++)
    {
        cin>>a[i];
    }
    for (int i=1;i<n;i++)
    {
        int k=i;
        for (int j=i+1;j<=n;j++)
        {
            if (a[j]<a[k])
            {
                k=j;
            }
        }
        swap(a[k],a[i]);
    }
    for (int i=1;i<=n;i++)
    {
        cout<<a[i]<<' ';
    }
	return 0;
}

可见效率极其低下

2.冒泡排序
#include<bits/stdc++.h>
using namespace std;
int n,a[100009];
int main()
{  
    cin>>n;
    for (int i=1;i<=n;i++)
    {
        cin>>a[i];
    }
    for (int i=n;i>1;i--)
    {
        for (int j=1;j<i;j++)
        {
            if (a[j]>a[j+1])
            {
                swap(a[j],a[j+1]);
            }
        }
    }
    for (int i=1;i<=n;i++)
    {
        cout<<a[i]<<' ';
    }
	return 0;
}

同样效率低下,但是可以优化,若数列有序则退出

#include<bits/stdc++.h>
using namespace std;
int n,a[100009];
int main()
{  
    cin>>n;
    for (int i=1;i<=n;i++)
    {
        cin>>a[i];
    }
    for (int i=n;i>1;i--)
    {
        int flag=1;
        for (int j=1;j<i;j++)
        {
            if (a[j]>a[j+1])
            {
                swap(a[j],a[j+1]);
                flag=0;
            }
        }
        if (flag)
        {
            break;
        }
    }
    for (int i=1;i<=n;i++)
    {
        cout<<a[i]<<' ';
    }
	return 0;
}

性能优化不少

3.插入排序
#include<bits/stdc++.h>
using namespace std;
int n,a[100009];
int main()
{  
    cin>>n;
    for (int i=1;i<=n;i++)
    {
        cin>>a[i];
    }
    int i,j,tem;
    for (i=1;i<=n;i++)
    {
        tem=a[i];
        for (j=i-1;j>=1;j--)
        {
            if (tem<a[j])
            {
                a[j+1]=a[j];
            }
            else
            {
                break;
            }
        }
        a[j+1]=tem;
    }
    for (int i=1;i<=n;i++)
    {
        cout<<a[i]<<' ';
    }
	return 0;
}

相对高效

4.桶排序
#include<bits/stdc++.h>
using namespace std;
int n,x,a[10000009]; 
int main()
{  
	cin>>n;
	for (int i=1;i<=n;i++)
	{
		cin>>x;
		a[x]++;
	}
	for (int i=1;i<=10000000;i++)
	{
		while(a[i])
		{
			cout<<i<<' ';
			a[i]--;
		}
	}
	return 0;
}

桶排序编码简单,速度快,但是只适用于小范围的数据,无法应用于大范围的数据排序,但是可以记录各类数据出现的次数

5.快速排序

思路:每次以区间中点数 mid 为标准,左右各用一根指针,在左区间找第一个 >=mid 的数,右区间找第一个 <=mid 的数,将他两交换,如此重复直至左指针跑到右指针的右边,这样保证了左指针左边的数均 <=mid ,右指针右边的数均 >=mid。接着递归两个区间,重复上述操作。

#include<bits/stdc++.h>
using namespace std;
int n,a[100009];
void qsort(int l,int r)
{
    int i=l,j=r,mid=a[(l+r)/2];
    do
    {
        while(a[i]<mid) i++;
        while(a[j]>mid) j--;
        if (i<=j)
        {
            swap(a[i],a[j]);
            i++;
            j--;
        }
    } while (i<=j);
    if (l<j) qsort(l,j);
    if (i<r) qsort(i,r);
}
int main()
{  
    cin>>n;
    for (int i=1;i<=n;i++)
    {
        cin>>a[i];
    }
    qsort(1,n);
    for (int i=1;i<=n;i++)
    {
        cout<<a[i]<<' ';
    }
	return 0;
}

快速排序实至名归,速度快,但是写法有几个细节

先来看看  ''=''  的细节

#include<bits/stdc++.h>
using namespace std;
int n,a[100009];
void qsort(int l,int r)
{
    int i=l,j=r,mid=a[(l+r)/2];
    do
    {
        while(a[i]<mid) i++;
        while(a[j]>mid) j--;
        if (i<j)//这里等号没了
        {
            swap(a[i],a[j]);
            i++;
            j--;
        }
    } while (i<j);//注意这里等号没了
    if (l<j) qsort(l,j);
    if (i<r) qsort(i,r);
}
int main()
{  
    cin>>n;
    for (int i=1;i<=n;i++)
    {
        cin>>a[i];
    }
    qsort(1,n);
    for (int i=1;i<=n;i++)
    {
        cout<<a[i]<<' ';
    }
	return 0;
}

将 if 和外层 while 处的 = 删除之后结果如下

竟然超时了,why?

不妨试试这组数据

2
0 1

看看vscode的报错

这说明在递归区间 ( i , r ) 时进入了死循环,这是因为只有 0 1 时,条件 i < r 是一直满足的,所以进入了死循环

再看看下一个细节

#include<bits/stdc++.h>
using namespace std;
int n,a[100009];
void qsort(int l,int r)
{
    int i=l,j=r,mid=a[(l+r)/2];
    do
    {
        while(a[i]<=mid) i++;//注意这里多写了 =
        while(a[j]>=mid) j--;//pay attention
        if (i<=j)
        {
            swap(a[i],a[j]);
            i++;
            j--;
        }
    } while (i<=j);
    if (l<j) qsort(l,j);
    if (i<r) qsort(i,r);
}
int main()
{  
    cin>>n;
    for (int i=1;i<=n;i++)
    {
        cin>>a[i];
    }
    qsort(1,n);
    for (int i=1;i<=n;i++)
    {
        cout<<a[i]<<' ';
    }
	return 0;
}

这种写法会导致什么后果?

这种写法下, i 会去寻找第一个大于 mid 的数,j 会寻找第一个小于 mid 的数

试想一下假如你开一个数组 int a[4] ,存入 5 5 5 5 ,则 mid = 5 ,那么,i 会一直找到 4,j 会找到 -1

,但是试图访问 a[4] 或  a[-1] 都是非法的。

6.归并排序

思路:先将数据二分直至一个数据,然后每次选择相邻的两组数据合并,保证左区间数据有序以及右区间数据有序,再从两个区间中按顺序取数,即合并

#include<bits/stdc++.h>
using namespace std;
int n,a[100009],tem[100009];
void msort(int l,int r)
{
    if (l==r) return;
    int mid=(l+r)/2,i=l,j=mid+1,k=l;
    msort(l,mid);
    msort(mid+1,r);
    while(i<=mid&&j<=r)
    {
        if (a[i]<=a[j]) tem[k++]=a[i++];
        else tem[k++]=a[j++];
    }
    while (i<=mid) tem[k++]=a[i++];
    while (j<=r) tem[k++]=a[j++];
    for (int i=l;i<=r;i++) a[i]=tem[i];
}
int main()
{  
    cin>>n;
    for (int i=1;i<=n;i++)
    {
        cin>>a[i];
    }
    msort(1,n);
    for (int i=1;i<=n;i++)
    {
        cout<<a[i]<<' ';
    }
	return 0;
}

效率与快速排序相当

7.堆排序

每次都保证根节点最小就行了,更新的时候每次都取最后一个数是因为要保证这棵树是完全二叉树

#include<bits/stdc++.h>
using namespace std;
int n,a[100009],cnt;
void put(int u)
{
    int fa=u/2;
    while(fa>=1)
    {
        if (a[u]<a[fa])
        {
            swap(a[u],a[fa]);
            u=fa;
            fa=u/2;
        }
        else break;
    }
}
void update()
{
    a[1]=a[cnt--];
    int fa=1,son=fa<<1;
    while(son<=cnt)
    {
        if (son<cnt&&a[son+1]<a[son]) son++;
        if (a[fa]>a[son])
        {
            swap(a[fa],a[son]);
            fa=son;
            son<<=1;
        }
        else break;
    }
}
int main()
{  
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    cin>>n;
    for (int i=1;i<=n;i++)
    {
        cin>>a[i];
        cnt=i;
        put(i);
    }
    for (int i=1;i<=n;i++)
    {
        cout<<a[1]<<" ";
        update();
    }
	return 0;
}

这样写的缺点就是只可以有序输出,但是不可以将排序结果存储在原数组中。

为了解决这种尴尬的处境,还可以这么写:

#include<bits/stdc++.h>
using namespace std;
int n,a[100009];
void update(int up,int down)
{
	int i=up,j=i<<1,x=a[up];
	while(j<=down)
	{
		if (j<down&&a[j]<a[j+1]) j+=1;
		if (a[i]<a[j]) swap(a[i],a[j]);
		else break;
		i=j;
		j=i<<1;
	}
}
int main()
{  
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    cin>>n;
    for (int i=1;i<=n;i++) cin>>a[i];
    for (int i=n/2;i>=1;i--) update(i,n);
    for (int i=n;i>=2;i--)
    {
    	swap(a[i],a[1]);
    	update(1,i-1);
	}
    for (int i=1;i<=n;i++) cout<<a[i]<<" ";
	return 0;
}

其中update(up,down)的作用是从节点up开始向下更新到节点down,保证以up为根的子树为大根堆,最后一个分支节点编号为n/2,这样也就保证了第一个update循环之后a[1]最大。最后一层循环逐个枚举,将最大的数安排到a[n],第二大的数安排到a[n-1],每次都记得update一次

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值