目录
1、排序的概念及其运用:
1.1、排序的概念:
1.2、常见的排序算法:
2、常见排序算法的实现:
2.1、插入排序:
2.1.1、基本思想:
2.1.2、直接插入排序:
2.1.3、希尔排序( 缩小增量排序 ):
2.2、选择排序:
2.2.1、基本思想:
2.2.2、直接选择排序:
2.2.3、堆排序:
2.3、交换排序:
2.3.1、冒泡排序:
不存在简单排序这个概念、
//插入排序、
//基本思想:有一个有序区间,插入一个数据后,仍保持有序、
//按照自己的方法直接计算时间复杂度则为:O(N^2),外层循环固定次数为:N-1,内存while循环次数不固定,则需要列出来具体的执行次数,而总的执行次数为:T(N)=(1+2+3+....+(N-1))、
//最坏:逆序最坏,比如排升序,但现在数组是降序,则是最坏的情况,则总的执行次数为:T(N)=(1+2+3+....+(N-1)),部分时间复杂度是:O(N^2)、
//最好:顺序最好,比如排升序,现在数组就是升序,则是最好的情况,则总的执行次数为:T(N)=N-1个1相加,等于N-1,则部分时间复杂度就是:O(N)、
//时间复杂度看最坏,则还是:O(N^2)、
//若不是最好的情况,即不是顺序的话,但是非常接近于顺序时,则总的执行次数就比最好情况下的执行次数N-1多一部分常数项次,则部分时间复杂度还是:O(N)、
//对于插入排序而言,顺序或者接近于顺序,是比较合适使用的,虽然时间复杂度是:O(N^2),但是若在该种情况下时,其部分时间复杂度是O(N),则很快就可以完成排序,时间效率较高、
//针对于插入排序进行优化,优化后叫做希尔排序,即希尔排序是插入排序的优化、
void InsertSort(int* a, int n)
{
assert(a);
for (int i = 0; i <= n - 2; i++)
{
//写排序时,先把单趟排序写出来,假设要把下标为end+1的元素插入到下标为[0,end]所构成的一个 有序 序列1中,使插入后构成的新的序列2仍是有序序列、
int end=i;
int tmp = a[end + 1];
while (end >= 0)
{
//降序、
//if (tmp > a[end])
//升序、
if (tmp < a[end])
{
a[end + 1] = a[end];
end--;
}
else
{
//若在while外部后面加上一条语句后,若是通过break出去的话,就会重复操作,所以可以直接把else里面的这条语句删除即可、
//删除-> a[end + 1] = tmp;
break;
}
}
//若要插入到有序序列1中的数据比该序列1中的所有元素都小的话,此时if中的end--后,则end就等于-1,然后再一次循环就进不去while循环内,则有序序列2
//中第一个位置上是空的,并没有把tmp放在该位置上,所以在while循环外部手动再把tmp放在有序序列2中的第一个位置上、
a[end + 1] = tmp;
}
}
//若要对比插入排序和冒泡排序的话,他们的时间复杂度都是:O(N^2),因为时间复杂度看的是最坏的情况、
//虽然时间复杂度是一样的,但是插入排序还是要比冒泡排序要好,两者在最坏的情况下执行的次数是相等的,但是在最好的情况下,两者的执行次数差的很多,即最好情况下的部分时间复杂度是不一样的,此时插入排序的时间效率要好于冒泡排序,即所用时间较少、
在直接插入排序之前若是能先将待排序列进行预排序,使待排序列成为顺序或者接近于顺序,然后再对该序列进行一次直接插入排序,因为此时直接插入排序的时
间复杂度为O(N),那么只要控制预排序阶段的时间复杂度不超过O(N^2),那么整体的时间复杂度就比直接插入排序的时间复杂度低、
希尔排序法又称缩小增量法,希尔排序法的基本思想是:先选定一个整数gap,把待排序文件中所有记录分成gap个组,同一组内数据间的间隔为gap,并对每一组
//希尔排序、
//平均时间复杂度为:O(N^1.3)、
//已知,直接插入排序的时间复杂度是:O(N^2),因为看的是最坏的情况,对于插入排序而言,最好的情况下的时间复杂度是:O(N),接近于顺序的情况下时间复杂度也是:O(N),若使用直接插入排序的方法,不考虑它的优化的话
//即使给定的是一个最好的情况,那么该方法的时间复杂度还是O(N^2),因为时间复杂度要看最坏的情况,虽然时间复杂度是O(N^2),但是还是希望是最好的情况或者接近于最好的情况,这样的话,就会节省很多时间,但是如果要求一个序列
//是顺序或者接近顺序,就比较难了,一般来说,要进行排序的序列都是随机的,并不能保证是顺序或者接近顺序,所以在使用插入排序之前能否先对序列进行调整,使之成为顺序或者接近于顺序,然后再进行插入排序就比较节省时间了,该方法
//即指所谓的希尔排序、
void ShellSort(int* a, int n)
{
assert(a);
方法一:
思路:一组间隔为gap的数据进行插入排序完毕后,再进行下一组间隔为gap的数据的插入排序,直到gap组数据都插入排序结束、
假设gap等于3、
int gap = 3;
先写出gap=3的预排序、
gap的值不能使用一个固定值,gap的最合适的值要随着数组元素个数N的不同而变换,gap的值要和数组元素个数N关联起来,N越来,最开始的gap就越大,预排序过程中并不是只能取一次gap值,可以取多次gap值、
先让gap从大开始,让大的数和小的数更快的到后面和前面去,然后gap再越来越小,再让序列越来越接近顺序,最后当gap等于1时,相当于进行插入排序即可,便可得到顺序序列了,即完成了升序排列、
控制gap组数据、
//int gap = n;
//while (gap > 1) 这个地方不能是>=1,因为经过后面的控制后,如果是>=1,就会陷入死循环、
//{
// //gap怎么变并没有规定,只是常使用如下方式:
// //当gap > 1 时,就是预排序的过程、
// //当gap = 1 时,就是直接插入排序的过程、
// //此处加1能保证最后一次一定是1,不管gap是大于3,等于3,还是小于3,最后的gap值一定是1、
// gap = gap / 3 + 1;
// for (int j = 0; j < gap; j++)
// {
// //控制间隔为gap的一组数据进行插入排序、
// for (int i = j; i < n - gap; i += gap) 为什么是n - gap?因为最后一个end是n-1-gap,end+gap一定小于n、
// {
// int end = i;
// int tmp = a[end + gap];
// while (end >= 0)
// {
// if (tmp < a[end])
// {
// a[end + gap] = a[end];
// end -= gap;
// }
// else
// {
// break;
// }
// }
// //到此有两种情况:
// //1、当tmp>=a[end]时,break出来的。
// //2、当end<0时,不进入while循环,从而达到此处、
// a[end + gap] = tmp;
// }
// }
//}
//方法一和方法二的时间复杂度是一样的、
//方法二:
//思路:和方法一的思路不太相同,把第一组数据中的第一个元素看做是一个有序序列,要排升序,则看做是升序序列,先进行第一组数据中第二个数据的插入,把第二组数据中的第一个元素看做是一个有序序列,要排升序,则看做是升序序列
//再进行第二组数据中第二个数据的插入,把第三组数据中的第一个元素看做是一个有序序列,要排升序,则看做是升序序列,再进行第三组数据中第二个数据的插入,则第一组数据中的前两个元素构成了升序序列,再进行第一组数据中第三个数据的插入,
//则第二组数据中的前两个元素构成了升序序列,再进行第二组数据中第三个数据的插入,则第三组数据中的前两个元素构成了升序序列,再进行第三组数据中第三个数据的插入,重复上述操作,直到把gap组数据中剩下的所有元素都插入排序结束、
//假设gap等于3、
//int gap = 3;
//gap的值不能使用一个固定值,gap的最合适的值要随着数组元素个数N的不同而变换,gap的值要和数组元素个数N关联起来,N越来,最开始的gap就越大,预排序过程中并不是只能取一次gap值,可以取多次gap值、
//先让gap从大开始,让大的数和小的数更快的到后面和前面去,然后gap再越来越小,再让序列越来越接近顺序,最后当gap等于1时,相当于进行插入排序即可,便可得到顺序序列了,即完成了升序排列、
int gap = n;
while (gap > 1)//这个地方不能是>=1,因为经过后面的控制后,如果是>=1,就会陷入死循环、
{
//gap怎么变并没有规定,只是常使用如下方式:
//当gap > 1 时,就是预排序的过程、
//当gap = 1 时,就是直接插入排序的过程、
//此处加1能保证最后一次一定是1,不管gap是大于3,等于3,还是小于3,最后的gap值一定是1、
//此处要是除2的话,直接写成:gap = gap/2即可,肯定能够使得gap取到1,除3要写成下面这样,除别的数不一定,但必须保证能够使得gap取到1才可以。
//gap = gap/2;
gap = gap/3+1;
for (int i = 0; i < n - gap; i++) //为什么是n - gap?因为最后一个end是n-1-gap,end+gap一定小于n、
{
int end = i;
int tmp = a[end + gap];
while (end >= 0)
{
if (tmp < a[end])
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
//到此有两种情况:
//1、当tmp>=a[end]时,break出来的。
//2、当end<0时,不进入while循环,从而达到此处、
a[end + gap] = tmp;
}
}
}
//如果不对插入排序进行优化的话,直接使用插入排序的话,若是逆序的情况,即最坏情况下,部分时间复杂度就是O(N^2),假设n>1、
//若先进行预排,当gap从大减小到1时,进入for循环就相当于是直接插入排序,而当gap等于1,但未进入for循环的时候,此时序列已经很接近于顺序了,
//就可以认为此时再对该序列进行插入排序操作的话,此时最后一步的直接插入的部分的部分时间复杂度就是O(N),效率非常高,只要预排序叠加最后一次的直接插入排序的部分时间小于O(N^2),则希尔排序就起到了作用、
//当n=1时,gap等于1,不进入while循环,则希尔排序调用函数不起作用,当n>1时,此时把n的值赋给gap,则gap也是大于1的,然后gap再逐渐减小,当gap等于1时,进入for循环就相当于是直接插入排序,
//而当gap等于1,但未进入for循环的时候,此时序列已经很接近于顺序了,就可以认为此时再对该序列进行插入排序操作的话,效率非常高、
//希尔排序的缺点就是,若对数组排升序,本来就是升序的话,或者本来就是很接近于升序的话那么预排序就相当于是白做了,虽然该种情况下,预排序很快就执行完了,但总的来说还是白做了,效果不大,
//但是这种情况很少,多数情况下,所给的数组都是随机的,并不是顺序或者接近顺序的,所以在大部分情况下,希尔排序还是能够起到很大的作用的、
//当顺序或者接近顺序时,不管数据量的大小,此时插入都比希尔要好,因为希尔前面的预排序相当于白做了,当随机,部分顺序,或者逆序时,若数据量较大时,则希尔比插入更加合适,但是如果数据量很小
//的时候,希尔并不一定比插入要好、
直接选择排序,直接选择排序优化的特性总结:
//直接选择排序优化、
//void NewSelectSort(int* a, int n)
//{
// assert(a);
// int left = 0;
// int right = n - 1;
// while (left < right)
// {
// //此处若定义成int i = left+1,则把left赋值给mini和maxi,这样就一定不会出现错误,如果在[left+1,right]中找到了比下标为left的值更小的数的话,则更新mini,若找到了比下标为left的值
// //更大的数的话,则更新maxi,若在该范围内找不到比下标为mini的值更小的数据的话,比下标为maxi的数更大的数,则此时mini和maxi所指的数,即left所指的数为最小值,则left所指的数即为最大值、
// //此处若定义成int i = left+1,则把right赋值给mini的话,这样就可能会出现错误,如果在[left+1,right]中找打了比下标为right的值更小的数的话,则更新mini,此时在范围[left+1,right]中的最小值就是下标为mini的数,
// //若在该范围内找不到比mini更小的数的话,则在范围[left+1,right]中的最小的数就是下标为right的数,但是这只是找到了在范围[left+1,right]中的最小值,该最小值并不一定比left所指的数要小,如果该最小值小于等于left所指的数,则是正确的,
// //但若该最小值大于left所指的数,则程序就是错误的,所以尽量不要这样写、
// //此处若定义成int i = left+1,则把right赋值给maxi的话,这样就可能会出现错误,如果在[left+1,right]中找打了比下标为right的值更大的数的话,则更新maxi,此时在范围[left+1,right]中的最大值就是下标为maxi的数,
// //若在该范围内找不到比maxi更大的数的话,则在范围[left+1,right]中的最大的数就是下标为right的数,但是这只是找到了在范围[left+1,right]中的最大值,该最大值并不一定比left所指的数要大,如果该最大值大于等于left所指的数,则是正确的,
// //但若该最大值小于left所指的数,则程序就是错误的,所以尽量不要这样写、
// //所以当定义成int i = left+1的话,一定要把left赋值给mini和maxi,这是一定不会出现错误的,若把right赋值给mini和maxi的话,则有可能会出现错误、
// //若定义成定义成int i = left,则令mini和maxi为left或者right都可以,都不会出现错误、
// int mini = left;
// int maxi = left;
// //若定义成int i = left的话,只需要让数组中的任意一个值作为最大值或者最小值都可以,并不是只能让left和right所指的数作为最大值或者最小值,数组中的元素任意一个都是可以的、
// //若定义成int i = left+1的话,则只能让left所指的数作为最小值或者最大值、
// for (int i = left+1; i <= right; i++)
// {
// if (a[i] < a[mini])
// {
// mini = i;
// }
// if (a[i] > a[maxi])
// {
// maxi = i;
// }
// }
// //此时,left和maxi重叠,当执行第一次Swap时,则最大值9所在的位置并不是maxi,被调换了位置,导致出现问题、
// //这只是凑巧了,出现了极端情况,在这次示例中出现了错误,可能在其他的示例中并不会出现错误、
// Swap(&a[mini], &a[left]);
// //如果left和maxi重叠,则修正一下maxi即可、
// if (left == maxi)
// {
// maxi = mini;
// }
// Swap(&a[maxi], &a[right]);
// left++;
// right--;
// }
//}
//若按照自己的方法求时间复杂度,若数组元素个数为偶数,则外层while循环的次数为固定值N/2次,若数组元素个数为奇数,则外层while循环的次数接近为固定值N/2次,内层循环也为固定值是N-1次,则总的执行次数为:T=(N/2)*(N-1),则时间复杂度就是:O(N^2)、
//选择排序优化中不存在break,故不区分最好最坏,两者都是一样的,则
//最坏:逆序最坏,比如排升序,但现在数组是降序,则是最坏的情况,若数组元素个数为奇数个,则总的执行次数为:T(N)=N+(N-2)+..+3+1,部分时间复杂度是:O(N^2),若数组元素个数为偶数个,则总的执行次数为:T(N)=N+(N-2)+..+2+0,则部分时间复杂度还是:O(N^2)、
//最好:顺序最好,比如排升序,现在数组就是升序,则是最好的情况,若数组元素个数为奇数个,则总的执行次数为:T(N)=N+(N-2)+..+3+1,部分时间复杂度是:O(N^2),若数组元素个数为偶数个,则总的执行次数为:T(N)=N+(N-2)+..+2+0,则部分时间复杂度还是:O(N^2)、
//因为算法中不存在break,当进行升序排序时即使数组已经是升序状态了,也不会直接break出来,所以两层for循环必须都要执行完,则在该种情况下,不区分最好最坏,部分时间复杂度都是O(N^2)、
//时间复杂度看的是最坏的情况,虽然其时间复杂度和冒泡排序优化的时间复杂度都是一样的,即都是O(N^2),但是具体的执行次数是不一样的,冒泡排序优化最坏情况下的执行次数为:T(N)=(N-1)+(N-2)+..+3+2+1
//而选择排序优化的执行次数不论数组元素个数是奇数还是偶数,都比冒泡排序优化最坏情况下的执行次数少了将近一半,故在测试中,选择排序优化会好于冒泡排序优化,即在一般情况下,随机或者逆序情况下,选择排序优化会好于冒泡排序优化、
//但是如果数组是顺序或者接近顺序时,冒泡排序优化在测试中就会比选择优化要好,是因为,此时冒泡排序优化的部分时间复杂度可看成O(N),而选择优化的部分时间复杂度还是O(N^2),因为选择优化中,不存在break,即使是顺序或者接近顺序,那么所有的循环也必须都要全部执行完毕才可以
//所以该种情况下,冒泡优化会比选择优化要好、
//插入排序,顺序则部分时间复杂度是O(N),接近顺序则部分时间复杂度也是O(N)、
//冒泡排序优化,顺序则部分时间复杂度是O(N),接近顺序则部分时间复杂度也是O(N)、
//选择排序优化,顺序则部分时间复杂度是O(N^2),接近顺序则部分时间复杂度也是O(N^2)、
//选择排序,顺序则部分时间复杂度是O(N^2),接近顺序则部分时间复杂度也是O(N^2)、
//对于整体时间复杂度为:O(N^2)的四个排序,即,插入排序,冒泡优化排序,选择优化排序,选择排序而言:
//若是随机或者逆序时,则最好的是选择排序优化,而其他三者差不多、
//若是顺序的话,则插入和冒泡优化差不多,都算最好,其次是选择优化,最坏的是选择、
//若是接近顺序的话,则插入要稍微好于冒泡优化一点,其次是选择优化,最后是选择、
//部分顺序的话,插入排序也能体现出优势,应是最好,其次是冒泡排序优化,exchange的优化没有插入排序的优势大,冒泡排序优化在部分顺序下和选择优化比较的话,要看部分顺序的程度,两者不能明确得出结果,但是冒泡排序优化和选择优化在部分顺序下一定比选择要好、
//此处所指的好坏只是计算了在某些情况下循环的次数,但是在所用时间测试中不一定是上述结果,是因为时间测试中,也把其他非循环的代码执行也计算了他们的时间,这样时间也和非循环代码的量有关系了、
//虽然在随机或逆序情况下,选择优化会比插入更好一点,但是从整体而言,插入是最合适的,即,要从这四个时间复杂度为O(N^2)的方法中选择的话,优先选择插入排序、
//若顺序或者接近顺序,则选择排序的时间效率低于冒泡排序优化、
//若随机或者逆序时,选择排序和冒泡优化,时间效率差不多、
//直接选择排序,升序、
void SelectSort(int* a, int n)
{
assert(a);
int left = 0;
int right = n - 1;
while (left < right)
{
int mini = left;
//若定义 int i=left,则int mini=left/right 均可,若定义int i=left+1,则必须定义为: int mini =left,否则可能会出问题、
//升序或者降序、
//此处若定义成int i = left+1,则把left赋值给mini和maxi,这样就一定不会出现错误,如果在[left+1,right]中找到了比下标为left的值更小的数的话,则更新mini,若找到了比下标为left的值
//更大的数的话,则更新maxi,若在该范围内找不到比下标为mini的值更小的数据的话,比下标为maxi的数更大的数,则此时mini和maxi所指的数,即left所指的数为最小值,则left所指的数即为最大值、
//此处若定义成int i = left+1,则把right赋值给mini的话,这样就可能会出现错误,如果在[left+1,right]中找打了比下标为right的值更小的数的话,则更新mini,此时在范围[left+1,right]中的最小值就是下标为mini的数,
//若在该范围内找不到比mini更小的数的话,则在范围[left+1,right]中的最小的数就是下标为right的数,但是这只是找到了在范围[left+1,right]中的最小值,该最小值并不一定比left所指的数要小,如果该最小值小于等于left所指的数,则是正确的,
//但若该最小值大于left所指的数,则程序就是错误的,所以尽量不要这样写、
//此处若定义成int i = left+1,则把right赋值给maxi的话,这样就可能会出现错误,如果在[left+1,right]中找打了比下标为right的值更大的数的话,则更新maxi,此时在范围[left+1,right]中的最大值就是下标为maxi的数,
//若在该范围内找不到比maxi更大的数的话,则在范围[left+1,right]中的最大的数就是下标为right的数,但是这只是找到了在范围[left+1,right]中的最大值,该最大值并不一定比left所指的数要大,如果该最大值大于等于left所指的数,则是正确的,
//但若该最大值小于left所指的数,则程序就是错误的,所以尽量不要这样写、
//所以当定义成int i = left+1的话,一定要把left赋值给mini和maxi,这是一定不会出现错误的,若把right赋值给mini和maxi的话,则有可能会出现错误、
//若定义成定义成int i = left,则令mini和maxi为left或者right都可以,都不会出现错误、
for (int i = left+1 ; i <= right; i++)
{
if (a[i] < a[mini])
{
mini = i;
}
}
Swap(&a[mini], &a[left]);
left++;
}
}
//若按照自己的方法求时间复杂度,则外层while循环的次数为固定值N-1次,内层循环也为固定值是N-1次,则总的执行次数为:T=(N-1)*(N-1),则时间复杂度就是:O(N^2)、
//选择排序中不存在break,故不区分最好最坏,两者都是一样的,则
//最坏:逆序最坏,比如排升序,但现在数组是降序,则是最坏的情况,则总的执行次数为:T(N)=(N-1)+(N-2)+..+2+1,部分时间复杂度是:O(N^2)、
//最好:顺序最好,比如排升序,现在数组就是升序,则是最好的情况,则总的执行次数为:T(N)=(N-1)+(N-2)+..+2+1,部分时间复杂度是:O(N^2)、
//因为算法中不存在break,当进行升序排序时即使数组已经是升序状态了,也不会直接break出来,所以两层for循环必须都要执行完,则在该种情况下,不区分最好最坏,部分时间复杂度都是O(N^2)、
此时是一个大堆,我们想要得到的是升序的数据,此时如果将堆顶的8和4进行交换后,两个8的相对位置就发生了改变,即,该堆排序算法无论何种写法,均不能
确保做到稳定,则堆排序是不稳定的、
//交换函数,传址调用、
void Swap(int* pa, int* pb)
{
int tmp = *pa;
*pa = *pb;
*pb = tmp;
}
//冒泡排序->本质上是交换排序、
//按照自己的方法直接计算时间复杂度则为:O(N^2),外层循环固定为N-1次,内层循环不固定,则总的执行次数为:T(N)=(N-1)+(N-2)+..+3+2+1、
//最坏:逆序最坏,比如排升序,但现在数组是降序,则是最坏的情况,则总的执行次数为:T(N)=(N-1)+(N-2)+..+3+2+1,部分时间复杂度是:O(N^2)、
//最好:顺序最好,比如排升序,现在数组就是升序,则是最好的情况,则总的执行次数仍为:T(N)=(N-1)+(N-2)+..+3+2+1,则部分时间复杂度就是:O(N^2),因为算法中不存在break,当进行升序排序时
//即使数组已经是升序状态了,也不会直接break出来,只是不进入if语句,但两层for循环必须都要执行完,则在该种情况下,不区分最好最坏,部分时间复杂度都是O(N^2)、
//时间复杂度看最坏,则还是:O(N^2)、
方法一:
//void BubbleSort(int* a, int n)
//{
// assert(a);
// for (int i = 0; i < n-1; i++)
// {
// for (int j = 0; j < n - 1 - i; j++)
// {
// if (a[j]>a[j + 1])
// {
// Swap(&a[j], &a[j + 1]);
// }
// }
// }
//}
方法二:
//void BubbleSort(int* a, int n)
//{
// assert(a);
// for (int i = 0; i < n-1; i++)
// {
// for (int j = 1; j < n- i; j++)
// {
// if (a[j-1]>a[j])
// {
// Swap(&a[j-1], &a[j]);
// }
// }
// }
//}
//冒泡排序优化、
//按照自己的方法直接计算时间复杂度则为:O(N^2),外层循环固定为N-1次,内层循环不固定,则总的执行次数为:T(N)=(N-1)+(N-2)+..+3+2+1、
//最坏:逆序最坏,比如排升序,但现在数组是降序,则是最坏的情况,则总的执行次数为:T(N)=(N-1)+(N-2)+..+3+2+1,部分时间复杂度是:O(N^2)、
//最好:顺序最好,比如排升序,现在数组就是升序,则是最好的情况,则总的执行次数仍为:T(N)=(N-1),则部分时间复杂度是:O(N),因为,若进行升序,而数组就是升序的话,
//这样的话,外层for循环则只进行了一次,在该次循环中,内层for循环进行了N-1次,然后从break直接出去了外层for循环、
//时间复杂度看最坏,则还是:O(N^2)、
//若不是最好的情况,即不是顺序的话,但是非常接近于顺序时,则总的执行次数就比最好情况下的执行次数N-1多一部分,有可能是T(N)=(N-1)+(N-2),则部分时间复杂度还是:O(N)、
void BubbleSort(int* a, int n)
{
assert(a);
for (int i = 0; i < n - 1; i++)
{
int exchange = 0;
for (int j = 0; j < n - 1 - i; j++)
{
if (a[j]>a[j + 1])
{
exchange = 1;
Swap(&a[j], &a[j + 1]);
}
}
//若前一趟冒泡排序若未进行交换,则说明已经满足升序或降序了,则剩余趟数的冒泡排序就不需要再进行了、
if (exchange == 0)
{
//前一趟冒泡排序若未进行交换、
break;
}
}
}
//不是特例的情况下,直接选择排序是最垃圾的、
//插入排序和冒泡排序优化的对比、
//虽然插入排序最坏是N^2,最好是N, 冒泡排序优化最坏是N^2,最好是N,但是两者还是不一样好、
//只有在逆序和顺序,即最坏和最好的情况下,两者是一样的,但是除了这两种情况,还是插入排序比较好,由于时间复杂度看的是最坏的情况,所以即使两者的时间复杂度相同,但是从细节上来说的话,还是插入排序更加合适、
//虽然插入排序在细节上比冒泡排序优化更加合适,但是对于两者而言,由于时间复杂度相同,所以还要看做是同一级别的排序、
//比如:
//数组元素分别为:1 2 3 4 5 6 8 7, ,此时插入排序只计算比较次数,则为:8次, 冒泡排序只计算比较次数则为:7+6=13次,这是因为第一趟冒泡排序比较了7次,交换了7和8,交换完之后
//虽然已经满足了升序,但是此时的exchange==1,所以还要再进入第二趟冒泡排序,第二趟冒泡排序比较次数为6次,但第二趟冒泡排序完之后,exchange仍为0,所以就不再进行第三趟冒泡排序了,
//则总的比较次数为13次,只有在前一趟冒泡排序中未进行交换,才不再进行下一趟冒泡排序,若在前一趟冒泡排序中进行了交换,但交换后满足了升序或者降序,此时是还需要再进行下一次冒泡排序的,这是因为
//前一趟冒泡排序进行了数据的交换,即使前一趟冒泡排序进行交换元素后是升序或者降序了,但是编译器还不知道此时已经是升序或者降序了,只有再进行一趟冒泡排序后未进行数据交换,编译器才知道已经是升序或者降序,就不再进行下一趟了、
//当是顺序时,即最好的情况下,两者一样,但是当部分顺序时,冒泡排序优化中的exchange优化作用不大,而对于插入排序而言,该情况下的那些部分顺序中,也能体现出它的价值,则插入排序更好,其适应性和比较次数都较少、
//当是逆序时,即最坏的情况下,两者一样,但是当部分顺序时,冒泡排序优化中的exchange优化作用不大,而对于插入排序而言,该情况下的那些部分逆序中,也能体现出它的价值,则插入排序更好,其适应性和比较次数都较少、
//所谓的接近顺序指的是接近顺序的程度很大的情况,若接近顺序的程度不大的话,则直接看做是局部顺序即可,当非常接近与顺序时,部分时间复杂度还是O(N),此时的exchange的优化作用很大、