算法笔记2——排序
基础排序
1.选择排序:
首先,找到数组中最小的那个元素,其次,将他和数组第一个元素交换位置,
再次,在剩下的元素中找到最小的元素,将他和数组的第二个元素交换位置。
如此反复,直到将整个数组排序。不断的选择剩余元素的最小值
2.插入排序:(对部分有序数组很有效)
为了给要插入的元素腾出空间,我们需要将其余所有元素在插入之前都向右移动一位。
public class Insertion {
public static void main(String[] args) {
int []arr= {12,16,3,64,-5,69};
for (int i = 1; i < arr.length; i++) {
for(int j=i;j>0&&arr[j]<arr[j-1];j--) {
int temp=arr[j];
arr[j]=arr[j-1];
arr[j-1]=temp;
}
}
System.out.println(Arrays.toString(arr));
}
}
插入排序不会访问索引右侧的元素,而排序不会访问索引左侧的元素。
对于随机排序数组,两者的运行时间都是平方级别的。
3.希尔排序
对于大规模乱序数组插入排序很慢,因为它只会交换相邻的元素,因此元素只能一点一点的从数组的一段移动到另外一端。
希尔排序为了加快速度简单的改进了插入排序,交换不相邻的元素以对数组的局部进行排序,并最终用插入排序将局部有序的数组进行排序。
希尔排序的思想是是数组中任意间隔为h的元素是有序的。这样的数组都是h有序数组。换句话说,一个h有序数组就是h个相互独立的有序数
组编制在一起组成的一个数组。在进行排序时,如果h很大,我们就能将元素移动到很远的地方,为实现更小的h有序创造方便。
用这种方式,对于任意以1结尾的h序列,我们都够能将数组排序。
希尔排序更高效的原因是它权衡了子数组的规模和有序性。排序之初,各个子数组都很短,排序之后子数组都是部分有序的, 这种情况很适合插入排序。
public class XirSort {
public static void main(String[] args) {
int []arr= {12,16,3,64,-5,69,25,75,95,-4,-36,52,76,-92,34,83};
int N=arr.length;
int h=1;
while(h<N/3)h=h*3+1;//1,4,13,40,121,364,1093, ...
while(h>=1)
{
for (int i = h; i < N; i++) {
for (int j = i; j>=h&&arr[j]<arr[j-h]; j-=h) {
int temp=arr[j];
arr[j]=arr[j-h];
arr[j-h]=temp;
}
}
h=h/3;
}
System.out.println(Arrays.toString(arr));
}
}
高级排序
4.归并排序:要将一个数组排序,可以先将它分成两半分别排序,然后将结果归并起来。
优点:保证任意长度为N的数组排序所需的时间和NlogN成正比
缺点:所需的额外空间和N成正比。
原地归并抽象方法:将子数组a[lo...mid]和a[mid+1...hi]归并为一个有序的数组并将结果保存到a[lo...hi]中
从aux[]两边取值,谁小取谁,左边取完了取右边,右边取完了取左边。
该方法将所有元素复制到aux[]中,然后再归并到a[]中。方法在归并时(第二个for循环)进行了四个条件判断:
左半边用尽(取右半边的元素)、右半边用尽(取左半边元素)、右半边的当前元素小于左半边的当前元素(取右半边的元素)
以及右半边的当前元素大于等于左半边的当前元素(取左半边元素)。
public static void merge(Comparable[]a,int lo,int mid,int hi)
{
int i=lo,j=mid+1;
for(int k=lo;k<=hi;k++)//将a[lo...hi]复制到aux[lo...hi]
aux[k]=a[k];
for(int k=lo;k<=hi;k++)//归并到
if(i>mid) a[k]=aux[j++];
else if(j>hi) a[k]=aux[i++];
else if(aux[j]<aux[i]) a[k]=aux[j++];
else a[k]=aux[i++];
}
自顶向下的归并排序:分治思想
要对子数组a[lo...hi]进行排序,先将它分为a[lo...mid]和a[mid+1...hi]两部分,
分别通过递归调用将他们单独排序,最后将有序的子数组归并为最终的排序结果。
public class Merge
{
private static Comparable[]aux; //归并所需的辅助数组
public static void sort(Comparable[]a)
{
aux=new Comparable[a.length]; //创建辅助数组
sort(a,0,a.length-1);
}
private static void sort(Comparable[]a,int lo,int hi)
{
if(hi<=ho) return;
int mid=lo+(hi-lo)/2;
sort(a,lo,mid); //将左半边排序
sort(a,mid+1,hi); //将右半边排序
merge(a,lo,mid,hi); //归并
}
}
自底向上的归并排序:先归并那些微型数组,然后再成对归并得到子数组,如此这般,知道整个数组归并到一起。
首先进行的是两两的归并(把每个元素想象成一个大小为1的数组),然后是四四归并(把两个大小为2的数组归并为大小为4的数组),然后一直下去。。。
在每一轮归并中,最后一次归并的第二个数组可能比第一个子数组要小,如果不是的话所有归并的中两个数组的大小都应该一样。而下一轮会翻倍。
自底向上的归并排序会多次遍历整个数组,根据子数组大小进行两两归并。子数组的大小sz的初始值为1,每次加倍。最后一个子数组的大小只有在数组是sz的偶数倍的时候才会等于sz(否则它会比sz小)。
public class MergeBU
{
private static Comparable[]aux; //归并所需的辅助数组
public static void sort(Comparable[]a)
{
int N=a.length;
aux=new Comparable[N];
for(int sz=1;sz<N;sz=sz+sz) //sz子数组的大小
for(int lo=0;lo<N-sz;lo+=sz+sz) //lo:子数组的索引
merge(a,lo,lo+sz-1,Math.min(lo+sz+sz-1,N-1));
}
}