平均情况下,插入排序比选择排序快的原因是插入排序具有更好的时间复杂度。选择排序的时间复杂度为 $O(n^2)$,其中 $n$ 表示需要排序的元素个数。而插入排序的时间复杂度为 $O(n^2)$ 到 $O(n)$,具体取决于需要排序的元素的初始顺序。
一、平均情况下插入排序比选择排序快的原因
平均情况下,插入排序比选择排序快的原因是因为插入排序具有更好的时间复杂度。选择排序的时间复杂度为 $O(n^2)$,其中 $n$ 表示需要排序的元素个数。而插入排序的时间复杂度为 $O(n^2)$ 到 $O(n)$,具体取决于需要排序的元素的初始顺序。
在选择排序中,每次需要找到未排序部分中的最小元素并将其放在已排序部分的末尾。这涉及到对未排序部分进行线性扫描,并将最小元素插入已排序部分的末尾。每次操作的时间复杂度为 $O(n)$,总时间复杂度为 $O(n^2)$。相比之下,插入排序从未排序的部分中逐个取出元素,并将它们插入到已排序部分中的正确位置。虽然也涉及到线性扫描,但是每次操作只需要考虑已排序部分中的元素,所以时间复杂度可以降低到 $O(n)$。特别地,如果需要排序的元素已经基本有序,插入排序的时间复杂度可以降低到 $O(n)$,这时一般表现良好,比选择排序的复杂度更低。因此,平均情况下,插入排序比选择排序更快。
二、插入排序
1、基本思想
插入排序的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。例如我们有一组数字:{5,2,4,6,1,3},我们要将这组数字从小到大进行排列。 我们从第二个数字开始,将其认为是新增加的数字,这样第二个数字只需与其左边的名列前茅个数字比较后排好序;在第三个数字,认为前两个已经排好序的数字为手里整理好的牌,那么只需将第三个数字与前两个数字比较即可;以此类推,直到最后一个数字与前面的所有数字比较结束,插入排序完成。
2、实现逻辑
- 从名列前茅个元素开始,该元素可以认为已经被排序
- 取出下一个元素,在已经排序的元素序列中从后向前扫描
- 如果该元素(已排序)大于新元素,将该元素移到下一位置
- 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
- 将新元素插入到该位置后
- 重复步骤2~5
3、性能分析
- 平均时间复杂度:O(N^2)
- 最差时间复杂度:O(N^2)
- 空间复杂度:O(1)
- 排序方式:In-place
- 稳定性:稳定
如果插入排序的目标是把n个元素的序列升序排列,那么采用插入排序存在较好情况和最坏情况:
较好情况:序列已经是升序排列,在这种情况下,需要进行的比较操作需(n-1)次即可。
最坏情况:序列是降序排列,那么此时需要进行的比较共有n(n-1)/2次。
插入排序的赋值操作是比较操作的次数减去(n-1)次。平均来说插入排序算法复杂度为O(N^2)。优异的空间复杂度为开始元素已排序,则空间复杂度为 0;最差的空间复杂度为开始元素为逆排序,则空间复杂度最坏时为 O(N);平均的空间复杂度为O(1)。
4、代码实现
// 插入排序
void InsertSort(int arr[], int len){
// 检查数据合法性
if(arr == NULL || len <= 0){ return; } for(int i = 1; i < len; i++){ int tmp = arr[i]; int j; for(j = i-1; j >= 0; j--){
//如果比tmp大把值往后移动一位
if(arr[j] > tmp){
arr[j+1] = arr[j];
}
else{
break;
}
}
arr[j+1] = tmp;
}
}
三、选择排序
1、基本思想
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。选择排序的思想其实和冒泡排序有点类似,都是在一次排序后把最小的元素放到最前面,或者将最大值放在最后面。但是过程不同,冒泡排序是通过相邻的比较和交换。而选择排序是通过对整体的选择,每一趟从前往后查找出无序区最小值,将最小值交换至无序区最前面的位置。
2、实现逻辑
- 名列前茅轮从下标为 1 到下标为 n-1 的元素中选取最小值,若小于名列前茅个数,则交换
- 第二轮从下标为 2 到下标为 n-1 的元素中选取最小值,若小于第二个数,则交换
- 依次类推下去……
3、复杂度分析
- 平均时间复杂度:O(N^2)
- 优异时间复杂度:O(N^2)
- 最差时间复杂度:O(N^2)
- 空间复杂度:O(1)
- 排序方式:In-place
- 稳定性:不稳定
选择排序的交换操作介于和(n-1)次之间。选择排序的比较操作为n(n-1)/2次之间。选择排序的赋值操作介于0和3(n-1)次之间。比较次数为O(n^2),比较次数与关键字的初始状态无关,总的比较次数N = (n-1) + (n-2) +…+ 1 = n x (n-1)/2。交换次数O(n),较好情况是,已经有序,交换0次;最坏情况是,逆序,交换n-1次。
4、代码实现
C版本:
void selection_sort(int arr[], int len) {
int i, j, min, temp;
for (i = 0; i < len - 1; i++) { min = i; for (j = i + 1; j < len; j++) if (arr[min] > arr[j])
min = j;
temp = arr[min];
arr[min] = arr[i];
arr[i] = temp;
}
}
C++版本:
template void selection_sort(T arr[], int len) {
int i, j, min;
for (i = 0; i < len - 1; i++) { min = i; for (j = i + 1; j < len; j++) if (arr[min] > arr[j])
min = j;
swap(arr[i], arr[min]);
}
}
延伸阅读1:选择排序的JAVA版本代码
public static void selection_sort(int[] arr) {
int i, j, min, temp, len = arr.length;
for (i = 0; i < len - 1; i++) {
min = i;
for (j = i + 1; j < len; j++)
if (arr[min] > arr[j])
min = j;
temp = arr[min];
arr[min] = arr[i];
arr[i] = temp;
}
}