var arr = [7,3,28,8,9,13,1500,45];
function qsort(a) {
if (a.length == 0) return [];
var left = [], right = [], pivot = a[0];
for (var i = 1; i < a.length; i++) {
a[i] < pivot ? left.push(a[i]) : right.push(a[i]);
}
return qsort(left).concat(pivot, qsort(right));
}
alert(qsort(arr));
此例程使用Quicksort算法对数组进行排序。
问题是基本案例if (a.length == 0) return [];
将如何真实地阻止递归?
答案 0 :(得分:6)
if (a.length == 0) return [];
当长度为0时,它会停止。
答案 1 :(得分:6)
传递给递归调用的数组总是至少比传递给函数的数组小一个,因为它不包含pivot元素。因此,您最终会遇到a.length == 0的基本情况,并返回而不会递归。
答案 2 :(得分:6)
请记住:
pivot
是数组left
是低于pivot
right
是高于pivot
所以你第一次拥有qsort( [7,3,28,8,9,13,1500,45] )
:
left=[3] pivot=7 right=[28,8,9,13,1500,45]
现在,qsort()
和left
数组都会递归调用right
。
我们只看left
,qsort( [3] )
:
left=[] pivot=3 right=[]
再次qsort()
以left
和right
阵列递归调用{。}}。
再次,我们只看left
,qsort( [] )
:
qsort()
的第一件事是什么?
if (a.length == 0) return [];
如果它收到一个空数组(它在这里做了),它只返回一个空数组,暂停该分支上的执行。
因为qsort()
的每次调用都会弹出数组中的第一项,所以每次调用qsort()
时,它收到的数组都会越来越短,直到空数组被发送到{{1} }。
可能令人困惑的部分是qsort()
关闭第一个项目,然后将数组的其余部分分成两部分。
想象一下,如果它没有拆分数组,而只是关闭了第一个项目。
这个函数实际上并没有做任何事情,只是用数组的其余部分递归调用自己。
qsort()
因此,当您调用该函数时,会发生以下情况:
function recursive_test( arr ) {
if( arr.length === 0 ) { return []; } // empty Array? Just return.
var first_item = arr.shift(); // first item in the Array (the head)
// now "arr" represents the remainder (the tail)
return recursive_test( arr ); // Just send the "tail" to the same function
// so the next time through, the Array is
} // shorter by 1
在你的函数中发生了同样的事情,除了递归传递“tail”而不是“head”和“tail”之外,你得到一个“head”(var array = [5,2,8,3,6,9,0]; // original Array
recursive_test( array );
// the first time it gets the full Array
// [5,2,8,3,6,9,0]
// but then it pops the first item off, and calls itself with the "tail" of the Array
// [2,8,3,6,9,0]
// again it calls itself, just with the "tail"
// [8,3,6,9,0]
// and again, and again, and again...
// [3,6,9,0]
// [6,9,0]
// [9,0]
// [0]
// []
// The last time it gets an empty Array.
// The function sees that it gets an empty Array, and just returns,
// halting the recursion
)和一个“尾巴”,分为2个阵列(pivot
和left
)。
尾部的两个部分都被发送到递归调用,弹出它们的头部,分割剩余部分,然后再次执行,直到没有任何东西为止。
答案 3 :(得分:2)
在快速排序中,你采取了一种分裂和征服的方法 - 而不是立即解决整个问题,你将问题分成两半,解决每一半,然后将答案合并在一起。
为了解决问题的每一半,我们只是递归地调用快速排序 - 也就是说,我们将问题再分成两半,直到我们得到一些我们无法进一步划分它的东西......这是由这条线处理的:
if (a.length == 0) return [];
所以现在我们已经将它分成了很多次并解决了所有这些问题,我们可以将它们合并在一起。
这一行:
return qsort(left).concat(pivot, qsort(right));
说“把左子问题和正确的子问题,把它们粘在一起,这就是我的答案”。
这种气泡直到顶部,将所有不同的子阵列粘在一起并生成一个带答案的数组。
这比那复杂一点,但无论如何这都是递归的。
答案 4 :(得分:2)
将此作为单独的答案发布,因为它占用了一点空间。
这是递归调用的表示。我将left
更改为lo
,将right
更改为hi
,将pivot
更改为piv
以便节省空间。
虽然连接可能很难遵循,但它可以很好地显示流程。
qsort( [7,3,28,8,9,13,1500,45] )
|
|-----------------------------v-----------------------------------|
| lo=[3] piv=7 hi=[28,8,9,13,1500,45] |
| |
| |
v v
qsort( [3] ) qsort( [28,8,9,13,1500,45] )
| |
|-------------v------------| |-----------------------------v--------------------------------------------|
| lo=[] piv=3 hi=[] | | lo=[8,9,13] piv=28 hi=[1500,45] |
| | | |
| | | |
v v v v
qsort( [] ) qsort( [] ) qsort( [8,9,13] ) qsort( [1500,45] )
| |
|------------v---------------| |-------------v--------------|
| lo=[] piv=8 hi=[9,13] | | lo=[45] piv=1500 hi=[] |
| | | |
| | | |
v v v v
qsort( [] ) qsort( [9,13] ) qsort( [45] ) qsort( [] )
| |
|-------------v -----------| |------------v-------------|
| lo=[] piv=9 hi=[13] | | lo=[] piv=45 hi=[] |
| | | |
| | | |
v v v v
qsort( [] ) qsort( [13] ) qsort( [] ) qsort( [] )
|
|-----------v-------------|
| lo=[] piv=13 hi=[] |
| |
| |
v v
qsort( [] ) qsort( [] )
要跟随连接,基本上从根开始,尽可能远地跟随lo
,直到你无法深入。
然后回溯记录您通过的最新piv
,并按照其hi
分支,然后执行您可以执行的所有lo
分支。
重复此过程,始终支持lo
分支,并在回溯时传递它们时记下piv
。
这将为您提供完全排序的数组。
答案 5 :(得分:0)
这会导致递归停止
if (a.length == 0) return [];
答案 6 :(得分:0)
这就是if (a.length == 0) return [];
行。一旦将空数组传递给函数,递归就会停止。由于每次递归都会将输入数组除以2,因此必然会发生这种情况。
底层示例:
+
是连接
["a","b","c"]
最终成为
[] + [a] + [] + [b] + [] + [c] + []
b是枢轴,(左)和c(右)都经过另一次快速排序迭代。这会在两者的每一侧添加[]
,然后将所有三个连接在一起。