常见的排序算法总结

所谓排序,就是要整理文件中的记录,使之按关键字递增(或递减)次序排列起来。当待排序记录的关键字都不相同时,排序结果是惟一的,否则排序结果不惟一。
在待排序的文件中,若存在多个关键字相同的记录,经过排序后这些具有相同关键字的记录之间的相对次序保持不变,该排序方法是稳定的;若具有相同关键字的记录之间的相对次序发生改变,则称这种排序方法是不稳定的。
要注意的是,排序算法的稳定性是针对所有输入实例而言的。即在所有可能的输入实例中,只要有一个实例使得算法不满足稳定性要求,则该排序算法就是不稳定的


第一种类型 插入排序

插入排序的基本思想是每一步都将一个待排序的记录,按照其排序码值的大小插入到已经排好序的序列中,主要有直接插入法,折半插入法和希尔排序法。


直接插入法(升序排列)

直接插入法原理相当于在按照个子高矮进行排队,R0、R1、R2...Rj都是已经排好队了,现在将Rj+1称为选定元素,为了插入到队五中,需要将Rj+1挑出来,然后同Rj比较,如果比Rj大,则返回原来位置,如果比Rj小则Rj移动到j+1位置,同时Rj再同更小的Rj-1比较,以此类推。

//示例代码

/*
*插入法排序升序
*@param int[] arr
*@return int[]arr
**/
publicstaticint[]sort(int[]arr){
for(inti=0;i<arr.length;i++){ //遍历数组
intt=arr[i]; //将第i个元素拿出来
intin=i; //记录起时点
while(in>0&&arr[in-1]>=t){ //循环终止调节in>0,当左边的数大于右边时
arr[in]=arr[in-1]; //向右移一位
--in; //游标再向左走
}
arr[in]=t;
}
returnarr;
}


代码分析:

最外层for循环用于将数组内所有元素遍历一遍,中间变量t用于保存被移出来的第i个元素,当该元素比左边的某一元素(in)还要小时,将in处放置t,同时t以上至i的元素统统右移一位。

二分法插入排序(升序)

直接插入法是逐次同已分好的序列中的元素从大到小进行比对,最坏的情况是需要将有序序列的所有元素遍历一遍,为了提高比对的效率,采用二分法思路来进行改进。二分插入排序算法的具体操作为:在将一个新元素插入已排好序的数组的过程中,寻找插入点时,将待插入区域的首元素设置为a[low],末元素设置为a[high],则轮比较时将待插入元素与a[m],其中m=(low+high)/2相比较,如果比参考元素小,则选择a[low]到a[m-1]为新的插入区域(即high=m-1),否则选择a[m+1]到a[high]为新的插入区域(即low=m+1),如此直至low<=high不成立,即将此位置之后所有元素后移一位,并将新元素插入a[low]。

//示例代码

/*
*二分插入法排序升序
*@param int[]
*@return int[]
**/
publicstaticint[]sortBin(int[]arr){
for(inti=1;i<arr.length;i++){ //遍历数组
intt=arr[i]; //将第i个元素取出
intmid=0; //初始化mid 
intlow=0; //初始化low
inthigh=i-1; //初始化high
while(low<=high){ //循环终止条件low>high
mid=(low+high)/2;
if(t>arr[mid]){
low=mid+1;
}else{
high=mid-1;
}
}
intj=i; //直接插入法
while(j>mid){ //将mid到i的数据右移
arr[j]=arr[j-1];
--j;
}
arr[low]=t; //低位放t
}
returnarr;
}


希尔排序:由于前述的两种插入排序都需要进行有序序列子段每一个元素的右移,因此需要有效减小这些移动。希尔排序原理是取一个小于数组长度的数d1作为第一个间隔数,将数组分为d1组,所有距离为d1倍数的放在一个组,然后对这些组进行正常的直接插入法排序,然后取增量d2(d2<d1)再进行一次分组,当所取的增量dt=1时截止,此时划分完毕

//示例代码

/*
*希尔排序升序
*@param int[]
*@return int[]
**/
publicstaticint[]sortShell(int[]arr){
intdis=arr.length/2; //设定初始d1
while(dis>0){ //循环终止条件dis=1
for(inti=dis;i<arr.length;i++){ //在dis左边的数组中进行排序,仅仅对间隔为dis的元素进行排序
intj=i; //直接插入法
intt=arr[i];
while(j>0&&arr[j-dis]>t){ //此时由于间隔不是1,而是dis,故arr[j-dis]而不是arr[j-1]
arr[j]=arr[j-dis];
j-=dis; //间隔为dis,故j-=dis而不是j-=1
}
arr[j]=t;
}
dis/=2;
}
returnarr;
}

//如果序列基本有序的话,插入排序基本只需要O(N)的时间,因此一些高级的排序方法常常选用插入排序法作为基本排序方法。


第二种类型选择排序

选择排序直观的原理是从数组中挑出最小的,然后放到最初始的位置,将初始位置的元素同该位置的元素调换,依次进行直到将所有数据排完。选择法相对于插入法的最大不同在于,先进行比较,找到合适位置后仅仅进行两个位置上元素的互换,而不涉及整体的互换

直接选择法:

直接选择法的思路可以非常形象地描述:有一排运动员需要从左往右按身高升序排列,排列方式为,先从队列最左端队员记录下其身高信息,并将一个标记放到其身上;然后依次记录后续队员身高信息,发现有比拿着该标记的该队员矮的,则将该标记放到该队员身上,然后调换带有这个标志的队员和待排序队员的位置。直接选择法是一种不稳定的排序方法,它的时间复杂度是O(n^2)

//示例代码

/*
*直接选择排序
*@param int[]
*@return int[]
**/
publicstaticint[]sortSelect(int[]arr){
for(intout=0;out<arr.length-1;out++){ //遍历,获取待排序队员,从最左端开始,到次右端
intmin=out; //标记为min
intt=arr[out]; //将带排序的队员的身高信息记下
for(intin=out+1;in<arr.length;in++){ //遍历,获取更矮的队员
if(arr[in]<arr[min]) 
min=in; //将标记放到该更矮的队员身上
}
arr[out]=arr[min]; //调换两者位置
arr[min]=t;
}
returnarr;
}

堆排序

堆是一种重要的数据结构,为一棵完全二叉树,底层如果用数组存储数据的话,假设某个元素为序号为i(Java数组从0开始,i为0到n-1),如果它有左子树,那么左子树的位置是2i+1,如果有右子树,右子树的位置是2i+2,如果有父节点,父节点的位置是(n-1)/2取整分为最大堆和最小堆,最大堆的任意子树根节点不小于任意子结点,最小堆的根节点不大于任意子结点。所谓堆排序就是利用堆这种数据结构来对数组排序,我们使用的是最大堆。处理的思想和冒泡排序,选择排序非常的类似,一层层封顶,只是最大元素的选取使用了最大堆。最大堆的最大元素一定在第0位置,构建好堆之后,交换0位置元素与顶即可。堆排序为原位排序(空间小),且最坏运行时间是O(n2),是渐进最优的比较排序算法






交换排序

冒泡排序:冒泡排序只看两步,即i和i+1的大小,如果i+1小于i则调换位置,依次循环即可

//示例代码
/*冒泡排序升序
* @param int[]
* @return int[]
* */
publicstaticint[]sortBubble(int[]arr){
for(inti=0;i<arr.length;i++){ //遍历数组
for(intj=i+1;j<arr.length;j++){ //从i+1开始遍历数组
if(arr[i]>arr[j]){ //交换位置
intt=arr[i];
arr[i]=arr[j];
arr[j]=t;
}
}
}
returnarr;
}