选择排序
选择排序是一种非常直观且简单的排序算法。它工作的流程是这样的:
首先找出数组中最小的那个元素,将它和数组的第一个元素交换位置;然后在第二个到最后一个元素中间找到最小的那个元素与数组的第二个元素交换位置。
就这样依次遍历,直到将整个数组排序。
选择排序不是稳定排序,但是是原地排序,时间复杂度是平方级,空间复杂度为1。
C++代码实现如下:
#include<iostream>
#include<vector>
template<typename T>
class SelectSort
{
public:
SelectSort(std::vector<T> &src)
{
items = src;
}
void sort()
{
int N = items.size();
for(int i = 0; i < N; ++i)
{ //第i小的元素
int min = i;
//从j到N为剩余没有排序的元素
for(int j = i+1; j < N; ++j)
if(less(j, min))
min = j;
exchange(i, min);
}
}
void display()
{
for(const T &item : items)
std::cout << item << " ";
std::cout << std::endl;
}
private:
// 默认的比较操作是<
// 也可自定义
bool less(int lhs, int rhs)
{
return items[lhs] < items[rhs];
}
void exchange(int lhs, int rhs)
{
T temp = items[lhs];
items[lhs] = items[rhs];
items[rhs] = temp;
}
std::vector<T> items;
};
插入排序
如果待排序的数据量非常小就可以用插入排序,并且性能非常高。
对待排序的数组用插入排序,在排序过程中可以看做两部分数组,一个是已经排好序的部分,一个是未排序的部分。排序的过程可以看作从未排序的数组中依次
抽取元素插入到已排序的数组中,当未排序的数组为空时数组排序完毕。
插入排序是稳定排序,并且是原地排序,时间复杂度介于线性级别和平方级别,空间复杂度为1。但插入排序的时间复杂度依赖于数据的输入情况,最好情况是
已经有序,时间复杂度为线性;最坏的情况是输入数据是逆序,时间复杂度为平方级别。
C++代码实现如下:
#include<iostream>
#include<vector>
template<typename T>
class InsertSort
{
public:
InsertSort(std::vector<T> &src)
{
items = src;
}
void sort()
{
int size = items.size();
// i之所以从1开始是因为把数组分成两份,前一部分有1个元素
for(int i = 1; i < size; ++i)
// 将j初始化为i,即排序操作只在前一部分进行
for(int j = i; j > 0 && less(j, j-1); --j)
exchange(j, j-1);
}
void Display()
{
for(const T &item : items)
std::cout << item << " ";
std::cout << std::endl;
}
private:
// 默认的比较操作是<
// 也可自定义Less
bool less(int lhs, int rhs)
{
return items[lhs] < items[rhs];
}
void exchange(int lhs, int rhs)
{
T temp = items[lhs];
items[lhs] = items[rhs];
items[rhs] = temp;
}
std::vector<T> items;
};
归并排序
归并排序是将待排序的数组分成两半分别排序,然后将这两个已经有序的数组归并起来。
归并排序是稳定的排序算法,不是原地排序,时间复杂度为线性对数级别,空间复杂度为N。
实现代码非常易懂,看一遍就会明白,C++代码实现如下:
#include<iostream>
#include<vector>
template<typename T>
class MergeSort
{
public:
MergeSort(std::vector<T> &src)
{
items = src;
}
void sort()
{
sort(0, items.size() - 1);
}
void display()
{
for(const T &item : items)
std::cout << item << " ";
std::cout << std::endl;
}
private:
void exchange(int lhs, int rhs)
{
T temp = items[lhs];
items[lhs] = items[rhs];
items[rhs] = temp;
}
void sort(int begin, int end)
{
if(end <= begin) return;
int mid = begin + (end - begin) / 2;
// 左半边排序
sort(begin, mid);
// 右半边排序
sort(mid + 1, end);
// 左右半边归并
merge(begin, mid, end);
}
void merge(int begin, int mid, int end)
{
// i指向数组前半部分
int i = begin;
// j指向数组后半部分
int j = mid + 1;
// 归并辅助数组
std::vector<T> aux = items;
for(int k = begin; k <= end; ++k)
// 下面四个条件判断不能反过来
// 左半边用尽取右半边元素
if(i > mid)
items[k] = aux[j++];
// 右半边用尽取左半边元素
else if(j > end)
items[k] = aux[i++];
// 右边当前元素小于左边当前元素,取右半边元素
else if(aux[i] < aux[j])
items[k] = aux[i++];
// 左半边当前元素小于右半边当前元素,取左半边元素
else
items[k] = aux[j++];
}
std::vector<T> items;
};
快速排序
快速排序是使用最为广泛的排序算法,它实现简单,适用于各种不同的输入数据且在一般情况下比其他算法更快。
快速排序是一种分治的排序算法,它将一个数组分成两个子数组,将两部分独立排序,快速排序和归并排序是互补的:归并排序将数组分成两个子数组分别排序,
并将有序的子数组归并以将整个数组排序;而快速排序将数组排序的方式是当两个子数组都有序时整个数组也就有序了。快速排序的核心和难点是切分。
快速排序不是稳定排序,不是原地排序,时间复杂度是线性对数级别,空间复杂度是lgN。但是快速排序对数据的输入情况有依赖,如果输入的数据本身是有序的
那么快速排序的时间复杂度为平方级别。
C++代码实现如下:
#include<iostream>
#include<vector>
#include<algorithm>
template<typename T>
class QuickSort
{
public:
QuickSort(std::vector<T> &src)
{
items = src;
}
void sort()
{
// 随机打乱容器内的元素,以免影响排序,此函数定义在头文件algorithm中
std::random_shuffle(items.begin(), items.end());
sort(0, items.size() - 1);
}
void display()
{
for(const T &item : items)
std::cout << item << " ";
std::cout << std::endl;
}
private:
bool less(int lhs, int rhs)
{
return items[lhs] < items[rhs];
}
void exchange(int lhs, int rhs)
{
T temp = items[lhs];
items[lhs] = items[rhs];
items[rhs] = temp;
}
void sort(int begin, int end)
{
if(end <= begin) return;
int mid = partition(begin, end);
sort(begin, mid - 1);
sort(mid + 1, end);
}
int partition(int begin, int end)
{
// 置左指针(也就是下标)
int i = begin;
// 置右指针
int j = end + 1;
// 待切分元素是下标为begin的元素
while(true)
{
// 从左向右找到第一个大于等于待切分元素的元素
while(less(++i, begin))
if(i == end)
break;
// 从右向左找到第一个小于等于待切分元素的元素
while(less(begin, --j))
if(j == begin)
break;
if(i >= j)
break;
exchange(i, j);
}
// 交换元素,将待切分元素放入正确的位置
// 使得待切分元素左边的元素都不比它大,右边的元素都不比它小
exchange(begin, j);
return j;
}
std::vector<T> items;
};
堆排序
堆排序是利用二叉堆的性质来将数组排序,分为两大步:
-
第一步构造堆,将数据构造成堆有序的状态(堆有序并不是元素有序)。
-
第二步依次取出最大元素,将元素变成有序状态。
堆排序不是稳定排序,不是原地排序,时间复杂度为线性对数级别,空间复杂度为1。
C++代码实现如下:
#include<iostream>
template<typename ValueType>
class HeapSort
{
public:
HeapSort(ValueType src[], int arraySize)
{
this->len = arraySize;
items = new ValueType[len];
for(int i = 0; i < len; ++i)
items[i] = src[i];
}
~HeapSort()
{
delete[] items;
}
void sort()
{
// 堆有序化
for (int i = len / 2 - 1; i >= 0; i--)
sink(i, len - 1);
// 排序
for (int i = len - 1; i > 0; i--) {
exchange(0, i);
sink(0, i - 1);
}
}
void display()
{
for(int i = 0; i < len; ++i)
std::cout << items[i] << " ";
std::cout << std::endl;
}
private:
bool less(int lhs, int rhs)
{
return items[lhs] < items[rhs];
}
void exchange(int lhs, int rhs)
{
ValueType temp = items[lhs];
items[lhs] = items[rhs];
items[rhs] = temp;
}
// 元素下沉
void sink(int start, int end)
{
int dad = start;
int son = dad * 2 + 1;
while (son <= end)
{
// 选择较大的子节点
if (son + 1 <= end && items[son] < items[son + 1])
son++;
if (items[dad] > items[son])
return;
else // 如果子节点大于父节点则交换两者位置
{
exchange(dad, son);
dad = son;
son = dad * 2 + 1;
}
}
}
int len;
ValueType *items;
};
各种排序算法的特点总结如下
算法 | 是否稳定 | 是否为原地排序 | 时间复杂度 | 空间复杂度 | 备注 |
---|---|---|---|---|---|
选择排序 | 否 | 是 | N^2 | 1 | 无 |
插入排序 | 是 | 是 | 介于N和N^2之间 | 1 | 取决于输入元素的排序情况 |
归并排序 | 是 | 否 | NlogN | N | 无 |
快速排序 | 否 | 是 | NlogN | lgN | 取决于输入元素的排序情况 |
堆排序 | 否 | 是 | NlogN | 1 | 无 |