文章目录
- 交换排序
- 冒泡排序
- 冒泡排序算法
- 算法分析
- 快速排序
- 改进后的快速排序算法
- 算法分析
交换排序
【基本思想】
两两比较,如果发生逆序则交换,直到所有记录都排好序为止。
常见的交换排序方法:
冒泡排序T(n)=O(n2)
快速排序T(n)=O( nlog2n)
冒泡排序
——基于简单交换思想
【基本思想】
每趟不断将记录两两比较,并按“前小后大”规则交换。
第一个数和第二个数比较,若第一个数大于第二个数,则交换位置,否则,位置不变;
完成之后第二个数和第三个数比较,若第二个数大于第三个数,则交换位置,否则位置不变;
完成之后第三个数和第四个数比较,……
直到比较到最后一个数。
例:
21<25,位置不变
25<49,位置不变
49>25,互换位置
……
总结:
n个数据记录,总共需要比较n-1趟。
第m趟需要比较n-m次。
冒泡排序算法
void bubble_sort(SqList &L){
int m,i,j;
RedType x;//交换时临时存储
for(m=1;m<=n-1;m++){//总共需要比较m趟
for(j=1;j<=n-m;j++){
if(L.r[j].key>L.r[j+1].key)//发生逆序
x=L.r[j];//交换
L.r[j]=L.r[j+1];
L.r[j+1]=x;
}
}
}
优点:
每趟结束时,不仅能挤出一个最大值到最后面位置,还能同时部方理顺其他元素。
提高排序算法效率:
一旦某一趟比较时不出现记录交换,说明已排好序了,就可以结束本算法。
void bubble_sort(SqList &L){
int m,i,j,flag=1;//flag作为是否有交换的标记,有交换flag=1,没交换flag=0
for(m=1;m<=n-1&&flag==1;m++){
flag=0;
for(j=1;j<=m;j++){
if(L.r[j].key>L.r[j+1].key)//发生逆序
flag=1;
x=L.r[j];//交换
L.r[j]=L.r[j+1];
L.r[j+1]=x;
}
}
}
算法分析
时间复杂度:
平均时间复杂度为O(n2)
最好时间复杂度(正序)是O(n)
最坏时间复杂度(逆序)为O(n2)
空间复杂度:
算法中增加一个辅助空间temp
空间复杂度S(n)=O(1)
稳定性:
冒泡排序是一种稳定的排序方法。
快速排序
——改进的交换排序
pivot:枢轴、中心点
【基本思想】
① 任取一个元素为中心(中间数),通常是取第一个元素(枢轴),中间数可以是第一个数、最后一个数、最中间一个数、任选个数等;
② 所有比它小的元素一律前放,比它大的元素一律后放;(小的从前往后放,大的从后往前放)
③ 形成左右两个子表;
④ 对各子表重新选择中心元素并依此规则调整;(递归思想)
⑤ 直到每个子表的元素只剩一个,就达到整个序列有序。
优点:简单;
缺点:花费空间。
改进:
① 每一趟的子表的形成是采用从两头向中间交替式逼近法;
② 由于每趟中对各子表的操作都相似,可采用递归算法。
把第一次比较结束后的表以49为中心分成左右两个子表,对各子表重新选择中心元素并依此规则调整,这里左边选择了27作为中心元素,右边选择了76作为中心元素。
【基本步骤】
① 设下标1号元素作中间数(界点)pivotkey放置到0号下标位置,low指向第一个空,high指向末尾下标为L.length的位置。
② 由于low指向空,所以从high开始比较:
若high<pivotkey,则high的元素放置到low位置,且low的位置往后移一个位置low+1;
若high>pivotkey,则high的元素位置不变,high的位置也不变;
若high=pivotkey,则high的元素位置不变,high的位置也不变。
③ 由于high指向空,所以从low开始比较:
若low>pivotkey,则low的元素放置到high位置,且high的位置往后移一个位置high-1;
若low<pivotkey,则low的元素位置不变,low的位置也不变;
若low=pivotkey,则low的元素位置不变,low的位置也不变。
④ 直到low=high,把界点pivotkey移到low和high的位置,停止这趟比较。
⑤ 把第一次比较结束后的表以界点pivotkey为中心分成左右两个子表。
④ 对各子表重新选择中心元素并依此规则调整。(递归思想)
⑤ 直到每个子表的元素只剩一个,就达到整个序列有序。
改进后的快速排序算法
//主函数
void main(){
QSort(L,1,L.length);//对顺序表L进行排序,需要排序的数据范围从位置1到位置L.length
}
void Partition(SqList &L,int low,int high){
L.r[0]=L.r[low];//把中间数放到0号空位置
pivotkey=L.r[low].key;
while(low<high){//直到low=high,循环结束
while(low<high&&L.r[high].key>=pivotkey)
--high;
L.r[low]=L.r[high];
while(low<high&&L.r[low].key<=pivotkey)
++low;
L.r[high]=L.r[low];
}
L.r[low]=L.r[0];//此时low的位置和high的位置是一样的,是一个空位置
return low;
}//时间复杂度:O(n)
void QSort(SqList &L,int low,int high){//对顺序表L进行排序,需要排序的数据范围从位置low到位置L.length
if(low<high){
pivotloc==Partition(L,low,high);
//将L.r[low]到L.r[high]一分为二,pivotloc为枢轴元素排好序的位置
QSort(L,low,pivotloc-1);//对低字表递归排序
QSort(L,pivotloc+1,high);//对高字表递归排序
}
}//时间复杂度:O(log^2^n)
算法分析
时间复杂度:
可以证明,平均计算时间是O(nlog2n)。
Qsort( ):O(log2n)
Partition( ):O(n)
实验结果表明:就平均计算时间而言,快速排序是我们所讨论的所有内排序方法中最好的一个。
空间复杂度:
快速排序不是原地排序
由于程序中使用了递归,需要递归调用栈的支持,而栈的长度取决于递归调用的深度。(即使不用递归, 也需要用用户栈)
在平均情况下:需要O(logn)的栈空间
最坏情况下:栈空间可达O(n)。
稳定性:
快速排序是一种不稳定的排序方法。
例如: 49, 38, 49*, 20, 97, 76
一次划分后: 20, 38, 49*, 49, 97, 76
49和49*位置互换,所有不稳定
划分元素的选取是影响时间性能的关键。
快速排序不适于对原本有序或基本有序的记录序列进行排序,输入数据次序越乱,所选划分元素值的随机性越好,排序速度越快,快速排序不是自然排序方法。