SDNUOJ 1093.DNA排序|排序算法的稳定性/结构体排序

目录

题面

题目分析

解题方法

方法一

AC代码一

方法二

AC代码二

总结

题外话


题面

Description

描述

逆序数可以用来是描述一个序列混乱成度的量。例如,“DAABEC”的逆序数为5,其中D大于它右边的4个数,E大于它右边的1个数,4+1=5;又例如“ZWQM”的逆序数为3+2+1+0=6。
现在有许多长度一样的字符串,每个字符串里面只会出现四种字母(A,T,G,C)。你现在被要求编写程序将这些字符串按照他们的逆序数进行排序。

Input

 输入格式

第一行包括两个正整数,第一个正整数N给出了字符串的长度,第二个正整数M给出了字符串的数量。(1<=N,M<=100)

接下来M行每行一个长度为N的字符串,只包含A,T,G,C四种字母。

Output

输出格式

将输入的字符串按照其逆序数进行排序,如果两个字符串的逆序数相等,则按照输入中两者的先后顺序进行排列。

Sample Input

样例输入

10 6
AACATGAAGG
TTTTGGCCAA
TTTGGCCAAA
GATCAGATTT
CCCGGGGGGA
ATCGATGCAT

Sample Output

样例输出

 

CCCGGGGGGA
AACATGAAGG
GATCAGATTT
ATCGATGCAT
TTTTGGCCAA
TTTGGCCAAA

Hint

选择排序是不稳定的

题目分析

题目中说,要我们计算每个字符串的逆序数,并按逆序数升序输出。

那么现在基本可以确定是个结构体排序题了。

对于每个给出的DNA序列,我们有多种成员属性来描述它,并且要根据成员属性的值来进行排序。

因此可以先写出结构体代码

struct ob
{
    char seq[105];
    int nixv;//逆序数
};

 逆序数的定义也很简洁,两层循环搞定

for(int i = 0; i < len ; i++)
{
    for(int j = i; j < len ; j++)
    {
        if(dna[x].seq[i] > dna[x].seq[j])
        {
            dna[x].nixv++;
        }
    }
}

题中给出的数据范围并不大,因此这题用什么排序都能保证不会TLE和MLE,但是重要的是题意中的一点以及提示

如果两个字符串的逆序数相等,则按照输入中两者的先后顺序进行排列。

选择排序是不稳定

 第一眼看过去云里雾里的……

 要知道,排序算法有稳定与不稳定两种,而不稳定排序有可能导致相等元素出现顺序与排序后顺序不同

 对于几个相等的数字来说,顺序是无法分辨的,但是对于拥有多重属性的结构体来说,问题就大了。

 因为不同的DNA序列任然有可能具有相等的逆序数,如果你使用 C++ 的 <algorithm> 库中自带的   sort() 函数(本质上是改良了的快速排序,仍然是不稳定的),贸然用一个关键词进行排序并不是明智的选择,这可能导致先出现的序列A在排序后出现在和它具有相同逆序数的序列B后面(无情wa掉hhh)。

至此,这道题的全部已经被我们挖掘出来,其实是双关键字结构体排序,一个是逆序数(代码中   nixv),另一个是出现顺序(代码中 sn)。

struct ob
{
    char seq[105];
    int sn;
    int nixv;
}

解题方法

方法一

既然是双关键字结构体排序,那就写一个对应的 cmp 函数

bool cmp(ob x, ob y)
{
    if(x.nixv == y.nixv)//注意:排序的优先级还是逆序数更高
    {
        return x.sn < y.sn;
    }else
    {
        return x.nixv < y.nixv;
    }
}

用这个排序规则来使用C++的<algorithm>库中自带的 sort 函数,就不会有任何问题

AC代码一

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;

struct ob
{
    char seq[105];
    int sn;
    int nixv;
}dna[105];
ob dna2[105];

bool cmp(ob x, ob y)
{
    if(x.nixv == y.nixv)
    {
        return x.sn < y.sn;
    }else
    {
        return x.nixv < y.nixv;
    }
}

int len,cnt;

int main()
{
    scanf("%d%d", &len, &cnt);
    getchar();
    for(int i = 0; i < cnt ; i++)
    {
        cin >> dna[i].seq;
        dna[i].sn = i;
        for(int j = 0; j < len ; j++)
        {
            for(int k = j; k < len ; k++)
            {
                if(dna[i].seq[j] > dna[i].seq[k])
                {
                    dna[i].nixv += 1;
                }
            }
        }
    }

    sort(dna, dna + cnt, cmp);

    for(int i = 0; i < cnt ; i++)
    {
        cout << dna[i].seq << endl;
    }
    return 0;
}

方法二

前面说过,不稳定的排序算法可能导致相等元素出现顺序与排序后顺序不同,那么,用稳定的排序算法就可以避免了,比如基数排序,归并排序等等。

C++本身有自带的稳定排序函数 stable_sort() ,我这里则选择手写的归并排序模板

AC代码二

#include <cstdio>
#include <iostream>
using namespace std;

struct ob
{
	char seq[105];
	int nixv;
}dna[105];

ob dna2[105];

void sort(ob a[],ob b[], int start, int end)
{
	if (start >= end)
		return;
	int len = end - start, mid = (len / 2) + start;
	int start1 = start, end1 = mid;
	int start2 = mid + 1, end2 = end;
	
	sort(a, b, start1, end1);
	sort(a, b, start2, end2);
	
	int pointer = start;
	while (start1 <= end1 && start2 <= end2)
	{
		b[pointer++] = a[start1].nixv <= a[start2].nixv ? a[start1++] : a[start2++];
	}
	while (start1 <= end1)
	{
		b[pointer++] = a[start1++];
	}
	while (start2 <= end2)
	{
		b[pointer++] = a[start2++];
	}
	for (pointer = start; pointer <= end; pointer++)
	{
		a[pointer] = b[pointer];
	}
}



int len,cnt;

int main()
{
	scanf("%d%d", &len, &cnt);
	getchar();
	for(int i = 0; i < cnt ; i++)
	{
		cin >> dna[i].seq;
		for(int j = 0; j < len ; j++)
		{
			for(int k = j; k < len ; k++)
			{
				if(dna[i].seq[j] > dna[i].seq[k])
				{
					dna[i].nixv += 1;
				}
			}
		}
	}
	
	sort(dna, dna2, 0, cnt - 1);
	
	for(int i = 0; i < cnt ; i++)
	{
		cout << dna[i].seq << endl;
	}
	return 0;
}

总结

不想wa就好好看题!

PS:归并排序此处三目运算符 ’?‘ 前面的 '<=' 替换成 '<',并不会影响排序结果,但是会让归并的排序结果具有不稳定排序算法的性质,即相对位置改变。我因为这个wa了很多次www

while (start1 <= end1 && start2 <= end2)
	{
		b[pointer++] = a[start1].nixv <= a[start2].nixv ? a[start1++] : a[start2++];
	}

题外话

本人是山师ACM队小菜鸡一枚,第一次发博客,如有不足还请在评论中指出。

☆*: .。. o(≧▽≦)o .。.:*☆

  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

虚引力子

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

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

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

打赏作者

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

抵扣说明:

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

余额充值