问题描述
int main() {
int arr[] = { 64,25,12,22,11 };
int n = sizeof(arr) / sizeof(arr[0]);
selectionSort(arr,n);
return 0;
}
void selectionSort(int arr[],int n) {
int i,j,min_idx;
// One by one move boundary of unsorted subarray
for (i = 0; i < n - 1; i++) {
// Find the minimum element in unsorted array
min_idx = i;
for (j = i + 1; j < n; j++)
if (arr[j] < arr[min_idx])
min_idx = j;
// Swap the found minimum element with the first element
swap(&arr[min_idx],&arr[i]);
}
}
我看到了这个 C 语言代码,它会执行称为选择排序的排序算法。但我的问题是在 selectionSort
函数中。
为什么在第一个 for
循环中,条件是 i < n - 1
而在第二个循环中它是 j < n
?
i < n - 1
究竟会做什么?为什么第二个循环的情况不同?你能像我在小学六年级一样向我解释这段代码吗?谢谢。
解决方法
第一个循环必须迭代到索引 n-2
(因此 i < n-1
),因为第二个 for 循环必须检查数字 i+1 直到 n-1
(因此 {{1} })。如果 j < n
可以得到值 i
,那么 n - 1
中的访问将越界,特别是 if (arr[j] < arr[min_idx])
将越界 arr[j]
。
您可能认为这种选择排序的实现在数组上从左向右移动,而在其左侧始终保留一个已排序的数组。这就是为什么第二个 for 循环从索引 j==n
开始访问元素。
您可以在网上找到许多资源来可视化选择排序的工作方式,例如 Selection sort in Wikipedia
,对 implementation on Wikipedia 进行了注释和解释。
/* advance the position through the entire array */
/* (could do i < aLength-1 because single element is also min element) */
for (i = 0; i < aLength-1; i++)
选择排序的工作原理是找到最小的元素并将其交换到位。当只剩下一个未排序元素时,它是最小的未排序元素,并且位于已排序数组的末尾。
例如,假设我们有 {3,5,1}
。
i = 0 {3,1} // swap 3 and 1
^
i = 1 {1,3} // swap 5 and 3
^
i = 2 {1,3,5} // swap 5 and... 5?
^
对于三个元素,我们只需要两次交换。对于 n 个元素,我们只需要 n-1 次交换。
这是一种优化,可能会在非常小的数组上稍微提高性能,但在 O(n^2) 算法(如选择排序)中则无关紧要。
,为什么在第一个for循环中,条件是i ?但是在第二个循环中是 j ?
内循环的循环条件是j < n
,因为要排序的最后一个元素的索引是n - 1
,所以当j >= n
我们知道它已经过了数据。
外循环的循环条件本来可以是 i < n
,但是请注意,当 i
取值 {{ 1}}。内循环中 n - 1
的初始值为 j
,在这种情况下为 i + 1
。因此不会执行内循环的迭代。
但是没有有用的工作与根本没有工作是不一样的。在每次 n
取值 i
的外循环迭代中,将执行一些簿记,并且 n - 1
将与自身交换。尽早停止外循环一次迭代可以避免那些保证无用的额外工作。
所有这一切都与不需要花费任何工作来对单元素数组进行排序这一事实直接相关。
,这是这些嵌套循环的逻辑:
- 对于数组中的每个位置
i
- 从这个位置开始,找到切片的最小元素,一直延伸到数组的末尾
- 交换最小元素和位置
i
处的元素
数组末尾的第1个元素切片的最小元素显然已经就位,因此无需运行外循环的最后一次迭代。这就是外循环有一个测试 i < n - 1
的原因。
但是请注意,此测试中有一个令人讨厌的陷阱:如果我们使用 int
代替 size_t
作为数组中元素的索引类型和计数,它作为数组更正确现在可以有比 int
类型的范围更多的元素,i < n - 1
对于 i = 0
和 n = 0
将是正确的,因为 n - 1
不是负数而是最大的 {{ 1}} 价值巨大。换句话说,代码会在空数组上崩溃。
这样写会更安全:
size_t