C++实现十大经典排序算法
十大排序算法的时间空间复杂度,如下图所示
1、冒泡排序
算法描述:
1)、比较两个相邻的元素,如果第一个比第二个大,就交换他们位置。
2)、从第一个元素开始与他的下一个元素比较,这样最后的元素就会是最大的元素。
3)、重复以上步骤。
图解
代码实现
template<class T>
void swap(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
void BubbleSort(std::vector<int>& nums, int n) {
if (n <= 1) return;
bool is_swap;
for (int i = 1; i < n; ++i) {
is_swap = false;
//设定⼀个标记,若为false,则表示此次循环没有进⾏交换,也就是待排序列已经有序,排序已经完成。
for (int j = 1; j < n - i + 1; ++j) {
if (nums[j] < nums[j - 1]) {
std::swap(nums[j], nums[j - 1]);
is_swap = true;//表示有数据交换
}
}
if (!is_swap) break;//没有数据交集,提前退出
}
}
2、插入排序
算法描述:
分为已排序和未排序 初始已排序区间只有⼀个元素 就是数组第⼀个遍历未排序的每⼀个元素在已排序区间⾥找到合适的位置插⼊并保证数据⼀直有序。
图解
代码实现
template<class T>
void swap(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
void InsertSort(std::vector<int>& nums, int n) {
if (n <= 1) return;
for (int i = 0; i < n; ++i) {
for (int j = i; j > 0 && nums[j] < nums[j - 1]; --j) {
std::swap(nums[j], nums[j - 1]);
}
}
}
3、选择排序
算法描述
分已排序区间和未排序区间。每次会从未排序区间中找到最⼩的元素,将其放到已排序区间的末尾。
图解
代码实现
template<class T>
void swap(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
void SelectSort(std::vector<int>& nums, int n) {
if (n <= 1) return;
int mid;
for (int i = 0; i < n - 1; ++i) {
mid = i;
for (int j = i + 1; j < n; ++j) {
if (nums[j] < nums[mid]) {
mid = j;
}
}
std::swap(nums[mid], nums[i]);
}
}
4、快速排序
算法描述
先找到⼀个枢纽;在原来的元素⾥根据这个枢纽划分 ⽐这个枢纽⼩的元素排前⾯;⽐这个枢纽⼤的元素
排后⾯;两部分数据依次递归排序下去直到最终有序。
图解
代码实现
void QuickSort(std::vector<int>& nums, int l, int r) {
if (l + 1 >= r) return;
int first = l, last = r - 1, key = nums[first];
while (first < last) {
while (first < last && nums[last] >= key) last--;//右指针 从右向左扫描 将⼩于piv的放到左边
nums[first] = nums[last];
while (first < last && nums[first] <= key) first++;//左指针 从左向右扫描 将⼤于piv的放到右边
nums[last] = nums[first];
}
nums[first] = key;//更新piv
QuickSort(nums, l, first);//递归排序 //以L为中间值,分左右两部分递归调⽤
QuickSort(nums, first + 1, r);
}
5、希尔排序
算法描述
通过将⽐较的全部元素分为⼏个区域来提升插⼊排序的性能。这样可以让⼀个元素可以⼀次性地朝最终
位置前进⼀⼤步。然后算法再取越来越⼩的步⻓进⾏排序,算法的最后⼀步就是普通的插⼊排序,但是到了这步,
需排序的数据⼏乎是已排好的了。
图解
代码实现
// 希尔排序
void shellSort(vector<int>& nums) {
for (int gap = nums.size() / 2; gap > 0; gap /= 2) {
for (int i = gap; i < nums.size(); ++i) {
for (int j = i; j - gap >= 0 && nums[j - gap] > nums[j]; j -= gap) {
std::swap(nums[j - gap], nums[j]);
}
}
}
}
6、归并排序
算法描述
归并排序是⼀个稳定的排序算法,归并排序的时间复杂度任何情况下都是 O(nlogn),归并排序不是原地
排序算法
⽤两个游标 i 和 j,分别指向 A[p…q] 和 A[q+1…r] 的第⼀个元素。⽐较这两个元素 A[i] 和 A[j],如果 A[i]<=A[j],我
们就把 A[i] 放⼊到临时数组 tmp,并且 i 后移⼀位,否则将 A[j] 放⼊到数组 tmp,j 后移⼀位。
递归
分解:将当前区间一分为二,即求分裂点 mid = (low + high) / 2;
求解:递归地对两个子区间 [low…mid] 和 [mid+1…high] 进行归并排序。递归的终结条件是子区间长度为1;
合并:将已排序的两个子区间 [low…mid] 和 [mid+1…high] 归并为一个有序的区间 [low…high]。
图解
代码实现
void mergeCount(int a[], int L, int mid, int R) {
int* tmp = new int[L + mid + R];
int i = L;
int j = mid + 1;
int k = 0;
while (i <= mid && j <= R) {
if (a[i] < a[j])
tmp[k++] = a[i++];
else
tmp[k++] = a[j++];
}
///判断哪个⼦数组中有剩余的数据
while (i <= mid)
tmp[k++] = a[i++];
while (j <= R)
tmp[k++] = a[j++];
/// 将 tmp 中的数组拷⻉回 A[p...r]
for (int p = 0; p < k; ++p)
a[L + p] = tmp[p];
delete tmp;
}
void mergeSort(int a[], int L, int R) {
///递归终⽌条件 分治递归
/// 将 A[L...m] 和 A[m+1...R] 合并为 A[L...R]
if (L >= R) { return; }
int mid = L + (R - L) / 2;
mergeSort(a, L, mid);
mergeSort(a, mid + 1, R);
mergeCount(a, L, mid, R);
}
7、推排序
算法描述
利⽤堆这种数据结构所设计的⼀种排序算法。堆积是⼀个近似完全⼆叉树的结构,并同时满⾜堆积的性
质:即⼦结点的键值或索引总是⼩于(或者⼤于)它的⽗节点。堆排序可以⽤到上⼀次的排序结果,所以不像其他
⼀般的排序⽅法⼀样,每次都要进⾏ n-1 次的⽐较,复杂度为O(nlogn)。
算法步骤:
1、利⽤给定数组创建⼀个堆 H[0…n-1](我们这⾥使⽤最⼩堆),输出堆顶元素
2、以最后⼀个元素代替堆顶,调整成堆,输出堆顶元素
3、把堆的尺⼨缩⼩ 1
4、重复步骤 2,直到堆的尺⼨为 1
图解
代码实现
// 下沉操作
void downAdjust(vector<int> &num, int parent, int n) {
// 临时保存要下沉的元素
int temp = num[parent];
// 定位左孩子节点的位置
int child = 2 * parent + 1;
// 开始下沉
while (child <= n) {
// 如果右孩子节点比左孩子大,则定位到右孩子
if (child + 1 <= n && num[child] < num[child + 1])
++child;
// 如果孩子节点小于或等于父节点,则下沉结束
if (num[child] <= temp)
break;
// 父节点进行下沉
num[parent] = num[child];
parent = child;
child = 2 * parent + 1;
}
num[parent] = temp;
}
// 堆排序
void HeapSort(vector<int> &num) {
int len = num.size();
// 构建大顶堆
for (int i = (len - 2) / 2; i >= 0; --i) {
downAdjust(num, i, len - 1);
}
// 进行堆排序
for (int i = len - 1; i >= 1; --i) {
// 把堆顶元素与最后一个元素交换
std::swap(num[0], num[i]);
// 把打乱的堆进行调整,恢复堆的特性
downAdjust(num, 0, i - 1);
}
}
8、桶排序
算法描述
将数组分到有限数ᰁ的桶⾥。每个桶再个别排序(有可能再使⽤别的排序算法或是以递归⽅式继续使⽤
桶排序进⾏排序)。
代码实现
// 桶排序
// 有负数的话需要进行预处理,本函数包含预处理部分
void BucketSort(vector<int>& num) {
int len = num.size();
// 得到数列的最大最小值
int max = num[0], min = num[0];
for (int i = 1; i < len; ++i) {
if (num[i] > max)
max = num[i];
if (num[i] < min)
min = num[i];
}
// 计算桶的数量并初始化
int bucketNum = (max - min) / len + 1;
vector<int> vec;
vector<vector<int>> bucket;
for (int i = 0; i < bucketNum; ++i)
bucket.push_back(vec);
// 将每个元素放入桶
for (int i = 0; i < len; ++i) {
// 减去最小值,处理后均为非负数
int pos = (num[i] - min) / len;
bucket[pos].push_back(num[i]);
}
// 对每个桶进行排序,此处可选择不同排序方法
for (int i = 0; i < bucket.size(); ++i)
sort(bucket[i].begin(), bucket[i].end());
// 将桶中的元素赋值到原序列
int index = 0;
for (int i = 0; i < bucketNum; ++i)
for (int j = 0; j < bucket[i].size(); ++j)
num[index++] = bucket[i][j];
}
9、计数排序
算法描述:
其基本思想是,⽤待排序的数作为计数数组的下标,统计每个数字的个数。然后依次输出即可得到有序
序列。
图解
代码实现
// 计数排序
void CountingSort(vector<int> &num) {
int len = num.size();
// 得到数列的最大和最小值
int max = num[0], min = num[0];
for (int i = 1; i < len; ++i) {
if(num[i] > max)
max = num[i];
if (num[i] < min)
min = num[i];
}
// 根据数列最大值确定统计数组的长度
vector<int> countArray(max - min + 1, 0);
// 遍历数列,填充统计数组
for (int i = 0; i < len; ++i) {
countArray[num[i] - min]++;
}
// 遍历统计数组,输出结果
int index = 0;
for (int i = 0; i < countArray.size(); ++i) {
for (int j = 0; j < countArray[i]; ++j) {
num[index++] = i + min;
}
}
}
10、基数排序
算法描述
基数排序对要排序的数据是有要求的,需要可以分割出独⽴的“位”来⽐较,⽽且位之间有递进的关系,
如果 a 数据的⾼位⽐ b 数据⼤,那剩下的低位就不⽤⽐较了。除此之外,每⼀位的数据范围不能太⼤,要可以⽤线
性排序算法来排序,否则,基数排序的时间复杂度就⽆法做到 O(n) 了。
基数排序相当于通过循环进⾏了多次桶排序。
代码实现
int getDigit(int x, int d) {
int t[] = { 1,1,10,100 };
return (x / t[d]) % 10;
}
void RadixSort(int* a, int begin, int end, int d) {
const int radix = 10;
int c[1000];
int bucket[1000];
for (int k = 1; k <= d; ++k) {
memset(c, 0, sizeof(c));
for (int i = begin; i <= end; ++i) {
c[getDigit(a[i], k)]++;//计算i号桶⾥要放多少数
}
for (int i = 1; i < radix; ++i) c[i] += c[i - 1];
// 把数据依次装⼊桶(注意:装⼊时的分配技巧)
for (int i = end; i >= begin; --i) {
int j = getDigit(a[i], k);//求出关键码的第k位的数值, 例如:576的第3位是5
bucket[c[j] - 1] = a[i];//放⼊对应的桶中(count[j]-1)表示第k位数值为j的桶底索引
--c[j];//当前第k位数值为j的桶底边界索引减⼀
}
for (int i = begin, j = 0; i <= end; ++i, ++j) {// 从各个桶中收集数据
a[i] = bucket[j];
}
}
}