由于是不同时间的组合,冒泡,选择,快排,插入 有一点外部数据外部处理,内部数据内部处理,为了更好的突出这点,理应在参数列表里加上const,这样更明显一些,多出来t 为左起点,len为元素个数,而 归并没有进行处理,直接使用自带的left 和right
冒泡 bubble_sort.h
#pragma once
//冒泡排序
//鱼吐泡泡,比较大的数一点一点向最大的方向移动
template<typename T>
void bubble_sort(T *srcData,int left,int right)
{
int t = left;
int len = right - left;
for (int i = 0; i < len-1; i++) //c0 第一次初始化 c1 判断 c2 自增
{
for (int j = 0; j < len - i - 1; j++) //c3 初始化 c4判断 c5自增
{
if (srcData[t + j]>srcData[t + j + 1]) //c6判断
{
T temp = srcData[t + j]; //3*c7 交换
srcData[t + j] = srcData[t + j + 1];
srcData[t + j + 1] = temp;
}
}
}
}
//c0 8第一次初始化 c1 8判断 c2 8自增 c3 10初始化 c4 10判断 c5 10自增 c6 12判断 3*c7 14-16交换
//全都顺序
//没有 c7 8循环n-1次 c4 (n+2)(n-1)/2 c5,c6 (n-1)*n/2
//t= c0 + c1*n + c2*(n-1) + c3*(n-1)+ c4*(n...2) +c5*((n-1)...1)+c6*((n-1)...1)
// 8 10 12
//t=(c4/2+c5/2+c6/2)n^2+(c1+c2+c3+c4/2-c5/2-c6/2)n+(c0-c2-c3-c4)
//全都逆序
//多了 (n-1)*n/2次的 3c7
//t=(c4/2+c5/2+c6/2+3*c7/2)n^2+(c1+c2+c3+c4/2-c5/2-c6/2-3*c7/2)n+(c0-c2-c3-c4)
选择排序 select_sort.h
#pragma once
//选择排序
//从一堆牌里面找到最小的那张,一张一张取出来
template<typename T>
void select_sort(T *srcData, int left, int right)
{
int t = left;
int len = right - left;
for (int i = 0; i < len - 1; i++) //c0初始化 c1判断 c2自增
{
T temp = i; //c3初始化
for (int j = i + 1; j < len; j++) //c4初始化 c5判断 c6 自增
{
if (srcData[t + temp]>srcData[t + j]) //c7判断
temp = j; //c8 赋值
}
T tempVal=srcData[t+temp]; //c9 swap
srcData[t+temp]=srcData[t+i];
srcData[t+i]=tempVal;
//发现一张比较小的牌,就记住他的位置,看完所有剩余的牌,最后取出来
#if 0
//发现一张比较小的牌就把他取出来,如果不是最小的牌就把他再放到牌堆里面,然后把那张更小的牌取出来
for (int j = i + 1; j < len; j++)
{
if (srcData[t+i]>srcData[t+j])
{
T temp = srcData[t+i];
srcData[t+i] = srcData[t+j] ;
srcData[t+j] = temp;
}
}
#endif
}
}
//c0 8第一次i初始化,c1 8i的判断,c2 8i自增,c3 10temp赋值,c4 11j初始化,c5 11j判断,c6 11j自增,c7 13判断,c8 14赋值 c9 交换
//全都顺序
//没有 c8 11 tj n...2次判断 (n-1)...1自增 13 (n-1)..1判断
// t=c0+c1*n+c2*(n-1) + c3*(n-1) +c4*(n-1) +c5*(2+n)*(n-1)/2 +c6*n*(n-1)/2+ c7*n*(n-1)/2 +c9*(n-1)
// 8 10 11 13 16
//t=(c5/2+c6/2+c7/2)*n^2+(c1+c2+c3+c4+c5/2-c6/2-c7/2)*n+(c0-c2-c3-c4-c5-c9)
//全都逆序
//有c8 赋值 n-1...1次
// 1...n 1...n-1 1...n-1
// t=c0+c1*n+c2*(n-1) + c3*(n-1) +c4*(n-1) +c5*(2+n)*(n-1)/2 +c6*n*(n-1)/2+ c7*n*(n-1)/2+ c8*n*(n-1)/2 +c9*(n-1)
// 8 10 11 13 14 16
//t=(c5/2+c6/2+c7/2+c8/2)*n^2+(c1+c2+c3+c4+c5/2-c6/2-c7/2-c8/2)*n+(c0-c2-c3-c4-c5-c9)
快排 quick_sort.h
#pragma once
//快速排序
//随便拿一张牌,然后把比这张牌小的扔一起,比这张牌大的扔到一起。对小堆,和大堆重复分牌操作
template<typename T>
void quick_sort(T *srcData, int left, int right)
{
int t = srcData[left];
int b = left + 1, e = right-1;
if (b > e) //确定牌堆里面还有牌
return;
while (b <= e) //只要还有牌
{
while (b<=e&&t >= srcData[b]) //只要还有牌,把连续小的扔一起,找到第一张应该在大牌堆里的牌,即这张牌左边都是小牌
b++;
while (b<=e&&t <= srcData[e]) //只要还有牌,把连续大的扔一起,找到第一张应该在小牌堆里的牌,即这张牌右边都是大牌
e--;
if (b<e) //只要还有牌,把这两张特殊的牌放到各自应该在的牌堆里面
{
T temp = srcData[b];
srcData[b] = srcData[e];
srcData[e] = temp;
b++;
e--;
}
}
srcData[left] = srcData[b-1]; //这张牌是最后一张小牌,是中间位置
srcData[b-1] = t;
quick_sort(srcData, left, b-1); //把小牌堆进行重复操作
quick_sort(srcData, b, right); //把大牌堆进行重复操作
}
插入排序 insert_sort.h
#pragma once
//插入排序 打牌的顺牌操作,摸了一张牌,找到它应该在的位置,放进去
//假设每步指令处理时间都是相同的1,输入规模n=10,sigma累加
template<typename T>
void insert_sort(T srcData[], int left, int right)
{
int t = left;
int len = right - left;
for (int i = 1; i < len; i++) //10次判断,9次自增--》9次循环
{
#if 0
//way a srcData 把这张牌从后往前一点一点移动
for (int j = i; j > 0; j--)
{
if (srcData[j + t] < srcData[j + t - 1])
{
T temp = srcData[j + t];
srcData[j + t] = srcData[j + t - 1];
srcData[j + t - 1] = temp;
}
}
#endif
#if 0
//way b 把这张牌,找到位置,然后放进去
T temp = srcData[t + i]; //9次赋值
int j=i; //9次初始化
for (; j > 0 && srcData[j + t - 1] > temp; j--) //sigma tj tj表示对这个j值执行循环的次数 9次赋值j=i,
srcData[j + t] = srcData[j + t - 1]; //sigma (tj-1)
srcData[j + t] = temp; //9次赋值
#endif
//#if 0
//way c 二分查找找位置,但由于赋值缓慢,最差结果为从头开始一个一个添加数据,n^2 ,最顺利结果,不需要赋值,时间全花在了查找上,为n*lgn
T temp = srcData[t + i];
int b=0,e=i,mid;
while(b+1<e)
{
mid=(b+e)>>1;
if(srcData[t+mid]>temp)
e=mid;
else
b = mid;
} //最后定位结果是b这个位置
if (srcData[b] > temp) //如果b这个位置比目标值大,那就在前面插入,主要是初始化第一次插入时,不论结果如何,b都为0
b--;
int j = i;
for (; j-1 > b; j--)
srcData[j + t] = srcData[j + t - 1];
srcData[j + t] = temp;
}
//#endif
}
//c0 9第一次i初始化,c1 9i的判断,c2 9i自增,c3 24temp赋值,c4 25j初始化,c5 26j判断,c6 26j自减,c7 27赋值,c8 28赋值
//全都顺序
//26 只需要进行判断,次数1, 没有自减,没有27
// t=c0+c1*n+c2*(n-1) + c3*(n-1) +c4*(n-1) +c5*(n-1) +c8*(n-1)
// 9 24 25 26 28
//t=(c1+c2+c3+c4+c5+c8)*n+(c0-c2-c3-c4-c5-c8)
//全都逆序
//26 判断j+1次 ,自减j次 27 j次赋值
// 1...n 1...n-1 1...n-1
//t=c0+c1*n+c2*(n-1) + c3*(n-1) +c4*(n-1) + c5*(1+n)*n/2 +c6*n*(n-1)/2 + c7*n*(n-1)/2 +c8*(n-1)
//t=(c5/2+c6/2+c7/2)n^2+(c1+c2+c3+c4+c5/2-c6/2-c7/2+c8)*n+(c0-c2-c3-c4-c8)
归并排序 merge_sort.h
#pragma once
//归并排序
//可以理解为建立在插入排序的基础之上
//插入排序是一张一张未知牌进行找位置,归并排序是一堆一堆已知顺序的牌进行找位置
template<typename T>
void merge(T arr[], int left, int mid, int right)
{
int n1 = mid - left;
int n2 = right - mid;
T *arrL = new T[n1];
T *arrR = new T[n2];
for (int i = 0; i < n1; i++) //记录已经整理好的左牌段
arrL[i] = arr[left + i];
for (int i = 0; i < n2; i++) //记录已经整理好的右牌段
arrR[i] = arr[mid + i];
//相当于把牌段里的所有牌先都取出来,然后再按照正确的顺序,一点一点放进去
#if 0
// 方法一 增加一个最大值以确定结尾,但是作为模板,这个最大值并不确定,这里直接要求所有的排序都为int,同时有出现值本来就是最大值的可能,这种增加值以确定结尾的方法并不安全,但是书写快
arrL[n1] = (unsigned int)(-1) >> 1;
arrR[n2] = (unsigned int)(-1) >> 1;
int i, j, k;
for (k = left, i = 0, j = 0; k < right; k++) //对数据直接进行修改,确定要整理的牌段 k 从left到right
{
if (arrL[i] < arrR[j]) //取出各牌段的最左边的那张牌,把小的那张牌放到正确位置上,然后取出下一张牌
{
arr[k] = arrL[i];
i++;
}
else
{
arr[k] = arrR[j];
j++;
}
}
#endif
//#if 0
//方法二 计数已经排了多少个数
int i=0, j=0, k=left;
while (k < right&&i < n1&&j < n2) //确定整理的牌段 left-right,只要左牌堆里面还有牌,只要右牌堆里面还有牌
{
while (i < n1&&arrL[i] < arrR[j]) //确认左牌堆里面还有牌,并且左牌堆里的这张牌比右牌堆里的那张牌小,把左牌堆这张牌放好
{
arr[k] = arrL[i];
k++;
i++;
}
while (j < n2&&arrL[i] >= arrR[j]) 确认右牌堆里面还有牌,并且右牌堆里的这张牌比左牌堆里的那张牌小,把右牌堆这张牌放好
{
arr[k] = arrR[j];
k++;
j++;
}
}
// 已经放空了一个牌堆,那么剩下的牌只需要按顺序放过去就好了
while (i < n1)
{
arr[k] = arrL[i];
k++;
i++;
}
while (j < n2)
{
arr[k] = arrR[j];
k++;
j++;
}
//#endif
delete arrL; //额外空间,自行释放
delete arrR;
}
template<typename T>
void merge_sort(T arr[], int left, int right)
{
if (left+1 >= right) //逾尾的话如果是left》=right 当不是2的n次幂时,会出现 0 0 1, 而 右半部分0 1会不断循环 成 左0 0 右 0 1 ,出现堆栈溢出
return; //确认牌堆里面不止一张牌
int mid = (right + left) >> 1; //非逾尾,首先使用就很麻烦,因为 传参为 0,len-1 或者 1,len 多一空间的浪费,把正中间最为一个基准
merge_sort(arr, left, mid); //左牌堆太大,再分一分,理一理
merge_sort(arr, mid, right);//右牌堆太大,再分一分,理一理
merge(arr, left, mid, right);//左右牌堆已经分好了,进行插入排序
}
归并在放牌的时候进行统计可以得到逆序对的个数(注意等于条件的判断,从小的开始放,当需要放右牌堆里的牌时,这张牌有关的逆序对个数为左牌堆里剩余牌的个数,从大的开始放,当需要放左牌堆里的牌时,有关这张牌的逆序对个数为右牌堆里剩余牌的个数),n1=左牌堆逆序对个数,n2=右牌堆逆序对个数,n3=对左右堆进行排队的逆序对个数,返回 n1+n2+n3
在规模较小的时候使用插入排序,可以提升速度,因为插入排序有更小的常数因子,假设当长度为k时进行插入排序,则一共要进行n/k次长度为k的插入排序,总时间为 n/k*(k^2) 总个数*单个牌堆排序的时间,总耗时为n*k,那么此时总归并排序可以分成两部分,上半部分进行归并,下半部分为插入,长度为k则进行插入,那么归并的深度为 lg (n/k)(可以把k个元素当成一个点,那么就只有n/k个点,n/k就是新的n*,我们对n*进行归并排序,所以深度为lg n* = lg n/k)元素个数没有变,所以归并上半部分耗时 n lg n/k 下半部分耗时 nk 总时长 n lg n/k + nk。
当 n特别大时,比如 k=2,n=1024 ,lg1024=10 》k 可以认为耗时长全在归并上,此时耗时总时长为 n lg n/k。
若要n lg n/k + nk = n lg n则, lg n/k + k=lg n--》 如果 lg n/k 远大于k 同上,那么 lg n/k< lg n 如果 k稍微大一点,比如n=2^20,k=8 (n lg n/k + nk)/(n lg n)----> (lg n - lg k + k)/(lg n) =1.25 *c (c<1)c是由于插入和归并的指令数量不同引起的,当数据规模比较小时,插入由于总指令比较少而优于归并,(c小于1但也没差多少),我们看前面的1.25,这是一个受数据规模影响的值,2^20 100万左右,挺小的,随便一个题都比这个数据大,当n变大时这个值会趋近于1 可以认为 两者相差不多,如果1.25也在你的接受范围内,那么此时已经算是两者有相同的运行时间