一、冒泡排序
【算法步骤】以n个数排序,从小到大排为例说明。
(1)待排序数中,从第1个数开始,每个数与它后面的数比较,若逆序则交换。完成后则最大数排到最后,待排序数个数减少一个。
(2)若待排序数个数为1,则它就是最小数,排在首位。否则,回到第(1)步。
具体讲解如下:
第一趟:第1、2个数比较,若逆序则交换;第2、3个数比较,若逆序则交换;……;第n-1、n个数比较,若逆序则交换。
比较次数为n-1次。比较结束后,最大数排到第n位。
第二趟:第1、2个数比较,若逆序则交换;第2、3个数比较,若逆序则交换;……;第n-2、n-1个数比较,若逆序则交换。
比较次数为n-2次。比较结束后,第二大的数排到第n-1位。
……
第n-1趟:第1、2个数比较,若逆序则交换。
比较次数为1次。比较结束后,第二小的数排在第2位。
第一小的数不用再比较。冒泡排序结束。
【注意】1、共进行n-1趟。第一趟比较n-1次,第二趟比较n-2次,……第n-1趟比较1次。2、可能会发生相邻元素间的交换。
【优化】引入布尔变量no_sort=1,如果某趟有交换,则no_sort=0,否则no_sort=1,内循环结束后判断no_sort是否为1,若为1,则退出外循环,否则,继续外循环。
【稳定性】稳定
【时间复杂度】
1、没有优化时为O(n^2)。
2、优化后:
最好情况下,待排序数已经按要求排好序,则只进行n-1次比较,不用交换,故时间复杂度为O(n)。
最坏情况下,待排序数刚好倒序,则要进行(n-1)+(n-2)+...+1=n*(n-1)/2次比较及交换,故时间复杂度为O(n^2)。
【示例代码】
//冒泡排序(优化后)
void bubblesort(int a[],int n){
bool no_sort;//判断是否有交换,如果有一轮没有交换,则随之终止循环
for(int i=n-1;i>=1;i--){//循环的次数为数组长度-1,排剩下的最后一个数不需要排序
no_sotr=true;
for(int j=1;j<=i;j++){//循环次数为待排序数-1
swap(a[j],a[j+1]);
no_sort=false;
}
if(no_sort)break;
}
}
二、快速排序
【算法步骤】以n个数排序,从小到大排为例说明。
(1)随机选取一个元素(通常取中间元素)作为枢轴或支点。
(2)将所有小于等于支点的元素放在左边序列,所有大于等于支点的元素放在右边序列。这样完成一趟快速排序。
(3)继续分别对这两个子序列进行快速排序,直到整个序列有序。
具体做法如下:
(1)引入两个指针i与j,分别指向待排序元素的首l与尾r。支点记为mid。
(2)i从左往右扫描,指向第一个大于等于mid的元素。j从右往左扫描,指向第一个小于等于mid的元素。交换这两个元素。如果i<=j,则重复(2)。
(3)如果左子序列元素个数大于等于2,则对左序列进行快速排序。右序列同样处理。如果元素个数为1个,则不用继续快速排序。
【注意】1、这是对冒泡算法的改进,采用了分治的思想。2、只关心支点的值,不用关心支点的位置。3、可能发生不相邻元素间的交换,从而改变两个相等元素的先后位置,所以快速排序是不稳定排序。
【稳定性】不稳定
【时间复杂度】平均为O(nlogn),最坏为O(n^2)。
平均情况下,每趟快排结束后,每次划分使两个子序列的长度大致相等,时间复杂度为O(nlogn)。
【证明】 已知T(n)=2T(n/2)+n
=2*(2T(n/4)+n/2)+n=2^2T(n/2^2)+2n
=2^2(2T(n/8)+n/4)+2n=2^3T(n/2^3)+3n
=…
令n=2^m,则
T(n)=2^mT(1)+mn=2^lognT(1)+nlogn=n+nlogn=O(nlogn)
补充说明:递归logn层,每层比较O(n)次,故时间复杂度为O(nlogn)。
最坏情况下,是待排序记录已经排好序。以每次快排都以第一个待排序元素为支点为例说明:第一趟经过n-1次比较后第一个记录保持位置不变,并得到一个n-1个元素的子序列;第二趟经过n-2次比较,将第二个记录定位在原来的位置上,并得到一个包括n-2个元素的子序列,依次类推,这样总的比较次数是: (n-1)+(n-2)+…+1=n(n-1)/2,从而时间复杂度为O(n^2)。
【示例代码】
下面两种不同代码有比较大的区别:
1、以中间位置元素为支点,将小于等于支点的数放在左边序列,大于等于支点的数放在右边序列。i与j是两个哨兵,i从左往右, j从右往左,直到两个哨兵错过身,也就是说,两个哨兵碰面了,也要作交换!
//快速排序,以中间位置元素为支点
void qsort(int l, int r)
{
int i=l,j=r,mid=a[(l+r)/2];//指针i,j。支点
while(i<=j){//不能少等号
while(a[i]<mid)i++;//不能有等号
while(a[j]>mid)j--;//不能有等号
if(i<=j){//不能少等号
swap(a[i],a[j]);i++;j--;
}
}
if(l<j)qsort(l,j);
if(i<r)qsort(i,r);
}
2、以序列第一个元素为支点。将小于支点的元素归在左子序列,大于支点的元素归在右子序列。支点不进入下一层递归。
//快速排序,以序列第一个元素为支点
void qsort(int l, int r)
{
if(l>=r) return;
int i=l,j=r;
int key=a[i]; /*用数组的第一个记录作为分区元素*/
while(i!=j){
while(i<j&&a[j]>=key) j--; //从右向左扫描,找第一个码值小于key的记录,并交换到a[i]
a[i]=a[j];
while(i<j&&a[i]<=key) i++; //从左向右扫描,找第一个码值大于key的记录,并交换到a[j]
a[j]=a[i];
}
a[i]=key; //分区元素放到正确位置
qsort(l,i-1);
qsort(i+1,r);
}
参考文献:
1、https://blog.csdn.net/K346K346/article/details/50791102
2、c++信息学奥赛一本通
3、https://blog.csdn.net/hn_gsf/article/details/52249621