交换排序的核心思想是:
给定一组数据,两两比较待排序记录的关键字,发现两个记录的次序相反时即进行交换,直到没有反序的记录为止。
基于交换思想的排序方法有两种,冒泡排序和快速排序。
冒泡排序
基于交换的思想其实比较符合人们的认知,冒泡排序就是依次比较两个关键字,如果发现两者的关系是逆序,就交换,第一次两两比较可以得出最大或最小的在相应位置,第二次比较可以得出在剩下的数中最大的或最小的,也就是整个数据中次大的或次小的。一个数要比较n-1次,共比较n(n-1)次,最坏交换次数是n-1 + n-2 + n-3 + …… + 1 = n(n-1)/2次,所以时间复杂度是o(n^2),原地排序,空间复杂度o(1)。
void bubbleSort(int* a, int len)
{
int i,j,key;
for (i=0; i<len-1; i++)//一共比较n-1次
{
for (j=0; j<len-i-1; j++)//第i次只用比较到前面的len-i位置的数
{
if (a[j] > a[j+1])//比较交换
{
key = a[j+1];
a[j+1] = a[j];
a[j] = key;
}
}
}
}
快速排序
冒泡排序有很多重复的比较,它的比较过程没有记录下可供下一次比较的有用信息。针对这一问题,有很多改进的排序算法,这里只介绍基于比较的快速排序。
时间复杂度和空间复杂度分析
快速排序是每次找到一个划分值排好位置,如升序排序,在它左边的数都比它小,在它右边的数都比它大,然后使用分治的思想递归左右两部分,使得每一部分都排好序。因为它划分出来的左右部分不用再比较,所以减少了比较次数。如果每一次划分左右两边个数都相等的话,划分log2n次,每次划分时间是n,时间复杂度是o(nlog2n),最坏情况是每次划分之后都有一边是1个元素,这样快速排序就变成了冒泡排序,时间复杂度o(n^2).
快速排序的空间复杂度分析:由于借助递归栈,最坏栈深度是n,最好是(log2n)+ 1(包括最外层的参数进栈).
经实践检验,快速排序是同类排序算法中时间复杂度常数因子最小的排序算法,就是说它的平均性能最好,所以叫快速排序。
算法的改进
1.为进一步提高快速排序的效率,使其不至于变成冒泡排序,取哨兵可以采用a[left], a[(right+left)/2] 和 a[right]三者取中的办法。还可以采用更为保险的办法,取中位数的办法,取中位数可以做到o(n)级别的复杂度。
2.在数据量比较小的情况下,快排不如插入排序。所以可以用插入排序代替小数据量的快排。
3.在有大量重复元素的情况下,可以把重复元素记录下来不做比较。在划分的过程中,在指针low+1和high-1的同时,交换相邻的两个记录,同时设置两个布尔型变量标记指针low和high在移动过程中是否有进行过交换记录的操作,如果没有,则不再需要对低端或高端子表进行排序。具体的代码参考博客快速排序的优化
int partition(int a[], int left, int right)
{
//一次划分,把最左边的数排好位置
int low = left;
int high = right;
int key = a[low]; //第一个记录作为哨兵
while (low < high)
{
while (low<high && key<=a[high]) high--;
a[low] = a[high]; //比哨兵小的放在左边
while (low<high && key>=a[low]) low++;
a[high] = a[low]; //比哨兵大的放在右边
}
a[low] = key; //哨兵放在合适的位置,左边都是比它小的,右边都是比它大的。
return low; //返回哨兵所在位置
}
void quick_sort(int a[], int left, int right)
{
int s;
if (left >= right)
return;
s = partition(a, left, right);
quick_sort(a, left, s-1);
quick_sort(a, s+1, right);
}