目录
如何分析评价一个排序算法?
- 最好、最坏情况和平均时间复杂度
- 空间复杂度,这里有一个专有名词原地排序,指的是空间复杂度为O(1)的排序
- 排序的稳定性
这里需要详细讲一下第三点,排序的稳定性:假设现在我们有一组数据{4,3,1,2,1}需要排序,排序后为{1,1,2,3,4},如果排序前后两个1的前后顺序没有改变,那么就说这个排序算法是稳定的排序算法。
这个稳定性有什么用?假设现在要为学校的学生的考试总成绩进行排序,分数相同的按照学号的大小顺序进行排序。
比较容易想到的做法是,直接按照成绩进行排序,然后在遍历数据,成绩相同的数据再按照学号大小排序,这种方法实现起来会比较复杂。
如果是稳定的排序算法,这个问题可以很好地解决:先将所有数据按照学号大小进行排序,再对排序好的数据按照成绩进行排序。第一次排序后,所有数据按照学号大小排好了,进行第二次排序,由于稳定排序可以保持排序前后成绩相同的两个数据前后顺序不变,所以第二次排序完成后,成绩相同的两个数据他们的学号大小也是排好的。
时间复杂度为O(n^2)的排序
冒泡排序
数组下标 | 原始数据 | ||||
---|---|---|---|---|---|
4 | 1 | 1 | 1 | 1 | 5 |
3 | 2 | 2 | 2 | 5 | 1 |
2 | 4 | 4 | 5 | 2 | 2 |
1 | 3 | 5 | 4 | 4 | 4 |
0 | 5 | 3 | 3 | 3 | 3 |
假设现在要为数组{5,3,4,2,1}进行排序,上面就是第一次进行冒泡的操作,从下标为0开始,比较这个位置和下一个位置的数组值的大小,如果大于就交换,否则就不交换,直到遍历完数组,这就完成了一次冒泡。可以看到在第一次冒泡的过程中,最大的值5慢慢往上冒,直到到达数组的最后的位置,这个过程很像气泡冒泡的过程,因此也叫作冒泡排序。
一次冒泡就会将最大值排到数组最后的位置,所以有n个数据就需要n次冒泡,每次冒泡排好一个数据。
数组下标 | 原始数据 | 第一次冒泡 | 第二次冒泡 | 第三次冒泡 | 第四次冒泡 | 第五次冒泡 |
---|---|---|---|---|---|---|
4 | 1 | 5 | 5 | 5 | 5 | 5 |
3 | 2 | 1 | 4 | 4 | 4 | 4 |
2 | 4 | 2 | 1 | 3 | 3 | 3 |
1 | 3 | 4 | 2 | 1 | 2 | 2 |
0 | 5 | 3 | 3 | 2 | 1 | 1 |
上面列出了5次冒泡的过程,可以看到最后一次的冒泡是不需要的,因为数组已经排好序了,所以我们可以优化代码,当没有数据交换时结束排序。
template<typename T>
void bubbleSort(std::vector<T> &vec) {
int n = vec.size();
if (n <= 1)
return;
for (int i = 0; i < n; i++) {
printVec(vec); //打印过程
bool flag = false; //flag表示是否有数据交换(冒泡)
for (int j = 0; j < n - i - 1;j++) {
//为什么n减i还要减1,因为要访问j+1
if (vec[j] > vec[j + 1]) {
T tmp = vec[j];
vec[j] = vec[j + 1];
vec[j + 1] = tmp;
flag = true; //如果有数据可以交换,那么将flag置为true
}
}
if (!flag)break; //如果没有数据交换,说明vec已经排好序了,直接跳出循环,算是一种优化
}
return;
}
template<typename T>
void printVec(std::vector<T> &vec) {
for (const auto& v : vec)
std::cout << v << " ";
std::cout << std::endl;
}
代码还是比较简单的,这里就不解释了。
下面进行算法分析:
- 冒泡排序是原地排序,只用了一个临时变量tmp进行数据交换
- 冒泡排序是稳定排序,因为在进行比较时,a[i]>a[i+1]才交换,等于时不交换,相同的数据在排序前后顺序不变。(如果是a[i]>=a[i+1]时交换就不是稳定排序了)
- 最好的情况是数据已经是有序的,此时只需要进行一次冒泡,时间复杂度是O(n);最坏的情况是数据倒序,此时需要进行n次冒泡,时间复杂度为O(n^2); 平均时间复杂度是O(n^2)。
插入排序
插入排序的思路也比较简单:将数据分割为有序区和无序区,初始时有序区就是数组的第一个元素,也就是下标为0的位置,无序区就是下标为1的位置开始到数组的末尾。有序区末尾的下标=有序区起始下标-1。
遍历无序区的元素,选到这个元素value后,从有序区的末尾开始往前遍历,找到第一个小于value的元素的位置,这个位置的后一个位置就是插入value的位置。
上图红色区域表示有序区,蓝色区域表示无序区,遍历无序区,每次都会拿到无序区的第一个元素,然后在有序区里找到插入的位置,执行插入操作。
template<typename T>
void insertionSort(std::vector<T>& vec) {
for (int i = 1; i < vec.size(); i++) {
//vec[i]是待排序的目标,在有序区中找位置插入
printVec(vec