从八大经典排序模版具体实现(包含自定义仿函数)到STL的sort函数灵活运用
文章目录
- 八大经典排序模版
- 1、模版
- 1.0、理解模版
- 1.2、函数模版
- 2、仿函数
- 2.1、什么是仿函数?
- 2.2、模版与仿函数示例演示
- 3、排序
- 3.0.0、排序分类
- 3.0.1、什么是排序稳定性?
- 3.1、冒泡排序
- 3.2、选择排序
- 3.3、插入排序
- 3.4、希尔排序
- 3.5、快速排序
- 3.6、归并排序
- 3.7、堆排序
- 3.8、计数排序
- 3.9、十大排序归纳
- 3.10、完整代码
- 4、STL的sort排序
- 4.0、为什么要会sort排序?
- 4.1、头文件
- 4.3示例演示
八大经典排序模版
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,否则返回falsecout << 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大于等于endif (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、得到数据的最大值和最小值,并算出差值dint 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大于等于endif (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、得到数据的最大值和最小值,并算出差值dint 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;
}
从八大经典排序模版具体实现(包含自定义仿函数)到STL的sort函数灵活运用相关推荐
- 【算法】八大经典排序算法详解
我记得上大学的时候,老师一直跟我们强调:"算法才是编程的灵魂",找工作面试的时候,算法和数据结构也是绝对不可避免的,面试官可能一言不合就让你手写一个排序算法. 我把最经典的八大排序 ...
- 数据结构与算法(十二):八大经典排序算法再回顾
文章出自汪磊的博客,未经允许不得转载 一.排序的理解 提到排序大部分同学肯定第一时间想到int数组的排序,简单啊,所谓排序不就是将int数组按照从大到小或者从小到大排序吗,如果我有个数组存放的不是in ...
- 【数据结构】八大经典排序(两万字大总结)
文章目录 排序的基础知识 1. 排序的概念 2. 常见排序分类 3. 排序的运用 常见排序算法的实现 1. 直接插入排序 1.1 排序思想 1.2 代码实现 1.3 复杂度及稳定性 1.4 特性总结 ...
- java八大经典排序算法
排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存.我们这里说说八大排序就是内部排序. 当n较大,则应采用 ...
- 八大经典排序算法(java版)
这里写目录标题 交换算法之冒泡排序 交换算法之快速排序 插入算法之插入排序 插入排序之希尔排序 选择排序之简单选择排序 排序算法之归并排序 排序算法之基数排序 常用排序算法之堆排序 交换算法之冒泡排序 ...
- c语言sort函数_C语言的那些经典程序 第八期
戳"在看"一起来充电吧! C语言的那些经典程序 第八期 上期带大家欣赏的指针经典程序,感觉如何?这期我们准备了几个新指针的内容,灵活运用指针可以大大减少程序的复杂度,接下来就让小C ...
- sort函数排序字母c语言,c语言数组用sort函数排序?
c语言运用sort 排序函数,需要的头文件是什么? sort不属于C语言的标准函数,所以也没有相应的头文件,但是可以自定义. sort 函数为将整型数组从小到大排序. voidsort(int*a,i ...
- C++中sort函数从大到小排序的两种方法
1.sort函数描述 而且,sort函数的算法效率相当于快排,使用sort函数有时候可能比我们自己写一个排序算法,可能效率更高. 2.使用sort函数排序 #include <iostream& ...
- 排序算法 | sort函数的使用
除了我们自己写的排序算法:冒泡排序.选择排序等,C语言中提供了库函数qsort或者C++中提供了sort函数可以直接调用进行排序.考虑到qsort函数的使用需要用到指针,且写法上也没有sort函数简洁 ...
最新文章
- AI K-means算法对数据进行聚类分析-实验报告
- 神策数据荣获 36 氪 「2020 中国新经济之王」之「最具影响力企业」和「最具竞争力企业」双奖 !
- P2408- 不同子串个数【SA】
- 重载跟重写--笔记2
- VSCode 1.35 发布,新的图标,支持远程开发
- lucene 增量 全量 更新索引_搜索引擎:该如何设计你的倒排索引?
- Jquery easyui 密码两次输入相等的验证
- 软件测试计划包括哪些内容,测试计划如何编写。分享测试计划模板
- 【01】SylixOS下LWIP的实现---动态内存管理
- 率辉考研oj——1323: 算法2-1:集合union
- vue项目引入三方字体
- linux的通配符有哪些,Linux通配符
- 用Java批量修改文件名称
- 高一计算机课期中考试总结反思,高一期中考试总结反思
- 电脑计算器所有快捷键
- 搭建最新版本的Android开发环境
- 语义分析- C-- 语言
- 保持冷静、继续前行——《白说》读后感
- QT的基本使用(一):计算器界面的简易设计及其简单功能实现
- 计算机视觉与图形学-神经渲染专题-StructNeRF室内重建
热门文章
- 直流稳压电源仿真_正负12伏+正负5伏+3.3伏输出,可直接制作
- java gif合成_javacv之于视频/GIF解帧及重新拼接生成GIF实现
- tspline工具_Rhino软件有哪些建模工具?rhino建筑建模方便吗?
- 打造Ubuntu媒体中心 —— 10款顶级视频处理软件推荐
- 《机械制造业智能工厂规划设计》——3.3 机械制造业智能工厂总体框架
- from_import详解
- 转录组解读及下游分析
- 来自苹果的编程语言——Swift简单介绍【整理】
- 自动(智能)驾驶系列|(二)环境感知与识别(1)
- “巨亏成名”的魔鬼交易员,你知道几个?