排序算法(Java版)

排序算法(Java版)

Posted by Reborn on November 10, 2018

排序算法(Java版)

algorithm

备注:以下动图来自于一像素排序算法

冒泡排序

public static void bubbleSort(int[] arr) {
    if(arr.length == null || arr.length == 0) return;
    
    int len = arr.length;
    for(int i = 0; i < len-1; i++) {
        for(int j = 0; j < len-i-1; j++) {
            if(arr[j]>arr[j+1]) swap(arr, j, j+1);
        }
    }
}

选择排序

public static void selectSort(int[] arr) {
    if(arr.length == null || arr.length == 0) return new int[];
    int len = arr.length;
    for(int i = 0; i < len-1; i++) {
        minIndex = i;
        for(int j = i+1; j < len; j++) {
            if(arr[j]< arr[minIndex]) minIndex = j;
        }
        swap(arr, i, minIndex);
    }
}

插入排序

public static void insertSort(int[] arr) {
    if(arr == null || arr.length == 0) return new int[];
    
    for(int i = 1; i < len; i++) {
        int current = arr[i];
        int preIndex = i-1;
        while(preIndex>=0 && arr[preIndex]>arr[i]) {
            arr[preIndex+1] = arr[preIndex];//后移
            preIndex--;
        }
        arr[preIndex+1]=current;
    }
}
// 重写版,更简洁
public static void insertSort(int[] arr) {
    if (arr == null || arr.length <= 1) {
        return;
    }

    for (int i = 1, len = arr.length; i < len; i++) {
        for (int j=i; j > 0 && arr[j]<arr[j-1]; j--) {
            swap(arr, j, j-1);
        }
    }
}

希尔排序

public class Shell {
    public static void shellSort(int[] a) {
        //升序排列
        int N = a.length;
        int h = 1;
        while(h < N/3) h = 3*h + 1;
        while(h>0) {
            for(int i = h; i < N; i++) {
                for(int j = i; j >=h && a[j]<a[j-h]; j -= h) {
                    swap(a, j, j-h);
                }
            }
            h = h/3;
        }
    }
}

归并排序

public static void mergeSort(int[] arr, int left, int right) {
    sort(arr, left, right);
}

public static void sort(int[] arr, int left, int right) {
    if(left>=right) return;

    int mid = (left+right)/2;
    sort(arr, left, mid);
    sort(arr, mid+1, right);
    merge(arr, left, right);

}

public static void merge(int[] arr, int left, int right) {
    int[] result = new int[arr.length];
    int mid = (left+right)/2;
    int leftIndex = left, rightIndex = mid+1, i = left;
    System.out.println("left:"+left+", right: "+right);
    for(int j = 0; j <= right; j++) {
        System.out.printf("%d\t", arr[j]);
    }
    System.out.printf("\n");
    while(leftIndex <= mid && rightIndex <= right) {
        if(arr[leftIndex] < arr[rightIndex]) {
            result[i++] = arr[leftIndex++];
        } else {
            result[i++] = arr[rightIndex++];
        }
    }

    while(leftIndex <= mid) {
        result[i++] = arr[leftIndex++];
    }

    while(rightIndex <= right) {
        result[i++] = arr[rightIndex++];
    }
    //对arr进行重新复制
    //for(int j = left; j <= right; j++) {
    //    arr[j] = result[j];
    //}
    System.arraycopy(result,left, arr, left, right-left+1);

    for(int j = 0; j <= right; j++) {
        System.out.printf("%d\t", arr[j]);
    }
    System.out.printf("\n");
}
left:0, right: 1
3       44
3       44
left:2, right: 3
3       44      38      5
3       44      5       38
left:0, right: 3
3       44      5       38
3       5       38      44
left:4, right: 5
3       5       38      44      47      36
3       5       38      44      36      47
left:4, right: 6
3       5       38      44      36      47      27
3       5       38      44      27      36      47
left:0, right: 6
3       5       38      44      27      36      47
3       5       27      36      38      44      47
left:7, right: 8
3       5       27      36      38      44      47      2       46
3       5       27      36      38      44      47      2       46
left:7, right: 9
3       5       27      36      38      44      47      2       46      4
3       5       27      36      38      44      47      2       4       46
left:10, right: 11
3       5       27      36      38      44      47      2       4       46      19      50
3       5       27      36      38      44      47      2       4       46      19      50
left:10, right: 12
3       5       27      36      38      44      47      2       4       46      19      50      48
3       5       27      36      38      44      47      2       4       46      19      48      50
left:7, right: 12
3       5       27      36      38      44      47      2       4       46      19      48      50
3       5       27      36      38      44      47      2       4       19      46      48      50
left:0, right: 12
3       5       27      36      38      44      47      2       4       19      46      48      50
2       3       4       5       19      27      36      38      44      46      47      48      50

快速排序

public static void quickSort(int[] arr, int left, int right) {
    if(left>=right) return;
    int partIndex = partion(arr, left, right);
    quickSort(arr, left, partIndex-1);
    quickSort(arr, partIndex+1, right);
}

public static int partion(int[] arr, int left, int right) {
    int pivot = arr[right];//每次以最后一个元素作为基准元素
    int i = left;
    for (int j = left; j < right; j++) {
        if (arr[j]<pivot) {
            swap(arr, i++, j);
        }
    }
    swap(arr, i, right);
    return i;
}

算法改进

最优情况下,刚好对半分,复杂度为O(NlogN),最差情况下为O(n^2)。因此对于快速排序可以有以下改进方法:

  • 数组个数较少时,切换为插入排序,所以可以设置当数组元素低于某个阈值时,设置为插入排序。

  • 三数取中,作为切分元素。就是把元素中最左最右和中间三个元素,取大小居中的元素作为切分元素,详情可见这篇文章

  • 三向切分,对于有大量重复元素的数组,可以将其分为小于,等于和大于三部分。

    //三向切分代码实现
    public void ThreeWayQuickSort(int[] arr, int l, int r) {
        if(l>=r) return;
          
        int lt = l, i = l+1, rt = r;
        int pivot = arr[l];
        while(i <= gt) {
            if(arr[i]<pivot) {
                swap(arr, i++, lt++);//把小于pivot的值放在最左边
            } else if(arr[i]>pivot) {
                swap(arr, i, gt--);//把大于pivot的值放在最右边
            } else {
                i++;//相等部分放在中间
            }
        }
          
        ThreeWayQuickSort(arr, l, lt-1);
        ThreeWayQuickSort(arr, gt+1, r);
    }
    

    切分还可以用于选择算法,这样就实现二分查找的效果,比较总次数为N+N/2+N/4+…,最后必然小于2N次的比较结果

堆排序

堆排序涉及到这个概念,堆的结点值总是大于等于其子节点,而且堆是一颗完全二叉树。因此堆可以用数组来表示,假设数组下标从1开始,那么位置为k的元素的父节点的位置为k/2,其子节点的位置分别为2K2k+1。堆排序的插入和删除操作的展示图,可以看这篇文章

public class Heap<T extends Comparable<T>> {
	//实现一个最大堆
    private T[] heap;
    private int N = 0;

    public Heap(int maxN) {
        this.heap = (T[]) new Comparable[maxN + 1];
    }

    public boolean isEmpty() {
        return N == 0;
    }

    public int size() {
        return N;
    }

    private boolean less(int i, int j) {
        return heap[i].compareTo(heap[j]) < 0;//说明位置i的数值小于位置j的数值,之后进行交换
    }

    private void swap(int i, int j) {
        T t = heap[i];
        heap[i] = heap[j];
        heap[j] = t;
    }
    
    public void swim(int k) {
        //上浮操作,不断跟父节点进行比较即可
        while(k>1 && less(k/2, k)) {
            swap(k, k/2);
            k = k/2;
        }
    }
	
    private void sink(int k) {
        //下沉操作,不断跟子节点比较
        while(2*k<=N) {//不超过元素的个数
            int j = 2*k;//左节点
            if(j < N&&less(j, j+1)) {//左节点小于右节点
            	j++;
            }
            if(less(j, k)) break;//大于子节点
            swap(k, j);
            k = j;
        }
    }
    
    private void insert(Comparable v) {
        heap[++N] = (T) v;//加入到数组尾,并增加数组个数N
        swim(N);//上浮到合适的位置
    }
    //删除最大元素
    private T deleteMax() {
        T max = heap[1];
        swap(1, N--);//将尾子节点跟堆顶交换
        head[N+1]=null;
        sink(1);//将堆顶下沉
        return max;
    }
}

总结比较

         
算法 稳定性 时间复杂度 空间复杂度 备注
选择排序 × N2 1  
冒泡排序 N2 1  
插入排序 N ~ N2 1 时间复杂度和初始顺序有关
希尔排序 × N 的若干倍乘于递增序列的长度 1  
快速排序 × NlogN logN  
三向切分快速排序 × N ~ NlogN logN 适用于有大量重复主键
归并排序 NlogN N