应用交换排序基本思想的主要排序方法有:冒泡排序和快速排序。这里将主要介绍快速排序,以及快速排序的递归与非递归的实现和对快速排序的优化
冒泡排序
冒泡排序是将大数或小数不断后移的一种思想,比较和交换都发生在两个相邻元素之间。
思想:
1.单趟排序:比较相邻的元素,如果第一个比第二个大(小),交换这两个元素,直到最后元素,则最后元素的值应该为最大或最小值,前面的元素可能有序也可能无序。
2.对前面元素在进行单趟排序,改变每趟排序的下标,进行单趟排序,直到没有一对数据进行比较时,则排序完成。
优化:
对于冒泡排序,经过单趟排序后,前面的元素可能有序,若前面元素已经有序,则可终止排序,提高效率。(设置标志flag=false,如果在每次单趟排序有数据交换,则将flag=true,, 每次单趟排序完可对flag进行判断,若flag==false,说明前面元素已经有序,直接跳出循环,否则,继续进行冒泡。)
图解:
实现:
void BubbleSort(int* a, size_t n)
{
assert(n);
bool finish = false; //堆排序进行优化
size_t end = n;
while(end > 0) //end表示每次冒泡的终止位置
{
//单趟冒泡
for(size_t i=1; i<end; i++)
{
if(a[i-1]>a[i])
{
swap(a[i-1],a[i]);
finish = true; //若单趟排序有数据交换,则将finish置为true
}
}
if(finish == false) //判断单趟排序中是否有交换
{
return;
}
--end;
}
}
算法分析:
算法时间复杂为O(n^2),属于稳定性算法
快速排序
快速排序由C. A. R. Hoare在1962年提出。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
图解:
分析:快速排序应用分治法的思想,将大问题分解为小问题,先求解子问题,以得到对原问题的求解。
递归实现:
//左右指针法 找一个key,将大数右移,小数左移,最后将数组区间划分为两部分 [] key []
int PartSort1(int* a, int begin, int end)
{
int left = begin, right = end; //right赋值为 end 还是 end-1
int key = a[right]; //选择最右边为key值
//单趟排序
while(begin < end)
{
//begin找大于
while(begin < end && a[begin] <= key) //注意条件a[begin] <= key
{
++begin;
}
//end找小
while(begin < end && a[end] >= key)
{
--end;
}
swap(a[begin], a[end]);
}
swap(a[begin],a[right]); //a[right]相当于中间划分的位置
return begin;
}
void QuickSort(int* a, int left, int right)
{
assert(a);
if(left >= right) //注意:结束条件的判断
{
return;
}
int div = PartSort1(a, left, right);
QuickSort(a, left,div-1);
QuickSort(a, div+1, right); //right为什么不-1;
}
算法分析
最坏情况:
如果我们在选取基准p的时候,每次选取的都是当前数组中最小的一个元素,那么每次划分都只是让数组中的元素少1(被筛选出来的那个元素当然有序),这样一来就需要反复遍历数组导致复杂度变成了O(n2)。
最好情况:
如果我们在选取基准p的时候,每次选取的都是当前数组中最中间的一个元素(是中位数,而不是元素位置上的中间),那么每次划分都把当前数组划分成了长度相等的两个子数组,这样一来复杂度变成了O(nlog2n)。
基准的选择
1.对于基准元素的选取,也可以采用随机数选取的方式
2.如果要按照元素在位置上的中间来选取基准元素,还可以将中间位置上的元素与第一个元素进行对换
优化:
三平均分区法
这一改进与其它的快速排序方法不同,它并不是选择待排数组的第一个元素作为中轴,而是选用待排数组最左边、最右边和最中间的三个元素的中间值作为中轴。这一改进的优势:
(1)首先,它使得最坏情况发生的几率减小了。
(2)其次,未改进的快速排序算法为了防止比较时数组越界,在最后要设置一个哨点。
非递归实现快排:
借助栈,栈中保存左右下标,先压右、在压左,栈为空时循环结束
注:压栈应注意左右下标有意义时才压栈及(left<right).
void QuickSortFD(int* a, int left, int right)
{
stack<int> s; //栈中存放下标
if(left < right) //首先要保证下标有意义再压栈
{
s.push(right);
s.push(left);
}
while(s.size()>0)
{
int left = s.top();
s.pop();
int right = s.top();
s.pop();
if(right - left <= 20)
{
InsertSort(a+left,right-left+1);
return;
}
else
{
int div = PartSort1(a,left,right);
if(div - 1 >left) //区间不合法时,不入栈
{
s.push(div-1);
s.push(left);
}
if(right > div+1)
{
s.push(right);
s.push(left);
}
}
}
}