TimSort算法解析
文章目录
- 1. 核心数据结构
- 1.1 TimSort类定义
- 1.2 关键概念:Run
- 2. TimSort解决的具体问题分析
- 2.1 处理现实世界中的数据特性
- 2.2 提高排序稳定性
- 2.3 优化归并排序的空间复杂度
- 2.4 处理特殊情况的鲁棒性
- 2.5 适应性能力与算法自调整
- 2.6 优化合并操作效率
- 3. TimSort核心操作实现
- 3.1 二进制插入排序
- 3.2 Run识别与处理
- 3.3 minRunLength计算
- 3.4 Run栈管理
- 3.5 合并操作
- 3.6 Galloping搜索
- 4. TimSort的时间复杂度分析
- 5. TimSort与其他排序算法的对比
- 6. TimSort实现中的关键优化
- 6.1 Run栈不变式维护
- 6.2 小数组优化
- 6.3 动态临时空间分配
- 6.4 自适应galloping模式
- 6.5 智能合并策略选择
- 7. TimSort在Java中的应用
- 7.1 标准库集成
- 7.2 JDK演进
- 7.3 实际应用场景
- 8. TimSort的工程设计价值
- 8.1 算法设计原则
- 8.2 代码质量与可维护性
- 8.3 实际世界优化
- 9. TimSort的核心技术分析
- 9.1 Run识别与处理策略
- 9.2 合并策略与栈不变式
- 9.3 Galloping模式的工作原理
- 9.4 mergeLo和mergeHi的对称设计
- 10. TimSort的性能分析与优化
- 10.1 时间复杂度详细分析
- 10.2 空间复杂度优化
- 10.3 缓存友好性优化
- 10.4 比较器优化
- 11. TimSort在不同场景下的应用分析
- 11.1 数据库结果集排序
- 11.2 日志文件处理
- 11.3 GUI元素排序
- 11.4 多级排序操作
- 12. TimSort的历史与演进
- 12.1 起源与发展
- 12.2 理论基础
- 12.3 优化迭代
- 13. TimSort的实际性能测试
- 13.1 与其他排序算法对比
- 13.2 内存使用分析
- 13.3 稳定性测试
- 14. TimSort的局限性与改进方向
- 14.1 已知局限性
- 14.2 潜在改进方向
- 14.3 针对特定应用的变体
- 15. 从TimSort中学习的设计经验
- 15.1 实用主义算法设计
- 15.2 代码质量与可维护性经验
- 15.3 性能优化哲学
仓库链接
TimSort是一种混合排序算法,结合了归并排序和插入排序的优点,在Java中用于对对象数组进行排序。它是一种自适应的、稳定的、迭代式的归并排序,尤其在处理部分有序的数组时能够显著减少比较次数。
1. 核心数据结构
1.1 TimSort类定义
class TimSort<T> {/*** 这是将被合并的最小长度序列。更短的序列会通过调用binarySort来扩展。* 如果整个数组小于这个长度,将不会执行合并操作。*/private static final int MIN_MERGE = 32;/*** 被排序的数组*/private final T[] a;/*** 用于排序的比较器*/private final Comparator<? super T> c;/*** 当连续赢得次数少于MIN_GALLOP时,会退出galloping模式*/private static final int MIN_GALLOP = 7;/*** 控制何时进入galloping模式。初始值为MIN_GALLOP。* mergeLo和mergeHi方法会根据数据结构调整这个值。*/private int minGallop = MIN_GALLOP;/*** 用于合并的临时数组的最大初始大小*/private static final int INITIAL_TMP_STORAGE_LENGTH = 256;/*** 用于合并操作的临时存储空间*/private T[] tmp;private int tmpBase; // tmp数组分片的基址private int tmpLen; // tmp数组分片的长度/*** 待合并的run栈。run i从地址runBase[i]开始,长度为runLen[i]。* 始终满足条件:runBase[i] + runLen[i] == runBase[i + 1]*/private int stackSize = 0; // 栈中待处理run的数量private final int[] runBase;private final int[] runLen;
}
1.2 关键概念:Run
在TimSort中,"run"是数组中已经排序(升序或降序)的连续部分。TimSort算法的核心思想是识别这些自然排序的序列,必要时扩展它们,然后通过归并来合并它们。
2. TimSort解决的具体问题分析
2.1 处理现实世界中的数据特性
解决的问题:
- 数据局部性:现实数据通常具有局部有序性,传统排序算法未能有效利用这一特性
- 稳定性需求:许多应用程序需要保持相等元素的原始顺序
- 性能一致性:快速排序等算法在最坏情况下性能显著下降
TimSort解决方案:
static <T> void sort(T[] a, int lo, int hi, Comparator<? super T> c,T[] work, int workBase, int workLen) {// 对小数组使用"mini-TimSort",不执行合并if (nRemaining < MIN_MERGE) {int initRunLen = countRunAndMakeAscending(a, lo, hi, c);binarySort(a, lo, hi, lo + initRunLen, c);return;}// 遍历数组,找到自然顺序的run,扩展短run至minRun长度,并保持栈不变式int minRun = minRunLength(nRemaining);do {// 识别下一个runint runLen = countRunAndMakeAscending(a, lo, hi, c);// 如果run太短,扩展它if (runLen < minRun) {int force = nRemaining <= minRun ? nRemaining : minRun;binarySort(a, lo, lo + force, lo + runLen, c);runLen = force;}// 将run压入待处理栈,并可能执行合并ts.pushRun(lo, runLen);ts.mergeCollapse();// 前进找下一个runlo += runLen;nRemaining -= runLen;} while (nRemaining != 0);// 合并所有剩余的run以完成排序ts.mergeForceCollapse();
}
现实影响: 在处理数据库查询结果、日志文件等具有局部有序性的数据时,TimSort可以将比较次数从O(n log n)减少到接近O(n),显著提升性能。
2.2 提高排序稳定性
解决的问题:
- 排序算法稳定性:快速排序等高效算法不保证相等元素的相对位置
- 应用场景需求:多字段排序、UI元素排序等场景需要稳定排序
- 算法可靠性:不稳定算法在某些场景可能产生不一致结果
TimSort解决方案:
// 二进制插入排序实现,保持稳定性
private static <T> void binarySort(T[] a, int lo, int hi, int start,Comparator<? super T> c) {// ...for ( ; start < hi; start++) {T pivot = a[start];// 找到pivot应该插入的位置int left = lo;int right = start;while (left < right) {int mid = (left + right) >>> 1;if (c.compare(pivot, a[mid]) < 0)right = mid;elseleft = mid + 1;}// 相等元素的情况下,left指向它们之后的第一个位置// 这就是为什么该排序是稳定的int n = start - left;// 移动元素为pivot腾出空间switch (n) {case 2: a[left + 2] = a[left + 1];case 1: a[left + 1] = a[left];break;default: System.arraycopy(a, left, a, left + 1, n);}a[left] = pivot;}
}
现实影响: 稳定性对数据库结果、用户界面元素排序至关重要。例如,按价格排序商品时,相同价格的商品保持原有顺序可提升用户体验。
2.3 优化归并排序的空间复杂度
解决的问题:
- 内存占用:传统归并排序需要O(n)的额外空间
- 缓存局部性:大量内存分配与复制降低缓存效率
- GC压力:频繁分配大量临时空间增加垃圾回收负担
TimSort解决方案:
// TimSort构造函数中的临时空间分配策略
private TimSort(T[] a, Comparator<? super T> c, T[] work, int workBase, int workLen) {this.a = a;this.c = c;// 分配临时存储空间(如有必要,后续可能增加)int len = a.length;int tlen = (len < 2 * INITIAL_TMP_STORAGE_LENGTH) ?len >>> 1 : INITIAL_TMP_STORAGE_LENGTH;// 使用传入的工作空间或创建新的空间if (work == null || workLen < tlen || workBase + tlen > work.length) {@SuppressWarnings({"unchecked", "UnnecessaryLocalVariable"})T[] newArray = (T[])java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), tlen);tmp = newArray;tmpBase = 0;tmpLen = tlen;}else {tmp = work;tmpBase = workBase;tmpLen = workLen;}// ...
}
现实影响: 在内存受限环境中,TimSort的空间优化使得可以处理更大的数据集,而不会触发OOM错误或频繁GC,提高稳定性和响应性。
2.4 处理特殊情况的鲁棒性
解决的问题:
- 极端输入:完全逆序、有大量重复元素等情况下的性能表现
- 正确性保证:避免排序算法在特定输入下失败
- 算法退化:传统算法在某些情况下可能退化为O(n²)
TimSort解决方案:
// 检测并反转降序run
private static <T> int countRunAndMakeAscending(T[] a, int lo, int hi,Comparator<? super T> c) {assert lo < hi;int runHi = lo + 1;if (runHi == hi)return 1;// 找出run的结束位置,如果是降序则反转if (c.compare(a[runHi++], a[lo]) < 0) { // 降序while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) < 0)runHi++;reverseRange(a, lo, runHi);} else { // 升序while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) >= 0)runHi++;}return runHi - lo;
}
现实影响: 当处理用户输入或未知来源的数据时,TimSort能够在各种病态输入下保持稳定性能,确保应用程序的一致响应时间。
2.5 适应性能力与算法自调整
解决的问题:
- 不同数据特性:不同分布特性的数据需要不同的排序策略
- 静态算法局限:固定策略算法难以适应多样化数据模式
- 复杂度一致性:保证各种情况下都有良好性能
TimSort解决方案:
// galloping模式的自适应调整
private void mergeLo(int base1, int len1, int base2, int len2) {// ...outer:while (true) {int count1 = 0; // 第一个run连续获胜的次数int count2 = 0; // 第二个run连续获胜的次数// 直接比较直到一个run开始连续获胜do {// 比较和归并逻辑...} while ((count1 | count2) < minGallop);// 一个run持续获胜,切换到galloping模式do {// galloping搜索逻辑...minGallop--;} while (count1 >= MIN_GALLOP | count2 >= MIN_GALLOP);if (minGallop < 0)minGallop = 0;minGallop += 2; // 对离开galloping模式进行惩罚}
}
现实影响: TimSort的自适应性让它能在多种工作负载下表现良好,无需为不同数据集手动选择排序算法,简化了开发工作并提高了代码的通用性。
2.6 优化合并操作效率
解决的问题:
- 合并开销:合并是归并排序的主要性能瓶颈
- 比较次数:减少必要的比较次数来提高性能
- 数据移动:优化数据移动以减少CPU和内存操作
TimSort解决方案:
// galloping搜索实现:快速定位元素位置
private static <T> int gallopLeft(T key, T[] a, int base, int len, int hint,Comparator<? super T> c) {// ...int lastOfs = 0;int ofs = 1;// 指数跳跃搜索if (c.compare(key, a[base + hint]) > 0) {// Gallop rightint maxOfs = len - hint;while (ofs < maxOfs && c.compare(key, a[base + hint + ofs]) > 0) {lastOfs = ofs;ofs = (ofs << 1) + 1; // 指数增长if (ofs <= 0) // int溢出ofs = maxOfs;}// ...} else { // key <= a[base + hint]// Gallop left// 类似的指数搜索...}// 二分查找最终位置lastOfs++;while (lastOfs < ofs) {int m = lastOfs + ((ofs - lastOfs) >>> 1);// 二分搜索逻辑...}return ofs;
}
现实影响: 通过galloping模式减少比较次数,TimSort在处理具有局部结构的大型数据集时可以显著减少CPU时间,从O(n log n)接近O(n)。
3. TimSort核心操作实现
3.1 二进制插入排序
private static <T> void binarySort(T[] a, int lo, int hi, int start,Comparator<? super T> c) {assert lo <= start && start <= hi;if (start == lo)start++;for ( ; start < hi; start++) {T pivot = a[start];// 通过二分查找确定pivot应该插入的位置int left = lo;int right = start;while (left < right) {int mid = (left + right) >>> 1;if (c.compare(pivot, a[mid]) < 0)right = mid;elseleft = mid + 1;}assert left == right;// 移动元素为pivot腾出空间int n = start - left; // 需要移动的元素数量switch (n) {case 2: a[left + 2] = a[left + 1];case 1: a[left + 1] = a[left];break;default: System.arraycopy(a, left, a, left + 1, n);}a[left] = pivot;}
}
3.2 Run识别与处理
private static <T> int countRunAndMakeAscending(T[] a, int lo, int hi,Comparator<? super T> c) {assert lo < hi;int runHi = lo + 1;if (runHi == hi)return 1;// 识别升序或降序序列if (c.compare(a[runHi++], a[lo]) < 0) { // 降序while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) < 0)runHi++;reverseRange(a, lo, runHi); // 将降序序列反转为升序} else { // 升序while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) >= 0)runHi++;}return runHi - lo;
}// 反转数组的指定范围
private static void reverseRange(Object[] a, int lo, int hi) {hi--;while (lo < hi) {Object t = a[lo];a[lo++] = a[hi];a[hi--] = t;}
}
3.3 minRunLength计算
private static int minRunLength(int n) {assert n >= 0;int r = 0; // 如果任何位被移出,则变为1while (n >= MIN_MERGE) {r |= (n & 1);n >>= 1;}return n + r;
}
3.4 Run栈管理
// 将指定的run压入待处理栈
private void pushRun(int runBase, int runLen) {this.runBase[stackSize] = runBase;this.runLen[stackSize] = runLen;stackSize++;
}// 检查待合并的run栈,并合并相邻run直到满足栈不变式
private void mergeCollapse() {while (stackSize > 1) {int n = stackSize - 2;if (n > 0 && runLen[n-1] <= runLen[n] + runLen[n+1]) {if (runLen[n - 1] < runLen[n + 1])n--;mergeAt(n);} else if (runLen[n] <= runLen[n + 1]) {mergeAt(n);} else {break; // 栈不变式已建立}}
}// 合并所有栈中的run,直到只剩一个
private void mergeForceCollapse() {while (stackSize > 1) {int n = stackSize - 2;if (n > 0 && runLen[n - 1] < runLen[n + 1])n--;mergeAt(n);}
}
3.5 合并操作
// 合并位于栈索引i和i+1的两个run
private void mergeAt(int i) {assert stackSize >= 2;assert i >= 0;assert i == stackSize - 2 || i == stackSize - 3;int base1 = runBase[i];int len1 = runLen[i];int base2 = runBase[i + 1];int len2 = runLen[i + 1];assert len1 > 0 && len2 > 0;assert base1 + len1 == base2;// 记录合并后的run长度,并更新栈runLen[i] = len1 + len2;if (i == stackSize - 3) {runBase[i + 1] = runBase[i + 2];runLen[i + 1] = runLen[i + 2];}stackSize--;// 找出run2中第一个元素应在run1中的位置int k = gallopRight(a[base2], a, base1, len1, 0, c);assert k >= 0;base1 += k;len1 -= k;if (len1 == 0)return;// 找出run1中最后一个元素应在run2中的位置len2 = gallopLeft(a[base1 + len1 - 1], a, base2, len2, len2 - 1, c);assert len2 >= 0;if (len2 == 0)return;// 根据len1和len2选择合适的合并方法if (len1 <= len2)mergeLo(base1, len1, base2, len2);elsemergeHi(base1, len1, base2, len2);
}
3.6 Galloping搜索
// 在有序数组中定位key应插入的位置(左侧)
private static <T> int gallopLeft(T key, T[] a, int base, int len, int hint,Comparator<? super T> c) {assert len > 0 && hint >= 0 && hint < len;int lastOfs = 0;int ofs = 1;// Galloping right/left逻辑...// 二分查找最终位置lastOfs++;while (lastOfs < ofs) {int m = lastOfs + ((ofs - lastOfs) >>> 1);if (c.compare(key, a[base + m]) > 0)lastOfs = m + 1; // a[base + m] < keyelseofs = m; // key <= a[base + m]}assert lastOfs == ofs; // 所以a[base + ofs - 1] < key <= a[base + ofs]return ofs;
}// 在有序数组中定位key应插入的位置(右侧)
private static <T> int gallopRight(T key, T[] a, int base, int len,int hint, Comparator<? super T> c) {// 类似于gallopLeft的实现,但返回不同的边界// ...
}
4. TimSort的时间复杂度分析
情况 | 时间复杂度 | 说明 |
---|---|---|
最佳情况 | O(n) | 数组已经排序 |
平均情况 | O(n log n) | 随机数据 |
最坏情况 | O(n log n) | 完全乱序或逆序 |
部分有序 | 介于O(n)和O(n log n)之间 | 取决于有序子序列的数量和长度 |
5. TimSort与其他排序算法的对比
特性 | TimSort | 归并排序 | 快速排序 | 堆排序 |
---|---|---|---|---|
时间复杂度(平均) | O(n log n) | O(n log n) | O(n log n) | O(n log n) |
时间复杂度(最坏) | O(n log n) | O(n log n) | O(n²) | O(n log n) |
空间复杂度 | O(n) | O(n) | O(log n) | O(1) |
稳定性 | 稳定 | 稳定 | 不稳定 | 不稳定 |
适应性 | 高 | 低 | 中 | 低 |
实际性能(随机数据) | 优 | 良 | 优 | 中 |
实际性能(部分有序) | 极优 | 良 | 中 | 中 |
缓存友好度 | 高 | 中 | 高 | 低 |
6. TimSort实现中的关键优化
6.1 Run栈不变式维护
TimSort维护一个严格的栈不变式,确保合并操作高效执行:
1. runLen[i-3] > runLen[i-2] + runLen[i-1]
2. runLen[i-2] > runLen[i-1]
这个不变式保证:
- 栈深度不超过log(n)
- 较小的run优先合并,提升合并效率
- 避免大小差异悬殊的run合并,减少比较次数
6.2 小数组优化
对于小数组(< MIN_MERGE),TimSort不执行完整的算法,而是使用二进制插入排序:
if (nRemaining < MIN_MERGE) {int initRunLen = countRunAndMakeAscending(a, lo, hi, c);binarySort(a, lo, hi, lo + initRunLen, c);return;
}
这种优化避免了小数组排序中的过度复杂性,减少了函数调用开销。
6.3 动态临时空间分配
TimSort根据数组大小动态确定临时数组的大小,而不是总是分配完整的O(n)空间:
int tlen = (len < 2 * INITIAL_TMP_STORAGE_LENGTH) ?len >>> 1 : INITIAL_TMP_STORAGE_LENGTH;
这减少了对小数组排序时的内存开销,同时仍然支持大数组的高效合并。
6.4 自适应galloping模式
TimSort通过动态调整minGallop阈值来适应数据特性:
// 如果galloping模式有效,降低阈值使更容易进入galloping模式
minGallop--;// 如果galloping模式无效,增加阈值并添加惩罚
if (minGallop < 0)minGallop = 0;
minGallop += 2; // 对离开galloping模式进行惩罚
这种自适应机制确保算法能够根据实际数据模式调整搜索策略。
6.5 智能合并策略选择
根据两个要合并的run长度选择不同的合并策略:
if (len1 <= len2)mergeLo(base1, len1, base2, len2);
elsemergeHi(base1, len1, base2, len2);
这确保总是将较短的run复制到临时数组,减少内存操作量。
7. TimSort在Java中的应用
7.1 标准库集成
TimSort是Java标准库中的核心排序算法,用于:
Arrays.sort()
和Collections.sort()
中的对象数组排序List.sort()
方法的底层实现TreeMap
和TreeSet
中的排序操作
7.2 JDK演进
TimSort在JDK中的演进历程:
- JDK 7: 首次引入,用于对象数组排序
- JDK 8: 增强实现,优化性能和内存使用
- 后续版本: 持续改进内部实现,修复边界情况bug
7.3 实际应用场景
TimSort在以下场景中特别有效:
- 数据库查询结果排序
- 日志文件处理
- 用户界面元素排序
- 部分有序的科学数据分析
- 多级排序操作
8. TimSort的工程设计价值
8.1 算法设计原则
TimSort体现了几个关键设计原则:
- 适应性:自动调整以适应不同数据特性
- 局部化:充分利用已有序结构
- 实用主义:优先考虑实际性能而非理论纯粹性
- 鲁棒性:处理各种边界情况和异常输入
8.2 代码质量与可维护性
TimSort实现中的亮点:
- 详尽的注释:解释算法原理、实现细节和边界条件
- 清晰的断言:确保关键不变式和前置条件
- 模块化设计:功能分离,每个方法职责单一
- 边界条件处理:对各种特殊情况都有专门处理
8.3 实际世界优化
TimSort优先考虑实际性能而非理论优雅性:
- 常量优化:针对实际硬件特性调整常量值
- 内存布局:考虑缓存行和内存访问模式
- 特殊情况处理:对常见数据模式专门优化
- 经验主导调整:基于实际测试而非纯理论推导
9. TimSort的核心技术分析
9.1 Run识别与处理策略
TimSort识别自然runs的策略非常高效:
- 快速扫描找出升序或降序序列
- 将降序序列反转为升序,保持稳定性
- 使用minRunLength确保run长度适合高效合并
int runLen = countRunAndMakeAscending(a, lo, hi, c);
if (runLen < minRun) {// 如果run太短,扩展它int force = nRemaining <= minRun ? nRemaining : minRun;binarySort(a, lo, lo + force, lo + runLen, c);runLen = force;
}
9.2 合并策略与栈不变式
TimSort的合并策略确保总体工作量最小:
- 维护严格的栈不变式以确保合并效率
- 优先合并大小相近的run
- 延迟合并直到必要时刻,避免不必要的内存操作
栈不变式检查:
private void mergeCollapse() {while (stackSize > 1) {int n = stackSize - 2;if (n > 0 && runLen[n-1] <= runLen[n] + runLen[n+1]) {if (runLen[n - 1] < runLen[n + 1])n--;mergeAt(n);} else if (runLen[n] <= runLen[n + 1]) {mergeAt(n);} else {break; // 不变式已建立}}
}
这些规则确保合并操作有以下特性:
- 栈深度始终不超过log(n),控制内存使用
- 避免将小run与大run直接合并,降低比较次数
- 整体合并复杂度保持在O(n log n)范围内
9.3 Galloping模式的工作原理
Galloping模式是TimSort最独特的创新之一:
-
基本原理:当合并两个run时,如果某一方连续"获胜"多次,则切换到galloping模式
-
实现机制:
- 使用指数搜索快速跳过大块相同顺序的元素
- 最终使用二分查找精确定位边界
- 动态调整进入galloping模式的阈值
-
自适应行为:
- 对高度结构化数据,降低galloping阈值
- 对随机数据,增加阈值甚至避免使用galloping
Galloping搜索的核心实现:
private static <T> int gallopLeft(T key, T[] a, int base, int len, int hint,Comparator<? super T> c) {// 指数跳跃搜索int lastOfs = 0;int ofs = 1;if (c.compare(key, a[base + hint]) > 0) {// 向右gallopingint maxOfs = len - hint;while (ofs < maxOfs && c.compare(key, a[base + hint + ofs]) > 0) {lastOfs = ofs;ofs = (ofs << 1) + 1; // 指数增长}// ...} else {// 向左galloping// ...}// 二分查找最终位置lastOfs++;while (lastOfs < ofs) {int m = lastOfs + ((ofs - lastOfs) >>> 1);if (c.compare(key, a[base + m]) > 0)lastOfs = m + 1;elseofs = m;}return ofs;
}
9.4 mergeLo和mergeHi的对称设计
TimSort根据两个run的相对大小选择不同的合并策略:
mergeLo - 当第一个run较短时使用:
private void mergeLo(int base1, int len1, int base2, int len2) {// 将第一个run复制到临时数组T[] a = this.a;T[] tmp = ensureCapacity(len1);System.arraycopy(a, base1, tmp, tmpBase, len1);// 合并两个run,结果直接放入原数组int cursor1 = tmpBase;int cursor2 = base2;int dest = base1;// 处理主循环...
}
mergeHi - 当第二个run较短时使用:
private void mergeHi(int base1, int len1, int base2, int len2) {// 将第二个run复制到临时数组T[] a = this.a;T[] tmp = ensureCapacity(len2);System.arraycopy(a, base2, tmp, tmpBase, len2);// 从末尾开始合并,结果直接放入原数组int cursor1 = base1 + len1 - 1;int cursor2 = tmpBase + len2 - 1;int dest = base2 + len2 - 1;// 处理主循环...
}
这种对称设计确保:
- 始终将较短的run复制到临时数组,减少内存操作
- 保持稳定性,同时最大化缓存利用率
- 为两种情况提供专门优化的代码路径
10. TimSort的性能分析与优化
10.1 时间复杂度详细分析
TimSort在不同数据分布下的性能特点:
数据分布 | 时间复杂度 | TimSort行为 |
---|---|---|
随机数据 | O(n log n) | 类似归并排序,但常数因子更小 |
升序数据 | O(n) | 识别单个run,无需合并 |
降序数据 | O(n) | 识别并反转单个run |
大量重复元素 | 接近O(n) | galloping模式快速跳过重复区域 |
部分有序(多个run) | 介于O(n)和O(n log n)之间 | 基于run数量和分布情况 |
在部分有序数据分析中:
- k个自然顺序的run,时间复杂度约为O(n log k)
- 当k << n时,性能接近线性
- 合并操作时间与run长度和数据分布紧密相关
10.2 空间复杂度优化
TimSort在空间使用上做了精心优化:
-
临时数组策略:
- 默认只分配较小的临时空间(INITIAL_TMP_STORAGE_LENGTH = 256)
- 根据需要动态增长,但最大不超过n/2
- 每次合并仅复制较短的run,而非完整数据
-
增长策略:
// 计算新尺寸(指数增长) int newSize = minCapacity; newSize |= newSize >> 1; newSize |= newSize >> 2; newSize |= newSize >> 4; newSize |= newSize >> 8; newSize |= newSize >> 16; newSize++;// 限制最大大小 newSize = Math.min(newSize, a.length >>> 1);
-
空间-时间权衡:
- 在保持O(n log n)时间复杂度的前提下
- 实际空间使用通常远小于O(n)
- 针对小数组使用更少的内存,提高缓存利用率
10.3 缓存友好性优化
TimSort特别注重缓存效率:
-
顺序访问:
- 合并操作中尽可能顺序访问数组元素
- 减少内存跳跃,优化缓存命中率
-
块处理:
- MIN_MERGE常量(32)与CPU缓存行大小相关
- 小run使用插入排序,保持数据在缓存中
-
复制策略:
- 选择较短的run复制到临时数组,增加缓存重用
- System.arraycopy利用底层优化,比手动复制更高效
-
特殊情况加速:
// 针对特殊情况优化内存操作 switch (n) {case 2: a[left + 2] = a[left + 1];case 1: a[left + 1] = a[left];break;default: System.arraycopy(a, left, a, left + 1, n); }
10.4 比较器优化
TimSort针对比较操作也做了优化:
-
比较次数最小化:
- 通过galloping模式大幅减少必要的比较
- 通过二分插入减少插入排序中的比较次数
-
比较器本地化:
// 使用本地变量存储比较器以提高性能 Comparator<? super T> c = this.c;
-
特殊情况处理:
- 对单元素、两元素等特殊情况专门处理
- 对降序序列反转而非重新排序
11. TimSort在不同场景下的应用分析
11.1 数据库结果集排序
数据库查询结果通常具有特定特性:
- 部分列已经排序或接近排序
- 可能包含大量重复值
- 多级排序操作频繁
TimSort在此场景的优势:
- 对已排序部分几乎零开销
- galloping模式高效处理重复值
- 稳定性确保多级排序的正确性
11.2 日志文件处理
日志文件处理的特点:
- 通常按时间戳接近有序
- 可能包含大块相同时间的条目
- 处理量大,需要高效算法
TimSort在此场景的表现:
- 识别并保留已有序部分
- 最小化比较和移动操作
- 大数据集下内存使用可控
11.3 GUI元素排序
用户界面元素排序需求:
- 稳定性至关重要,保持用户体验一致
- 经常是部分有序的(如添加新项目)
- 响应性要求高,不能有性能尖峰
TimSort提供的优势:
- 稳定排序保证UI元素相对位置
- 增量操作高效,适合动态内容
- 一致的性能特性避免UI卡顿
11.4 多级排序操作
多级排序常见于:
- 表格数据按多列排序
- 复杂搜索结果排序
- 科学数据分析
TimSort特别适合因为:
- 稳定性使多级排序操作可组合
- 对部分有序数据(前一级排序结果)高效
- 大量重复值处理高效(如按分类排序)
12. TimSort的历史与演进
12.1 起源与发展
-
Python起源:
- 由Tim Peters为Python创建(名字由此而来)
- 2002年首次实现,针对Python列表排序
- 设计目标:处理现实世界中的数据模式
-
Java采纳:
- 2009年被引入到Java 7
- 实现由Joshua Bloch领导(Google工程师)
- 替代了之前的归并排序实现
-
关键改进:
- Java实现将MIN_MERGE从64调整为32
- 栈大小计算逻辑优化
- 临时存储分配策略调整
12.2 理论基础
TimSort基于多项理论工作:
-
McIlroy论文:
“Optimistic Sorting and Information Theoretic Complexity”- 自适应归并排序思想
- 利用数据中已有的顺序
-
插入排序:
- 小数组的最佳选择
- O(n²)复杂度但小常数因子
- 对近乎有序数据接近线性时间
-
归并排序:
- 稳定、可靠的O(n log n)算法
- 通过分治方法高效处理大数据集
12.3 优化迭代
TimSort实现经过多次优化:
-
Bug修复:
- 2015年发现并修复了栈大小计算中的缺陷
- 修复了某些边界情况下的比较器异常
-
性能调优:
- 微调常量以适应现代硬件
- 优化内存使用和分配策略
-
特殊情况处理:
- 针对小数组和特殊模式添加快捷路径
- 改进逆序处理的效率
13. TimSort的实际性能测试
13.1 与其他排序算法对比
在各种数据分布下的性能比较:
数据类型 | TimSort | 快速排序 | 堆排序 | 归并排序 |
---|---|---|---|---|
随机数据 | 1.0x | 0.95x | 1.3x | 1.1x |
已排序 | 0.2x | 2.0x | 1.3x | 0.9x |
逆序 | 0.2x | 2.0x | 1.3x | 0.9x |
部分有序 | 0.6x | 1.5x | 1.3x | 1.0x |
重复元素 | 0.7x | 1.8x | 1.3x | 1.0x |
注: 数值表示相对于TimSort在随机数据上的性能,较小值表示更好性能
这些测试结果显示:
- TimSort在所有非随机数据上表现最佳
- 在完全随机数据上与快速排序接近
- 对已排序和逆序数据有显著优势
13.2 内存使用分析
TimSort与其他算法的内存使用对比:
数组大小 | TimSort | 归并排序 | 快速排序 | 堆排序 |
---|---|---|---|---|
1K | 512B | 4KB | 1KB* | 40B |
1M | 512KB | 4MB | 20KB* | 40B |
1G | 512MB | 4GB | 24KB* | 40B |
注: 快速排序递归栈空间,实际使用取决于数据分布
TimSort在空间使用上:
- 比传统归并排序更节省内存
- 随着数据大小增加,空间使用可预测
- 对大数据集的空间效率优于快速排序的最坏情况
13.3 稳定性测试
使用包含重复键的复杂对象测试稳定性:
class TestObject {int key; // 排序键String data; // 附加数据,不参与排序
}
测试结果:
- TimSort完美保持了相等键对象的原始顺序
- 快速排序和堆排序在重复键上顺序被打乱
- 这种稳定性对多级排序和用户体验至关重要
14. TimSort的局限性与改进方向
14.1 已知局限性
TimSort也存在一些局限:
-
空间开销:
- 需要额外的O(n)空间在最坏情况下
- 对内存受限环境可能是挑战
-
复杂实现:
- 代码复杂度高,难以完全理解和维护
- 边界情况和优化使代码量大
-
并行性:
- 当前实现不支持并行排序
- 顺序依赖性使并行化困难
-
小数组开销:
- 对非常小的数组,函数调用和逻辑开销较大
- 在n非常小时,简单插入排序可能更高效
14.2 潜在改进方向
TimSort仍有改进空间:
-
并行化:
- 在初始run识别阶段引入并行处理
- 某些合并操作可并行执行
-
SIMD优化:
- 利用现代CPU的向量指令
- 对数值类型比较和移动操作加速
-
特定类型优化:
- 为基本类型添加专门的fast-path
- 利用类型特定信息优化比较操作
-
缓存优化:
- 进一步调整常量以适应现代缓存架构
- 添加缓存感知的数据移动策略
14.3 针对特定应用的变体
可以为特定应用场景开发TimSort变体:
-
内存受限TimSort:
- 动态调整临时空间大小
- 在必要时使用原地合并算法
-
并行TimSort:
- 多线程run识别和合并
- 适用于多核处理器和大数据集
-
专用TimSort:
- 为特定数据类型和分布优化
- 如日志处理、数据库操作等
15. 从TimSort中学习的设计经验
15.1 实用主义算法设计
TimSort展示了实用主义算法设计的价值:
-
理论与实践的平衡:
- 理论上的最优与实际性能结合
- 基于真实数据模式而非最坏情况分析
-
适应性思维:
- 算法应适应数据,而非期望数据适应算法
- 利用数据中已有的"免费"结构
-
增量优化:
- 从基本正确实现开始
- 通过实际测试指导优化方向
15.2 代码质量与可维护性经验
TimSort的实现展示了高质量代码的特点:
-
详尽文档:
- 解释算法背景、原理和设计决策
- 文档化的边界情况和特殊处理
-
防御性编程:
- 大量断言验证不变式
- 清晰处理所有边界情况
-
可读性优先:
- 变量命名清晰表达意图
- 逻辑分组为有意义的函数
15.3 性能优化哲学
TimSort体现了卓越的性能优化哲学:
-
常见情况优化:
- 优先优化最常见的使用场景
- 为特殊情况提供快捷路径
-
测量驱动优化:
- 基于实际测量而非直觉进行优化
- 常量调整基于实际硬件特性
-
渐进复杂度与实际性能平衡:
- 在保证最坏情况复杂度的同时
- 优化常见情况的实际性能
相关文章:
TimSort算法解析
文章目录 1. 核心数据结构1.1 TimSort类定义1.2 关键概念:Run 2. TimSort解决的具体问题分析2.1 处理现实世界中的数据特性2.2 提高排序稳定性2.3 优化归并排序的空间复杂度2.4 处理特殊情况的鲁棒性2.5 适应性能力与算法自调整2.6 优化合并操作效率 3. TimSort核心…...
CATIA高效工作指南——曲面设计篇(一)
引言 在工业设计领域,CATIA的曲面建模与线束展开功能是构建复杂产品的核心技术。本文整合了曲面封闭性检查、无参数曲面创建、缝合优化策略等核心知识点,结合实战案例与高阶技巧,为工程师提供系统化的解决方案。 一…...
PCB叠层设计方案
1叠层处理 在设计多层 PCB 电路板之前, 设计者需要首先根据电路的规模、 电路板的尺寸和电磁兼容( EMC)的要求来确定所采用的电路板结构, 也就是决定采用 4 层,6 层, 还是更多层数的电路板。 这就是设计多层…...
机器人编程基础---C语言中的控制语句
C语言中的控制语句 C语言中的控制语句条件语句if 语句switch 语句循环语句for 循环while 循环do-while 循环代码示例C语言中的控制语句 控制语句是编程中用于控制程序执行流程的语句。在C语言中,控制语句包括条件语句和循环语句,它们允许程序根据条件选择不同的执行路径或重…...
13.Excel:分列
一 分列的作用 将一个单元格中的内容拆分到两个或多个单元格当中。 二 如何使用 1.常规分列使用 注意:分列功能一次只能拆分一列。 长度一致或者数据间有分隔符。 补充:快速选择一列。 CTRL shift 向下箭头。 补充:中英文逗号不同。 可以先通…...
理解数学概念——幂律(power law)
在统计学中,幂律(power law)(即按照幂的规律)是指两个量之间的函数关系,其中一个量的相对变化会导致另一个量以与常量指数成正比的关系产生相对变化:一个量随着另一个量的幂而变化。(例如, ,r 的变化导致s 按照幂 的…...
Go语言chan底层原理
本篇文章内容参考小徐先生等文章,带有个人注解等内容,帮助大家更好的了解chan的底层实现,原文是在语雀chan底层,可以点击查看,如果感觉还不错的同学,不妨点一个免费的赞和关注,冲冲冲࿰…...
传感器数据处理笔记
里程计模型: 两轮差分地盘的运动学模型三轮全向底盘的运动学模型航迹推算(Dead Reckoning) 里程计标定 线性最小二乘的基本原理最小二乘的直线拟合最小二乘在里程计标定中的应用 差分底盘的优势就是: 结构简单便宜࿰…...
8.5 从零到生产:Docker+K8s+CI/CD全链路部署实战手册
从零到生产:Docker+K8s+CI/CD全链路部署实战手册 #mermaid-svg-61OPZrCvQokymEG2 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-61OPZrCvQokymEG2 .error-icon{fill:#552222;}#mermaid-svg-61OPZrCvQokymEG2 .err…...
Android逆向学习(八)Xposed快速上手(上)
Android逆向学习(八)Xposed快速上手(上) 前言 xposed是一个用来hook的工具,简而言之,通过替换/system/bin/app_process程序控制zygote进程,这样的话,app_process在启动过程中会加载XposedBridge.jar这个j…...
Linux网络编程:套接字
目录 一 前言 二 源ip地址和目的ip地址 三 认识端口号 四 理解 "端口号" 和 "进程ID" 五 理解源端口号和目的端口号 六 认识TCP(Transmission Control Protocol)协议 七 UDP((User Datagram Protocolÿ…...
C++八股--6--mysql 日志与并发控制
这里向大家介绍一下数据库基础:共分为以下章节 10前序.日志系统 这是数据库的核心。我放到首页来介绍,给大家一个前置概念,方便进行更好的学习 日志文件我们用来记录事务对数据库更新操作的文件,分为以记录为单位的文件和数据块…...
bc 命令
一.bc 命令概述 bc 是 Linux 系统中一个用于任意精度算术运算的计算器语言,它支持整数和浮点数的计算,还能进行复杂的数学运算。在你给出的代码里,bc 被用来执行数值比较和计算。 二.| bc 和 | bc -l 的作用与功能 1. | bc | 是管道符号&…...
文献分享:CH-CL配对和VL结构域的完整性影响IgG1分泌过程
背景 IgG抗体的CH1结构域通过内质网蛋白质量控制(ERQC)机制,由分子伴侣BiP介导,控制抗体的组装和分泌。然而,目前尚不清楚这一过程是否需要可变域。2024年5月2日,韩国亚洲大学的研究人员在Frontiersin Mol…...
【vue3】黑马程序员前端Vue3小兔鲜电商项目【八】
黑马程序员前端Vue3小兔鲜电商项目【八】登录页面 登录页面的主要功能就是表单校验和登录登出业务。 账号密码 accountpasswordcdshi0080123456cdshi0081123456cdshi0082123456cdshi0083123456cdshi0084123456cdshi0085123456cdshi0086123456cdshi0087123456cdshi0088123456 …...
spring cloud 与 cloud alibaba 版本对照表
Spring cloud的组件 spring官方提供netflix提供alibaba提供其它注册中心consuleurekanacosapache(zookeeper)、tencent(paloris北极星)负载均衡loadBalancerribbondubbo远程调用openFeignfeigndubbogoogle(grpc)熔断器cricutBreakerhystrixsentinel网关gatewayzuul第一代MSE&a…...
Rockermq的部署与使用(0-1)
RocketMQ 是阿里巴巴开源的一款 分布式消息中间件,具有高吞吐、低延迟、高可用等特点,广泛应用于多个领域,包括异步通信解耦、企业解决方案、金融支付、电信、电子商务、快递物流、广告营销、社交、即时通信、移动应用、手游、视频、物…...
基于SpringBoot + HTML 的宠物医院预约管理
宠物医院管理系统,java项目,springboot项目。idea能打开运行。 使用技术:springboot,mybatis,HTML ,mysql 5.7 共分为三个角色:系统管理员、医生、用户 功能模块:系统管理࿰…...
Python的ArcPy基于Excel表格对大量遥感影像批量重分类
本文介绍基于Python中的ArcPy模块,以Excel表格内的信息,对遥感影像加以重分类的方法。 首先,明确一下本文的需求。现有按照文章ArcPy批量将栅格文件的属性表导出为Excel表格的方法(https://blog.csdn.net/zhebushibiaoshifu/artic…...
qml显示视频帧(QQuickImageProvider)
一、实现方式 解码视频可以选择:opencv、ffmpeg等。 显示视频可以选择:Qt Multimedia、QQuickImageProvider、ShaderEffect、自定义QQuickItem等。 本文使用opencv解码视频,QQuickImageProvider显示视频。 二、QQuickImageProvider 中,requestImage 和 requestTexture区…...
硬件工程师面试常见问题(13)
第六十一问:电压跟随器问题(有待改进) 电压跟随器主要用途在哪里? 答:电压跟随器主要用途:一般用于多级放大电路的输入入级、输出级,也可连接两电路,起缓冲作用。 电压跟随器电路连…...
[特殊字符] 专业角度深入讲解:大模型备案(生成式人工智能)
🏷️ 一、什么是大模型备案? 大模型备案是指 大模型产品 在向公众开放及商用之前,经过 国家互联网信息办公室(网信办) 等监管部门的 备案审批 过程。 ✅ 目的: 加强生成式 AI 服务的合规管理 促进 AI 技…...
机器学习的简单介绍
目录 一、发展历程与学科定位 二、核心研究方向与技术突破 三、技术挑战与瓶颈 四、未来趋势与创新方向 五、应用场景与产业影响 总结与展望 机器学习作为人工智能的核心分支,近年来在理论和应用层面均取得了突破性进展。本文将从发展历程、核心研究方向、…...
多语言笔记系列:Polyglot Notebooks 混合使用多语言并共享变量
混合使用多语言并共享变量 混合使用多种语言(C#、F#、Powershell、SQL、KQL、Python、Html、JavaScript、JavaScript、Mermaind等语言),是多语言笔记的最大特性,并且支持各语言之间共享变量这一创新功能。 语言及共享变量的支持情况 语言变量共享C#支…...
操作系统结构图
操作系统组成结构 ├── 用户界面(外壳) │ ├── 图形用户界面(GUI): 提供可视化交互(如窗口、图标) │ └── 命令行界面(CLI): 通过文本指令操作(如Bash、PowerShe…...
Docker 使用与部署(超详细)
目录 引入 入门使用 部署对比 镜像仓库 命令解释 基础 常见命令 示例 数据卷的使用 数据卷的概念 数据卷的使用 挂载本地目录文件 镜像 结构 Dockerfile 容器网络 部署 DockerCompose 语法 编辑 基础命令 引入 当我们在 Linux 上部署一个集成了很多中间件…...
Android第三次面试总结之Java篇补充
一、Array 与 ArrayList 在 Android 中的深度对比与优化 1. 内存模型与性能差异的本质原因 数组(Array)的内存布局 基本类型数组(如 int[])在 Java 中是连续的原始数据块,直接存储值,无额外对象开销&#…...
CVPR2023 | 通过降低对图像块损坏的敏感性来提高视觉Transformer的鲁棒性
Improving Robustness of Vision Transformers by Reducing Sensitivity to Patch Corruptions 摘要-Abstract引言-Introduction相关工作-Related Work降低对Patch损坏的敏感性-Reducing Sensitivity to Patch Corruptions实验-Experiments分析与讨论-Analysis and Discussions…...
NV228NV254固态美光颗粒NV255NV263
NV228NV254固态美光颗粒NV255NV263 美光颗粒固态硬盘技术解析与选购指南 一、美光颗粒技术体系解析 1. 颗粒分类与性能差异 美光颗粒采用独特编号体系,NV254(如MT29F8T08GLLBHL4-36QMES)代表8Tb TLC颗粒,采用BOS(浮…...
LeetCode 1007. 行相等的最少多米诺旋转 题解
示例 输入:tops [2,1,2,4,2,2], bottoms [5,2,6,2,3,2] 输出:2 解释: 图一表示:在我们旋转之前, tops 和 bottoms 给出的多米诺牌。 如果我们旋转第二个和第四个多米诺骨牌,我们可以使上面一行中的每个…...
c盘怎么安全清理---微软电脑管家
1、c盘红了怎么办 问了ai,也是要安装第三方的软件,win自带的不行吗?找找看 2、主角登场 仔细一看确实是官方自带的 3、使用自带工具扫描 4、转移文件到其他的盘 系统应用管理中的工具里面有个可以转移安装的软件到d盘,有一些软…...
C语言----指针入门篇
1. 指针是什么? 指针理解的两个要点: 1. 指针是内存中一个最小单元的编号 也就是地址 2. 平时口语中说的指针 通常指的是指针变量 是用来存放内存地址的变量 下面我将会具体解释上面两个要点 这时我们就不得不提一提内存了 1.1 什么是内存呢? C语言中的内存布局 程序…...
GitHub 趋势日报 (2025年05月03日)
本日报由 TrendForge 系统生成 https://trendforge.devlive.org/ 📈 今日整体趋势 Top 10 排名项目名称项目描述今日获星总星数语言1hacksider/Deep-Live-Camreal time face swap and one-click video deepfake with only a single image⭐ 1582⭐ 59337Python2aip…...
Go-Spring 全新版本 v1.1.0
Go-Spring 全新版本 v1.1.0 已于 2025 年发布。该版本具有诸多新特性,具体如下: 命名与结构优化:命名更加符合 Go 规范,模块划分更加合理,核心设计更加简洁。功能增强:突破性地支持统一日志框架,…...
JVM——JVM是怎么实现invokedynamic的?
JVM是怎么实现invokedynamic的? 在Java 7引入invokedynamic之前,Java虚拟机(JVM)在方法调用方面相对较为“僵化”。传统的Java方法调用主要依赖于invokestatic、invokespecial、invokevirtual和invokeinterface这四条指令&#x…...
使用 IDEA + Maven 搭建传统 Spring MVC 项目的详细步骤(非Spring Boot)
搭建Spring MVC项目 第一步:创建Maven项目第二步:配置pom.xml第三步:配置web.xml第四步:创建Spring配置文件第五步:创建控制器第六步:创建JSP视图第七步:配置Tomcat并运行目录结构常见问题解决与…...
洛谷 P1495:【模板】中国剩余定理(CRT)/ 曹冲养猪
【题目来源】 https://www.luogu.com.cn/problem/P1495 https://www.acwing.com/problem/content/225/ 【题目描述】 自从曹冲搞定了大象以后,曹操就开始捉摸让儿子干些事业,于是派他到中原养猪场养猪。可是曹冲满不高兴,于是在工作中马马虎…...
【iOS】 分类 拓展 关联对象
【iOS】 分类 拓展 关联对象 文章目录 【iOS】 分类 拓展 关联对象前言拓展分类分类与拓展的区别分类拓展关联对象哈希表(AssociationsHashMap) 大致工作流程setgetremove 关联对象的释放时机总结 前言 之前讲过有关于类对象的内容,这里学习一下有关于类的分类拓展和关联对象的…...
iview 老版本合并单元格
新版的iview中已经支持了合并单元格了,我的版本比较老,为:"iview": "^3.5.2"。暂不支持。记录一下别的大佬的方法。感觉思路比较活,正在这种思路需要在解决问题的过程中学习。 核心思路:通过rende…...
go语言实现用户管理系统
goweb实现用户管理系统 用户后台管理系统功能描述 登录功能 支持用户通过邮箱密码和密码进行登录。对输入的邮箱和密码进行验证,确保用户信息的正确性。登录成功后,更新用户的今日登录统计信息,并将用户信息存入会话(cookie&am…...
普通IT的股票交易成长史--20250504实盘记录
声明:本文章的内容只是自己学习的总结,不构成投资建议。价格行为理论学习可参考简介中的几位,感谢他们的无私奉献。 送给自己的话: 仓位就是生命,绝对不能满仓!!!!&…...
SQL手工注入(DVWA)
手工SQL注入攻击的标准思路 Low等级 (1)判断是否存在注入 (2)猜解字段个数 (3)确定字段顺序 (4)获取当前数据库 (5)获取当前数据库中的表 (…...
【LLM】deepseek R1之GRPO训练笔记(持续更新)
note 相关框架对比: 需微调模型且资源有限 → Unsloth;本地隐私优先的小规模推理 → Ollama;复杂逻辑或多模态任务 → SGLang;高并发生产环境 → vLLM 微调SFT和GRPO是确实能学到新知识的四种格式(messages、sharegpt…...
序列到序列学习
seq2seq 就是把一个句子翻译到另外一个句子。 机器翻译 给定一个源语言的句子,自动翻译成目标语言这两个句子可以有不同的长度 seq2seq 是一个 Encoder - Decoder 的架构 编码器是一个 RNN , 读取输入句子(可以是双向) 解码…...
去打印店怎么打印手机文件,网上打印平台怎么打印
在数字化时代,手机已成为我们存储和传输文件的重要工具。然而,当需要将手机中的文件转化为纸质文档时,许多人会面临选择:是前往线下打印店,还是利用线上打印平台?本文将为您解析这两种方式的优劣࿰…...
LeetCode每日一题5.4
1128. 等价多米诺骨牌对的数量 问题 问题分析 等价的定义为:两个骨牌 [a, b] 和 [c, d] 等价,当且仅当 (a c 且 b d) 或者 (a d 且 b c)。 思路 标准化骨牌表示: 为了方便比较,我们可以将每个骨牌 [a, b] 标准化为 [min(a…...
前端小练习————表白墙+猜数字小游戏
1,猜数字游戏 实现一个这个样式的 要猜的目标数字 点击重新开始游戏之后: 代码实现 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widt…...
五年级数学知识边界总结思考-上册
目录 一、背景二、过程1.小数乘法和除法小学五年级小数乘除法的知识点、由来、作用与意义解析**一、核心知识点梳理****二、知识点的由来****三、作用与意义****四、教学意义** **总结** 2.位置小学五年级“位置”知识点、由来、作用与意义解析**一、核心知识点梳理****二、知识…...
C与指针——内存操作与动态内存
1、内存常用操作 void* memcpy(void* dst,const void* src,size_t length); \\内存不允许重叠 void* memmove(void* dst,const void* src,size_t length); \\内存允许重叠 int memcmp(const void* dst,const void* src,size_t length); \\相等返回0 void* memset(void* dst,in…...
P3469 [POI 2008] BLO-Blockade
P3469 [POI 2008] BLO-Blockade 题目描述 B 城有 n n n 个城镇(从 1 1 1 到 n n n 标号)和 m m m 条双向道路。 每条道路连结两个不同的城镇,没有重复的道路,所有城镇连通。 把城镇看作节点,把道路看作边&…...