冒泡排序和快速排序都属于交换排序,其基本思想是两两比较待排序对象,如果次序与预期次序相反的话,则交换彼此的位置,直到所有哦对象排好序为止。
冒泡排序:过程很简单,以递增为例,从数组第一个元素开始,与下一个元素值两两比较,若后一个元素值大于前一个元素值,则交换位置,直到最后两个元素相比较。从而把最 大的移动到最后一个位置;再从第一个元素开始寻找前n-1个元素中的最大值,置于数组n-2位置。不断重复,直至第一个元素。
//**************3冒泡排序************************
//无论是升序还是降序,最下面的位置的元素最先确定 ,所以j的下界一直为0;上界值变化
void BubbleSort(int a[],int len)
{
int i,j,temp;
for (i=0;i<len-1;i++)//没必要i=len-1
for(j=0;j<len-i-1;j++)//j>0且:i=0,j+1=0~len-1;i=1,j+1=0~len-2;...
if(a[j]>a[j+1])
{
temp=a[j];
a[j]=a[j+1];
a[j+1]=temp;
}
}
//**************3.1双向冒泡排序************************
//无论是升序还是降序,最下面的位置的元素最先确定 ,所以j的下界一直为0;上界值变化
void DoubleBubbleSort(int a[],int len)
{
int i,temp;
int start=0,end=len-1;
while(start<=end)
{
for (i=start;i< end;i++)
if(a[i]>a[i+1])
{
temp=a[i];
a[i]=a[i+1];
a[i+1]=temp;
}
end--;
for (i=end;i>start;i--)
if(a[i]<a[i-1])
{
temp=a[i];
a[i]=a[i-1];
a[i-1]=temp;
}
start++;
}
}
时间复杂度:
最好为O(n) 待排序序列排序之前就已经是有序的,需要进行的比较操作为(n-1)次。交换操作为0次此时两两比较都不需要交换位置,说明有序,比较n-1次就完成排序。
最差的情况是每次元素都要查到最后O(n^2) ,序列是降序排列,那么此时需要进行的比较共有n(n-1)/2次。交换操作数和比较操作数一样
稳定性分析:冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。所以,如果两个元素相等,我想你是不会再无 聊地把他们俩交换一下的;如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候也不会交换,所以相同元素的前后顺序并没有改 变,所以冒泡排序是一种稳定排序算法。
改进:双向冒泡排序(鸡尾酒排序)
双向冒泡排序在一次排序中让重的气泡沉到地下,然后再让轻的气泡浮到上面,从而一次完成两个元素的定位。时间复杂度也为O(n^2).
快速排序
思想:分治策略
快排原理:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分数据的所有数据都要笑,然后再按此方法对这两部分数据分别进行快速排序。保证列表的前半部分都小于后半部分就使得任何一个数从此以后都不再跟后半部分的数进行比较了,大大减少了数字间的比较次数。
//**************4快速排序************************
void QuickSort(int a[],int left,int right)
{ //调用时 left=0 right=n-1
int i=left,j=right;
int key=a[left];
if(left>=right)
return;
while(i<j)
{
//顺序很重要,先从右往左扫描
//找到第一个小于temp的关键字=号不能去
while(i<j && a[j]>=key)
j--;
a[i]=a[j];//a[i++]=a[j]是错误的
//再找左边的
while(i<j && key>=a[i])
i++;
a[j]=a[i];//a[j--]=a[i]是错误的
}
//中间数变为基准(第一个数的值),左右两边的数分别比他小和大
a[i]=key;
QuickSort(a,left,i-1);
QuickSort(a,i+1,right);
}
/******************快排分治法**********************/
int Partion(int a[],int p,int r)
{
int i=p;
int j=r;
int key=a[i];
while(i<j)
{
while(i<j && a[j]>=key)
j--;
if(i<j)
a[i]=a[j];
while(i<j && a[i]<=key)
i++;
if(i<j)
a[j]=a[i];
}
a[i]=key;
return i;
}
void QuickSort_2(int a[],int p,int r)
{
int q;
if(p<r)
{
q=Partion(a,p,r);
QuickSort_2(a,p,q-1);
QuickSort_2(a,q+1,r);
}
}
快排时间复杂度依赖于划分是否平衡。
最坏情况划分:本身已经有序。除了关键字,划分子问题分别包含n-1个元素和0个元素 划分操作的时间复杂度为O(n),这样划分和冒泡一样需要n(n-1)/2次比较
T(n)=T(n-1)+T(0)+O(n)=T(n-1)+O(n)=O(n^2) --------->也可看做是冒泡的时间复杂度递归形式
最好情况划分:划分的两个子问题规模都近似于n/2 则:
T(n)=T(n/2)+T(n/2)+O(n)=O(nlgn)
事实上,非平衡划分,任何一种常数比例的划分都会产生深度为O(lgn)的递归树,其中每一层的时间代价都是O(n)
例:T(n)=T(0.9n)+T(0.1n)+O(n)=O(nlgn) 用主方法也可以求得。
空间复杂度:O(1)
稳定性:快速排序有两个方向,左边的i下标一直往右走,当a[i] <= a[center_index],其中center_index是中枢元素的数组下标,一般取为数组第0个元素。而右边的j下标一直往左走,当a[j] > a[center_index]。如果i和j都走不动了,i <= j, 交换a[i]和a[j],重复上面的过程,直到i>j。 交换a[j]和a[center_index],完成一趟快速排序。在中枢元素和a[j]交换的时候,很有可能把前面的元素的稳定性打乱,比如序列为 5 3 3 4 3 8 9 10 11, 现在中枢元素5和3(第5个元素,下标从1开始计)交换就会把元素3的稳定性打乱,所以快速排序是一个不稳定的排序算法,不稳定发生在中枢元素和a[j] 交换的时刻。
快排为什么比冒泡排序快:快排是冒泡的改进(冒泡也可以写成递归形式),每一趟快排的时间复杂度是O(n),接下来对快排划分出来的两个子数组进行递归快排,递归快排的深度是O(lgn).所以总的时间复杂度是O(nlgn)
优化改进
当每次分区后,两个分区的元素个数相近时,效率最高。所以找一个比较有代表性的基准值就是关键。通常会采取如下方式:
1. 选取分区的第一个元素做为基准值。这种方式在分区基本有序情况下会分区不均。
2. 随机快排:每次分区的基准值是该分区的随机元素,这样就避免了有序导致的分布不均的问题
3. 平衡快排:取开头、结尾、中间3个数据,通过比较选出其中的中值。