在排序算法中,冒泡排序和快速排序都可以归为交换排序算法。这是因为这两个算法就是通过不断的元素之间的交换来实现元素的排序。其中快速排序是所有排序算法中综合性能最佳的算法,因此需要深入理解。
冒泡排序:以数组为例,给定一组数a[n]。
(1)选择a[0]、a[1].....a[i].....a[n-2]中一个元素进行一趟冒泡,假设为a[i]。(一共n-1趟冒泡)。
(2)这趟冒泡中需要逐个对比a[n-1]到a[i]的值,将最小值交换到a[i]。
(3) i++。 重复(1)、(2)。
这样经过n-1趟后,n-1个元素都已经排好序,则n个元素都已经排好序。
代码实现如下,以数组为例:
void bubblesort(int a[],int n)
{
for(int i=0;i<n-1;i++) //从a[0]到a[n-2],n-1趟冒泡
{
bool flag = false;
for(int j=n-1;j>i;j--) //每一趟从a[n-1]到a[i]中选出最小的到a[i]
{
if(a[j]>a[j-1])
{
swap(a[j],a[j-1]); //可以把最小元素的逐步交换到a[i]
flag=true; //如果移动过元素,flag置为true
}
}
if(flag == false) //如果flag为true,说明上次遍历没交换元素,则数组已经有序。提早结束节省运行时间
return ;
}
}
冒泡排序的时间复杂度最坏情况是O(n^2),平均也是O(n^2)。空间复杂度为O(1)。
冒泡排序算法是稳定的。
快速排序:快速排序实际是对冒泡排序的一种改进,基本的思想是分治法。以数组为例:
(1)在数组a[n]中,选择一个元素为基准,假设这个元素的值为pivot。
(2)经过一趟排序,将a[n]分成两部分,a[0~k-1]和a[k+1~n-1]。其中a[0~k-1]的值都小于pivot,a[k+1~n-1]的值都大于pivot,a[k]=pivot。
(3)再对两个子序列a[0~k-1]和a[k+1~n-1]进行同样的操作,直到子序列只有一个元素,排序就完成了。
代码如下,以数组为例:
void quicksort(int a[],int low,int high)
{
if(low<high) //递归的跳出条件
{
int pivot = partition(a,low,high); //partition函数将数组划分,以pivot为界
quicksort(a,low,pivot-1); //对两个子序列递归排序
quicksort(a,pivot+1,high);
}
}
int partition(int a[],int low,int high)
{
int p = a[low]; //将第一个元素作为基准,对当前序列进行划分
while(low<high){
while(low<high&&a[high]>=p) --high;
a[low] = a[high]; //找到一个比基准元素小的然后移到左端
while(low<high&&a[low]<=p) ++low;
a[high] = a[low]; //找到一个比基准元素大的然后移到右端,如果没找到保证low是基准元素的最终位置
}
a[low] = p; //基准元素的最终存放位置
return low; //返回基准元素的位置,也就是对当前序列的划分的位置
}
上述代码是递归的方法,代码可能有些难懂。其实只要理解第一次的排序过程就好,子序列的操作与第一次是一样的。不需要深究其中的递归过程。
举例:有一组数:
4 | 5 | 1 | 2 | 6 | 3 |
以4为基准元素,第一趟排序过程如下:
第一次循环:
3 | 5 | 1 | 2 | 6 | 3 |
3 | 5 | 1 | 2 | 6 | 5 |
第二次循环:
3 | 2 | 1 | 2 | 6 | 5 |
3 | 2 | 1 | 4 | 6 | 5 |
第一趟排序完成。可以看出,基准元素4现在所在的位置也是最终排序完成后它该在的位置。对4左右的序列进行同样的过程,即可以完成排序。
时间复杂度:最坏情况O(n^2),最佳情况O(nlogn)。实际情况下的时间复杂度接近最佳情况O(nlogn)。
空间复杂度:最坏情况是O(n),最佳情况O(logn)。
快速排序算法是不稳定的,它会导致两个相等值的元素在排序后相对位置发生变化。