排序算法相关接口
排序关注的对象是数组元素(支持随机访问),每个元素都有一个key
值,key
值是能够按照某种方式排列(即可排序)的。
一个排序算法大概需要实现如下接口:
class SortFunc {
public:
void sort(Comparables a);
private:
bool less(const Comparable &a, const Comparable &b);
void exch(Comparable &a, Comparable &b)
{
Comparable t = a;
a = b;
b = t;
}
};
Comparable
是单个数组元素的类型,Comparables
是支持随机访问的元素集合的类型。
要完成sort
,需要提供比较两个元素的方法less
以及根据比较结果交换元素的方法exch
。
在分析某个排序算法时,除了关注less
和exch
的调用次数外,还需要关注是否配置了额外内存空间存储数组的副本。
选择排序
- 找到数组中最小的那个元素
- 将它和数组的第一个元素交换位置
- 在剩下的元素中找到最小的元素
- 将它和数组中第二个元素交换位置
- 如此往复,直到只剩下一个元素
这种算法叫做选择排序,因为它在不断地选择剩余元素中的最小者。
class Solution {
public:
vector<int> sortArray(vector<int>& nums)
{
int n = nums.size();
for (int i = 0; i < n; ++i) {
int minIdx = i;
for (int j = i + 1; j < n; ++j) {
if (less(nums, j, minIdx)) {
minIdx = j;
}
}
exch(nums, i, minIdx);
}
return nums;
}
private:
bool less(const vector<int>& nums, int i, int j)
{
return nums[i] < nums[j];
}
void exch(vector<int>& nums, int i, int j)
{
int t = nums[i];
nums[i] = nums[j];
nums[j] = t;
}
};
选择排序有两个鲜明的特点:
- 运行时间和输入无关。一个有序的数组(或
key
全部相等的数组)和元素随机排列的数组的排序时间一样长。 - 元素移动次数少。元素交换的次数和数组的大小是线性关系。
插入排序
整理桥牌的时候,将每一张牌插入到其它已经有序的牌中的适当位置。
在计算机的实现中,为了给要插入的元素腾出空间,我们需要将其余所有元素在插入之前都向右移动一位,这种算法叫做插入排序。
class Solution {
public:
vector<int> sortArray(vector<int>& nums)
{
int n = nums.size();
for (int i = 1; i < n; ++i) {
int j = i;
while (j >= 1 && less(nums, j, j - 1)) {
exch(nums, j, j - 1);
--j;
}
}
return nums;
}
private:
bool less(const vector<int>& nums, int i, int j)
{
return nums[i] < nums[j];
}
void exch(vector<int>& nums, int i, int j)
{
int t = nums[i];
nums[i] = nums[j];
nums[j] = t;
}
};
优化版:在内循环中将较大的元素都向右移动而不总是交换元素,这样访问数组的次数能减半。
class Solution {
public:
vector<int> sortArray(vector<int>& nums)
{
int n = nums.size();
for (int i = 1; i < n; ++i) {
int tmp = nums[i];
int j = i;
while (j >= 1 && less(tmp, nums[j - 1])) {
nums[j] = nums[j - 1];
--j;
}
nums[j] = tmp;
}
return nums;
}
private:
bool less(int i, int j)
{
return i < j;
}
void exch(vector<int>& nums, int i, int j)
{
int t = nums[i];
nums[i] = nums[j];
nums[j] = t;
}
};
每次内循环时,要保留
nums[i]
的副本,并使用这个副本做比较和赋值运算。
选择排序和插入排序的共同点:
- 当前索引左边的所有元素都是有序的。
- 无额外空间消耗。
O(1)
空间复杂度、O(n^2)
时间复杂度。
选择排序和插入排序的不同点:
- 选择排序中,每次循环都会确定一个元素的最终位置。但插入排序不会。
- 插入排序所需的时间取决于输入元素的初始顺序 。但选择排序不会。