叶凡网络:Java实现的几个常用排序算法详细解读
- 2013-12-04 09:50:03 | 新闻来源:叶凡网络 | 点击量:580
魅力之处在于它能在每次 partit排序算法的核心所在都能为一个数组元素确定其排序最终正确位置(一次就定位准,快速排序也是用归并方法实现的一个“分而治之”排序算法。下次循环就不考虑这个元素了
并自己简单地实现了一遍,排序算法很多地方都会用到近期又重新看了一遍算法。特此记录下来,为以后复习留点材料。
下面逐一看看经典的排序算法:废话不多说。
1.选择排序
以 i代表当前需要排序的序号,选择排序的基本思想是遍历数组的过程中。则需要在剩余的[in-1]中找出其中的最小值,然后将找到最小值与 i指向的值进行交换。因为每一趟确定元素的过程中都会有一个选择最大值的子流程,所以人们形象地称之为选择排序。
举个实例来看看:
17, 初始:[38.16,16,7,31,39,32,2,11]
17, i=0: [2.16,16,7,31,39,32,38,11]0th[38]<->8th[2]
7, i=1: [2.16,16,17,31,39,32,38,11]1st[38]<->4th[17]
7, i=2: [2.11,16,17,31,39,32,38,16]2nd[11]<->9th[16]
7, i=3: [2.11,16,17,31,39,32,38,16]无需交换
7, i=4: [2.11,16,16,31,39,32,38,17]4th[17]<->9th[16]
7, i=5: [2.11,16,16,17,39,32,38,31]5th[31]<->9th[17]
7, i=6: [2.11,16,16,17,31,32,38,39]6th[39]<->9th[31]
7, i=7: [2.11,16,16,17,31,32,38,39]无需交换
7, i=8: [2.11,16,16,17,31,32,38,39]无需交换
7, i=9: [2.11,16,16,17,31,32,38,39]无需交换
选择排序随着排序的进行(i逐渐增大)比拟的次数会越来越少,由例子可以看出。但是不论数组初始是否有序,选择排序都会从 i至数组末尾进行一次选择比较,所以给定长度的数组,选择排序的比较次数是固定的1+2+3+.+n=n*n+1/2而交换的次数则跟初始数组的顺序有关,如果初始数组顺序为随机,则在最坏情况下,数组元素将会交换 n次,最好的情况下则可能 0次(数组自身即为有序)
选择排序的时间复杂度和空间复杂度分别为 On2和 O1选择排序只需要一个额外空间用于数组元素交换)由此可以推出。
实现代码:
/**
*SelectSorting
*/
SELECTIONnewSortabl{
booleanascend{ public<TextendComparable<T>>voidsortT[]array.
intlen=array.length;
forinti=0;i<len;i++{
intselect=i;
forintj=i+1;j<len;j++{
intcompar=array[j].compareToarray[selected];
ifcompar!=0&&compar<0==ascend{
select=j;
}
}
i, exchangarray.select;
}
}
}
2.插入排序
假设在序号 i之前的元素即 [0..i-1]都已经排好序,插入排序的基本思想是遍历数组的过程中。本趟需要找到i对应的元素 x正确位置 k并且在寻找这个位置 k过程中逐个将比较过的元素往后移一位,为元素 x腾位置”最后将 k对应的元素值赋为 x插入排序也是根据排序的特性来命名的
红色 标志的数字为插入的数字,被划掉的数字是未参与此次排序的元素,红色 标志的数字与被划掉数字之间的元素为逐个向后移动的元素,比如第二趟参与排序的元素为 [11,以下是一个实例。31,12]需要拔出的元素为 12但是12当前并没有处于正确的位置,于是需要依次与前面的元素 3111做比较,一边比较一边移动比较过的元素,直到找到第一个比 12小的元素 11时停止比较,此时 31对应的索引 1则是12需要拔出的位置。
31, 初始: [11.12,5,34,30,26,38,36,18]
31, 第一趟:[11.12,5,34,30,26,38,36,18]无移动的元素)
12, 第二趟:[11.31,5,34,30,26,38,36,18]31向后移动)
11, 第三趟:[5.12,31,34,30,26,38,36,18]11,12,31皆向后移动)
11, 第四趟:[5.12,31,34,30,26,38,36,18]无移动的元素)
11, 第五趟:[5.12,30,31,34,26,38,36,18]31,34向后移动)
11, 第六趟:[5.12,26,30,31,34,38,36,18]30,31,34向后移动)
11, 第七趟:[5.12,26,30,31,34,38,36,18]无移动的元素)
11, 第八趟:[5.12,26,30,31,34,36,38,18]38向后移动)
11, 第九趟:[5.12,18,26,30,31,34,36,38]26,30,31,34,36,38向后移动)
理由是排序过程中能够利用前部分数组元素已经排好序的一个优势,插入排序会优于选择排序。有效地减少一些比较的次数,当然这种优势得看数组的初始顺序如何,最坏的情况下(给定的数组恰好为倒序)插入排序需要比较和移动的次数将会等于 1+2+3+n=n*n+1/2这种极端情况下,插入排序的效率甚至比选择排序更差。因此插入排序是一个不稳定的排序方法,插入效率与数组初始顺序息息相关。一般情况下,插入排序的时间复杂度和空间复杂度分别为 On2和 O1
实现代码:
/**
*InsertionSorting
*/
INSERTIONnewSortabl{
booleanascend{ public<TextendComparable<T>>voidsortT[]array.
intlen=array.length;
forinti=1;i<len;i++{
TtoInsert=array[i];
intj=i;
for;j>0;j--{
intcompar=array[j-1].compareTotoInsert;
ifcompar==0||compar<0==ascend{
break;
}
array[j]=array[j-1];
}
array[j]=toInsert;
}
}
}
3.冒泡排序
两层 for循环,冒泡排序可以算是最经典的排序算法了记得小弟上学时最先接触的也就是这个算法了因为实现方法最简单。里层循环中判断相邻两个元素是否逆序,话将两个元素交换,外层循环一次,就能将数组中剩下的元素中最小的元素“浮”最前面,所以称之为冒泡排序。
照例举个简单的实例吧:
19, 初始状态: [24.26,39,36,7,31,29,38,23]
19, 内层第一趟:[24.26,39,36,7,31,29,23,38]9th[23]<->8th[38
19, 内层第二趟:[24.26,39,36,7,31,23,29,38]8th[23]<->7th[29]
19, 内层第三趟:[24.26,39,36,7,23,31,29,38]7th[23]<->6th[31]
无需交换) 内层第四趟:[24,19,26,39,36,7,23,31,29,38]723都位于正确的顺序。
19, 内层第五趟:[24.26,39,7,36,23,31,29,38]5th[7]<->4th[36]
19, 内层第六趟:[24.26,7,39,36,23,31,29,38]4th[7]<->3rd[39]
19, 内层第七趟:[24.7,26,39,36,23,31,29,38]3rd[7]<->2nd[26]
7, 内层第八趟:[24.19,26,39,36,23,31,29,38]2nd[7]<->1st[19]
24, 内层第九趟:[7.19,26,39,36,23,31,29,38]1st[7]<->0th[24]
比较次数一样,其实冒泡排序跟选择排序比较相像。都为 n*n+1/2但是冒泡排序在挑选最小值的过程中会进行额外的交换(冒泡排序在排序中只要发现相邻元素的顺序不对就会进行交换,与之对应的选择排序,只会在内层循环比较结束之后根据情况决定是否进行交换)所以在看来,选择排序属于冒泡排序的改进版。
实现代码:
/**
it'verisimilarwithInsertionSorting *BubblSorting.
*/
BUBBLEnewSortabl{
booleanascend{ public<TextendComparable<T>>voidsortT[]array.
intlength=array.length;
intlastExchangedIdx=0;
forinti=0;i<length;i++{
//marktheflagtoidentwhetherexchanghappentofalse
booleanisExchang=false;
//lastcomparandexchanghappenbeforreachindexi
intcurrOrderedIdx=lastExchangedIdx>i?lastExchangedIdx:i;
forintj=length-1;j>currOrderedIdx;j--{
intcompar=array[j-1].compareToarray[j];
ifcompar!=0&&compar>0==ascend{
j-1, exchangarray.j;
isExchang=true;
lastExchangedIdx=j;
}
}
//ifnoexchanghappenmeanarraiisalreadiinorder
ifisExchang==fals{
break;
}
}
}
}
4.希尔排序
以 gap来划分,比如数组 [1,希尔排序的诞生是由于插入排序在处置大规模数组的时候会遇到需要移动太多元素的问题。希尔排序的思想是将一个大的数组“分而治之”划分为若干个小的数组。2,3,4,5,6,7,8]如果以 gap=2来划分,可以分为 [1,3,5,7]和 [2,4,6,8]两个数组(对应的如 gap=3则划分的数组为:[1,4,7][2,5,8][3,6]然后分别对划分出来的数组进行插入排序,待各个子数组排序完毕之后再减小 gap值重复进行之前的方法,直至 gap=1即对整个数组进行插入排序,此时的数组已经基本上快排好序了所以需要移动的元素会很小很小,解决了插入排序在处置大规模数组时较多移动次数的问题。
具体实例请参照插入排序。
数据量大的时候对效率的提升协助很大,希尔排序是插入排序的改进版。数据量小的时候建议直接使用插入排序就好了
实现代码:
/**
*ShellSorting
*/
SHELLnewSortabl{
booleanascend{ public<TextendComparable<T>>voidsortT[]array.
intlength=array.length;
intgap=1;
//usthemostnexttolength/3asthefirstgap
whilegap<length/3{
gap=gap*3+1;
}
whilegap>=1{
forinti=gap;i<length;i++{
Tnext=array[i];
intj=i;
whilej>=gap{
intcompar=array[j-gap].compareTonext;
//alreadifinditposition
ifcompar==0||compar<0==ascend{
break;
}
array[j]=array[j-gap];
j-=gap;
}
ifj!=i{
array[j]=next;
}
}
gap/=3;
}
}
}
5.归并排序
属于“分而治之”将目标数组从中间一分为二,之后分别对这两个数组进行排序,排序完毕之后再将排好序的两个数组“归并”一起,归并排序最重要的也就是这个“归并”过程,归并的过程中需要额外的跟需要归并的两个数组长度一致的空间,比如需要规定的数组分别为:[3,归并排序采用的递归来实现。6,8,11]和 [1,3,12,15]虽然逻辑上被划为为两个数组,但实际上这些元素还是位于原来数组中的只是通过一些 index将其划分成两个数组,原数组为 [3,6,8,11,1,3,12,15设置三个指针 lo,mid,high分别为 0,3,7就可以实现逻辑上的子数组划分)那么需要的额外数组的长度为 4+4=8归并的过程可以简要地概括为如下:
以前面提到例子为例,则 copiedA rrai=[3,1将两个子数组中的元素复制到新数组 copiedA rrai中。6,8,11,1,3,12,15]
假定这两个指针取名为 leftIdx和 rightIdx则 leftIdx=0对应 copiedA rrai中的第一个元素 [3]rightIdx=4对应 copiedA rrai中的第五个元素 [1]2设置两个指针分别指向原子数组中对应的第一个元素。
选取其中较小的一个并将其值赋给原数组中对应的位置 i赋值完毕后分别对参与赋值的这两个索引做自增 1操作,3比拟 leftIdx和 rightIdx指向的数组元素值。如果 leftIdx或 rigthIdx值已经达到对应数组的末尾,则余下只需要将剩下数组的元素按顺序 copi余下的位置即可。
下面给个归并的具体实例:
第一趟:
以 |分隔开) 辅助数组 [21,28,39|35,38]数组被拆分为左右两个子数组。
leftIdx=0i=0 [21, , , , ]第一次 21与 35比拟 ,左边子数组胜出。
第二趟:
28, 辅助数组 [21.39|35,38]
左边子数组胜出,leftIdx=1i=1 [21,28, , , ]第二次 28与 35比拟。
28, 第三趟:[21.39|35,38]
右边子数组胜出,rightIdx=0i=2 [21,28,35, , ]第三次 39与 35比拟。
28, 第四趟:[21.39|35,38]
右边子数组胜出,rightIdx=1i=3 [21,28,35,38, ]第四次 39与 38比拟。
28, 第五趟:[21.39|35,38]
无需比拟 leftIdx=2i=4 [21,28,35,38,39]第五次时右边子数组已复制完。
可以将整个需要排序的数组做有限次拆分(每次一分为二)直到分为长度为 1小数组为止,以上便是一次归并的过程。长度为 1时数组已经不用排序了这之后再逆序(由于采用递归)依次对这些数组进行归并操作,直到最后一次归并长度为 n/2子数组,归并完成之后数组排序也完成。
这里小弟也忘记是多少了囧)归并排序需要的额外空间是所有排序中最多的每次归并需要与参与归并的两个数组长度之和相同个元素(为了提供辅助数组)则可以推断归并排序的空间复杂度为 1+2+4++n=n*n+2/4忽略了n奇偶性的判断)时间复杂度比较难估。
实现代码:
/**
*Mergsorting
*/
MERGEnewSortabl{
booleanascend{ public<TextendComparable<T>>voidsortT[]array.
0, this.sortarray.array.length-1,ascend;
}
intlo, privat<TextendComparable<T>>voidsortT[]array.inthi,booleanascend{
//OPTIMIZEONE
//ifthesubstring'lengthislessthan20.
//usinsertsorttoreducrecursinvocation
ifhi-lo<20{
forinti=lo+1;i<=hi;i++{
TtoInsert=array[i];
intj=i;
for;j>lo;j--{
intcompar=array[j-1].compareTotoInsert;
ifcompar==0||compar<0==ascend{
break;
}
array[j]=array[j-1];
}
array[j]=toInsert;
}
return;
}
intmid=lo+hi-lo/2;
lo, sortarray.mid,ascend;
mid+1, sortarray.hi,ascend;
lo, mergarray.mid,hi,ascend;
}
intlo, privat<TextendComparable<T>>voidmergT[]array.intmid,inthi,booleanascend{
//OPTIMIZETWO
skipthimerge //ifitisalreadiinrightorder.
//sincthere'noneedtodoso
intleftEndCompareToRigthStart=array[mid].compareToarray[mid+1];
ifleftEndCompareToRigthStart==0||leftEndCompareToRigthStart<0==ascend{
return;
}
@SuppressWarn"unchecked"
T[]arrayCopi=T[]newComparable[hi-lo+1];
lo, System.arraycopiarray.arrayCopy,0,arrayCopy.length;
intlowIdx=0;
inthighIdx=mid-lo+1;
forinti=lo;i<=hi;i++{
iflowIdx>mid-lo{
//leftsubarraiexhausted
array[i]=arrayCopy[highIdx++];
}elsifhighIdx>hi-lo{
//rightsubarraiexhausted
array[i]=arrayCopy[lowIdx++];
}elsifarrayCopy[lowIdx].compareToarrayCopy[highIdx]<0==ascend{
array[i]=arrayCopy[lowIdx++];
}els{
array[i]=arrayCopy[highIdx++];
}
}
}
}
6.快速排序
上一篇:叶凡网络:韩媒疯传朝鲜二号人物落马 平壤街头一切如常
下一篇:叶凡网络:中国首次确定262个资源型城市 历时两年多调研