排序算法
在计算机程序中,排序算法是很多其他算法的基础,可以为我们更好地完成其他工作,提升效率。
常见的排序算法有
- 冒泡排序
- 选择排序
- 插入排序
- 希尔排序
- 快速排序
- 归并排序
- 堆排序
1、冒泡排序
冒泡排序的原理就是通过一次次把最小或者最大的元素推到未排序序列的一端,直到有序
template <class T>
void bubbleSort(T* arr, int size) {
for (int i = 0; i < size - 1; i++) {
bool change = false;
for (int j = 0; j < size - 1 - i; j++) {
if (_less(arr[j + 1], arr[j])) {
_swap(arr[j], arr[j + 1]);
change = true;
}
}
if (!change)
break;
}
}
其中_swap和_less的定义如下,之后也会一直使用这两个函数
template <class T>
inline bool _less(const T& a, const T& b) {
return a < b;
}
template <class T>
inline void _swap(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
2、选择排序
选择排序每次找到最小(大)的值,放到已排序片段的末端
template <class T>
void selectionSort(T* arr, int size) {
for (int i = 0; i < size; i++) {
int minIndex = i;
for (int j = i + 1; j < size; j++) {
if (_less(arr[j], arr[minIndex])) {
minIndex = j;
}
}
if (minIndex != i) {
_swap(arr[minIndex], arr[i]);
}
}
}
3、插入排序
插入排序就类似整理扑克牌,过程上是这样的:
- 假定之前元素的已经排好序
- 取下一个元素的值,往前面插入到正确的位置,在其后面的元素需要整体向后挪一位
- 重复步骤1 2
template <class T>
void insertionSort(T*arr, int size) {
for (int i = 1; i < size; i++) {
T temp = arr[i];
int j = i - 1;
for (; j >= 0; j--) {
if (_less(temp, arr[j]))
arr[j + 1] = arr[j];
else break;
}
arr[j + 1] = temp;
}
}
4、希尔排序
希尔排序是插入排序的一个改进,插入排序的步长是1,而希尔排序的步长是由一个递减序列给定的,当步长是1的时候,每次移动一位,每次减少 1 个倒置数,所以倒置数是多少,就要移动多少次,而当步长从一个较大的值n开始,一次移动n个位置,倒置数就能一次减少n,这样极大的削减了倒置数,并且使数组局部有序,之后减少步长直到1,就相当于使用插入排序,此时插入排序效率很高。
template < class T>
void shellSort(T * arr, int size) {
int h = 1;
while (3 * h + 1 < size)
h = 3 * h + 1;
while (h) {
for (int i = h; i < size; i++) {
T temp = arr[i];
int j = i - h;
for (; j >= 0; j -= h) {
if (_less(temp, arr[j]))
arr[j + h] = arr[j];
else break;
}
arr[j + h] = temp;
}
h /= 3;
}
}
5、快速排序
快速排序的做法十分有创造力,在一个无序序列中取一个主元,把小于主元的部分放到左边,把大于主元的部分放到右边,再让左边和右边的序列递归重复这个过程,直到子序列长度<=1(不需要排序)。
template <class T>
void quickSort(T * arr, const int lo, const int hi) {
if (hi <= lo)
return;
int lt = lo + 1, gt = hi;
while (1) {
while (lt <= hi && _less(arr[lt], arr[lo]))
lt++;
while (gt > lo && !_less(arr[gt], arr[lo]))
gt--;
if (lt < gt)
_swap(arr[lt], arr[gt]);
else
{
_swap(arr[lo], arr[gt]);
break;
}
}
quickSort(arr, lo, gt - 1);
quickSort(arr, lt, hi);
}
6、 归并排序
归并排序的原理是
1. 把无序序列切分成小块,直到长度为1
2. 假定归并的序列都是有序的(上面的1,就是有序)
3. 逐渐扩大范围进行归并
template <class T>
void merge(T* arr, int lo, int mid, int hi, T* arr2) {
if (mid > hi)
mid = hi;
int i = lo, j = mid + 1;
for (int k = lo; k <= hi; k++)
if (i > mid)
arr2[k] = arr[j++];
else if (j > hi)
arr2[k] = arr[i++];
else if (_less(arr[i], arr[j]))
arr2[k] = arr[i++];
else
arr2[k] = arr[j++];
}
template <class T>
void mergeSort(T * arr, int lo, int hi) {
T* originArr = arr;
T * arr2 = new T[hi + 1];
for (int step = 1; lo + step <= hi; step += step) {
for (int i = lo; i <= hi; i += step + step) {
merge(arr, i, i + step - 1, _min(i + step + step - 1, hi), arr2);
}
_swap(arr, arr2);
}
if (arr == originArr) {
delete[]arr2;
return;
}
else {
for (int i = lo; i <= hi; i++) {
arr2[i] = arr[i];
}
delete[]arr;
}
}
上面的 merge 函数将 arr 看成两个有序部分,以 mid 分开,并将合并的结果放在 arr2中。
mergeSort 函数中将函数按步长切分,调用merge,每次循环交换arr 和 arr2的值(数组地址)。最后还原到原始数组中。
7、堆排序
堆排序利用了完全二叉树的概念。
完全二叉树按照从左到右,从上到下的顺序将二叉树排满。
其原理如下:
- 首先将数组想象成一个完全二叉树,一般数组元素要从下标 1 开始存储,下标为 n 的元素的子节点分别是下标为 2n 和 2n+1。
- 对整个数组使用自上而下使用上浮函数,或者自下而上使用下沉函数。
- 交换下标 1 和 下标 size 的元素,size–,对下标 1 的元素使用下沉函数
- 重复步骤 3 直到 size==1
template <class T>
inline void swim(T *arr, int i) {
int j = i / 2;
while (j && _less(arr[i], arr[j])) {
_swap(arr[i], arr[j]);
i = j;
j /= 2;
}
}
template <class T>
void sink(T* arr, int i, int size) {
while (2 * i <= size) {
int j = 2 * i;
if (j < size && _less(arr[j + 1], arr[j]))
j++;
if (_less(arr[j], arr[i]))
_swap(arr[j], arr[i]);
i = j;
}
}
template <class T>
void heapSort(T* arr, int size) {
size--;
for (int i = 2; i <= size; i++) {
swim(arr, i);
}
for (int i = size; i > 1; ) {
_swap(arr[1], arr[i]);
sink(arr, 1, --i);
}
for (int i = 0; i < size; i++) {
if (_less(arr[i], arr[i + 1])) {
_swap(arr[i], arr[i + 1]);
}
}
}