1.常用排序算法总结

常见的排序算法有8种:


表格:

排序方法 时间复杂度(平均) 时间复杂度(最坏) 时间复杂度(最好) 空间复杂度 稳定性
冒泡排序 O(n2) O(n2) O(n) O(1) 稳定
直接选择排序 O(n2) O(n2) O(n2) O(1) 不稳定
直接插入排序 O(n2) O(n2) O(n) O(1) 稳定
希尔排序 O(nlog2n) O(n2) O(n) O(1) 不稳定
堆排序 O(nlog2n) O(nlog2n) O(nlog2n) O(1) 不稳定
快速排序 O(nlog2n) O(n2) O(nlog2n) O(nlog2n) 不稳定
归并排序 O(nlog2n) O(nlog2n) O(nlog2n) O(n) 稳定
基数排序 O(d(n+r)) O(d(n+r)) O(d(n+r)) O(n+r) 稳定

图片为转载,如侵删:

1.常用排序算法总结_第1张图片


  • 冒泡排序选择排序用的不是很多,因为时间复杂度高,但是因为常数项少,小规模排序执行时间不一定比其他排序长。

  • 插入排序 虽然时间复杂度高,但是因为常数项比较小,通常用在大致排序完成后最后的调整阶段。比如Java中Array.sort()的底层实现便用了二分插入排序(长度小于32的Tim sort算法)。

  • 快速排序归并排序是最常用的。堆排序的思想比较重要,涉及到优先队列的问题。

  • 基数排序 能够解决一些特定的问题。


下面排序都会用到的辅助代码:

    //交换位置
    public static void swap(int[] arr, int i, int j) {
        /* 不能用于浮点数和交换自身
            arr[i] = arr[i] ^ arr[j];
            arr[j] = arr[i] ^ arr[j];
            arr[i] = arr[i] ^ arr[j];
        */
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }

1. Bubble Sort(冒泡排序)

过程:
1. 从0位置开始,比较相邻两个数的大小,如果后面的数小于前面,则交换位置。
2. 遍历一遍下来,最后一个数为整个数组中的最大值。
3. 把最后一个数排除,继续比较剩下的数组。
4. 总共比较次数为N*N,时间复杂度为O(n²)。

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


2. Selection Sort (选择排序)

过程:

  1. 遍历一遍,找到整个数组中最小的数,与位置0的数交换位置。
  2. 从1位置开始,继续遍历,找到最小的数,与1位置交换。以此类推。
  3. 冒泡排序,复杂度为O(n²)。
    //Selection Sort
    public static void selectionSort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        for (int i = 0; i < arr.length - 1; i++) {
            int minIndex = i;
            for (int j = i + 1; j < arr.length; j++) {
                minIndex = arr[j] < arr[minIndex] ? j : minIndex;
            }
            swap(arr, i, minIndex);
        }
    }

3. Insertion Sort(插入排序)

过程:

  1. 1位置开始,比较与前面数的大小,如果小于前面的数,则交换位置,直到不再小于停止。
  2. 接着从2位置开始,重复这个过程。直到最后位置为止。
  3. 时间复杂度取决于数组的排序情况,当数组基本有序时候,复杂度很低,接近O(n)。当数组完全无序时,每个数都要经过多次移动,复杂度趋近于O(n²)。
    //Insertion Sort
    public static void insertionSort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        for (int i = 1; i < arr.length; i++) {
            for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
                swap(arr, j, j + 1);
            }
        }
    }

4. Shell Sort(希尔排序)

过程:
1. 过程类似于插入排序,算是插入排序的一种优化。
2. 首先,需要确定一个步长k,根据步长,把数组分为N/k部分,每一部分单独排序。
3. 把步长缩短,继续排序,直到步长为1。
4. 通过步长,减少了数组需要移动的次数,从而降低了复杂度。
5. 所以复杂度的高低完全取决于步长的好坏,是一种特别不稳定的算法,也是一种实现简单分析困难的算法。

//维基上找的,没自己写
  # Sort an array a[0...n-1].
gaps = [701, 301, 132, 57, 23, 10, 4, 1]

# Start with the largest gap and work down to a gap of 1
foreach (gap in gaps)
{
    # Do a gapped insertion sort for this gap size.
    # The first gap elements a[0..gap-1] are already in gapped order
    # keep adding one more element until the entire array is gap sorted
    for (i = gap; i < n; i += 1)
    {
        # add a[i] to the elements that have been gap sorted
        # save a[i] in temp and make a hole at position i
        temp = a[i]
        # shift earlier gap-sorted elements up until the correct location for a[i] is found
        for (j = i; j >= gap and a[j - gap] > temp; j -= gap)
        {
            a[j] = a[j - gap]
        }
        # put temp (the original a[i]) in its correct location
        a[j] = temp
    }
}

5. Quick Sort(快速排序)

需要重点掌握:

  • 快速排序是一种很重要也很常用的排序,也有一些很重要的应用,比如说BFPRT算法,荷兰国旗问题。
  • 快速排序如果每次都选到最大值,或者最小值,就会产生最坏的情况,使复杂度达到O(n²)级别。但是可以通过随机选择partition值,从数学期望上避免这种情况的发生。所以可以默认其复杂度为O(N * lg N)。
  • 一般默认快速排序是非稳定的。但是有论文级别的方法,可以使其实现稳定(0-1 stable sort)。看看就好。。。

过程:

  1. 随机选出一个partition值,把大于partition值的放在它右边,小于它的放在它左边。
  2. 从partition值的左右两边分割,调用自己,开始递归。
  3. 这里有一点优化,因为partition值在数组中可能不止一个,因此返回一个长度为2的数组,代表partition的左右边界,从边界两端进行递归,更加快速。
    public static void quickSort(int[] arr) {
        if ( arr == null || arr.length < 2) {
            return;
        }
        quickSort(arr, 0, arr.length-1);
    }

    public static void quickSort(int[] arr, int left, int right) {
        if ( left >= right ) {
            return;
        }
        //随机产生partition值,防止出现最坏情况
        swap(arr, right, (int) (Math.random() * ( right - left + 1)  )+ left );
        //返回的数组p为partition的左右边界
        int[] p = partition(arr, left, right);
        quickSort(arr, left, p[0]-1);
        quickSort(arr, p[1]+1, right);
    }

    public static int[] partition(int[] arr, int left, int right) {
        int less = left - 1, more = right;
        while ( left < more ) {
            if ( arr[left] < arr[right] ) {
                swap(arr, ++less, left++);
            } else if ( arr[left] > arr[right] ) {
                swap(arr, --more, left);
            } else {
                left++;
            }
        }
        swap(arr, more, right);
        return new int[] {less+1,more};
    }

6. Merge Sort(归并排序)

需要重点掌握:

  • 归并排序的优势很明显,它是稳定排序。同时相对于快排,它占用较多的空间。
  • 递归的思想很重要,分治法的应用也很广泛,把大问题分解成小问题一步步解决。
  • 递归过程要掌握,递归过程一定要有一个终止条件。
  • 压栈的过程,空间的占用要理解。
  • 同样有论文级别的算法可以把归并排序的空间省下来,知道就好。ORZ。。。 默认空间复杂度 O(n)。

过程:

  1. 把数组分成两部分,分别比较大小,最后合并。
  2. 递归调用自己。
    public static void mergeSort(int[] arr) {
        if ( arr == null || arr.length < 2 ) {
            return;
        }
        mergeSort(arr, 0, arr.length - 1 ) ;
    }

    public static void mergeSort(int[] arr, int l, int r) {
        if (l == r) {
            return;
        }
        int mid = l + ((r - l) >> 1);
        mergeSort(arr, l, mid);
        mergeSort(arr, mid + 1, r);
        merge(arr, l, mid, r);
    } 

    public static void merge(int[] arr, int l, int m, int r) {
        int[] help = new int[r - l + 1];
        int i = 0;
        int p1 = l;
        int p2 = m + 1;
        while (p1 <= m && p2 <= r) {
            help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
        }
        while (p1 <= m) {
            help[i++] = arr[p1++];
        }
        while (p2 <= r) {
            help[i++] = arr[p2++];
        }
        for (i = 0; i < help.length; i++) {
            arr[l + i] = help[i];
        }
    }

7. Heap Sort(堆排序)

重点:

  • 堆排序是一种复杂度很稳定的算法,没有最差或者最好,稳定的 n * lg N。
  • 堆排序可以拓展到优先队列的实现,在贪心算法中经常用到。

过程:

  1. 首先用数组构造一个大根堆。
  2. 把大根堆队顶和最后位置的元素交换位置,最后一个元素脱离大根堆,即数组长度减一。
  3. 队顶下沉,再次构造大根堆,重复这个过程,直至完全排序。
    public static void HeapSort(int[] arr) {
        if ( arr == null || arr.length < 2 ) {
            return;
        }
        for ( int i = 1; i < arr.length; i++ ) {
            insertHeap(arr, i);
        }
        int r = arr.length - 1;
        while(r > 0 ) {
            swap(arr, 0, r);
            heapify(arr, 0, r--);
        }
    }

    public static void insertHeap(int[] arr, int i) {
        while (arr[i] > arr[(i - 1) / 2]) {
            swap(arr, i, (i - 1) / 2);
            i = (i - 1) / 2;
        }
    }

    public static void heapify(int[] arr, int index, int size) {
        int left = index * 2 + 1;
        while ( left < size ) {
            int largest = left+1 < size && arr[left] < arr[left + 1] ? left+1 : left;
            if ( arr[index] > arr[largest] ) {
                return;
            }
            swap(arr, index, largest);
            index = largest;
            left = index * 2 + 1;
        }
    }



8. Radix Sort / Count Sort(基数排序 / 计数排序)

  • 桶排序(Bucket Sort)属于非比较排序,因此适用范围很窄,但是在特定问题上可以把时间复杂度做到 O(n)。
  • 桶排序只是一个概念,一种非比较排序的思想。基数排序和计数排序都算桶排序的一种落地(实现)。

计数排序(Count Sort)

适用范围:

  • 计数排序适用于明确范围的数字排序

过程:

  1. 找的数组最大值max,new出来max+1个桶。
  2. 遍历整个数组,把数组里的数全部扔到对应的桶里。
  3. 从第一个桶开始遍历所有桶,把遍历的值填充到原来的数组中。

其他:

  • 计算排序其实可以优化,但这个瓶颈会一直存在。即必须为一定范围内的数组集合。

代码:

// only for 0~200 value
    public static void bucketSort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        int max = Integer.MIN_VALUE;
        for (int i = 0; i < arr.length; i++) {
            max = Math.max(max, arr[i]);
        }
        int[] bucket = new int[max + 1];
        for (int i = 0; i < arr.length; i++) {
            bucket[arr[i]]++;
        }
        int i = 0;
        for (int j = 0; j < bucket.length; j++) {
            while (bucket[j]-- > 0) {
                arr[i++] = j;
            }
        }
    }

基数排序(Radix Sort)

过程:

  1. 首先需要理解一点:当个位排好顺序时,再对十位开始排序时,个位的相对位置不变。
  2. 什么叫相对位置?比如对23,26,58,93进行排序,个位排好后是23,93,26,58。
  3. 这时再对十位进行排序,23,26的相对位置是不会变的,排序结束为23,26,58,93。
  4. 这个思维过程有点像动态规划,把问题分为n个小步骤,每个下一步都会用到上一步的结果。

代码:

    // only for no-negative value
    public static void radixSort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        radixSort(arr, 0, arr.length - 1, maxbits(arr));
    }

    public static int maxbits(int[] arr) {
        int max = Integer.MIN_VALUE;
        for (int i = 0; i < arr.length; i++) {
            max = Math.max(max, arr[i]);
        }
        int res = 0;
        while (max != 0) {
            res++;
            max /= 10;
        }
        return res;
    }

    public static void radixSort(int[] arr, int begin, int end, int digit) {
        final int radix = 10;
        int i = 0, j = 0;
        int[] count = new int[radix];
        int[] bucket = new int[end - begin + 1];
        for (int d = 1; d <= digit; d++) {
            for (i = 0; i < radix; i++) {
                count[i] = 0;
            }
            for (i = begin; i <= end; i++) {
                j = getDigit(arr[i], d);
                count[j]++;
            }
            for (i = 1; i < radix; i++) {
                count[i] = count[i] + count[i - 1];
            }
            for (i = end; i >= begin; i--) {
                j = getDigit(arr[i], d);
                bucket[count[j] - 1] = arr[i];
                count[j]--;
            }
            for (i = begin, j = 0; i <= end; i++, j++) {
                arr[i] = bucket[j];
            }
        }
    }

    public static int getDigit(int x, int d) {
        return ((x / ((int) Math.pow(10, d - 1))) % 10);
    }

你可能感兴趣的:(算法-java)