交换排序
概念
根据序列中两个元素的关键字的大小比较结果交换两个元素在序列中的位置
1、冒泡排序
基本思想
从前往后或者从后往前两两比较相邻元素的值,如果为逆序(与事先规定顺序:递增/递减相反),交换两个数的位置,从第一对到最后一对均完成交换,称为一次冒泡,它会将整个序列里最小的或者最大的元素换到第一个或者最后一个位置,一般结果将最小的元素交换到待排序序列的第一个位置(关键字最小的元素如气泡般向上漂浮,直至水面,这就是冒泡排序的由来)。下一次冒泡到来之时,已经排好的就不再参与冒泡了。最多n-1趟冒泡排好序。
#include <iostream>
using namespace std;
void BubbleSort(int * Array,int length);
void BubbleSort1(int *Array,int length);
int main ()
{
int Array[11]={0,2,4,1,6,3,7,8,9,5,1};
int length=10;
BubbleSort(Array,length);
cout<<"Result:";
for(int i=1;i<=10;i++)
cout<<Array[i]<<" ";
cout<<endl;
BubbleSort1(Array, length);
cout<<"Result1:";
for(int i=1;i<=10;i++)
cout<<Array[i]<<" ";
cout<<endl;
return 0;
}
//最朴素的冒泡排序
void BubbleSort(int *Array,int length)
{
for(int i=1;i<length;i++)
{
for(int j=i+1;j<=length;j++)
{
if(Array[i]>Array[j])
{
Array[0]=Array[i];
Array[i]=Array[j];
Array[j]=Array[0];
}
}
}
}
//冒泡排序进阶版
void BubbleSort1(int *Array,int length)
{
//在一次冒泡之后可能对于当前的元素,不需要再与后面的元素比较了
//这样就需要一个判断的标志,来证明当前已不需重排
//如何设置呢?上一次没有交换的就可以证明上一次的是已经排好的了
//所以在每轮比较中都更改flag的值,在进入下一个比较的时候就知道是否已经比较好了
bool flag=false;
for(int i=1;i<=length;i++)
{
for(int j=i+1;j<=length;j++)
{
if(Array[i]>Array[j])
{
Array[0]=Array[i];
Array[i]=Array[j];
Array[j]=Array[0];
flag = true;
}
}
if(flag==false)//本趟遍历之后没有发生交换,说明表已经有序
return ;
}
}
⚠️冒泡排序中所产生的有序子序列一定是全局有序的(不同于直接插入排序),也就是有序子序列中的所有元素的关键字一定小于或大于无序子序列中所有元素的关键字,这样每一趟排序都会将一个元素放置在其最终的位置上。
冒泡排序性能分析
性能 | 分析 |
---|---|
时间 | 最好:初始情况为有序,第一趟冒泡flag=false,比较次数n-1,交换次数0,时间复杂度O(1); 最坏:初始情况为逆序,需要n-1趟排序,第i趟需要进行n-i次比较,每次比较都必须移动3次元素,时间复杂度O(n2);平均时间复杂度O(n2) |
空间 | O(1) |
稳定性 | 稳定,其实取决于if中的设置有没有= |
适用性 | 顺序表 |
2、 快速排序
基本思想
基于分治法的思想,在整个序列中找到一个元素作为基准元素pivot,然后通过一趟排序确定pivot的位置,使pivot左边的所有元素都小于pivot,pivot右边的元素都大于pivot,把pivot放置在最终属于它的位置,然后便不再移动它。接下来是递归处理左边序列和右边序列的过程。
#include <iostream>
using namespace std;
void QuickSort(int *Array,int low,int high);
int Partition(int *Array,int low,int high);
int main ()
{
int Array[11]={0,2,4,1,6,3,7,8,9,5,1};
int length=10;
QuickSort(Array,1,length);
cout<<"Result1:";
for(int i=1;i<=10;i++)
cout<<Array[i]<<" ";
cout<<endl;
return 0;
}
//注意算法跳出的条件
void QuickSort(int *Array,int low,int high)
{
if(low<high)
{
int pivotpos=Partition(Array, low, high);
QuickSort(Array,low, pivotpos-1);//基准值前一半排序
QuickSort(Array, pivotpos+1, high);//基准值后一半排序
}
}
//快速排序算法的性能主要取决于划分操作的好坏,这里假设每次都是以当前表中第一个元素为pivot对表进行划分
//每次从后往前找,当数值比pivot大的时候继续向前寻找,直到找到比pivot小的数或者low=high
//然后从前往后找,当数值比pivot小的时候继续向后寻找,直到找到比pivot大的数或者low=high
int Partition(int *Array,int low,int high)
{
int pivot=Array[low];//设定基准值
while (low<high)
{//先一直向前找
while(low<high&&pivot<=Array[high]) --high;
Array[low]=Array[high];
//然后一直向后找
while(low<high&&pivot>=Array[low]) ++low;
Array[high]=Array[low];
}
//其实现在把low或者high传入都可以,因为现在low=high
Array[low]=pivot;
return low;
}
快速排序性能分析(有待仔细演算)
- 空间效率
快速排序是递归的,需要借助一个递归工作栈保存每一层递归调用的必要信息,容量应该与递归调用的最大深度一直。最好情况下是log2(n+1)(取roof);最坏情况下,要进行n-1次递归调用,栈的深度为O(n);平均情况下,栈的深度为O(log2n). - 时间效率
具体使用的划分算法->划分是否对称->快速排序的运行时间
最坏情况发生在两个区域分别包含n-1个和0个元素是,这种最大程度的不对称发生在每一层递归上,即对于初始排序表基本有序或基本逆序是,就得到最坏情况下的时间复杂度O(n2). - 稳定性
不稳定的排序算法
⚠️提高算法效率
- 当递归过程中划分得到的子序列的规模较小时不使用递归而采用直接插入排序
- 选取一个可以将数据中分的pivot(eg:从序列的头尾及中间选取三个元素,再取这三个元素的中间值作为最终的pivot;随机从当前表中选取pivot,这样使得最坏的情况在实际排序中几乎不会发生)
- 在最理想的情况下,也就是partition算法可能做到最平衡的划分中,得到的两个子问题的大小都不可能大于n/2,在这种情况下,快速排序的运行速度大幅度提升,此时时间复杂度为O(nlog2n)
- 快速排序平均情况下的运行时间与最佳情况下运行时间很接近,快速排序是所有内部排序算法中平均性能最优的算法