目录
1、几种排序算法汇总
#include<iostream>
#include<cstdlib>
#include<ctime>
#include<string>
using namespace std;
#define MAX_NUM 200000
//求数组长度的模板函数
template<typename T>
int getArryLen(T& arr) {
return (sizeof(arr) / sizeof(arr[0]));
}
//交换两个数
void mySwap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
//1.冒泡排序
/*
基本思想:
1.比较相邻的元素。如果第一个比第二个大,就交换他们两个。
2.对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
3.针对所有的元素重复以上的步骤,除了最后一个。
4.持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
优点:比较简单,空间复杂度较低,是稳定的
缺点:时间复杂度太高,效率不好
*/
void bubbleSort(int arr[],int len) {
for (int i = 0; i < len; i++) {
for (int j = i+1; j < len; j++) {
if (arr[i] > arr[j]) {
mySwap(arr[i], arr[j]);
}
}
}
}
//2.选择排序:选择排序只是减少了交换的次数
/*
基本思想:每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。
优点:移动数据的次数已知(n-1 次);
缺点:比较次数多。
*/
void selectSort(int arr[], int len) {
int min;
for (int i = 0; i < len; i++) {
min = i;
for (int j = i + 1; j < len; j++) {
if (arr[j] < arr[min]) {
min = j;
}
}
if (min != i) {
mySwap(arr[i], arr[min]);
}
}
}
//3.插入排序:将无序元素插入到有序的序列中。
/*
基本思想:每步将一个待排序的序列按其数据的大小插到前面已经排好序的序列中,直到全部记录插入完毕为止。适用于基本有序或者元素个数比较少的情行。
优点:稳定,快;
缺点:比较次数不一定,插入后的数据移动多,特别是当数据总量庞大的时候,但用链表可以解决这个问题。
*/
void insertSort(int arr[], int len) {
for (int i = 1,j; i < len; i++) {//把第一数看作一个有序的序列,后面的序列看作一个待排序的序列,把后面的序列插入前面的序列中
if (arr[i] < arr[i - 1]) {//说明后面的数据比前面的小
int temp = arr[i];
for (j = i - 1; j >= 0 && temp < arr[j]; j--) {//交互数据,条件:j>=0且临时数据比前面的还要小
arr[j + 1] = arr[j];
}
arr[j + 1] = temp;//因为上面的for循环中j是--了的,判断后不再具备循环条件,所以就应该把最开始比较的临时数据放在此处
}
}
}
//或者写成下面的格式
void insertSort(int arr[], int len) {
for (int i = 1, j; i < len; i++) {
if (arr[i] < arr[i - 1]) {
int temp = arr[i];
for (j = i; j > 0 && temp < arr[j - 1]; j--) {
arr[j] = arr[j - 1];
}
arr[j] = temp;
}
}
}
//下面是插入排序方式二,加了一个模板,变成了一个模板函数,并且更简洁点,但是这样交换次数多,速度比上面的慢
template<typename T>
void insertionSort(T arr[], int len) {
for (int i = 1, i < len; i++) {
for (int j = i, j > 0 && arr[j]<arr[j-1]; j--) {
swap(arr[j], arr[j - 1]);//直接使用C++11中std中的函数
}
}
}
//插入排序中对数组中的部分序列排序
template<typename T>
void insertionSort(T arr[], int l,int r) {
for (int i = l + 1; i <= r; i++) {
int temp = arr[i];
for (int j = i; j > 0 && temp < arr[j - 1]; j--) {
arr[j] = arr[j - 1];
}
arr[j] = temp;
}
}
//4.希尔排序:先根据增量间隔分组后再对每一组分别进行插入排序,也叫减少增量排序,其分组增量逐渐减少,直到1
/*
基本思想:希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
优点:快,数据移动少
缺点:不稳定,增量的取值无确切答案,只能凭经验来取
*/
void shellSort(int arr[], int len) {
int increment = len;//增量,用于设置选择的数据间隔,第一次计算后才是真正开始使用
do
{
increment = increment / 3 + 1;//计算增量的一个约定俗成,增量多大就分多少个组。这是经过大量尝试后惯用的分组间隔,每次增量逐渐减小直到1。
for (int i = 0,j,k; i < increment; i++) {
for (j = i + increment; j < len; j += increment) {
if (arr[j] < arr[j - increment]) {
int temp = arr[j];
for (k = j - increment; k >= 0&&temp < arr[k]; k-= increment) {
arr[k + increment] = arr[k];
}
arr[k + increment] = temp;
}
}
}
} while (increment>1);//使用do while循环是为了考虑incresment==1的情况。
}
//5.快速排序:分治法+挖坑填数(非常非常重要!!!!!!!!)
/*
快速排序是C.R.A.Hoare 于1962 年提出的一种划分交换排序。它采用了一种分治的策略,通常称其为分治法(Divide-and-ConquerMethod)。
基本思想:1.先从数列中找出一个数作为基准数(枢轴)2.将比它大的数全部放在它的右边,小于或等于的数全部放在左边(升序)
3.再对左右区间重复第二步,直到各区间只有一个数,通过递归实现!
*/
void quickSort(int arr[], int start, int end) {//后面两个参数是数组的开始和结尾的位置
if (start >= end) return;//终止条件,或者在下面中添加if(i<j){}函数,确保递归的返回或不操作
int i = start;
int j = end;
//基准数
int temp = arr[start];
while (i < j) {
//从右到左不断找,直到不满足情况,j停下来
while (i < j&&arr[j] >= temp) {
j--;//移动j指针
}
if (i < j) {
arr[i] = arr[j];//填坑,把右边小于基准数的数交换到左边
}
//从左到右不断找,直到不满足情况,i停下来
while (i < j&&arr[i] < temp) {
i++;//移动i指针
}
if (i < j) {
arr[j] = arr[i];//填坑,把左边大于于基准数的数交换到右边
}
}
//这个时候i==j了,那么就把基准数给填到这个arr[i]的坑里面,给基准数找到了合适的位置
arr[i] = temp;
//再调用递归,分别对左右两边重复以上操作
quickSort(arr, start, i - 1);//左边区排序
quickSort(arr, i + 1, end);//右边区排序
}
//6.归并排序
/*
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divideand Conquer)的一个非常典型的应用。
基本思路就是将数组分成二组A,B,如果这二组组内的数据都是有序的,那么就可以很方便的将这二组数据进行排序。如何让这二组组内数据有序了?
可以将A,B 组各自再分成二组。依次类推,当分出来的小组只有一个数据时,可以认为这个小组组内已经达到了有序,然后再合并相邻的二个小组就可以了。
这样通过先递归的分解数列,再合并数列就完成了归并排序。
如何合并两个有序序列呢?只要从比较二个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。
然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可。
*/
//定义合并两个有序的数组的函数,对,调用这个方法的前提是两个数组都是有序的,而且还要告诉中间值
void merge(int arr[], int first, int mid, int last, int temp[]) {
int i = first;//左边有序序列的开始位置
int j = mid + 1;//右边有序序列的开始位置
int length = 0;//临时数组的长度
while (i <= mid && j <= last) {
if (arr[i] < arr[j]) {
temp[length] = arr[i];
i++;
}else{
temp[length] = arr[j];
j++;
}
length++;//临时数组指针+1
}
//现在这个时候这两个有序序列中肯定有一个的最后一个数据还未放入临时数组中,所以下面要判断并放入临时数组,而且这个数是最大(升序)
while (i <= mid) {
temp[length] = arr[i];
i++;
length++;
}
while (j <= last) {
temp[length] = arr[j];
j++;
length++;
}
//将临时空间中排好序的数组覆盖原空间的数据中
for (int i = 0; i < length; i++) {
arr[i + first] = temp[i];
}
}
void mergeSort(int arr[], int first, int last, int temp[]) {//temp是一个用户开辟的用于临时存储数据的堆内存空间,参数类型也可写成int* temp
if (first >= last) return;
int mid =(first +last)/2;//通过mid把数组分成左右两边
//左半边排序
mergeSort(arr, first, mid, temp);
//右半边排序
mergeSort(arr, mid+1, last, temp);
//合并两个有序的数组
merge(arr, first, mid, last, temp);
}
//7.堆排序:即把整个数组看成一个完全二叉树,然后再进行堆调整,把最大或最小的数调整到根节点,再与最后一个数交换,再继续调整余下的节点...
//对二叉树节点进行堆的调整,即调整为大堆或小堆
/*
@param arr 待调整的数组
@param index 待调整的结点的下标
@param len 数组的长度
*/
void heapAdjust(int arr[],int index,int len) {
int max = index;//该二叉树根节点
int lChild = index * 2 + 1;//该父节点的左子节点的下标,计算方法来源于完全二叉树,也可以观察得出
int rChild = index * 2 + 2;//该父节点的右子节点的下标
if (lChild < len && arr[lChild] > arr[max]) {
max = lChild;
}
if (rChild < len && arr[rChild] > arr[max]) {
max = rChild;
}
if (max != index) {//说明max的位置变了,说明子节点比根节点更大,那么就交换
mySwap(arr[max],arr[index]);
//下面再用递归,继续调整它的子节点,直到max的值没有变,或超出了len的范围
heapAdjust(arr, max, len);
}
}
void heapSort(int arr[], int len) {//len为数组的长度
int index = len / 2 - 1;//获得最后一个二叉树的根节点的下标,也就是这个完全二叉树有index+1个小树
//1.对二叉树进行堆初始化调整,即把这个完全二叉树调整为大顶堆(即每个根节点都比子节点大,用于升序)
for (int i = index;i >= 0; i--) {//因为有index+1棵树,所以要调整index+1次,把每棵小树都调整成堆的样子
heapAdjust(arr, i, len);
}
//2.交换堆顶元素和最后一个元素,因为堆顶元素是调整后最大的,最后一个元素相当于数组的最后一个
for (int i = len - 1; i >= 0; i--) {
mySwap(arr[0], arr[i]);//堆顶元素也就是数组的第一个位置的元素
//继续进堆调整
heapAdjust(arr, 0, i);//现在我们将调整的初始节点设置为整个输的根节点,也就是数组的第一个数,因为这个点不符合堆的样子,所以要调整它,调整这个点的另外一个好处就是可以至上而下地递归调整各个小树
}
}
//打印数组
void printArray(int arr[],int len) {
for (int i = 0; i < len; i++) {
cout << arr[i] << " ";
}
cout << endl;
}
typedef void(*sortFunction)(int*,int);//冒、选、插、希排序函数指针声明
typedef void(*sortFunction2)(int*, int,int);//快速排序函数指针声明
typedef void(*sortFunction3)(int*, int,int,int*);//归并排序函数指针声明
//排序并输出排序所用时间
void sortAndTimereport(sortFunction sf, int arr[], int len, string sortType) {//第一个参数是接收排序函数,最后一个参数用来说明是什么排序类型
//排序前的时间
time_t startSecondTime, endSecendTime;
clock_t cstartMilliSecondTime, cendMilliSecondTime;
startSecondTime = time(NULL);//获取当前系统时间
cstartMilliSecondTime = clock();
//排序
sf(arr, len);
//排序后的时间
endSecendTime = time(NULL);//获取当前系统时间
cendMilliSecondTime = clock();
//打印
cout <<"【"+ sortType+ "】" << len << "个元素大约用时:\t" << difftime(endSecendTime, startSecondTime) << "秒, 精确用时:\t" << cendMilliSecondTime - cstartMilliSecondTime << "毫秒" << endl;
}
//排序并输出排序所用时间(专门用于快速排序)
void sortAndTimereport(sortFunction2 sf,int arr[], int len,string sortType) {//第一个参数是接收排序函数,最后一个参数用来说明是什么排序类型
//排序前的时间
time_t startSecondTime, endSecendTime;
clock_t cstartMilliSecondTime, cendMilliSecondTime;
startSecondTime = time(NULL);//获取当前系统时间
cstartMilliSecondTime = clock();
//排序
sf(arr, 0,len-1);
//排序后的时间
endSecendTime = time(NULL);//获取当前系统时间
cendMilliSecondTime = clock();
//打印
cout << "【" + sortType + "】" << len << "个元素大约用时:\t" << difftime(endSecendTime, startSecondTime) << "秒, 精确用时:\t" << cendMilliSecondTime - cstartMilliSecondTime << "毫秒" << endl;
}
//排序并输出排序所用时间(专门用于归并排序)
void sortAndTimereport(sortFunction3 sf,int arr[], int len,string sortType,int* tempArray) {//第一个参数是接收排序函数,最后一个参数用来接收临时数组指针
//排序前的时间
time_t startSecondTime, endSecendTime;
clock_t cstartMilliSecondTime, cendMilliSecondTime;
startSecondTime = time(NULL);//获取当前系统时间
cstartMilliSecondTime = clock();
//排序
sf(arr, 0,len-1, tempArray);
//排序后的时间
endSecendTime = time(NULL);//获取当前系统时间
cendMilliSecondTime = clock();
//打印
cout << "【" + sortType + "】" << len << "个元素大约用时:\t" << difftime(endSecendTime, startSecondTime) << "秒, 精确用时:\t" << cendMilliSecondTime - cstartMilliSecondTime << "毫秒" << endl;
}
int main() {
//创建数组并赋值
static int numArry1[MAX_NUM];//在占用内存空间较大的局部数组声明的前面加static将其从堆栈数据段挪到全局数据段即可避开因局部数组大小超过默认堆栈大小1MB造成程序不能正常运行的问题。
static int numArry2[MAX_NUM];
static int numArry3[MAX_NUM];
static int numArry4[MAX_NUM];
static int numArry5[MAX_NUM];
static int numArry6[MAX_NUM];
static int numArry7[MAX_NUM];
srand((unsigned)time(NULL));//设置随机源,使生产的随机数发生变化
for (int i = 0; i < MAX_NUM; i++) {
int temp = rand() % MAX_NUM;
numArry1[i] = temp;
numArry2[i] = temp;
numArry3[i] = temp;
numArry4[i] = temp;
numArry5[i] = temp;
numArry6[i] = temp;
numArry7[i] = temp;
}
//获得数组长度
int length = getArryLen(numArry1);
//为归并排序开辟临时的数组
int* tempArray = new int[MAX_NUM];//或者使用c语言格式:int* tempArray=(int*)malloc(sizeof(int)*MAX_NUM)
//排序并报告排序所用时间
sortAndTimereport(quickSort, numArry2, length, "快速排序");
sortAndTimereport(mergeSort, numArry1, length, "归并排序",tempArray);
sortAndTimereport(shellSort, numArry3, length, "希尔排序");
sortAndTimereport(heapSort, numArry7, length, " 堆排序");
sortAndTimereport(insertSort, numArry4, length, "插入排序");
sortAndTimereport(selectSort, numArry5, length, "选择排序");
sortAndTimereport(bubbleSort, numArry6, length, "冒泡排序");
delete[] tempArray;//释放掉自己申请的内存。
system("pause");
return 0;
}
2、排序耗时
(1)20万数据时:
(2)10万数据时:
(3)一千万数据时:
3、归并排序实现2
//归并排序实现2:归并排序是O(nlogn)级的排序算法
//归并排序的归并方法
template<typename T>
void mergeFunction(T arr[], int l, int mid, int r) {
//将两个序列归并到一个序列中,这儿要临时开辟一个arr相同大小的数组
T aux[r - l + 1];//新建一个临时数组,并把arr的值赋到临时数组中
for (int i = l; i <= r; i++) {
aux[i - l] = arr[i];
}
int i = l, j = mid + 1;//i表示左边序列指针,j表示右边序列指针
for (int k = l, k <=r; k++) {
if (i > mid) {
arr[k] = aux[j - l];
j++;
}else if(j>r){
arr[k] = aux[i - l];
i++;
}else if (aur[i-l]< aur[j-l]){
arr[k] = aux[i - l];
i++;
}else {
arr[k] = aux[j - l];
j++;
}
}
}
//归并排序内部递归函数
template<typename T>
void mergeSortInner(T arr[], int l, int r) {
if (l >= r) return;
int mid = (l + r) / 2;
//左半边排序
mergeSortInner(arr, l, mid);
//右半边排序
mergeSortInner(arr, mid+1,r);
//归并
mergeFunction(arr, l,mid, r);
}
//归并排序
template<typename T>
void mergeSort(T arr[], int len) {
mergeSortInner(arr,0,len - 1);
}
3.1 归并排序的进一步优化
//对归并排序的进一步的优化
//优化1:mergeSortInner的排序方法中加入判断,如果排序的数据大体都是有序的,那么可以加入判断
template<typename T>
void mergeSortInnerPro1(T arr[], int l, int r) {
if (l >= r) return;
int mid = (l + r) / 2;
//左半边排序
mergeSortInner(arr, l, mid);
//右半边排序
mergeSortInner(arr, mid + 1, r);
//有判断的归并,适用于数据基本有序的情况,也就是如果左右两个序列已经有序了,就不再合并了,减少合并次数
if(arr[mid]>arr[mid+1])
mergeFunction(arr, l, mid, r);
}
//优化2:在对数据分组分到足够小的时候就不再分组了,而将这些小的分组使用插入排序进行排序
template<typename T>
void mergeSortInnerPro2(T arr[], int l, int r) {
//当分组分到20的时候,就不再继续往下分组,而将这个规模的数据使用插入排序进行排序
if (r - l <= 20) {//这个数可以自定义
insertionSort(arr, l, r);
return;
}
int mid = (l + r) / 2;
//左半边排序
mergeSortInner(arr, l, mid);
//右半边排序
mergeSortInner(arr, mid + 1, r);
//有判断的归并,适用于数据基本有序的情况
if (arr[mid] > arr[mid + 1])
mergeFunction(arr, l, mid, r);
}
4、快速排序实现2
//快速排序的实现2
//分割函数,目的就是找出基准数的位置并返回,当然我们需要指定基准数的大小,一般以第一个数作为基准数
template<typename T>
int partition(T arr[],int start,int end) {
T baseValue = arr[start];
int j = start;//用j来记录分界点
for (int i = start + 1; i <= end; i++) {
if (arr[i] < baseValue) { //只考虑比基准值小的,小的就交换
swap(arr[j + 1], arr[i]);//当然这儿也可以写成swap(arr[++j],arr[i]),然后就不需要后面的j++了.
j++;
}
}
//现在的j的位置,就是左边及j都是值小于baseValue的位置
swap(arr[start], arr[j]);//将基准值交换到对应的真实位置,那么现在j左边的都小于基准值,j右边的都大于等于基准值
return j;
}
template<typename T>
void quickSort(T arr[], int start, int end) {
if (start >= end) return;
int p = partition(arr, start, end);
quickSort(arr, start, p-1);
quickSort(arr, p+1, end);
}
4.1快速排序优化1:随机化快速排序法
//快速排序的优化1:随机化快速排序法。如果是面对近乎有序的数据,快速排序的方法可能会退化到O(n2)级别了,这样就会非常的慢
//解决办法就是:基准数设置为随机,而不是之前的第一个
template<typename T>
int partitionPro1(T arr[], int start, int end) {
swap(arr[start], arr[rand() % (end - start + 1) + start]);//在start-end间随机选择一个数作为基准数
T baseValue = arr[start];
int j = start;//用j来记录分界点
for (int i = start + 1; i <= end; i++) {
if (arr[i] < baseValue) { //只考虑比基准值小的,小的就交换
swap(arr[j + 1], arr[i]);//当然这儿也可以写成swap(arr[++j],arr[i]),然后就不需要后面的j++了.
j++;
}
}
//现在的j的位置,就是左边及j都是值小于baseValue的位置
swap(arr[start], arr[j]);//将基准值交换到对应的真实位置,那么现在j左边的都小于基准值,j右边的都大于等于基准值
return j;
}
template<typename T>
void quickSortPro1(T arr[], int start, int end) {
if (start >= end) return;//这儿递归的地方还可以优化,就是在递归到只有几十个数据的时候采用插入排序,
/*在递归到数据量小的时候采用插入排序
if (end - start <= 20) {//这个数可以自定义
insertionSort(arr, l, r);
return;
}
*/
srand(time(NULL));//设置随机种子
int p = partitionPro1(arr, start, end);
quickSortPro1(arr, start, p - 1);
quickSortPro1(arr, p + 1, end);
}
4.2快速排序优化2:双路快速排序法
//快速排序优化2:双路快速排序。这个优化解决的问题是:当判断的指针所指的数是等于V,而且有大量的与V相等数,如果按照之前的v小于在左边,v大于等于在右边
//就会造成左边很小,右边很大,影响效率,这个优化就是让等于V的数平均分配到左边和右边。
// 双路快速排序的partition
// 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]
template <typename T>
int _partition2(T arr[], int l, int r) {
// 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot
swap(arr[l], arr[rand() % (r - l + 1) + l]);
T v = arr[l];
// arr[l+1...i) <= v; arr(j...r] >= v
int i = l + 1, j = r;
while (true) {
// 注意这里的边界, arr[i] < v, 不能是arr[i] <= v
// 思考一下为什么?
while (i <= r && arr[i] < v)
i++;
// 注意这里的边界, arr[j] > v, 不能是arr[j] >= v
// 思考一下为什么?
while (j >= l + 1 && arr[j] > v)
j--;
// 对于上面的两个边界的设定, 有的同学在课程的问答区有很好的回答:)
// 大家可以参考: http://coding.imooc.com/learn/questiondetail/4920.html
//答案:这是因为对于连续出现相等的情况,a方式(<)会交换i和j的值;而b方式(<=)则会将连续出现的这些值归为其中一方,使得两棵子树不平衡
if (i > j)
break;
swap(arr[i], arr[j]);
i++;
j--;
}
4.3快速排序优化3:三路快速排序法
//快速排序优化3:三路快速排序。将<v,==v,>v的数据都进行归类,对应含有大量相同元素的数组,优化效果非常明显
// 递归的三路快速排序算法
template <typename T>
void __quickSort3Ways(T arr[], int l, int r) {
// 对于小规模数组, 使用插入排序进行优化
if (r - l <= 15) {
insertionSort(arr, l, r);
return;
}
// 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot
swap(arr[l], arr[rand() % (r - l + 1) + l]);
T v = arr[l];
int lt = l; // arr[l+1...lt] < v
int gt = r + 1; // arr[gt...r] > v
int i = l + 1; // arr[lt+1...i) == v
while (i < gt) {
if (arr[i] < v) {
swap(arr[i], arr[lt + 1]);
i++;
lt++;
}
else if (arr[i] > v) {
swap(arr[i], arr[gt - 1]);
gt--;
}
else { // arr[i] == v
i++;
}
}
swap(arr[l], arr[lt]);
__quickSort3Ways(arr, l, lt - 1);
__quickSort3Ways(arr, gt, r);
}
template <typename T>
void quickSort3Ways(T arr[], int n) {
srand(time(NULL));
__quickSort3Ways(arr, 0, n - 1);
}