无显式返回值时归并排序为何修改列表?含递归栈及Java相关困惑
搞懂Python中mergeSort无返回值却修改原列表的问题
嘿,我完全理解你的困惑——刚接触递归和Python对象模型的时候,这种“没return却改了原列表”的情况确实让人摸不着头脑。咱们一步步拆解清楚:
一、为什么没显式return,原列表还是被修改了?
核心原因在于Python对可变对象的参数传递机制:
- 列表是可变对象,当你把
alist传给mergeSort函数时,函数拿到的是原列表的引用(不是整个列表的副本)。 - 在merge阶段(就是代码里把左右子列表合并的部分),我们直接通过
alist[k] = ...这样的语句修改了原列表的元素值——因为引用指向的是同一个内存里的列表,所以这些修改会直接反映到函数外部的原列表上。
对比一下:如果传的是整数、字符串这种不可变对象,函数里修改的只是它们的副本,原对象不会变;但列表、字典这种可变对象,修改操作会直接作用在原对象上。
二、递归调用在栈中是怎么返回的?
递归的本质是利用调用栈来管理每一层的函数执行,咱们以你的mergeSort为例:
- 拆分阶段(压栈):每次调用
mergeSort,如果列表长度大于1,就把它拆成左右两半,然后分别递归调用mergeSort(left_half)和mergeSort(right_half)。每一次递归调用都会被压入调用栈,直到拆分到单个元素(这就是递归的base case,因为单个元素本身就是有序的)。 - 合并阶段(回溯出栈):当某个递归调用遇到base case(列表长度≤1),就会执行完当前函数的剩余代码(也就是merge部分),然后从栈中弹出,回到上一层递归。上一层递归拿到已经排好序的左右子列表,执行merge操作把它们合并成有序的子列表,再弹出栈回到更上层……直到最顶层的
mergeSort(alist)执行完merge,整个原列表就排好序了。
举个小例子:比如列表[3,1,2],调用栈的过程大概是:
- 压入
mergeSort([3,1,2]),拆分出[3]和[1,2] - 压入
mergeSort([3]),触发base case,执行完弹出栈 - 压入
mergeSort([1,2]),拆分出[1]和[2] - 压入
mergeSort([1]),触发base case,弹出栈 - 压入
mergeSort([2]),触发base case,弹出栈 - 回到
mergeSort([1,2]),合并[1]和[2]得到[1,2],弹出栈 - 回到
mergeSort([3,1,2]),合并[3]和[1,2]得到[1,2,3],弹出栈,整个过程结束
三、你需要补充哪些信息才能彻底理解?
- Python可变/不可变对象的区别:搞清楚哪些对象是可变的(列表、字典、集合),哪些是不可变的(int、str、tuple),以及它们在函数传参时的行为差异。
- 递归的栈帧模型:理解调用栈如何保存每一层递归的上下文(比如局部变量、返回地址),base case的作用,以及回溯过程是怎么回事。
- mergeSort的具体实现细节:重点看merge阶段是如何把排序后的子列表元素赋值回原列表的——这是原列表被修改的直接原因。
完整的mergeSort代码示例
def mergeSort(alist): print("Splitting ", alist) if len(alist) > 1: mid = len(alist) // 2 left_half = alist[:mid] # 创建左半部分的副本 right_half = alist[mid:] # 创建右半部分的副本 mergeSort(left_half) # 递归排序左半副本 mergeSort(right_half) # 递归排序右半副本 # 合并左右排序后的副本到原列表 i = j = k = 0 while i < len(left_half) and j < len(right_half): if left_half[i] < right_half[j]: alist[k] = left_half[i] i += 1 else: alist[k] = right_half[j] j += 1 k += 1 # 处理左半剩余元素 while i < len(left_half): alist[k] = left_half[i] i += 1 k += 1 # 处理右半剩余元素 while j < len(right_half): alist[k] = right_half[j] j += 1 k += 1 print("Merging ", alist) # 测试 alist = [54,26,93,17,77,31,44,55,20] mergeSort(alist) print(alist) # 输出: [17, 20, 26, 31, 44, 54, 55, 77, 93]
你看,虽然递归时排序的是left_half和right_half这两个副本,但最后我们把它们的元素逐个赋值回原alist的对应位置,所以原列表就被修改成有序的了。
内容的提问来源于stack exchange,提问作者heretoinfinity
相关产品推荐
相关产品推荐

