八大排序算法

名称 数据对象 稳定性 时间复杂度 空间复杂度 描述
冒泡排序 数组 稳定 \(O(n^2)\) \(O(1)\)
选择排序 数组、链表 不稳定/稳定 \(O(n^2)\) \(O(1)\) 找到最小元素的下标,依次向前部放置
插入排序 数组、链表 稳定 \(O(n^2)\) \(O(1)\) 扑克牌插法
希尔排序 数组 不稳定 \(O(n\log n)\) \(O(1)\) 每次按照事先的间隔进行插入排序,间隔依次减小,最后一次一定为1
归并排序 数组、链表 稳定 \(O(n\log n)\) 向上:\(O(1)\);向下\(O(n)\) 向下的空间复杂度因为 有一个tempArray所以不是\(O(\log n)\)\(O(n)\)
快速排序 数组 不稳定 \(O(n\log n)\) \(O(\log n)\)
堆排序 数组 不稳定 \(O(n\log n)\) \(O(1)\)
计数排序 数组、链表 稳定 \(O(n+m)\) \(O(n+m)\) 相对于比较排序速度快得多, 但是空间消耗较大。当存在负数的时候,需要将其整体置为正数。
桶排序 数组、链表 稳定 \(O(n)\) \(O(m)\) bucketSize为1的时候,直接退化为计数排序
基数排序 数组、链表 稳定 \(O(k\times n)\) \(O(m)\)
败者树 数组、链表

分类

排序算法可以分为内部排序外部排序内部排序是指数据记录再内存中进行排序,而外部排序则是因为数据量非常大,无法一次读入内存,再排序的工程中经常需要访问外存。

常见的内部排序算法:插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。


冒泡排序

image

public class Solution {
    public int[] bubbleSort(int[] array) {
        int len = array.length;
        int[] arr = Arrays.copyOf(array, len);
        for (int i = 1; i < len; i++) {
            boolean flag = true;
            for (int j = 0; j < len - i; j++) {
                if (arr[j] > arr[j + 1]) {
                    swap(arr, j, j + 1);
                    flag = false;
                }
            }
            if (flag) {
                break;
            }
        }
        return arr;
    }
    private void swap(int[] array, int x, int y) {
        int temp = array[x];
        array[x] = array[y];
        array[y] = temp;
    }
}

选择排序

image

选择排序也可以对链表进行操作,具体操作步骤和数组的一致。

public class Solution {
    public int[] selectionSort(int[] array) {
        int len = array.length;
        int[] arr = Arrays.copyOf(array, len);
        for (int i = 0; i < len; i++) {
            int minIndex = i;
            for (int j = i + 1; j < len; j++) {
                if (arr[j] < arr[minIndex]) {
                    minIndex = j;
                }
            }
            if (i != minIndex) {
                swap(arr, i, minIndex);
            }
        }
        return arr;
    }
    private void swap(int[] array, int x, int y) {
        int temp = array[x];
        array[x] = array[y];
        array[y] = temp;
    }
}

插入排序

image

public class Solution {
    public int[] insertionSort(int[] array) {
        int len = array.length;
        int[] arr = Arrays.copyOf(array, len);
        for (int i = 1; i < len; i++) {
            int temp = arr[i];
            int j = i;
            while (j > 0 && temp < arr[j - 1]) {
                arr[j] = arr[j - 1];
                j--;
            }
            if (j != i) {
                arr[j] = temp;
            }
        }
        return arr;
    }
}

希尔排序

image

其成为递减增量排序算法,是插入排序的一种更高效的版本,

  • 插入排序对已经已经有序的数组进行排序的时候,效率可以达到线性排序的效果。

希尔排序的基本思想是:先将整个待排序的序列分割为若干子序列进行插入排序,待整个序列中的记录基本有序的时候,再对全体记录依次进行直接插入排序。

public class Solution {
    public int[] shellSort(int[] array) {
        int len = array.length;
        int[] arr = Arrays.copyOf(array, len);
        for (int step = len / 2; step >= 1; step /= 2) {
            for (int i = step; i < len; i++) {
                int temp = arr[i];
                int j = i - step;
                while (j >= 0 && arr[j] > temp) {
                    arr[j + step] = arr[j];
                    j -= step;
                }
                arr[j + step] = temp;
            }
        }
        return arr;
    }
}

归并排序

image

自顶向下的方法/递归法

空间复杂度:此处的merge方法,因为其中的tempArray,所以空间复杂度应该为\(O(\log n+n)\),所以此处时间复杂度为\(O(n)\)

时间复杂度:因为merge的时间复杂度为\(O(n)\)splitArray需要调用\(\log n\)merge,所以此处时间复杂度为\(O(n\log n)\)

public class Solution {
    public int[] mergeSort(int[] array) {
        int len = array.length;
        int[] arr = Arrays.copyOf(array, len);
        splitArray(arr, 0, arr.length);
        return arr;
    }

    private void splitArray(int[] array, int start, int end) {
        if (end - start < 2) {
            return;
        }
        int middle = (start + end) / 2;

        splitArray(array, start, middle);
        splitArray(array, middle, end);

        merge(array, start, middle, end);
    }

    private void merge(int[] array, int start, int middle, int end) {
        int i = 0, j = 0, tempIndex = 0;
        int[] tempArray = new int[end - start];
        while (i + start < middle && j + middle < end) {
            if (array[start+i] < array[middle+j]) {
                tempArray[tempIndex] = array[start+i];
                tempIndex++;
                i++;
            } else {
                tempArray[tempIndex] = array[middle+j];
                tempIndex++;
                j++;
            }
        }
        while (j+middle<end){
            tempArray[tempIndex] = array[j+middle];
            j++;
            tempIndex++;
        }
        while (start+i<middle){
            tempArray[tempIndex] = array[start+i];
            i++;
            tempIndex++;
        }
        for(i=start;i<end;i++){
            array[i] = tempArray[i-start];
        }
    }
}


自下向上的方法/迭代法

public class Solution {
    public int[] mergeSort(int[] array) {
        int len = array.length;
        int[] arr = Arrays.copyOf(array, len);
        int[] tempArray = new int[len];
        int leftMin = 0, leftMax = 0, rightMin = 0, rightMax = 0, tempIndex = 0;
        for (int i = 1; i < len; i *= 2) {
            for (leftMin = 0; leftMin < len-i; leftMin=rightMax) {
                leftMax = rightMin = leftMin + i;
                rightMax = rightMin + i;
                if (rightMax > len) {
                    rightMax = len;
                }
                tempIndex = 0;
                while (leftMin < leftMax && rightMin < rightMax) {
                    if (arr[leftMin] < arr[rightMin]) {
                        tempArray[tempIndex++] = arr[leftMin++];
                    } else {
                        tempArray[tempIndex++] = arr[rightMin++];
                    }
                }
                while(leftMax-leftMin>0){
                    tempArray[tempIndex++] = arr[leftMin++];
                }
                while (rightMax-rightMin>0){
                    tempArray[tempIndex++] = arr[rightMin++];
                }
                while (tempIndex>0){
                    arr[--rightMin] =tempArray[--tempIndex];
                }
            }
        }
        return arr;
    }
}

快速排序

image

public class Solution {
    public int[] sort(int[] array) {
        int len = array.length;
        int[] arr = Arrays.copyOf(array, len);

        return quickSort(arr, 0, len - 1);
    }

    private int[] quickSort(int[] arr, int left, int right) {
        if (left < right) {
            int index = partition(arr, left, right);
            quickSort(arr, left, index - 1);
            quickSort(arr, index + 1, right);
        }
        return arr;
    }

    public int partition(int[] arr, int left, int right) {
        Random random = new Random();
        swap(arr,left,random.nextInt(left, right + 1));
        int temp = arr[left];
        while (left < right) {
            while (arr[right] >= temp && left < right) {
                right--;
            }
            arr[left] = arr[right];
            while (arr[left] < temp && left < right) {
                left++;
            }
            arr[right] = arr[left];
        }
        arr[left] = temp;
        return left;
    }
    private void swap(int[] nums,int x,int y){
        int temp = nums[x];
        nums[x] = nums[y];
        nums[y] = temp;
    }
}

堆排序

image

public class Solution {

    /**
     * 大根堆, 每次讲最大的数字放到最后,所以是从小到达排列。
     *
     * @param array
     */
    private void heapSort(int[] array) {
        buildHeap(array);
        for (int i = array.length - 1; i >= 0; i--) {
            swap(array, 0, i);
            heapify(array, i, 0);
        }
    }

    private void buildHeap(int[] array) {
        int len = array.length;
        int toHeapIndex = (len - 2) / 2;
        for (int i = toHeapIndex; i >= 0; i--) {
            heapify(array, len, i);
        }
    }

    /**
     * @param array 需要heapify的数组
     * @param len   数组的长度,或者说是需要heapify的长度
     * @param i     从i开始进行递归的heapify
     */
    private void heapify(int[] array, int len, int i) {
        int c1 = 2 * i + 1;
        int c2 = 2 * i + 2;
        int max = i;
        if (c1 < len && array[c1] > array[max]) {
            max = c1;
        }
        if (c2 < len && array[c2] > array[max]) {
            max = c2;
        }
        if (max != i) {
            swap(array, max, i);
            heapify(array, len, max);
        }
    }

    private void swap(int[] array, int i, int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
}

计数排序

public class Solution {

    public int[] countingSort(int[] array) {
        int maxValue = getMaxValue(array);
        int[] arr = Arrays.copyOf(array, array.length);
        int[] bucketArray = new int[maxValue + 1];
        for (int i : arr) {
            bucketArray[i]++;
        }
        int arrayIndex = 0;
        for (int i = 0; i < bucketArray.length; i++) {
            while (bucketArray[i] > 0) {
                arr[arrayIndex++] = i;
                bucketArray[i]--;
            }
        }
        return arr;
    }


    private int getMaxValue(int[] array) {
        int maxValue = Integer.MIN_VALUE;
        for (int i : array) {
            maxValue = Math.max(i, maxValue);
        }
        return maxValue;
    }
}

桶排序

桶排序的话,和计数排序相比,就是多了一个参数,通过该参数指定\(n\)个连续的数字存到一个桶内。数字越小(假如为\(1\)),就直接变成了计数排序。

public class Solution {

    public int[] bucketSort(int[] arr, int bucketSize) {
        if (arr.length == 0) {
            return arr;
        }
        int minvalue = arr[0], maxValue = arr[0];
        for (int i : arr) {
            minvalue = Math.min(minvalue, i);
            maxValue = Math.max(maxValue, i);
        }
        int bucketCount = (int) (Math.floor((maxValue - minvalue) / bucketSize) + 1);
        int[][] buckets = new int[bucketCount][0];

        for (int i = 0; i < arr.length; i++) {
            int index = (int) Math.floor((arr[i] - minvalue) / bucketSize);
            buckets[index] = arrAppend(buckets[index], arr[i]);
        }
        int arrIndex = 0;
        for (int[] bucket : buckets) {
            if(bucket.length<=0){
                continue;
            }
            Arrays.sort(bucket);  // 此处使用 插入替换
            for (int i : bucket) {
                arr[arrIndex++] = i;
            }
        }
        return arr;


    }

    private int[] arrAppend(int[] arr, int value) {
        arr = Arrays.copyOf(arr, arr.length + 1);
        arr[arr.length - 1] = value;
        return arr;
    }
}

基数排序

image

  • 计数排序:每个桶都存储单一的值
  • 桶排序:每个桶存放一定范围内的数值
  • 基数排序:根据键值的每个数字分配桶

但是这几个都只能对正整数进行排序,前面的比较排序的方法可以对任意数字进行排序。

public class Solution {

    public int[] radixSort(int[] array) {
        int numLength = getMaxNumLength(array);
        int mod = 10;
        int dev = 1;
        for (int i = 0; i < numLength; i++, dev *= 10, mod *= 10) {
            int[][] counter = new int[mod][0];
            for (int value : array) {
                int bucket = (value % mod) / dev;
                counter[bucket] = arrayAppend(counter[bucket], value);
            }
            int tempIndex = 0;
            for (int[] ints : counter) {
                for (int anInt : ints) {
                    array[tempIndex++] = anInt;
                }
            }
        }
        return array;
    }

    private int getMaxNumLength(int[] array) {
        int maxValue = array[0];
        for (int i : array) {
            maxValue = Math.max(maxValue, i);
        }
        return String.valueOf(maxValue).length();
    }

    private int[] arrayAppend(int[] arr, int value) {
        arr = Arrays.copyOf(arr, arr.length + 1);
        arr[arr.length - 1] = value;
        return arr;
    }
}

败者树

概念

外部排序,就是对外存中的记录进行排序。其主要作用就是将内存作为工作空间来辅助外村数据的排序。

外部排序最常用的算法是归并排序,之所以归并排序常用,是因为它不需要将全部记录读入内存,即可完成排序。因此,可以解决由于内存空间不足导致的无法对大规模记录排序的问题。

假设我们需要对外存中一组大规模的无需记录进行排序,则可以按照下面的步骤进行。

置换-选择排序

假设我们有下面一组再外存中的记录\(\{15,19,04,83,12,27,11,25,16,34,26,07,10,90,06\}\),内存缓冲区可以容纳四个记录,我们需要对其进行生成初始归并段,下面的Gif展示了如何操作。

image

通过置换选择排序,我们得到了\(m\)棵长短不一的树,然后我们需要对这些树进行归并排序,由于内存大小的限制,我们最大只能选择\(K\)路归并排序,但是不同的归并顺序会带来不同的\(IO\)次数,此时我们要选择一种最小\(IO\)次数的归并方法,所以就有了下面的最佳归并树。

最佳归并树

假设,我们由选择-置换排序得到了9个初始归并段,其长度(记录数量)依次为\(\{9,30,12,18,3,17,2,6,24\}\),请做出三路归并树,并计算\(IO\)次数。

由此我们可以使用哈夫曼树的构建方法去构建最佳归并树

image

\(IO次数 = 2\times(2\times3+3\times3+6\times3+9\times2+12\times2+30\times1+17\times2+18\times2+24\times2)=446\)
此处\(2\times\)的原因的是因\(IO\)是两次,读入一次,写出一次。

败者树

此前,经过置换-选择排序我们得到了初始归并段,通过最佳归并树我们得到了如何选择不同的段进行归并,才能使\(IO\)次数最小。

image

posted @ 2017-03-09 10:30  X-POWER  阅读(214)  评论(0编辑  收藏  举报