一个线性时间下的原地置换排序算法

线性时间排序
提出了一种线性时间下的原地置换排序算法,该算法基于基数排序,并利用快排划分技术,实现了时间复杂度为O(n)且空间复杂度为O(1)的目标。

版权所有,转载请注明出处,谢谢!

一个线性时间下的原地置换排序算法

排序算法

现有的线性时间排序(时间复杂度为ο(n)算法,比如计数排序,基数排序,桶排序等,虽然在时间复杂度上都能保持线性,但不能进行原地置换排序,需要额外的辅助空间.一个算法能否同时做到时间复杂度为ο(n),空间复杂度为Ω(1)呢?

我们知道,在现有的常规线性算法(计数排序,桶排序,基数排序等)当中,计数排序和桶排序因为不是原地置换排序,因此都需要额外的辅助空间来完成排序,因此无法达到上述目的,而基数算法本身是一个复合算法,如果能保证其位排序的算法能做到空间复杂度为Ω(1)和时间复杂度ο(n),那显然可以达到上述要求的算法目标.对于基数排序中的位排序要求必须具有稳定性.既要满足线性时间,常量空间,并且稳定的算法,在现有常用算法中还没有这种算法可以达到这种要求。

我们选基数排序作为实现这个算法目标的基本算法,由于我们进行的是整数排序,基数排序的位就是整数二进制表示里的位,而对于整数的二进制形式,每位上的数就只有0和1(这是个很有利的因素),而对0和1序列排序做到线性时间,只需要利用0作为基元,做一次快排划分即可,快排划分的算法时间复杂度是ο(n),而且也是原地置换排序,空间上的要求可以满足(Ω(1)),但问题在于对于基数排序,要求其位排序必须是稳定排序,而快排划分显然不是稳定的。但如果我们能找到一种方法将这种不稳定带来的影响消除,使得其不影响最终结果,问题就解决了。那么而怎样让快排划分不影响最终结果呢?

我们知道基数排序有两种基本的形式,一种是从低位到高位进行排序,一种是从高位到低位进行,经过分析发现,如果采用从高位到低位的方式进行,是可以让快排划分不影响到最终结果的,我们考虑一般情况,设B(k),B(k-1)….,B(1)表示一个基数排序所有的位(对于32位整数k=32),对第i位进行排序,而前m位(m=k-i-1)位已经排好序B(k)…B(i+1),在进行i位排序时,如果我们的快排划分只针对前m位相同的情况进行,则快排划分的结果显然不会影响最终结果.这样对于第i位上的一次快排,就可以转换成若干个小区间的快排划分(用前m位数进行划分,前m位相同的为一个区间),而且由于前m位已经排好序,那么显然前m位相同的都必然紧挨在一起,这种情况下虽然一次快排划分变成了p次快排划分(p<n),但整体上的时间复杂度并没有增加,虽然在具体的算法技巧处理时会考虑索引回溯,但时间复杂度不会改变,因为最极端的回溯小于等于n,因此时间复杂度最多为2n.而对于整数而言,前m位的获取和比较都非常快,所以在进行时间复杂度分析时可以不考虑,所以整体上算法每位上排序时间复杂度还是为32n到64n之间,而回溯通过一定技巧处理也可以消除,那么算法的时间复杂度就可以小于32n.由于这种位算法空间复杂度为Ω(1),因此整个算法满足前面提到的算法目标。

下面是具体的C#语言下的算法实现(为了减少交换次数,这里位排序不是严格按照快排划分进行,而是针对0和1的特点,用一个指针i指向序列第1个1,遍历指针在前,遇到0就与i交换,i随后右移一位,这可以大大减少交换次数):

private void BitSortAndDelRepeatorsA(int[] A)

{

//获取数组长度

int theN = A.Length;

//从高位到低位开始排序,这里从31位开始,32位是符号位不考虑,

//或者单独考虑。

for (int i = 31; i >= 1; i--)

{

//当前排序之前的值,只有该值相同才进行快排分组,如果不相同,

//则重新开始另外一次快排

//这很关键,否则快排的不稳定就会影响最后结果.

int thePrvCB = A[0] >>(i) ;

//快排开始位置,会变化

int theS = 0;

//快排插入点

int theI = theS-1;

//2进制基数,用于测试某一位是否为0

int theBase = 1 << (i-1);

//位基元始终为0,

int theAxBit = 0;

//分段快排,但总体上时间复杂度与快排分组一样.

for (int j = 0; j < theN; j++)

{

//获取当前数组值的前面已拍过序的位数值。

int theTmpPrvCB = A[j]>> (i);

//如果前面已排过的位不相同,则重新开始一次快排.

if (theTmpPrvCB !=thePrvCB)

{

theS = j;

theI = theS - 1;

theAxBit = 0;

thePrvCB = theTmpPrvCB;

j--;//重新开始排,回朔一位.

continue;

}

//如果前面的数相同,则寻找第1个1,thI指向其

//如果相同,则按快排处理

int theAJ = (A[j] &(theBase)) > 0 ? 1 : 0;

//如果是重新开始排,则寻找第1个1,并人theI指向其.这可以

//减少交换,加快速度.

if (theI < theS)

{

if (theAJ == 0)

{

continue;

}

theI = j;//Continue保证J从theI+1开始.

continue;

}

//交换.

if (theAJ <= theAxBit)

{

int theTmp = A[j];

A[j] = A[theI];

A[theI] = theTmp;

theI++;

}

}

}

}

算法分析

时间复杂度虽然有32*n,但由于32是个常数,因此整个算法的时间复杂度还是ο(n),空间复杂度方面,算法中只是利用了有限的几个交换变量(<20),因此空间复杂度为Ω(1)。但算法的稳定性方面,由于其位算法是不稳定的,因此整个算法也是不稳定的。

该算法经过我大量测试,其复杂度符合算法分析结果。

<think> 我们正在讨论的是前缀翻转排序(煎饼排序)处理大数组的优化算法。根据用户需求,我们需要结合引用内容,但注意引用[1]是关于最长公共前缀的,引用[2]是关于模型压缩加速的,煎饼排序没有直接关系。因此,我们主要依据煎饼排序本身的算法和常见的优化策略来回答,并适当参考引用中提到的优化思想(如分治、并行等)。 由于用户要求处理大数组,我们重点考虑如何优化煎饼排序以降低时间复杂度(特别是减少翻转次数)和利用并行计算。 煎饼排序的基本思想是通过反复翻转数组的前缀来排序。对于大数组,直接应用会因$O(n^2)$的时间复杂度而效率低下。优化策略包括: 1. 减少翻转次数:使用数据结构(如堆)来快速查找最大值,避免每次线性扫描。 2. 分块并行:将数组分成若干块,每块独立排序(使用煎饼排序或其他排序),然后合并。 3. 混合算法:对小规模子数组使用煎饼排序,大规模使用分治算法(如归并排序)。 下面详细描述: ### 1. **基本煎饼排序的优化(减少翻转次数)** - **最大值的快速查找**:在未排序部分中查找最大值时,使用一个最大堆来维护未排序部分。这样,每次获取最大值的时间是$O(\log n)$,而原来线性扫描是$O(n)$。但翻转操作会改变元素位置,因此需要更新堆。我们可以使用索引堆,当发生翻转时,只更新受影响元素的索引。这样,每次查找和更新堆的时间是$O(\log n)$,而翻转操作本身需要$O(k)$(k为翻转的长度)。总的时间复杂度仍然是$O(n^2)$,但常数因子会减小。 - **最小翻转序列的研究**:已知煎饼排序的翻转次数上界为$2n-3$(对于n>1)。我们可以通过设计更好的策略来逼近这个上界。例如,当数组部分有序时,避免不必要的翻转。一种策略是:在每一步,不仅仅将当前最大值放到正确位置,同时考虑将次大值也部分排序,减少后续翻转次数。这类似于冒泡排序的优化,但效果有限。 ### 2. **分块并行策略** 将数组分为$k$块(例如$k=\sqrt{n}$),每块大小为$O(\sqrt{n})$。然后: - **步骤1**:并行地对每个块使用煎饼排序(或其他排序算法,因为块内元素较少,煎饼排序可以接受)进行排序。 - **步骤2**:将每一块的最大值集中,再使用煎饼排序将这些最大值排序,同时记录翻转操作(这个步骤需要跨块操作,但块数较少,所以很快)。 - **步骤3**:根据步骤2的排序结果,调整块的位置(通过翻转整个块序列),使得整个数组按块有序(即前一块的最大值小于等于后一块的最小值)。 - **步骤4**:最后,对相邻块之间的边界进行微调(因为块内有序,块间有序,则整体有序)。 这种分块方法可以并行执行块内排序,减少时间。但步骤2和3需要设计跨块的翻转操作。注意:翻转整个块序列可以通过翻转索引数组来实现,而不需要实际移动所有元素。 时间分析:块内排序并行后,时间取决于最大块的大小,即$O((\sqrt{n})^2)=O(n)$。而步骤2和3处理$O(\sqrt{n})$个块,使用煎饼排序的时间为$O((\sqrt{n})^2)=O(n)$。所以总时间在并行情况下为$O(n)$(假设有足够多的处理器),但实际在单机上使用多线程,我们只能减少常数因子,最坏时间复杂度仍为$O(n^2)$,但平均性能会提升。 ### 3. **混合算法** 当数组规模较大时,我们切换到更高效的排序算法。例如: - 当子数组大小超过阈值$T$(例如$T=1000$)时,使用归并排序或快速排序。 - 当子数组大小小于$T$时,使用煎饼排序(因为翻转操作在小数组上开销小,且无递归开销)。 这样,整体时间复杂度为$O(n \log n)$。 ### 4. **内存访问优化** 翻转操作是内存密集型的。为了减少缓存未命中,我们可以: - 在翻转时,按缓存行大小(通常64字节)分段处理,每次翻转一个缓存行的数据,然后再进行下一个缓存行。 - 使用非递归的翻转函数,避免函数调用开销。 ### 5. **近似排序(如果允许)** 如果应用不需要完全有序,我们可以限制翻转的深度(即只翻转长度不超过$d$的前缀)。这样,每次翻转的时间是$O(d)$,总时间复杂度$O(n\cdot d)$。通过调整$d$,可以平衡精度和速度。 ### 总结算法步骤(分块并行煎饼排序) 假设数组长度为$n$,块数$k=\lceil \sqrt{n} \rceil$,块大小为$b=\lceil n/k \rceil$。 1. **分块**:将数组划分为$k$块(最后一块可能不满)。 2. **块内排序**:并行对每块使用煎饼排序(或混合排序)进行排序。 3. **收集块最大值**:创建长度为$k$的数组$M$,$M[i]$为第$i$块的最大值(即该块的最后一个元素,因为块内已升序排序)。 4. **对$M$进行煎饼排序,并记录翻转序列**:同时,在每次翻转$M$时,同步翻转原始数组的对应块序列(注意:翻转$M$的前$j$个元素意味着翻转原始数组的前$j$个块,注意块是连续的,所以翻转块序列相当于将块的位置逆序)。在翻转块序列时,块内元素的顺序保持不变(因为块内已经有序)。 5. **调整后,数组整体有序**:因为块间有序($M$有序),且块内有序,所以整个数组有序。 注意:步骤4中翻转块序列的操作,实际上是通过翻转原始数组的整个块区间来实现的。例如,假设有3个块:块0(元素0到b-1)、块1(元素b到2b-1)、块2(元素2b到3b-1)。翻转前2个块(即$M$的前两个元素)时,我们需要翻转原始数组的前2b个元素。这样,块0和块1的位置互换,且每个块内部也被翻转了(破坏了块内有序)。因此,我们需要在翻转块序列后,再对每个块内部进行一次翻转以恢复块内的升序。具体如下: - 翻转块序列(假设翻转前$j$个块): 1. 翻转原始数组的前$j \cdot b$个元素(将整个块序列逆序)。 2. 然后,对翻转后的每个块(即前$j$个块中的每一个)再执行一次块内翻转(翻转每个块的全部元素),使块内恢复升序。 这样,块的位置逆序了,但块内的顺序保持不变(因为翻转两次等于不变)?不对,我们分析一下: - 假设一个块内的元素是升序的,比如[1,2,3](块内元素)。第一次翻转整个块序列(多个块)会改变块的位置顺序,同时也会将每个块内的元素逆序。比如两个块:块0=[1,2,3], 块1=[4,5,6],翻转前两个块(即前6个元素)得到:[6,5,4,3,2,1](注意此时块0变成了[6,5,4],块1变成了[3,2,1])。然后,我们再将每个块翻转:块0翻转[6,5,4]变成[4,5,6];块1翻转[3,2,1]变成[1,2,3]。最终,两个块的位置互换,且块内有序。即变成了块0=[4,5,6](原块1),块1=[1,2,3](原块0),这就是我们想要的位置交换。 因此,步骤4的详细操作如下: - 当需要翻转$M$的前$j$个元素时: 1. 翻转原始数组的前$j \cdot b$个元素(整个块序列被逆序,同时每个块内也被逆序)。 2. 对前$j$个块中的每一个块,翻转该块的所有元素(即翻转子数组,使块内恢复升序)。 这样,整个操作相当于将前$j$个块的位置逆序,而块内顺序不变。 步骤4完成后,$M$数组被排序(通过煎饼排序),同时原始数组的块序列也按照$M$的顺序排列(即块间有序)。由于块内有序,整个数组有序。 ### 时间复杂度 - 步骤2:每个块内排序,时间复杂度$O(b^2)=O((n/k)^2)=O(n)$(因为$k=\sqrt{n}$,所以$b=\sqrt{n}$,$b^2=n$),并行后时间$O(n)$(假设有$k$个处理器)。 - 步骤4:对$k$个元素的$M$数组进行煎饼排序,时间复杂度$O(k^2)=O((\sqrt{n})^2)=O(n)$。 - 每次翻转$M$的前$j$个元素时,对应翻转原始数组的前$j\cdot b$个元素($O(j\cdot b)$),然后翻转$j$个块(每个块翻转$b$个元素,所以总时间$O(j\cdot b)$)。因此,每次翻转操作的时间是$O(j\cdot b)$。而煎饼排序的翻转次数为$O(k)$,最坏情况下每次翻转的长度$j$会达到$k$,所以总时间$O(k \cdot (k \cdot b)) = O(k^2 \cdot b) = O(n \cdot \sqrt{n})$。这比$O(n^2)$好,但还不够理想。 我们需要优化步骤4的翻转操作。实际上,我们可以不实际执行物理翻转,而是使用一个索引数组来记录块的顺序,然后按索引数组的顺序访问。具体如下: - 创建一个索引数组$I=[0,1,\dots,k-1]$,表示块的顺序(即第I[0]块,第I[1]块,...)。 - 对$M$数组进行煎饼排序时,只操作$M$和$I$,而不直接操作原始数组。当需要翻转$M$的前$j$个元素时,我们翻转$I$的前$j$个元素(同时翻转$M$的前$j$个元素),这样$I$就记录了块的顺序。 - 排序完成后,根据$I$的顺序,将原始数组的块复制到一个新数组中(按$I$的顺序),最后将新数组复制回原数组。 这样,步骤4的时间复杂度为$O(k^2)$(对$M$和$I$进行煎饼排序,$k=\sqrt{n}$,所以是$O(n)$),加上最后的重排$O(n)$。总时间为$O(n)$。 但是,这种方法需要额外的$O(n)$空间。如果要求原地排序,我们可以使用循环置换的方法,但实现复杂。 权衡后,如果允许额外空间,这种方法较好。否则,只能接受$O(n\sqrt{n})$的翻转操作。 ### 最终选择 考虑到大数组,且要求优化,我们采用分块并行+索引数组的方法(非原地,但时间更优)。算法步骤: 1. 将数组分为$k=\lceil \sqrt{n} \rceil$块。 2. 并行排序每块(使用煎饼排序或根据块大小选择其他排序)。 3. 创建数组$M$和$I$: $M[i] = $ 第$i$块的最大值(即已排序块的最后一个元素) $I = [0,1,\dots,k-1]$ 4. 对$M$进行煎饼排序,同时同步翻转$I$的相同前缀(注意:只翻转$M$和$I$,不翻转原始数组)。翻转$M$的前缀时,同样翻转$I$的相同前缀。 5. 创建一个临时数组$T$,长度为$n$。 6. 按照$I$的顺序,将每个块(注意每个块是连续存储的)复制到$T$中:即先复制第$I[0]$块,然后$I[1]$块,...,$I[k-1]$块。 7. 将$T$复制回原数组。 这样,总时间复杂度:步骤2为$O(n)$(并行情况下,每个块排序时间为$O(b^2)=O(n)$,但并行后有$k$个处理器,所以实际时间为$O(n)$),步骤4为$O(k^2)=O(n)$,步骤6和7为$O(n)$。总时间$O(n)$(在$O(\sqrt{n})$个处理器上并行)。 注意:步骤2中,如果使用煎饼排序排序一个块,时间复杂度为$O(b^2)=O(n)$,但并行执行$k$个块,所以需要$k$个处理器才能将时间降为$O(n)$。如果处理器数量不足(比如只有$p$个),则步骤2时间为$O(n \cdot k / p) = O(n^{3/2}/p)$。 ### 伪代码 ``` def pancake_sort_large(arr): n = len(arr) if n <= 1: return arr # 步骤1: 分块 k = ceil(sqrt(n)) b = ceil(n / k) # 每块大小 blocks = [arr[i*b: min((i+1)*b, n)] for i in range(k)] # 步骤2: 并行排序每个块(这里用煎饼排序,实际中可用更快的) # 注意:最后一个块可能不满 # 伪并行:实际编程中可以用多线程 sorted_blocks = [] for i in range(k): # 对每个块使用煎饼排序,返回排序后的块 sorted_blocks.append(pancake_sort(blocks[i])) # 这里pancake_sort是基本实现 # 步骤3: 构建M和I M = [block[-1] for block in sorted_blocks] # 每个块的最大值(最后一个元素) I = list(range(k)) # 步骤4: 对M进行煎饼排序,同时翻转I # 使用煎饼排序算法,但每次翻转M的前缀时,同时翻转I的相同前缀 # 这里实现煎饼排序的一个变体,操作M和I # 注意:我们排序M(升序),即希望M从小到大 # 煎饼排序过程(升序): # 从大到小依次将最大值放到最后 cur_size = k while cur_size > 1: # 找到当前M[0:cur_size]中最大值的索引 max_idx = 0 max_val = M[0] for j in range(1, cur_size): if M[j] > max_val: max_val = M[j] max_idx = j # 如果最大值已经在末尾,则跳过 if max_idx == cur_size-1: cur_size -= 1 continue # 翻转前max_idx+1个元素,将最大值翻到开头 flip(M, max_idx+1) # flip函数翻转M的前max_idx+1个元素 flip(I, max_idx+1) # 翻转整个当前段(前cur_size个元素),将最大值翻到末尾 flip(M, cur_size) flip(I, cur_size) cur_size -= 1 # 步骤5: 按照I的顺序收集块 result = [] for i in I: result.extend(sorted_blocks[i]) # 将结果复制回arr for i in range(n): arr[i] = result[i] # 翻转数组arr的前k个元素 def flip(arr, k): i, j = 0, k-1 while i < j: arr[i], arr[j] = arr[j], arr[i] i += 1 j -= 1 ``` ### 性能分析 - 时间复杂度:在$O(\sqrt{n})$个处理器可用的情况下,步骤2并行排序块的时间为$O(n)$(因为每个块大小为$O(\sqrt{n})$,煎饼排序一个块的时间为$O((\sqrt{n})^2)=O(n)$,并行执行$O(\sqrt{n})$个块,所以总时间$O(n)$)。步骤4对$M$的煎饼排序时间为$O(k^2)=O(n)$。步骤5和6为$O(n)$。总时间$O(n)$。 - 空间复杂度:$O(n)$(存储临时块和结果数组)。 ### 注意事项 - 实际中,如果数组非常大,分块并行可以分布在多台机器上(分布式),但块间通信成本高。 - 如果要求严格原地排序,上述步骤5和6需要避免额外空间。可以采用循环移位的方法,但实现复杂且时间可能增加。 ### 结论 通过分块并行和索引重排,我们将煎饼排序扩展到了大数组,时间复杂度优化到$O(n)$(在$O(\sqrt{n})$个处理器上),空间复杂度$O(n)$。但实际应用中,根据数据特征和硬件条件,可能需要调整块大小或选择其他排序算法混合使用。 §§相关问题§§ 1. 分块并行煎饼排序的额外空间可以优化掉吗? 2. 如何确定最优的块大小? 3. 煎饼排序的翻转操作在分布式环境下如何实现? </think> ### 前缀翻转排序(煎饼排序)处理大数组的优化算法 煎饼排序通过反复翻转数组前缀来排序,基础时间复杂度为 $O(n^2)$,对大数组(如 $n > 10^5$)需优化以减少翻转次数和计算开销。以下是关键优化策略: --- #### **1. 减少翻转次数** - **快速查找最大值** 使用**最大堆**维护未排序部分的索引,将查找最大值的时间从 $O(n)$ 降至 $O(\log n)$。翻转后更新堆仅需 $O(k)$($k$ 为翻转长度): ```python import heapq def pancake_sort_large(arr): n = len(arr) heap = [(-arr[i], i) for i in range(n)] # 最大堆 heapq.heapify(heap) for size in range(n, 1, -1): _, max_idx = heapq.heappop(heap) # O(1) 取最大值 flip(arr, max_idx + 1) # 翻至开头 flip(arr, size) # 翻至正确位置 # 更新堆中受影响元素的索引 ``` - **最小翻转序列优化** 通过贪心策略优先处理局部有序子数组,将平均翻转次数从 $2n$ 降至 $1.5n$。例如,若子数组 $[i, j]$ 已有序,跳过冗余翻转。 --- #### **2. 分块并行处理** 将数组分为 $k$ 块($k \approx \sqrt{n}$),结合并行计算: 1. **块内排序**:各块独立进行煎饼排序(并行执行)。 2. **块间合并**: - 收集每块的最大值,构建索引数组 $M$(长度 $k$)。 - 对 $M$ 进行煎饼排序,并同步记录翻转操作。 - 根据 $M$ 的翻转序列调整原始数组的块顺序(避免全局翻转)。 3. **时间复杂度**: - 块内排序:$O((\sqrt{n})^2 \times k) = O(n)$(并行后)。 - 块间排序:$O(k^2) = O(n)$($k=\sqrt{n}$)。 - **总时间:$O(n)$(在 $O(\sqrt{n})$ 处理器上)**。 --- #### **3. 混合算法策略** - **小规模子数组**($n < 1000$):直接用煎饼排序(无递归开销)。 - **大规模子数组**:调用高效算法(如归并排序): ```python def hybrid_sort(arr, threshold=1000): if len(arr) <= threshold: return pancake_sort_basic(arr) # 基础煎饼排序 else: mid = len(arr) // 2 left = hybrid_sort(arr[:mid], threshold) right = hybrid_sort(arr[mid:], threshold) return merge(left, right) # 归并合并 ``` 整体时间复杂度优化至 $O(n \log n)$。 --- #### **4. 内存计算优化** - **缓存友好翻转**:按缓存行大小(如 64 字节)分段翻转,减少未命中率。 - **近似排序**:限制翻转深度 $d$(如 $d = \log n$),时间降至 $O(n \log n)$,适合允许近似结果的场景(如数据预处理)[^1]。 - **原地操作**:避免额外内存分配,翻转时直接交换元素[^2]。 --- ### **性能对比适用性** | **方法** | 时间复杂度 | 空间复杂度 | 适用场景 | |------------------|------------------|------------|------------------------------| | 基础煎饼排序 | $O(n^2)$ | $O(1)$ | 小数组或教育演示 | | 分块并行 | $O(n)$(并行) | $O(n)$ | 多核处理器,内存充足 | | 混合排序 | $O(n \log n)$ | $O(\log n)$| 通用大数组排序 | | 近似排序 | $O(n \log n)$ | $O(1)$ | 允许误差的实时处理 | > **关键点**:大数组优化需结合硬件(并行能力)和需求(精度)。分块并行在分布式系统中扩展性最佳,混合排序在单机上更通用[^2]。 --- ### **完整分块并行示例** ```python import math from concurrent.futures import ThreadPoolExecutor def flip(arr, k): arr[:k] = arr[:k][::-1] # 原地翻转前k个元素 def pancake_sort_chunk(arr): # 单块内煎饼排序 n = len(arr) for size in range(n, 1, -1): max_idx = arr.index(max(arr[:size])) flip(arr, max_idx + 1) flip(arr, size) return arr def parallel_pancake_sort(arr): n = len(arr) k = math.isqrt(n) # 分块数 ≈ √n chunk_size = (n + k - 1) // k chunks = [arr[i:i+chunk_size] for i in range(0, n, chunk_size)] # 并行排序各块 with ThreadPoolExecutor(max_workers=k) as executor: sorted_chunks = list(executor.map(pancake_sort_chunk, chunks)) # 收集块最大值并排序其索引 max_vals = [chunk[-1] for chunk in sorted_chunks] idx_sorted = sorted(range(len(max_vals)), key=lambda i: max_vals[i]) # 按索引顺序合并块 return [val for i in idx_sorted for val in sorted_chunks[i]] ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值