文章目录
八大经典排序模版
1、模版
1.0、理解模版
C++中提供了对函数重载机制的支持:
定义重载函数时,必须明确处理什么类型的数据,如果对之后新出现的数据类型做相同的操作类型,则要在此定义重载函数。
函数模版就解决了函数重载中多次定义函数的问题。
由于事物的相似性,C++程序设计的类类型和函数有时也是相似的。
类模版就是对一批仅仅成员数据类型不同的类的抽象。
以简单的函数模版举例:
//举例的第一个函数
int add(int a,int b){
return a+b;
}
//举例的第二个函数
double add(double a,double b){
return a+b;
}
不难发现这两个函数基本类似,只是参数类型不同。此时就能将两个函数写成一个模版函数:
//接上一个函数举例
template<class T>
T add(T a,T b){
return a+b;
}
此时该模版函数就接受多种不同类型的数据。
1.2、函数模版
函数模版定义由模版参数说明和函数定义组成,语法如下:
template <class 类型参数名1,class 类型参数名2,...>
函数返回值类型 函数名(形式参数表){
函数体
}
说明:
- 模版参数说明的每个类型参数必须在函数定义形参表中至少出现一次;
- 函数形参表中可以使用模版类型参数,也可以使用一般类型参数。
- 注意在类中写模板,如果再.h中声明,则要在其声明的地方.h中定义,否则会报错
本文主要以函数模版进行排序算法的实现。
2、仿函数
2.1、什么是仿函数?
仿函数,就是仿造函数。它并不是函数,但却有着类似与函数的行为。简单的来说就是重载()运算符。
仿函数的优点:
- 仿函数可以拥有自己的成员和成员变量;
- 仿函数通常比一般函数更块的速度。
//可以在结构体或者在类中定义,以在结构体中定义为例
typedef struct functor {
template <class T>
bool operator()(T a, T b) {
return a < b ? true : false;
}
}FUNCT;
//调用仿函数的模版函数
template<class T,class FUNCTO1>
void function(T a, T b, FUNCTO1 FUN) {
if (FUN(a, b)) { //判断a,b大小,若a<b,则返回true,否则返回false
cout << a << endl;
}
else {
cout << b << endl;
}
}
2.2、模版与仿函数示例演示
代码编译器:VS2019
#include <iostream>
using namespace std;
typedef struct functor {
template <class T>
bool operator()(T a, T b) {
return a < b ? true : false;
}
}FUNCT;
template<class T,class FUNCTO1>
void function(T a, T b, FUNCTO1 FUN) {
if (FUN(a, b)) {
cout << a << endl;
}
else {
cout << b << endl;
}
}
int main() {
int a = 1, b = 0;
struct functor f; //实例化一个
function(a, b,f);
double a1 = 0.66, b1 = 0.99;
function(a1, b1, f);
system("pause"); //暂停屏幕
}
3、排序
3.0.0、排序分类
根据时间复杂度的不同,十大经典排序算法可分为三大类:
- 时间复杂度为O(n2)的排序算法:
- 冒泡排序
- 选择排序
- 插入排序
- 希尔排序(其时间复杂度O(n1.5))
- 时间复杂度为O(nlogn)的排序算法:
- 快速排序
- 归并排序
- 堆排序
- 时间复杂度为线性的排序算法:
- 计数排序
- 桶排序
- 基数排序
3.0.1、什么是排序稳定性?
排序算法根据其稳定性,划分为稳定排序和非稳定排序,
稳定性判断:如果值相同的元素在排序后仍然保持排序前的顺序,则这样的排序算法就是稳定排序;如果值相同的元素在排序后打乱了排序前的顺序,则这样的排序算法就是不稳定排序。
举例:
排序前:
7(0) | 8 | 7(1) | 9 | 0 | 1 |
---|
稳定排序:
0 | 1 | 7(0) | 7(1) | 8 | 9 |
---|
(其中,相同值7的位置没有改变,即7(0)仍在7(1)的前面)
不稳定排序:
0 | 1 | 7(1) | 7(0) | 8 | 9 |
---|
(其中,相同值7的位置发生改变,即7(1)在7(0)的前面)
3.1、冒泡排序
思想:把相邻的元素两两比较,当一个大于(或小于)右侧相邻元素时,交换它们的位置;当一个元素小于等于(或大于等于)右侧相邻元素时,位置保持不变。
以有7个数字组成的无序数列{3,5,2,6,9,8,1}排列成升序为例。
排序步骤:
-
进行第一次排序
两两元素进行比较,当前一项大于后一项则进行交换
3 | 2 | 5 | 6 | 8 | 1 | 9 |
---|
元素9作为数列最大一项,排到了最右侧,此时有序区域只有一个元素9;
-
接下来,进行第二次排序
两两元素进行比较,当前一项大于后一项则进行交换
2 | 3 | 5 | 6 | 1 | 8 | 9 |
---|
第二次排序结束时,数列右侧的有序区有2个元素,分别为8,9;
- 进行第三次排序
两两元素进行比较,当前一项大于后一项则进行交换
2 | 3 | 5 | 1 | 6 | 8 | 9 |
---|
第三次排序结束时,数列右侧的有序区有3个元素,分别为6,8,9;
- 进行第四次排序
两两元素进行比较,当前一项大于后一项则进行交换
2 | 3 | 1 | 5 | 6 | 8 | 9 |
---|
第四次排序结束时,数列右侧的有序区有4个元素,分别为5,6,8,9;
- 进行第五次排序
两两元素进行比较,当前一项大于后一项则进行交换
2 | 1 | 3 | 5 | 6 | 8 | 9 |
---|
第五次排序结束时,数列右侧的有序区有5个元素,分别为3,5,6,8,9;
- 进行第六次排序
两两元素进行比较,当前一项大于后一项则进行交换
1 | 2 | 3 | 5 | 6 | 8 | 9 |
---|
第六次排序结束时,数列右侧的有序区有6个元素,分别为2,3,5,6,8,9;
- 进行第七次排
两两元素进行比较,当前一项大于后一项则进行交换
1 | 2 | 3 | 5 | 6 | 8 | 9 |
---|
//主要代码:
//比较大于仿函数
struct greater1 {
template <class T>
bool operator()(T a, T b) {
return a > b ? true : false;
}
};
//比较小于仿函数
struct less {
template <class T>
bool operator()(T a, T b) {
return a < b ? true : false;
}
};
//冒泡排序 模版接受三个参收,第一个参数为数据存储的起始迭代器,第二个参数为数据存储的尾迭代器,第三个为自定义仿函数也可接受
//系统自带的比较类型仿函数
//迭代器通俗地可以理解为一个指针
//冒泡排序
template<class iterater,class functor>
void bubble_sort(iterater start, iterater end, functor FUNC) {
for (auto it=start; it != end; it++) {
for (auto its = start; its != end; its++) {
if (FUNC(*(its), *(it))) { //调用仿函数进行比较
swap(*it, *its); //系统自带的交换值的函数
}
}
}
}
3.2、选择排序
思路:每一轮选出最小元素(或最大元素)直接交换到左侧。
选择优势排序:省去多余的元素交换。
以有6个数字组成的无序数列{3,5,2,6,9,1}排列成升序为例。
排序步骤:
- 进行第一次排序
在无序区选出最小的元素放在左侧
1 | 5 | 2 | 6 | 9 | 3 |
---|
元素1作为无序区最小一项,排到了最左侧,此时有序区域只有1个元素为1;
- 进行第二次排序
在无序区选出最小的元素放在左侧
1 | 2 | 5 | 6 | 9 | 3 |
---|
元素2作为无序区最小一项,排到了元素1的右侧,此时有序区域有2个元素分别为1,2;
- 进行第三次排序
在无序区选出最小的元素放在左侧
1 | 2 | 3 | 6 | 9 | 5 |
---|
元素3作为无序区最小一项,排到了元素2的右侧,此时有序区域有3个元素分别为1,2,3;
- 进行第四次排序
在无序区选出最小的元素放在左侧
1 | 2 | 3 | 5 | 9 | 6 |
---|
元素5作为无序区最小一项,排到了元素3的右侧,此时有序区域有4个元素分别为1,2,3,5;
- 进行第五次排序
在无序区选出最小的元素放在左侧
1 | 2 | 3 | 5 | 6 | 9 |
---|
元素6作为无序区最小一项,排到了元素5的右侧,此时有序区域有5个元素分别为1,2,3,5,6;
- 进行第六次排序
在无序区选出最小的元素放在左侧
1 | 2 | 3 | 5 | 6 | 9 |
---|
//主要代码:
//比较大于仿函数
struct greater1 {
template <class T>
bool operator()(T a, T b) {
return a > b ? true : false;
}
};
//比较小于仿函数
struct less {
template <class T>
bool operator()(T a, T b) {
return a < b ? true : false;
}
};
//选择排序 模版接受三个参收,第一个参数为数据存储的起始迭代器,第二个参数为数据存储的尾迭代器,第三个为自定义仿函数也可接受
//系统自带的比较类型仿函数
//迭代器通俗地可以理解为一个指针
template <class iterator,class functor>
void selection_sort(iterator start, iterator end, functor FUNC) {
for (auto it = start; it != end; it++) {
auto mIndex = it;
for (auto its = it + 1; its != end; its++) {
if (FUNC(*mIndex, *its)) {
mIndex = its;
}
}
if (it != mIndex) {
swap(*it, *mIndex);
}
}
}
3.3、插入排序
思想:维护一个有序区,把元素一个个插入有序区的适当位置,直到所有元素都是有序为止,类似于摸扑克牌,拿到一张牌即可将其插入到适当位置。
以有6个数字组成的无序数列{3,5,2,6,9,1}排列成升序为例。
排序步骤:
把数组的首元素3作为有序区,此时有序区只有一个元素3。
- 进行第一次排序
取第二个元素5,让元素5与有序区的元素依次比较,由于元素5大于元素3,故不交换。有序区的元素为3,5;
3 | 5 | 2 | 6 | 9 | 1 |
---|
- 进行第二次排序
取第三个元素2,让元素2与有序区的元素依次比较,由于元素5大于元素2,元素3大于元素2,故将元素2插入到元素3前,有序区的元素为2,3,5;
2 | 3 | 5 | 6 | 9 | 1 |
---|
- 进行第三次排序
取第四个元素6,让元素6与有序区的元素依次比较,由于元素6大于元素5,故保持不变。有序区的元素为2,3,5,6;
2 | 3 | 5 | 6 | 9 | 1 |
---|
- 进行第四次排序
取第五个元素9,让元素9与有序区的元素依次进行比较,由于元素9大于元素6,故保持不变。有序区的元素为2,3,5,6,9;
2 | 3 | 5 | 6 | 9 | 1 |
---|
- 进行第五次排序
取第六个元素1,让元素1与有序区的元素依次进行比较,由于元素9大于元素1,元素6大于元素1,元素5大于元素1,元素3大于元素1,元素2大于元素1,故将元素1插入到元素2前,有序区的元素为1,2,3,5,6,9。
1 | 2 | 3 | 5 | 6 | 9 |
---|
//主要代码:
//比较大于仿函数
struct greater1 {
template <class T>
bool operator()(T a, T b) {
return a > b ? true : false;
}
};
//比较小于仿函数
struct less {
template <class T>
bool operator()(T a, T b) {
return a < b ? true : false;
}
};
//插入排序 模版接受三个参收,第一个参数为数据存储的起始迭代器,第二个参数为数据存储的尾迭代器,第三个为自定义仿函数也可接受
//系统自带的比较类型仿函数
//迭代器通俗地可以理解为一个指针
template <class iterator,class functor>
void insertion_sort(iterator start, iterator end, functor FUNC) {
for (auto it = start + 1; it != end; it++) {
auto insertValue = *it;
auto j = it - 1;
//从右往左比较元素的同时,进行元素复制
for (; (j >= start) && FUNC(*j, insertValue); j--) {
*(j + 1) = *j;
}
//insertValue插入到适当的位置
*(j + 1) = insertValue;
}
}
3.4、希尔排序
思想:逐步分组进行粗调,再进行直接插入排序,所谓的分组,就是让元素相隔相同跨度的为一组。其中所使用的分组跨度,如{4,2,1}被称为希尔增量。
以有8个数字组成的无序数列{5,8,6,3,9,2,1,7}排列成升序为例。
排序步骤:
5 | 8 | 6 | 3 | 9 | 2 | 1 | 7 |
---|
- 以分组跨度4,即元素5和元素9一组,元素8和元素2一组,元素6和元素1一组,元素3和元素7一组进行排序,排序结果为:
5 | 2 | 1 | 3 | 9 | 8 | 6 | 7 |
---|
- 以分组跨度2,即元素5,1,9,6为一组,元素2,3,8,7为一组进行排序,排序结果为:
1 | 2 | 5 | 3 | 6 | 7 | 9 | 8 |
---|
3.最后,把分组跨度进一步减小,即跨度为1,也就是等同于做直接插入排序。经过之前的一系列粗调,直接插入排序的工作量减少很多,排序结果为:
1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 |
---|
//主要代码:
//比较大于仿函数
struct greater1 {
template <class T>
bool operator()(T a, T b) {
return a > b ? true : false;
}
};
//比较小于仿函数
struct less {
template <class T>
bool operator()(T a, T b) {
return a < b ? true : false;
}
};
//希尔排序 模版接受三个参收,第一个参数为数据存储的起始迭代器,第二个参数为数据存储的尾迭代器,第三个为自定义仿函数也可接受
//系统自带的比较类型仿函数
//迭代器通俗地可以理解为一个指针
template <class iterator,class functor>
void shell_sort(iterator start, iterator end, functor FUNC) {
//希尔增量
int shellIncrement = end - start;
while (shellIncrement > 1) {
shellIncrement = shellIncrement / 2;
for (iterator it = start; it != start + shellIncrement; it++) {
for (iterator its = it + shellIncrement; its < end; its = its + shellIncrement) {
auto temp = *its;
iterator j;
for (j = its - shellIncrement; (j >= start)&& FUNC(*j, temp);
j = j - shellIncrement) {
*(j + shellIncrement) = *j;
}
*(j + shellIncrement) = temp;
}
}
}
}
3.5、快速排序
思想:在每一轮挑选一个基准元素,并让其他比它大的元素移到数组的一边,比它小的元素移到数组的另一边,即基准元素在每一次排序完后会在固定且正确的位置,从而把数组拆分为两个部分。其中这种思路叫作分治法。
基准选择:最简单的方式是选择数列的第一个元素,当数组的第一个元素为最大值,或者最小值时,无法将数组分为两个部分,此时快速排序时间复杂度退化成O(n2)。避免发生,可以随机选择一个元素作为基准,并且让基准元素和数据的首元素交换位置,在本文中直接选取第一个元素作为基准元素,并未进行随机选取。
以有8个数字组成的无序数列{4,7,3,5,6,2,8,1}排列成升序为例。
- 选定基准元素pivot=4,同时设置一个mark指针指向数据起始位置,这个mark指针代表一个小于基准元素的区域边界。(mark也可以理解位索引)
- 从基准的下一个位置开始遍历数组:
- 如果遍历到的元素大于基准元素,就继续往后遍历
- 如果遍历到的元素小于基准元素,则需要做两件事:
- 把mark指针右移一位,因为小于pivot的区域边界增大了1;
- 让最新遍历到的元素和mark指针所在位置的元素交换位置,因为最新遍历的元素归属小于pivot的区域。
单边排序步骤:
4 | 7 | 3 | 5 | 6 | 2 | 8 | 1 |
---|
- 首先遍历到元素7,7>4,继续遍历,接下来遍历到的元素是3,3<4,所以mark指针右移1位(mark=1,即数据索引1);让元素3和mark指针所在的元素7交换位置,因为元素3归属小于pivot的区域(mark不变)。
4 | 3 | 7 | 5 | 6 | 2 | 8 | 1 |
---|
- 继续遍历元素5>4,元素6>4,元素2<4,mark指针右移,元素2与mark指针的所在位置进行交换,
4 | 3 | 2 | 5 | 6 | 7 | 8 | 1 |
---|
- 继续元素遍历,元素8>4,元素1<4,mark指针右移,元素1与mark指针的所在位置进行交换。
4 | 3 | 2 | 1 | 6 | 7 | 8 | 5 |
---|
- 最后把pivot元素与mark指针的元素进行交换,即第一轮排序结束
1 | 3 | 2 | 4 | 6 | 7 | 8 | 5 |
---|
-
进入下一轮排序,分别以元素4位分界值,元素4左边部分进行重新排序,元素值右边进行重新排序,在左边,以元素1为基准进行排序,则排序结果为:
1 3 2 在右边,以元素6为基准元素进行排序,则排序结果为:
6 7 8 5 再以基准1和基准6为分界值,在进行重新排序,由于基准1和基准6的左边没有数据,则停止递归调用。
-
重复上述选取基准,以及mark指针的操作,一直递归调用直到递归调用停止。
//主要代码:
//比较大于仿函数
struct greater1 {
template <class T>
bool operator()(T a, T b) {
return a > b ? true : false;
}
};
//比较小于仿函数
struct less {
template <class T>
bool operator()(T a, T b) {
return a < b ? true : false;
}
};
//快速排序 模版接受三个参收,第一个参数为数据存储的起始迭代器,第二个参数为数据存储的尾迭代器,第三个为自定义仿函数也可接受
//系统自带的比较类型仿函数
//迭代器通俗地可以理解为一个指针
//快速单边排序
template <class iterator,class functor>
void unilateral_quick_sort(iterator start, iterator end, functor FUNC) {
//递归结束条件:start大于等于end
if (start >= end - 1) {
return;
}
//得到基准元素位置
iterator pivotIndex = partition(start, end, FUNC);
//根据基准元素,分成两部分进行递归排序
unilateral_quick_sort(start, pivotIndex, FUNC);
unilateral_quick_sort(pivotIndex + 1, end, FUNC);
}
template <class iterator, class functor>
iterator partition(iterator start, iterator end, functor FUNC) {
//取第一个位置元素为基准元素
auto pivot = *start;
iterator mark = start;
for (iterator it = start + 1; it < end; it++) {
if (FUNC(pivot, *it)) {
mark++;
swap(*it,*mark);
}
}
*start = *mark;
*mark = pivot;
return mark;
}
3.6、归并排序
思想:将数据进行一个分组、再进行归并。其主要的思想是分治思想和递归思想
分组:假设集合一共有n个元素,算法将会对集合进行逐层的对半分组
- 第一层分为2组,每组n/2个元素;
- 第二层分为4组,每组n/4个元素;
- …
- 一直到每组只有一个元素。
归并:当每组元素内部比较出先后顺序后,小组之间进一步比较和排序,合并成一个大组;大组之间再进一步操作…最终,所有元素都合并成一个有序的集合。
以有7个数字组成的无序数列{38,27,43,3,9,82,10}排列成升序为例。
对数据分组和归并操作:
//主要代码:
//比较大于仿函数
struct greater1 {
template <class T>
bool operator()(T a, T b) {
return a > b ? true : false;
}
};
//比较小于仿函数
struct less {
template <class T>
bool operator()(T a, T b) {
return a < b ? true : false;
}
};
//归并排序 模版接受三个参收,第一个参数为数据存储的起始迭代器,第二个参数为数据存储的尾迭代器,第三个为自定义仿函数也可接受
//系统自带的比较类型仿函数
//迭代器通俗地可以理解为一个指针
//归并排序
template <class iterator,class functor>
void merge_sort(iterator start, iterator end, functor FUNC) {
if (end - 1 >= start) {
merge_sort1(start, -- end, FUNC);
}
}
//归并排序
template <class iterator, class functor>
void merge_sort1(iterator start, iterator end, functor FUNC) {
if (start < end) {
int mid = (int)(end - start) / 2;
//折半成两个小集合,分别进行递归
merge_sort1(start, start + mid, FUNC);
merge_sort1(start + mid + 1, end, FUNC);
//把两个有序集合,归并成一个大集合
merge(start, start + mid, end, FUNC);
}
}
template <class iterator, class functor>
void merge(iterator start, iterator mid, iterator end, functor FUNC) {
//开辟额外大的集合
int tempArray[100]; //如果为double类型,则归并排序中的这个临时数组也要改为double类型,
//其它的排序则没有这个影响
iterator p1 = start;
iterator p2 = mid + 1;
int i = 0;
for (iterator it = start; it <= end; it++) {
tempArray[i++] = *it;
}
for (iterator it = start; it <= end; it++) {
if (p1 > mid) {
*it = tempArray[p2 - start];
p2++;
}
else if (p2 > end) {
*it = tempArray[p1 - start];
p1++;
}
else if (FUNC(tempArray[p1 - start], tempArray[p2 - start])) {
*it = tempArray[p2 - start];
p2++;
}
else {
*it = tempArray[p1 - start];
p1++;
}
}
}
3.7、堆排序
思路:先建立大根堆(或小根堆),然后将堆顶元素与末尾未排序的元素进行交换,重复上述过程,直到所有元素有序(即最终所创建的大根堆或者小根堆只有一个元素)。
主要步骤:
- 把无序的数据构建成二叉堆。需要从小到大排序构建大根堆;需要从小到大排序构建小根堆。
- 将堆顶元素与无序区的最后一个元素进行交换,调整堆产生新堆。
以有8个数字组成的无序数列{1,5,2,6,7,3,8,9,10}排列成降序为例。
排序步骤:
- 构建小根堆
此时将堆顶元素1与无序区的最后一个元素10进行交换,即排序为
10 | 5 | 2 | 6 | 7 | 3 | 8 | 9 | 1 |
---|
- 将剩余无序区的元素重新构建小根堆。
此时将堆顶元素2与无序区的最后一个元素9进行交换,即排序为
9 | 5 | 3 | 6 | 7 | 10 | 8 | 2 | 1 |
---|
- 将剩余无序区的元素重新构建小根堆。
此时将堆顶元素3与无序区的最后一个元素8进行交换,即排序为
9 | 5 | 8 | 6 | 7 | 10 | 3 | 2 | 1 |
---|
- 重复上述过程直到所有元素有序
最终排序为:
10 | 9 | 8 | 7 | 6 | 5 | 3 | 2 | 1 |
---|
具体二叉堆的介绍请看从树的创建、遍历(包括递归、非递归)到二叉堆的构建、插入和删除最后到优先队列(含STL优先队列)
//主要代码:
//比较大于仿函数
struct greater1 {
template <class T>
bool operator()(T a, T b) {
return a > b ? true : false;
}
};
//比较小于仿函数
struct less {
template <class T>
bool operator()(T a, T b) {
return a < b ? true : false;
}
};
//堆排序 模版接受三个参收,第一个参数为数据存储的起始迭代器,第二个参数为数据存储的尾迭代器,第三个为自定义仿函数也可接受
//系统自带的比较类型仿函数
//迭代器通俗地可以理解为一个指针
//堆排序
template <class iterator,class functor>
void heap_sort(iterator start, iterator end, functor FUNC) {
//把无序数组构建成堆
for (auto it = (end - start - 2) / 2 + start; it >= start; it--) {
down_Adjust(start, it, end, FUNC);
}
for (auto it = end - 1; it > start; it--) {
//最后一个元素和第一个元素进行交换
swap(*start, *it);
for (auto itParentIndex = (it - start - 2) / 2 + start;
itParentIndex >= start; itParentIndex--) {
down_Adjust(start, itParentIndex, it, FUNC);
}
}
}
template <class iterator, class functor>
void down_Adjust(iterator start, iterator parentIndex, iterator end, functor FUNC) {
//temp用于保存父节点值,用于最后赋值
auto temp = *parentIndex;
int childIndex = (parentIndex - start) * 2 + 1;
while (childIndex < (end - start)) {
//如果有右孩子,且右孩子大于(或小于)左孩子的值,则定位到右孩子
if (childIndex + 1 < (end - start)
&& FUNC(*(start + childIndex + 1), *(start + childIndex))) {
childIndex++;
}
//如果父节点大于(或小于)任何一个孩子节点的值,则直接跳出
if (FUNC(temp, *(childIndex + start))) {
break;
}
*parentIndex = *(childIndex + start);
parentIndex = start + childIndex;
childIndex = childIndex * 2 + 1;
}
*parentIndex = temp;
}
3.8、计数排序
思想:将数据放入到特定且连续的集合中,并未进行比较元素大小,将元素放完后,再从集合中依次读取,此时所得到元素所有都是有序的。
计数排序的缺陷:1、当数据的最大值和最小值差距过大时,不适合运用计数排序;
2、当数据不是整数时,操作性太小,也不适合计数排序。
以有8个数字组成的无序数列{91,95,92,96,97,93,95,99,100}排列成降序为例。
排序步骤:
-
计算所有数据的最大值max,最小值min;然后依次开辟一个临时空间,其空间的大小为max-min,并将临时开辟的空间赋初值0;
min ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ max
0 0 0 0 0 0 0 0 0 0 -
接下来依次读取元素,取第一个元素91,将其放在索引为91-min中(即是让临时数组的值加一,eg: tempArray[91-min]++),
1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
---|
- 取第二个元素95,将其放在索引为95-min中(即是让临时数组的值加一,eg: tempArray[95-min]++);
1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
---|
- 重复上述操作,直到所有元素都放入到临时数组中
1 | 1 | 1 | 0 | 2 | 1 | 1 | 0 | 1 | 1 |
---|
- 再读出元素,返回到原来的数据中(即为索引值+min,eg: Array[i] = tempArray[i]+min)
91 | 92 | 93 | 95 | 95 | 96 | 97 | 99 | 100 |
---|
//计数排序 模版接受三个参收,第一个参数为数据存储的起始迭代器,第二个参数为数据存储的尾迭代器
//系统自带的比较类型仿函数
//迭代器通俗地可以理解为一个指针,未进行比较大小,故统一升序排序
template <class iterator>
void count_sort(iterator start, iterator end) {
//1、得到数据的最大值和最小值,并算出差值d
int min = *start;
int max = *start;
for (iterator it = start + 1; it != end; it++) {
if (*it > max) {
max = *it;
}
if (*it < min) {
min = *it;
}
}
int d = max - min;
//2、创建统计数据并统计对应元素的个数
//由于计数排序只适用于正数,故所开数据的类型已知
int* countArray = (int*)malloc((end - start) * sizeof(int));
for (int i = 0; i < end - start; i++) {
countArray[i] = 0;
}
for (iterator it = start; it != end; it++) {
countArray[*it - min]++;
}
//将排好序的元素重新返回到原数据中
iterator it = start;
for (int i = 0; i < end - start; ) {
if (countArray[i] != 0) {
*it = i + min;
countArray[i]--;
it++;
}
else {
i++;
}
}
}
当然,排序还有桶排序、基数排序,在此不再叙述。
3.9、十大排序归纳
排序算法平均时间复杂度、空间复杂度、稳定性判断归纳:
排序算法 | 时间复杂度 | 空间复杂度 | 是否为稳定排序 |
---|---|---|---|
冒泡排序 | O(n2) | O(1) | 稳定排序 |
选择排序 | O(n2) | O(1) | 非稳定排序 |
插入排序 | O(n2) | O(1) | 稳定排序 |
希尔排序 | O(n1.5) | O(1) | 非稳定排序 |
快速排序 | O(nlogn) | O(logn) | 非稳定排序 |
归并排序 | O(nlogn) | O(n) | 稳定排序 |
堆排序 | O(nlogn) | O(1) | 非稳定排序 |
计数排序 | O(n) | O(m) | 稳定排序 |
桶排序 | O(n) | O(n) | 稳定排序 |
基数排序 | O(n) | O(n+m) | 稳定排序 |
3.10、完整代码
代码编译器:VS2019
//主要代码 创建sort类,对8种排序模版进行封装 sort.h
//注意再类中写模板,如果在.h中声明,则要在其声明的地方.h中定义,否则会报错
#pragma once
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
struct greater1 {
template <class T>
bool operator()(T a, T b) {
return a > b ? true : false;
}
};
struct less1 {
template <class T>
bool operator()(T a, T b) {
return a < b ? true : false;
}
};
class Sort{
public:
//冒泡排序
template<class iterater,class functor>
void bubble_sort(iterater start, iterater end, functor FUNC) {
for (auto it=start; it != end; it++) {
for (auto its = start; its != end; its++) {
if (FUNC(*(its), *(it))) { //调用仿函数进行比较
swap(*it, *its); //系统自带的交换值的函数
}
}
}
}
//选择排序
template <class iterator,class functor>
void selection_sort(iterator start, iterator end, functor FUNC) {
for (auto it = start; it != end; it++) {
auto mIndex = it;
for (auto its = it + 1; its != end; its++) {
if (FUNC(*mIndex, *its)) {
mIndex = its;
}
}
if (it != mIndex) {
swap(*it, *mIndex);
}
}
}
//插入排序
template <class iterator,class functor>
void insertion_sort(iterator start, iterator end, functor FUNC) {
for (auto it = start + 1; it != end; it++) {
auto insertValue = *it;
auto j = it - 1;
//从右往左比较元素的同时,进行元素复制
for (; (j >= start) && FUNC(*j, insertValue); j--) {
*(j + 1) = *j;
}
//insertValue插入到适当的位置
*(j + 1) = insertValue;
}
}
//希尔排序
template <class iterator,class functor>
void shell_sort(iterator start, iterator end, functor FUNC) {
//希尔增量
int shellIncrement = end - start;
while (shellIncrement > 1) {
shellIncrement = shellIncrement / 2;
for (iterator it = start; it != start + shellIncrement; it++) {
for (iterator its = it + shellIncrement; its < end; its = its + shellIncrement) {
auto temp = *its;
iterator j;
for (j = its - shellIncrement; (j >= start)&& FUNC(*j, temp); j = j - shellIncrement) {
*(j + shellIncrement) = *j;
}
*(j + shellIncrement) = temp;
}
}
}
}
//快速单边排序
template <class iterator,class functor>
void unilateral_quick_sort(iterator start, iterator end, functor FUNC) {
//递归结束条件:start大于等于end
if (start >= end - 1) {
return;
}
//得到基准元素位置
iterator pivotIndex = partition(start, end, FUNC);
//根据基准元素,分成两部分进行递归排序
unilateral_quick_sort(start, pivotIndex, FUNC);
unilateral_quick_sort(pivotIndex + 1, end, FUNC);
}
//归并排序
template <class iterator,class functor>
void merge_sort(iterator start, iterator end, functor FUNC) {
if (end - 1 >= start) {
merge_sort1(start, -- end, FUNC);
}
}
//堆排序
template <class iterator,class functor>
void heap_sort(iterator start, iterator end, functor FUNC) {
//把无序数组构建成堆
for (auto it = (end - start - 2) / 2 + start; it >= start; it--) {
down_Adjust(start, it, end, FUNC);
}
for (auto it = end - 1; it > start; it--) {
//最后一个元素和第一个元素进行交换
swap(*start, *it);
for (auto itParentIndex = (it - start - 2) / 2 + start; itParentIndex >= start; itParentIndex--) {
down_Adjust(start, itParentIndex, it, FUNC);
}
}
}
//计数排序
template <class iterator>
void count_sort(iterator start, iterator end) {
//1、得到数据的最大值和最小值,并算出差值d
int min = *start;
int max = *start;
for (iterator it = start + 1; it != end; it++) {
if (*it > max) {
max = *it;
}
if (*it < min) {
min = *it;
}
}
int d = max - min;
//2、创建统计数据并统计对应元素的个数
//由于计数排序只适用于正数,故所开数据的类型已知
int* countArray = (int*)malloc((end - start) * sizeof(int));
for (int i = 0; i < end - start; i++) {
countArray[i] = 0;
}
for (iterator it = start; it != end; it++) {
countArray[*it - min]++;
}
//将排好序的元素重新返回到原数据中
iterator it = start;
for (int i = 0; i < end - start; ) {
if (countArray[i] != 0) {
*it = i + min;
countArray[i]--;
it++;
}
else {
i++;
}
}
}
//输出
template <class T>
void display(T start, T end) {
auto it =start;
while (it != end) {
cout << *(it) << " ";
it++;
}
}
private:
//快速排序
template <class iterator, class functor>
iterator partition(iterator start, iterator end, functor FUNC) {
//取第一个位置元素为基准元素
auto pivot = *start;
iterator mark = start;
for (iterator it = start + 1; it < end; it++) {
if (FUNC(pivot, *it)) {
mark++;
swap(*it,*mark);
}
}
*start = *mark;
*mark = pivot;
return mark;
}
//堆排序
template <class iterator, class functor>
void down_Adjust(iterator start, iterator parentIndex, iterator end, functor FUNC) {
//temp用于保存父节点值,用于最后赋值
auto temp = *parentIndex;
int childIndex = (parentIndex - start) * 2 + 1;
while (childIndex < (end - start)) {
//如果有右孩子,且右孩子大于(或小于)左孩子的值,则定位到右孩子
if (childIndex + 1 < (end - start)
&& FUNC(*(start + childIndex + 1), *(start + childIndex))) {
childIndex++;
}
//如果父节点大于(或小于)任何一个孩子节点的值,则直接跳出
if (FUNC(temp, *(childIndex + start))) {
break;
}
*parentIndex = *(childIndex + start);
parentIndex = start + childIndex;
childIndex = childIndex * 2 + 1;
}
*parentIndex = temp;
}
//归并排序
template <class iterator, class functor>
void merge_sort1(iterator start, iterator end, functor FUNC) {
if (start < end) {
int mid = (int)(end - start) / 2;
//折半成两个小集合,分别进行递归
merge_sort1(start, start + mid, FUNC);
merge_sort1(start + mid + 1, end, FUNC);
//把两个有序集合,归并成一个大集合
merge(start, start + mid, end, FUNC);
}
}
template <class iterator, class functor>
void merge(iterator start, iterator mid, iterator end, functor FUNC) {
//开辟额外大的集合
int tempArray[100]; //如果为double类型,则归并排序中的这个临时数组也要改为double类型,
//其它的排序则没有这个影响
iterator p1 = start;
iterator p2 = mid + 1;
int i = 0;
for (iterator it = start; it <= end; it++) {
tempArray[i++] = *it;
}
for (iterator it = start; it <= end; it++) {
if (p1 > mid) {
*it = tempArray[p2 - start];
p2++;
}
else if (p2 > end) {
*it = tempArray[p1 - start];
p1++;
}
else if (FUNC(tempArray[p1 - start], tempArray[p2 - start])) {
*it = tempArray[p2 - start];
p2++;
}
else {
*it = tempArray[p1 - start];
p1++;
}
}
}
};
//演示代码 main.cpp
#include <iostream>
#include <algorithm>
#include <string>
#include "Sort.h"
using namespace std;
int main() {
Sort sort;
struct greater1 great;
struct less1 less1;
int array[] = { 10,9,7,1,3,6,8,9,4,2};
double dArray[] = { 1.99,0.11,1.22,1.2,1.33,2.33,1.99,1.88,2.22,2.33,1.77 };
//sort.bubble_sort(array, array + 10, greater<int>());
//sort.bubble_sort(array, array + 10, less1);
//sort.selection_sort(array, array + 10,less<int>());
//sort.selection_sort(array, array + 10, great);
//sort.selection_sort(array, array + 10, less1);
//sort.insertion_sort(array, array + 10, less1);
//sort.insertion_sort(array, array + 10, great);
//sort.shell_sort(array, array + 10, less1);
//sort.shell_sort(array, array + 10, great);
//sort.unilateral_quick_sort(array, array + 10, less1);
//sort.unilateral_quick_sort(array, array + 10, great);
//sort.merge_sort(array, array + 10, great);
//sort.merge_sort(array, array + 10, less1);
//sort.heap_sort(array, array + 10, great);
//sort.heap_sort(dArray, dArray + 11,less1);
sort.count_sort(array, array + 10); //计数排序不是基于元素比较,故不接受比较仿函数
//sort.display(dArray, dArray + 11);
sort.display(array, array + 10);
system("pause");
}
4、STL的sort排序
4.0、为什么要会sort排序?
经历了前面八种金典排序的学习,不难发现当我们每次需要排序时,如果都要去实现一种排序是很麻烦的一件事,简单排序耗时长,复杂排序不好写。因此,STL就带有排序的函数sort,其本质上sort函数就是利用模版实现的,sort函数的底层用到的是内省式排序以及插入排序,内省排序首先从快速排序开始,当递归深度超过一定深度(深度为排序元素数量的对数值)后转为堆排序。
4.1、头文件
#include <algorithm>
###4.2、sort函数运用
sort(数据起始迭代器,数据末尾迭代器,比较仿函数)
其中:1、迭代器在一定程度上也可以理解为指针
2、比较仿函数有 less<数据类型>()
greater<数据类型>() //注意数据类型和比较排序元素的数据类型要一致
其中比较仿函数可以省略,省略则让为是升序排序
4.3示例演示
代码编译器:VS2019
//演示代码 main.cpp
#include <iostream>
#include <algorithm>
using namespace std;
template<class T>
void display(T start, T end) {
for (auto it = start; it != end; it++) {
cout << *it << " ";
}
cout << endl;
}
int main() {
int array[10] = { 3,4,2,1,9,7,10,2,1,6 };
double dArray[10] = { 1.22,0.22,0.77,0.67,2.78,1.78,1.89,0.45,0.90,1.00 };
//sort(array, array + 10, less<int>());
//sort(array, array + 10, greater<int>());
sort(dArray, dArray + 10);
//display(array, array + 10);
display(dArray, dArray + 10);
return 0;
}