整理科研数据文件
题目分析
给定 个文件名,要求按照"自然排序"规则排序输出。自然排序与普通字典序排序的区别在于:数字部分按数值大小比较而非逐字符比较,例如
Run2 应排在 Run10 前面。
排序规则:
- 将文件名拆分为交替的非数字段和数字段,如
ts010tc12拆分为["ts", "010", "tc", "12"]。 - 逐段比较:两个非数字段按字典序比较;两个数字段按数值比较;类型不同时数字段排在前面。
- 前缀优先:一个文件名是另一个的前缀时,较短的排在前面。
- 稳定性:数值相等的项(如
Run01与Run1)保持原始输入顺序。
思路
分段 + 自定义比较器
核心思想非常直接:先对每个文件名做一次预处理,将其拆分成若干段,然后用自定义比较函数对所有文件名做稳定排序。
拆分过程: 从左到右扫描字符串,遇到连续数字就提取为一个数字段(同时记录其数值),遇到连续非数字就提取为一个字符串段。
比较过程: 对两个文件名的段序列,从第一段开始逐段比较:
- 若两段类型相同:数字段比数值,字符串段比字典序。
- 若类型不同:数字段优先(排在前面)。
- 若所有公共段都相等,段数少的排在前面。
由于题目要求等值项保持输入顺序,使用稳定排序即可。
以样例 1 验证: 三个文件名拆分结果:
ts1tc1->["ts", 1, "tc", 1]ts1tc01->["ts", 1, "tc", 1]ts0tc1->["ts", 0, "tc", 1]
比较 ts0tc1 与 ts1tc1:第一段 "ts" 相同,第二段 ,所以
ts0tc1 排前面。比较 ts1tc1 与 ts1tc01:所有段的值都相等,保持原始顺序,ts1tc1 在 ts1tc01 前面。最终输出 ts0tc1, ts1tc1, ts1tc01,与预期一致。
复杂度
- 时间复杂度:
,其中
为文件名的平均长度,排序进行
次比较,每次比较
- 空间复杂度:
,存储所有文件名的分段结果
代码
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
scanf("%d", &n);
vector<string> names(n);
for (int i = 0; i < n; i++) {
char buf[1001];
scanf("%s", buf);
names[i] = buf;
}
// 将字符串拆分为交替的非数字段和数字段
auto parse = [](const string& s) -> vector<pair<string, long long>> {
vector<pair<string, long long>> segs;
int i = 0, len = s.size();
while (i < len) {
if (isdigit(s[i])) {
int j = i;
while (j < len && isdigit(s[j])) j++;
segs.push_back({s.substr(i, j - i), stoll(s.substr(i, j - i))});
i = j;
} else {
int j = i;
while (j < len && !isdigit(s[j])) j++;
segs.push_back({s.substr(i, j - i), -1}); // -1 表示非数字段
i = j;
}
}
return segs;
};
vector<int> idx(n);
iota(idx.begin(), idx.end(), 0);
vector<vector<pair<string, long long>>> parsed(n);
for (int i = 0; i < n; i++) parsed[i] = parse(names[i]);
stable_sort(idx.begin(), idx.end(), [&](int a, int b) {
auto& sa = parsed[a];
auto& sb = parsed[b];
int m = min(sa.size(), sb.size());
for (int i = 0; i < m; i++) {
bool aDigit = (sa[i].second >= 0);
bool bDigit = (sb[i].second >= 0);
if (aDigit != bDigit) return aDigit; // 数字段排前面
if (aDigit) {
if (sa[i].second != sb[i].second)
return sa[i].second < sb[i].second;
} else {
if (sa[i].first != sb[i].first)
return sa[i].first < sb[i].first;
}
}
return sa.size() < sb.size();
});
for (int i = 0; i < n; i++) printf("%s\n", names[idx[i]].c_str());
return 0;
}
import java.util.*;
import java.io.*;
public class Main {
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(br.readLine().trim());
String[] names = new String[n];
for (int i = 0; i < n; i++) names[i] = br.readLine().trim();
// 预处理:将每个文件名拆分为段
List<List<String>> segs = new ArrayList<>();
List<List<Boolean>> types = new ArrayList<>();
for (int i = 0; i < n; i++) {
List<String> seg = new ArrayList<>();
List<Boolean> typ = new ArrayList<>();
int j = 0, len = names[i].length();
while (j < len) {
if (Character.isDigit(names[i].charAt(j))) {
int k = j;
while (k < len && Character.isDigit(names[i].charAt(k))) k++;
seg.add(names[i].substring(j, k));
typ.add(true);
j = k;
} else {
int k = j;
while (k < len && !Character.isDigit(names[i].charAt(k))) k++;
seg.add(names[i].substring(j, k));
typ.add(false);
j = k;
}
}
segs.add(seg);
types.add(typ);
}
Integer[] idx = new Integer[n];
for (int i = 0; i < n; i++) idx[i] = i;
Arrays.sort(idx, (a, b) -> {
List<String> sa = segs.get(a), sb = segs.get(b);
List<Boolean> ta = types.get(a), tb = types.get(b);
int m = Math.min(sa.size(), sb.size());
for (int i = 0; i < m; i++) {
boolean ad = ta.get(i), bd = tb.get(i);
if (ad != bd) return ad ? -1 : 1;
if (ad) {
long va = Long.parseLong(sa.get(i));
long vb = Long.parseLong(sb.get(i));
if (va != vb) return Long.compare(va, vb);
} else {
int cmp = sa.get(i).compareTo(sb.get(i));
if (cmp != 0) return cmp;
}
}
return sa.size() - sb.size();
});
StringBuilder out = new StringBuilder();
for (int i = 0; i < n; i++) out.append(names[idx[i]]).append('\n');
System.out.print(out);
}
}
import sys
from functools import cmp_to_key
def parse(s):
segs = []
i = 0
while i < len(s):
if s[i].isdigit():
j = i
while j < len(s) and s[j].isdigit():
j += 1
segs.append((True, int(s[i:j]), s[i:j]))
i = j
else:
j = i
while j < len(s) and not s[j].isdigit():
j += 1
segs.append((False, 0, s[i:j]))
i = j
return segs
def compare(a, b):
sa, sb = a[1], b[1]
m = min(len(sa), len(sb))
for i in range(m):
ad, av, at = sa[i]
bd, bv, bt = sb[i]
if ad != bd:
return -1 if ad else 1 # 数字段排前面
if ad: # 都是数字段,比数值
if av != bv:
return -1 if av < bv else 1
else: # 都是字符串段,比字典序
if at != bt:
return -1 if at < bt else 1
if len(sa) != len(sb):
return -1 if len(sa) < len(sb) else 1
return 0
input_data = sys.stdin.read().split('\n')
n = int(input_data[0])
names = []
for i in range(1, n + 1):
s = input_data[i].strip()
names.append((s, parse(s)))
names.sort(key=cmp_to_key(compare))
print('\n'.join(name for name, _ in names))
const readline = require('readline');
const rl = readline.createInterface({ input: process.stdin });
const lines = [];
rl.on('line', line => lines.push(line.trim()));
rl.on('close', () => {
const n = parseInt(lines[0]);
const names = [];
for (let i = 1; i <= n; i++) names.push(lines[i]);
function parse(s) {
const segs = [];
let i = 0;
while (i < s.length) {
if (s[i] >= '0' && s[i] <= '9') {
let j = i;
while (j < s.length && s[j] >= '0' && s[j] <= '9') j++;
segs.push({ isDigit: true, num: parseInt(s.substring(i, j)), text: s.substring(i, j) });
i = j;
} else {
let j = i;
while (j < s.length && !(s[j] >= '0' && s[j] <= '9')) j++;
segs.push({ isDigit: false, num: 0, text: s.substring(i, j) });
i = j;
}
}
return segs;
}
const parsed = names.map(s => parse(s));
const idx = Array.from({ length: n }, (_, i) => i);
idx.sort((a, b) => {
const sa = parsed[a], sb = parsed[b];
const m = Math.min(sa.length, sb.length);
for (let i = 0; i < m; i++) {
if (sa[i].isDigit !== sb[i].isDigit) return sa[i].isDigit ? -1 : 1;
if (sa[i].isDigit) {
if (sa[i].num !== sb[i].num) return sa[i].num - sb[i].num;
} else {
if (sa[i].text !== sb[i].text) return sa[i].text < sb[i].text ? -1 : 1;
}
}
return sa.length - sb.length;
});
const result = [];
for (let i = 0; i < n; i++) result.push(names[idx[i]]);
console.log(result.join('\n'));
});

京公网安备 11010502036488号