数据结构Java版之排序算法(二)


  排序按时间复杂度和空间复杂度可分为 低级排序 和 高级排序 算法两种。下面将对排序算法进行讲解,以及样例的展示。

低级排序:冒泡排序、选择排序、插入排序。  

  冒泡排序:

核心思想,小的数往前移。假设最小的数在最后一位,将最后一位一步步移到第一位。

public void bubbleSort(int[] list) {
    int n = list.length - 1;
    for(int i = 0; i < n; i ++) {
        for(int j = 0; j < n - i; j ++) {
            if(list[j] > list[j + 1])
                swap(j, j + 1, list);
        }
    }
}
public void swap(int i, int j, int[]list) {
    int temp = list[i];
    list[i] = list[j];
    list[j] = temp;
}

  选择排序:

选择最小数按顺序排列。从当前未排序的数组中,找出一个最小的数,放在第一位。第二小的放在第二位······

public void selectSort(int[] list) {
    int min; //记录最小下标
    for(int i = 0; i < list.length - 1; i ++) {    //对n个数排序,只需将n-1个数排好序,就ok了。所以循环条件是list.length-1.
        min = i;
        for(int j = i + 1; j < list.length; j ++) {     //循环一次找出一个最小数
            if(list[j] < list[min]) {
                min = j;
            }
        }
        swap(i, min, list);
    }
}
public void swap(int i, int j, int[]list) {
    int temp = list[i];
    list[i] = list[j];
    list[j] = temp;
}

  插入排序:

取出元素,从后往前依次比较前面的元素。直到前面的数比自己小,则插入到当前位置。在此过程中,大于或等于自己的元素一直往后移位。

public void insertSort(int[] list) {
    int sign, temp; // sign记录空值下标,temp记录取出值
    for(int i = 1; i < list.length; i ++) {
        temp = list[i];
        sign = i;
        //如果取出值小于前一个值,则把前一个值往后移,覆盖当前值
        while(sign > 0 && list[sign - 1] > temp) {
            list[sign] = list[sign - 1];
            sign --;
        }
        list[sign] = temp;
    }
}

插入排序优化写法:

//优化插入:将数组第一个元素用来保存数据,达到减少比较次数
public void insertSort_2(int[] list) {
    int sign, temp; // sign记录空值下标,temp记录取出值
    for(int i = 2; i < list.length; i ++) {
        temp = list[i];
        list[0] = temp;
        sign = i - 1;
        //如果取出值小于前一个值,则把前一个值往后移,覆盖当前值
        while(list[sign] > temp) {
            list[sign + 1] = list[sign];
            sign --;
        }
        list[sign + 1] = temp;
    }
}

高级排序:快速排序、归并排序。

  快速排序: 最流行的排序算法、速度最快的排序算法。选定一个枢轴,小于枢轴的数放左边,大于枢轴的数放右边,交换枢轴两边的数。
public void quickSort(int left, int right, int[] list) {
    //选枢轴进行划分
    if(left < right) {
        int i = left;
        int j = right + 1;
        int privot = list[left]; //定义一个枢轴
        do{
            do{
                i ++;
            }while(list[i] < privot);//找到一个比枢轴大的数
            do{
                --j;
            }while(list[j] > privot);//找到一个比枢轴小的数
            if(i<j) swap(i, j, list);
        }while(i < j);
            
        swap(left, j, list);    //将基准数放置到中间
            
        quickSort(left, j - 1, list);
        quickSort(j + 1, right, list);
    }
}
public void swap(int i, int j, int[]list) {
    int temp = list[i];
    list[i] = list[j];
    list[j] = temp;
}

  归并排序:

将两个排好序的数组合并在一起,并且重新排序。比如1->2>3 、1->4->5 合并在一起后就是:1->2>3 ->1->4->5  排好序为:1->1->2->3->4->5.   在此演示的示例为直接合并的数组。

  首先,为方便写程序,数组第一个元素将废弃使用,不对其进行排序。

基础归并函数:归并两个排序的数组

public void Merge(int[] initList, int[] mergeList, int l, int m, int n) {    
   //  initList 初始化数组, mergeList存放排好序的数组。
// l表示第一个数组下标,m表示第一个数组最后一个元素下标,n表示第二个数组最后一个元素下标。result表示新建数组下标 int i1, i2, result; for(i1 = l, i2 = m + 1, result = i1; i1 <= m && i2 <= n; result ++) { if(initList[i1] <= initList[i2]) { mergeList[result] = initList[i1]; i1 ++; }else { mergeList[result] = initList[i2]; i2 ++; } }
  //  将第一个数组和第二个数组未放进mergeList的元素复制到mergeList中。(如果没有剩余元素,则复制元素个数为0,不产生影响,所以可以放心对两个数组都进行剩余元素复制)
System.arraycopy(initList, i1, mergeList, result, m - i1 + 1); System.arraycopy(initList, i2, mergeList, result, n - i2 + 1); }

其次,有了前面的基础归并函数,那么就可以实现乱序数组的归并排序。其思想,进行多次基础归并。

     

实现单次归并:划分数组进行归并。第一次单个元素作为划分对象,第二次两个元素作为划分对象·········

public void MergePass(int[] initList, int[] mergeList, int n, int s) {  //  n表示初始化数组最后一位元素下标,s表示第几次归并。
    int i;
    for(i = 1; i <= n - 2*s + 1; i += 2*s) {   //对划分的数组两两归并
        Merge(initList, mergeList, i, i + s - 1, i + 2*s - 1);
    }
        
    if((i + s - 1) < n) {  //有剩余未归并的元素则进行归并
        Merge(initList, mergeList, i, i + s - 1, n);
    }else {    //将剩余元素拷贝下来
        System.arraycopy(initList, i, mergeList, i, n + 1 - i);
    }
}

  最后通过控制归并次数,真正达到乱序数组归并排序的效果:

public void MergeSort(int[] list) {
    int[] newList = new int[list.length];

  for(int i = 1; i < list.length; i *= 2) { MergePass(list, newList, list.length - 1, i); i *= 2;    //只所以在此进行i*2,是因为归并后的结果放在newList里面,重新进行归并时。省掉将newList重新拷贝到list的麻烦 MergePass(newList, list, list.length - 1, i); } }

接下来,来个归并算法的全家桶吧:

public class MergeSort {
    @Test
    public void fun() {
        int[] a = {0, 23, 47, 81, 95, 7, 14, 39, 55, 62, 74};
        MergeSort(a);
        for(int i : a) {
            System.out.print(i + "\t");
        }
    }
    public void Merge(int[] initList, int[] mergeList, int l, int m, int n) {    
        //    l表示第一个数组下标,m表示最后一个数组下标,result表示新建数组下标
        int i1, i2, result;
        for(i1 = l, i2 = m + 1, result = i1; i1 <= m && i2 <= n; result ++) {
            if(initList[i1] <= initList[i2]) {
                mergeList[result] = initList[i1];
                i1 ++;
            }else {
                mergeList[result] = initList[i2];
                i2 ++;
            }
        }
        System.arraycopy(initList, i1, mergeList, result, m - i1 + 1);
        System.arraycopy(initList, i2, mergeList, result, n - i2 + 1);
    }
    public void MergePass(int[] initList, int[] mergeList, int n, int s) {
        int i;
        for(i = 1; i <= n - 2*s + 1; i += 2*s) {
            Merge(initList, mergeList, i, i + s - 1, i + 2*s - 1);
        }
        
        if((i + s - 1) < n) {
            Merge(initList, mergeList, i, i + s - 1, n);
        }else {
            System.arraycopy(initList, i, mergeList, i, n + 1 - i);
        }
    }
    public void MergeSort(int[] list) {
        int[] newList = new int[list.length];
        for(int i = 1; i < list.length; i *= 2) {
            MergePass(list, newList, list.length - 1, i);
            i *= 2;
            MergePass(newList, list, list.length - 1, i);
        }
    }
}