ok,新的文章又出来啦~~.这次我们将会重点关注冒泡排序和快速排序.和上一篇文章一样,这次将对于内部排序的这两种思路分别进行介绍,同时也会提出这两种方法之间的联系.由简到难,让我们由冒泡排序开始本次的探讨吧~~.
ps:忽然发现忘记在最开始对于内部排序进行定义了,,囧..这里补充下.所谓内部排序,指的是所有待排序列都存放在内存中来直接进行的排序操作. 与之相对的是外部排序, 对规模非常巨大的数据进行排序处理时, 不 能一次性全部放入内存中进行,需要内存和外部存储器之间进行多次数据交换,来最终完成排序工作的排序操作. 我们一般所说的快排,堆排,归并排等,都是内部排序.
冒泡排序(Bubblesort)
冒泡排序是一种非常简单,容易理解的排序操作,它可以向上或者向下进行起泡操作.它的基本思想是, 经过了每一次的内循环操作后,都可以将待排数组中的某一个元素起泡到正确的位置上.这样,在经过了一次完整的外循环之后,可以保证每一个元素都已经是有序的了.
下面是冒泡排序的过程图示:
冒泡排序之所以能够实现,关键在于每一次的内循环操作之后,都能保证一个元素有序.这也是冒泡排序和快速排序思想的相同之处.
下面给出冒泡排序的实现代码:
void bubsort(int a[],int sz) //这里为了方便,直接使用的整形数组.
{
for(int i=0;i<sz;i++) //外循环,
for(int j=sz-1;j>0;j--) //内循环,
if(a[j]<a[j-1])swap(a[j],a[j-1]);
} //本例采用的是自下而上,向上起泡的思想
明显的,冒泡排序的时间复杂度是O(n²),是一个简单排序.
///
ok,下面就开始对快速排序的介绍吧.
快速排序(Quicksort)
快速排序是个很恰当的名字,因为它是迄今为止所有内排序算法中平均情况下最快的一种.当然,最坏的情况下是惨不忍睹的.最差时间代价是快排使用的最大的阻碍.
快排的基本思路和二叉查找树的建树思路有点相似. 二叉查找树的最大特点是每个父节点都将子节点分成了两类:所有比节点值要小的元素都放在了节点的左子树中,而所有比它大的都出现在右子树中.这种处理方式隐含了"分治法"的思想,而快速排序也是利用的分治法对待排数据进行的处理.
基本思路:首先在待排数据中选取一个轴值(pivot),经过处理后,pivot将处在正确的位置上,即比pivot小的元素都排在了它的前面.由pivot将会把待排数组分割成两个部分,前面的都比pivot小,而后面的都比它大.在pivot的位置确定之后,递归的对pivot之前的子区间继续进行以上操作,即 选取轴值->确定位置->分割区间->处理子区间. 递归的出口是区间内所有的元素都已有序,即 区间内元素个数为1或者0的情况.
快排之所以能实现排序功能的根本原因也是由于在每次的选取pivot之后,经过一系列操作以后,一定可以确定该值的位置,在经过全部的递归迭代之后,所有的元素的位置都可以确定了. 这种思路和冒泡排序的思想(参上)是一样的.
下面给出快排的过程图示:
在确定pivot的位置时,我们将调用一个名为partition的函数来处理.该函数也是快排的核心.这里先给出函数的代码,在注释中进行简单分析.
int partition(int A[], int l, int r,int& pivot)
{
do{ //循环操作,直到pivot位置确定,即l与r重合.
while (A[++l]<pivot); //停止条件,遇到比pivot大的元素,l指示位置
while ((r != 0) && (A[--r]>pivot); //遇到比pivot小的元素,停止,r指示位置
swap(A, l, r); //将前面比pivot大的(L)和后面比pivot小的元素(R)进行交换
} while (l < r);
swap(A, l, r); //消除最后一次多余交换的影响
return l; // l 就是pivot的最终位置
}
这里也给出partition的过程图示:
ok,完成对partition的简单介绍后,开始对快排主体的描述.
实现代码:
void qsort(int A[], int i, int j)
{
if (j <= i) return; // 区间的排序完成,递归的出口
int pivotindex = findpivot(A, i, j); // findpivot(),选取轴值.方式可以自己定义.
swap(A, pivotindex, j); //将轴值与最后元素交换,避免分割对最后元素影响.
int k = partition(A, i-1, j, A[j]); //调用partition函数求解pivot位置
swap(A, k, j); //将pivot放入正确的位置
qsort(A, i, k-1); //递归实现左区间的排序
qsort(A, k+1, j); //然后右边
}
快排的时间复杂度在平均情况下是 O(nlogn) (由partition和findpivot决定),然而在最坏的情况下(轴值的一边为空区间),复杂度达到了令人发指的 O(n²). 所以对于轴值的选取是非常关键的.本例中的findpivot()函数就是实现轴值选取这一功能的.一般看来,最合理的选取方式似乎是随机选取轴值,但是在实现过程中开销太大,并不适合.
另外的,快排对规模不大的数据进行处理时,速度并不快,所以针对这一点可以对于快排进行改进.运用前一篇文章中提到的插入排序或者选择排序来对已经划分到小规模的数据进行排序处理.即 快排将最后几次的排序工作交给其它排序来完成.
好了 ,本次对于冒泡和快排的介绍就到这里了,下次的文章将继续探讨其它的内部排序的方式.
再次申明, 本博客中不可避免的会有瑕疵, 真心希望各位不吝指正,共同进步.