常见排序算法总结
这里总结了常见的排序算法,包括冒泡排序、选择排序、直接插入排序、快速排序、归并排序、计数排序、桶排序、堆排序,这里关注的重点实际应该是算法的思想,运用这个思想可以解决很多实际的问题。另外根据思想,能够快速写出算法的实现也是非常有必要的,特别是堆排序、快速排序、归并排序,很多时候要用到其变体,这就要有根据原始思想,快速写出的水平。
#include<iostream>
#include<math.h>
using namespace std;
/**
* 以从大到小排序为例,手写各个排序算法
* 输入:待排序的数组
* 输出:从达到小排好序的数组
*/
void swap(int *a, int *b) {
int tmp = *a;
*a = *b;
*b = tmp;
}
void show(int *a, int n) {
for (int i = 0; i < n; i++) {
cout << a[i] << " ";
}
cout << endl;
}
/**
* 选择排序:每次从剩余的数组中选择最大的数放到已排序好的数组后面
* @return
*/
void selectSort(int *a, int n) {
for (int i = 0; i < n; i++) {
int cur = a[i];
int k = i;
for (int j = i + 1; j < n; j++) {
if (a[j] > cur) {
cur = a[j];
k = j;
}
}
if (i != k) {
swap(&a[i], &a[k]);
}
}
}
/*
* 冒泡排序:n轮比较,当前值和下一个值比较,如果比下一个值小,交换;
* 然后下一个值为当前值,继续往后与下一个值比较,一轮下来,最小的值就冒泡到数组最最末端
*/
void pubbleSort(int *a, int n) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (a[j] < a[j + 1]) {
swap(a[j], a[j + 1]);
}
}
}
}
/**
* 快速排序:快速排序使用了分治法来加速排序
* 有一个基准元素,快排将数组小于基准元素的都移动到数组右边,大于基准元素的都移动到数组左边,基准元素最后放中间
* 然后对大于基准元素部分递归,对小于记住元素部分也进行递归
*/
int quickSortParition(int *a, int i, int j) {
int tmp = a[i];
while (i < j) {
while (i < j && a[j] <= tmp) {
j--;
}
if (i < j) {
a[i] = a[j];
i++;
}
while (i < j && a[i] >= tmp) {
i++;
}
if (i < j) {
a[j] = a[i];
j--;
}
}
a[i] = tmp; // 子数组分离好之后基准元素放到它应该在地方
return i;
}
void quickSort(int *a, int left, int right) {
if (left < right) {
int index = quickSortParition(a, left, right);
quickSort(a, 0, index - 1);
quickSort(a, index + 1, right);
}
}
/**
* 归并排序:采用分治法思想,对两个有序的子数组进行排序,这里利用了子数组的有序特性
* 有点类似于树的后序遍历,数组拆分为两部分,一部分视作左子树,一部分视作右子树
* 现将左边的数组排好序,再讲右边的数组排好序,然后再整体排序
* @return
*/
void merge(int *a, int left, int mid, int right, int *tmp) {
int i = left, j = mid + 1, k = left;
while (i <= mid && j <= right) {
if (a[i] >= a[j]) {
tmp[k++] = a[i];
i++;
} else {
tmp[k++] = a[j];
j++;
}
}
while (i <= mid) {
tmp[k++] = a[i++];
}
while (j <= right) {
tmp[k++] = a[j++];
}
// 将排好序的数组复制到原数组
k = left;
while (k <= right) {
a[k] = tmp[k];
k++;
}
}
void mergeSortFun(int *a, int left, int right, int *tmp) {
if (left < right) {
int mid = (left + right) / 2;
mergeSortFun(a, left, mid, tmp);
mergeSortFun(a, mid + 1, right, tmp);
merge(a, left, mid, right, tmp);
}
}
void mergeSort(int *a, int n) {
int *tmp = new int[n];
mergeSortFun(a, 0, n - 1, tmp);
}
/**
* 直接插入排序:将数组看成一个有序数组和一个无序数组
* 每次从无序数组中选择一个数插入到有序数组的合适位置
* @return
*/
void insertSort(int *a, int n) {
for (int i = 1; i < n; i++) {
int tmp = a[i];
int j = i - 1;
for (; j >= 0; j--) {
if (a[j] < tmp) {
a[j + 1] = a[j];
} else { // 已经找到合适的位置了,停止
break;
}
}
a[j + 1] = tmp;
}
}
/**
* 计数排序:这种排序算法可以在线性时间复杂度完成数据排序
* 当然,根据奥卡姆剃刀原则,计数排序在时间上这么快,那么必然在其他地方很慢,
* 那就是需要空间比较大,适用的范围比较小
* @return
*/
int findMax(int *a, int n) {
int tmp = a[0];
for (int i = 1; i < n; i++) {
if (a[i] > tmp) {
tmp = a[i];
}
}
return tmp;
}
void countSort(int *a, int n) {
int maxNum = findMax(a, n);
int *tmp = new int[maxNum + 1];
for (int i = 0; i < maxNum + 1; i++) {
tmp[i] = 0;
}
for (int i = 0; i < n; i++) {
tmp[a[i]]++;
}
int index = 0;
for (int i = maxNum; i >= 0; i--) {
while (tmp[i] > 0) {
a[index] = i;
index++;
tmp[i]--;
}
}
}
/**
* 桶排序:桶排序将数据分到不同的桶里,每个桶里再进行排序
* 会有一个映射函数,把每个数都映射到对应的桶中,然后对每个桶排序,可以使用快速排序、插入排序
* 然后顺序读取每个桶的数据得到一个排好序的数组
* 桶排序非常快,但是耗费的空间也是非常大
*
* @return
*/
struct BucketNode {
BucketNode *next;
int value;
};
int bucketFun(int value, int intervalLength) {
return (int) ceil((double) value / intervalLength);
}
// 每个桶内部使用插入排序
void bucketSort(int *a, int n) {
const int bucketNum = 5;
BucketNode *bucket = new BucketNode[bucketNum];
for (int i = 0; i < bucketNum; i++) {
bucket[i].value = -1;
bucket[i].next = NULL;
}
int amax = a[0];
int amin = a[0];
for (int i = 1; i < n; i++) {
if (a[i] > amax) {
amax = a[i];
}
if (a[i] < amin) {
amin = a[i];
}
}
int intervalLength = (int) ceil((amax - amin) * 1.0 / bucketNum);
cout << "max=" << amax << " min" << amin << " bucketNum" << bucketNum << endl;
for (int i = 0; i < n; i++) {
int bucketNo = bucketFun(a[i], intervalLength) - 1; // 这里减1因为数组下标是从0开始的
BucketNode *p = &bucket[bucketNo];
while (p->next) { // 桶的内部进行直接插入
auto cur = p->next;
if (a[i] <= cur->value) { // 这里考虑到稳定性,加了等号
p = cur; // 因为要求逆序,如果a[3]=5,a[4]=5,
} else { // 第二个5需要排在后面
BucketNode *q = new BucketNode;
q->value = a[i];
q->next = p->next;
p->next = q;
break;
}
}
if (p->next == NULL) { // 如果是在桶的末尾插入
BucketNode *q = new BucketNode;
q->value = a[i];
q->next = p->next;
p->next = q;
}
}
int k = 0;
for (int i = bucketNum - 1; i >= 0; i--) {
BucketNode *p = &bucket[i];
while (p->next) {
p = p->next; // 桶头会跳过去
a[k++] = p->value;
}
}
}
/**
* 堆排序:堆排序首先需要新建一个堆,然后使用堆调整算法,将堆顶的元素取下来
* 放到排好序的数组中,调整堆使之变成一个标准堆,再取下堆顶元素,依次进行
* 调整算法:从最后一个非叶子结点开始,依次往前,首先比较该结点左右孩子的大小
* 然后取较小者与该结点比较,如果该结点比孩子节点小,两个节点互换
* @return
*/
// 倒序排序,需要构造小根堆,堆顶元素最小
// {a[0], a[1], ... ,a[n]} 的最后一个非叶子节点编号为 (n-1) / 2
// 因为下标编号是从0开始的,需要减1
// eg a[0]-a[5] 最后一个元素编号为2,a[0]-a[6]的最后一个编号也是2
void adjustHeap(int *a, int i, int n) {
int cur = a[i];
int j = i * 2 + 1;
while (j < n) {
if (j + 1 < n && a[j + 1] < a[j]) {
j++;
}
if (a[j] < cur) {
a[(j - 1) / 2] = a[j];
i = j;
j = j * 2 + 1;
} else {
break;
}
}
a[i] = cur;
}
void buildHeap(int *a, int n) {
for(int i = (n-1) / 2; i >=0; i--){
adjustHeap(a, i, n);
}
cout<<endl;
for (int i = n - 1; i >= 0; i--) {
swap(a[0], a[i]);
adjustHeap(a, 0, i);
}
}
int main() {
int a[10] = {3, 6, 1, 9, 5, 3, 2, 8, 3, 2};
// selectSort(a, 10);
// quickSort(a, 0, 9);
// mergeSort(a, 10);
// insertSort(a, 10);
// countSort(a, 10);
// bucketSort(a, 10);
buildHeap(a, 10);
show(a, 10);
return 0;
}