【排序算法】插入、选择、堆排、快排、归并、计数

  • 一、插入排序 insertSort
    • 1、实现
    • 2、性能分析
    • 3、折半插入排序(了解)
  • 二、希尔排序 ShellSort
    • 1、原理
    • 2、实现
    • 3、性能分析
  • 三、选择排序 selectSort
    • 1、原理
    • 2、实现
    • 3、性能分析
    • 4、双向选择排序(了解)
  • 四、堆排序 headSort
    • 1、原理
    • 2、实现
    • 3、性能分析
  • 五、冒泡排序 bubbleSort
    • 1、实现
    • 2、实现
    • 3、性能分析
  • 六、快速排序(重要)quickSort
    • 1、原理
      • 1.1、Hoare 法:
      • 1.2、前后遍历法:
      • 1.3、挖坑法实现(未优化)
    • 2、性能分析
    • 3、原理-基准值的选择
      • ==3.1、优化代码:==
    • 4、非递归
  • 七、归并排序(重要)mergeSort
    • 1、原理
    • 2、实现
    • 3、性能分析
    • 4、非递归
    • 5、海量数据的排序问题
  • 八、 排序总结
  • 九、其他非基于比较的排序(了解)
    • 1、基数排序
    • 2、桶排序
    • 3、计数排序 countingSort
      • 3.1、原理
      • 3.2、实现
      • 3.3、性能分析

概念

  • 排序
    排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
    平时的上下文中,如果提到排序,通常指的是排升序(非降序)。
    通常意义上的排序,都是指的原地排序(in place sort) 。

  • 稳定性(重要)
    两个相等的数据,如果经过排序后,排序算法能保证其相对位置不发生变化,则我们称该算法是具备稳定性的排序算法。
    一个稳定的排序,可以实现为不稳定的排序
    但是一个本身就不稳定的排序,是不可以变成稳定的排序

在这里插入图片描述

七大基于比较的排序-总览
【排序算法】插入、选择、堆排、快排、归并、计数_第1张图片


一、插入排序 insertSort

1、实现

整个区间被分为
有序区间 [0, i),无序区间 [i, array.length)
每次选择无序区间的第一个元素,在有序区间内选择合适的位置插入

import java.util.Arrays;

public class TestDemo {

    public static void insertSort(int[] array) {
        for (int i = 1; i < array.length; i++) { // 假定第一个有序 从第二个开始
            int tmp = array[i];
            int j = i - 1;
            for (; j >= 0; j--) {
                if(array[j] > tmp) {
                // if(array[j] >= tmp) {  加等号 不稳定
                    array[j + 1] = array[j];
                } else {
                    // array[j + 1] = tmp;  只要j回退的时候,遇到了 比tmp小的元素就结束这次的比较
                    break;
                }
            }
            //j回退到了 小于0 的地方
            array[j + 1] = tmp;
        }
    }
    
    public static void main(String[] args) {
        int[] array = {3, 12, 5, 17, 2, 8};
        insertSort(array);
        System.out.println(Arrays.toString(array)); // [2, 3, 5, 8, 12, 17]
        System.out.println(123);
    }
}

【排序算法】插入、选择、堆排、快排、归并、计数_第2张图片


2、性能分析

直接插入排序:数据越有序,越快
经常用在,数据量不多 ,且趋近于有序

  • 稳定性:稳定的
时间复杂度 空间复杂度
最好 平均 最坏
O(n) O(n^2) O(n^2) O(1)
数据有序 数据逆序

3、折半插入排序(了解)

在有序区间选择数据应该插入的位置时,因为区间的有序性,可以利用折半查找的思想

	public static void bsInsertSort(int[] array) {
        for (int i = 1; i < array.length; i++) {
            int v = array[i];
            int left = 0;
            int right = i;
            // [left, right)
            // 需要考虑稳定性
            while (left < right) {
                int m = (left + right) / 2;
                if (v >= array[m]) {
                    left = m + 1;
                } else {
                    right = m;
                }
            }
            // 搬移
            for (int j = i; j > left; j--) {
                array[j] = array[j - 1];
            }
            array[left] = v;
        }
    }

二、希尔排序 ShellSort

1、原理

思考: 假设现在有10000个数据如果对这组数据进行排序,使用插入排序:
10000个数据* 10000 = 1 0000 0000 -》1个亿

如果分成100组,每组使用插入排序:
100组100 100 =100 0000
100组
100
100=100 0000
如果采用分组的思想,我们会发现时间复杂度会有一个很大的改变。也就是希尔排序:

希尔排序法 又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。 然后,取重复上述分组和排序的工作。当到达=1时,所有记录在统一组内排好序。

  1. 希尔排序是对直接插入排序的优化
  2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。

分组
【排序算法】插入、选择、堆排、快排、归并、计数_第3张图片

【排序算法】插入、选择、堆排、快排、归并、计数_第4张图片


2、实现

public class TestDemo {
	/**
     * @param array 待排序的序列
     * @param gap 组数
     */
    public static void sell(int[] array, int gap) {
        for (int i = gap; i < array.length; i++) {
            int tmp = array[i];
            int j = i - gap;
            for (; j >= 0; j -= gap) {
                if(array[j] > tmp) {
                    array[j + gap] = array[j];
                } else {
                    break;
                }
            }
            array[j + gap] = tmp;
        }
    }

	/**
     * 希尔排序
     * @param array
     */
    public static void sellSort(int[] array) {
        int gap = array.length;
        while(gap > 1) {
            sell(array, gap);
            gap /= 2;
        }
        sell(array, 1); // 保证最后是 1 组
    }
}

【排序算法】插入、选择、堆排、快排、归并、计数_第5张图片


3、性能分析

时间复杂度:和增量有关

时间复杂度 空间复杂度
O(n^1.3 到n^1.5) O(1)
稳定性:不稳定
看在比较的过程当中是否发生了跳跃式的交换如果发生了跳跃式的交换
那么就是不稳定的排序

三、选择排序 selectSort

1、原理

j 每次选出最大(或最小)的一个元素,存放在无序区间的最后(或最前),i有序,然后i++,直到全部待排序的数据元素排完
优化:每次获取最小值下标,用minIndex保存,直接交换

【排序算法】插入、选择、堆排、快排、归并、计数_第6张图片


2、实现

public class TestDemo {
	/**
     * 选择排序
     * @param array 待排序的序列
     */
    public static void selectSort(int[] array) {
        for (int i = 0; i < array.length; i++) {
            int minIndex = i; // 最小值下标
            for (int j = i + 1; j < array.length; j++) {
                if(array[j] < array[minIndex]) {
                    minIndex = j;
                }
            }
            int tmp = array[i];
            array[i] = array[minIndex];
            array[minIndex] = tmp;
        }
    }

    public static void selectSort1(int[] array) {
        for (int i = 0; i < array.length; i++) {
            for (int j = i + 1; j < array.length; j++) {
                if(array[j] < array[i]) {
                    int tmp = array[i];
                    array[i] = array[j];
                    array[j] = tmp;
                }
            }
        }
    }
}

3、性能分析

时间复杂度 空间复杂度
O(n^2) O(1)
数据不敏感-与数据有序无序无关 数据不敏感

稳定性:不稳定

int[] a = { 9, 2, 5a, 7, 4, 3, 6, 5b };
// 交换中该情况无法识别,保证 5a 还在 5b 前边

测试:
直接插入排序有序的情况是O(n) 原因是遇到小的数据直接break了。j 并不会再减减。
选择排序来说,有序无序都是O(N^2) 原因就是时间复杂度 != 运行时间。现在之所以有序的情况下,选择排序的时间更快,是因为,交换的少了。j 本质上还是在++;

public class TestDemo {
	public static void test1(int capacity) {
        int[] array = new int[capacity];
        for (int i = 0; i < array.length; i++) {
            array[i] = i;
        }
        long start = System.currentTimeMillis(); // 开始时间
        //insertSort(array); // 2
        //sellSort(array); // 4
        selectSort(array); // 717
        long end = System.currentTimeMillis(); // 结束时间
        System.out.println(end - start);
    }

    public static void test2(int capacity) {
        int[] array = new int[capacity];
        Random random = new Random();
        for (int i = 0; i < array.length; i++) {
            array[i] = random.nextInt(capacity);
        }
        long start = System.currentTimeMillis(); // 开始时间
        // insertSort(array); // 1806
        //sellSort(array); // 12
        selectSort(array); // 2362
        long end = System.currentTimeMillis(); // 结束时间
        System.out.println(end - start);
    }

    public static void main(String[] args) {
        test1(10_0000);
        test2(10_0000);
    }
}

4、双向选择排序(了解)

每一次从无序区间选出最小 + 最大的元素,存放在无序区间的最前和最后,直到全部待排序的数据元素排完

public class Test {
	public static void selectSortOP(int[] array) {
        int low = 0;
        int high = array.length - 1;
        // [low, high] 表示整个无序区间
        // 无序区间内只有一个数也可以停止排序了
        while (low <= high) {
            int min = low;
            int max = low;
            for (int i = low + 1; i <= max; i++) {
                if (array[i] < array[min]) {
                    min = i;
                }
                if (array[i] > array[max]) {
                    max = i;
                }
            }
            swap(array, min, low);
            if (max == low) {
                max = min;
            }
            swap(array, max, high);
        }
    }

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

四、堆排序 headSort

1、原理

优先级队列(堆)、java 对象的比较

基本原理也是选择排序,只是不在使用遍历的方式查找无序区间的最大的数,而是通过堆来选择无序区间的最大的数。
注意: 排升序要建大堆;排降序要建小堆


2、实现

【排序算法】插入、选择、堆排、快排、归并、计数_第7张图片

public class TestDemo {
	/**
     * 堆排序
     * @param array
     */
    public static void headSort(int[] array) {
        // 1、建堆 O(N)
        creatHeap(array);
        int end = array.length - 1;
        while (end > 0) {
            //  2、交换调整 O(NlogN)
            swap(array, 0, end);
            shiftDown(array, 0, end);
            end--;
        }
    }

    public static void creatHeap(int[] array) {
        for (int parent = (array.length-1-1)/2; parent >= 0; parent--) {
            shiftDown(array, parent, array.length);
        }
    }

    public static void shiftDown(int[] array, int parent, int len) {
        int child = 2 * parent + 1; // 左孩子下标
        while(child < len) {
            if(child + 1 < len && array[child] < array[child + 1]) {
                child++; // 指向最大的孩子下标
            }
            if(array[child] > array[parent]) {
                swap(array, child, parent);
                parent = child;
                child = 2 * parent + 1;
            } else {
                break;
            }
        }
    }

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

3、性能分析

稳定性:不稳定

时间复杂度 空间复杂度
O(N*logN) O(1)
数据不敏感 数据不敏感

五、冒泡排序 bubbleSort

1、实现

在无序区间,通过相邻数的比较,将最大的数冒泡到无序区间的最后,持续这个过程,直到数组整体有序
【排序算法】插入、选择、堆排、快排、归并、计数_第8张图片


2、实现

public class TestDemo {
	/**
     * 冒泡排序
     * @param array
     */
    public static void bubbleSort(int[] array) {
        for (int i = 0; i < array.length - 1; i++) {
            for (int j = 0; j < array.length - 1 - i; j++) {
                if(array[j] > array[j + 1]) {
                    int tmp = array[j];
                    array[j] = array[j + 1];
                    array[j + 1] = tmp;
                }
            }
        }
    }

    public static void bubbleSort1(int[] array) {
        for (int i = 0; i < array.length - 1; i++) {
            boolean flg = true; // 假设有序
            for (int j = 0; j < array.length - 1 - i; j++) {
                if(array[j] > array[j + 1]) {
                    int tmp = array[j];
                    array[j] = array[j + 1];
                    array[j + 1] = tmp;
                    flg = false;
                }
            }
            if(flg) {
                break;
            }
        }
    }
}

3、性能分析

稳定性:稳定的

时间复杂度 空间复杂度
(优化的) 未优化都是
O(n) O(n^2) O(n^2) O(1)

六、快速排序(重要)quickSort

1、原理

  1. 从待排序区间选择一个数,作为基准值(pivot)
  2. Partition: 遍历整个待排序区间,将比基准值小的(可以包含相等的)放到基准值的左边,将比基准值大的(可以包含相等的)放到基准值的右边
  3. 采用分治思想,对左右两个小区间按照同样的方式处理,直到小区间的长度 == 1,代表已经有序,或者小区间的长度 == 0,代表没有数据。

【排序算法】插入、选择、堆排、快排、归并、计数_第9张图片


1.1、Hoare 法:

	private static int partition(int[] array, int left, int right) {
        int i = left;
        int j = right;
        int pivot = array[left];
        while (i < j) {
            while (i < j && array[j] >= pivot) {
                j--;
            }
            while (i < j && array[i] <= pivot) {
                i++;
            }
            swap(array, i, j);
        }
        swap(array, i, left);
        return i;
    }

1.2、前后遍历法:

	private static int partition(int[] array, int left, int right) {
        int d = left + 1;
        int pivot = array[left];
        for (int i = left + 1; i <= right; i++) {
            if (array[i] < pivot) {
                swap(array, i, d);
                d++;
            }
        }
        swap(array, d, left);
        return d;
    }

1.3、挖坑法实现(未优化)

挖坑法基本思路和Hoare 法一致,只是不再进行交换,而是进行赋值(填坑+挖坑)

public class TestDemo {
	/**
     * 快速排序
     * @param array
     */
    public static void quickSort(int[] array) {
        quick(array, 0, array.length - 1);
    }

    // 递归基准左右区间
    public static void quick(int[] array, int left, int right) {
        if(left >= right) {
            return;
        }
        int pivot = partition(array, left, right); // 基准
        quick(array, left, pivot - 1);
        quick(array, pivot + 1, right);
    }

    // 找基准
    public static int partition(int[] array, int start, int end) {
        int tmp = array[start];
        while(start < end) {
            while(start < end && array[end] >= tmp) { // 不加等号将死循环
                end--; // 从后往前找一个 < tmp 的值
            }
            array[start] = array[end];
            while(start < end && array[start] <= tmp) {
                start++; // 从前往后找一个 > tmp 的值
            }
            array[end] = array[start];
        }
        array[start] = tmp; // start 和 end 相遇的位置就是基准
        return start;
    }
}

注意:
不加等号将死循环地交换
【排序算法】插入、选择、堆排、快排、归并、计数_第10张图片


问题:
测试一百万数据 -> 栈溢出了
所以我们需要优化

【排序算法】插入、选择、堆排、快排、归并、计数_第11张图片


2、性能分析

稳定性:不稳定

时间复杂度 空间复杂度
最好(每次可以均匀地分割待排序 序列) 最坏(数据有序 或逆序) 最好 最坏(单分支的一棵树)
O(K*N*logn) O(n^2) O(logn) O(n)

3、原理-基准值的选择

  1. 选择边上(左或者右)
  2. 随机选取基准法:有可能每次随机的数据,作为基准的时候,也会出现单分支的情况。
  3. 几数取中(例如三数取中):array[left], array[mid], array[right] 大小是中间的为基准值
  4. partition 过程中把和基准值相等的数也选择出来
  5. 分治处理左右两个小区间,直到小区间数目小于一个阈值,使用插入排序
    利用直接插入排序,越有序越快的规则,进行优化
    上面写的直插方法只有一个参数,我们在写一个加入参数:

3.1、优化代码:

public class TestDemo {
	/**
     * 快速排序
     * @param array
     */
    public static void quickSort(int[] array) {
        quick(array, 0, array.length - 1);
    }

    // 递归基准左右区间
    public static void quick(int[] array, int left, int right) {
        if(left >= right) {
            return;
        }

        // 2、如果区间内的数据 在排序的过程中 小于某个阈值 使用直接插入排序
        if(right - left + 1 <= 1500) {
            insertSort2(array, left, right);
            return;
        }

        // 1、三数取中法  找基准之前,我们找到中间大小的值 交换
        int midValIndex = findMidValIndex(array, left, right);
        swap(array, midValIndex, left);

        int pivot = partition(array, left, right); // 基准
        quick(array, left, pivot - 1);
        quick(array, pivot + 1, right);
    }

    // (2.) 直接插入排序
    public static void insertSort2(int[] array, int start, int end) {
        for (int i = 1; i < end; i++) { // 假定第一个有序 从第二个开始
            int tmp = array[i];
            int j = i - 1;
            for (; j >= start; j--) {
                if(array[j] > tmp) {
                    // if(array[j] >= tmp) {  加等号 不稳定
                    array[j + 1] = array[j];
                } else {
                    // array[j + 1] = tmp;  只要j回退的时候,遇到了 比tmp小的元素就结束这次的比较
                    break;
                }
            }
            //j回退到了 小于0 的地方
            array[j + 1] = tmp;
        }
    }

    // (1.) 找三数中间值下标
    public static int findMidValIndex(int[] array, int start, int end) {
        int mid  = start + ((end - start) >>> 1);
        if(array[start] < array[end]) {
            if(array[mid] < array[start]) {
                return start;
            } else if (array[mid] > array[end]) {
                return end;
            } else {
                return mid;
            }
        } else {
            if(array[mid] > array[start]) {
                return start;
            } else if (array[mid] < array[end]) {
                return end;
            } else {
                return mid;
            }
        }
    }

    // 找基准
    public static int partition(int[] array, int start, int end) {
        int tmp = array[start];
        while(start < end) {
            while(start < end && array[end] >= tmp) {
                end--; // 从后往前找一个 < tmp 的值
            }
            array[start] = array[end];
            while(start < end && array[start] <= tmp) {
                start++; // 从前往后找一个 > tmp 的值
            }
            array[end] = array[start];
        }
        array[start] = tmp; // start 和 end 相遇的位置就是基准
        return start;
    }

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

4、非递归

先找一次基准,将left 基准减一 基准加一 right 一次放入栈中
拿出的时候,根据放入的顺序,先拿出的是 right

前提:
Pivo 左边有2个元素pivot > left+1
右边有2个元素:pivot < right-1

【排序算法】插入、选择、堆排、快排、归并、计数_第12张图片

代码:

public class TestDemo {
	/**
     * 非递归 快速排序
     * @param array
     */
    public static void quickSort(int[] array) {
        Stack<Integer> stack = new Stack<>();
        int left = 0;
        int right = array.length - 1;
        int pivot = partition(array, left, right);
        if(pivot > left + 1) {
            // 左边有两个元素
            stack.push(left);
            stack.push(pivot - 1);
        }
        if(pivot < right - 1) {
            // 右边有两个元素
            stack.push(pivot + 1);
            stack.push(right);
        }

        while(!stack.isEmpty()) {
            right = stack.pop(); // 先右
            left = stack.pop();

            pivot = partition(array, left, right);

            if(pivot > left + 1) {
                stack.push(left);
                stack.push(pivot - 1);
            }
            if(pivot < right - 1) {
                stack.push(pivot + 1);
                stack.push(right);
            }
        }
    }

	// 找基准
    public static int partition(int[] array, int start, int end) {
        int tmp = array[start];
        while(start < end) {
            while(start < end && array[end] >= tmp) {
                end--; // 从后往前找一个 < tmp 的值
            }
            array[start] = array[end];
            while(start < end && array[start] <= tmp) {
                start++; // 从前往后找一个 > tmp 的值
            }
            array[end] = array[start];
        }
        array[start] = tmp; // start 和 end 相遇的位置就是基准
        return start;
    }
}

七、归并排序(重要)mergeSort

1、原理

归并排序(MERGE-SORT) 是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并

分解与合并:
【排序算法】插入、选择、堆排、快排、归并、计数_第13张图片
两有序数组合并:
要学习归并排序,我们要知道如何合并两个有序数组,
思路:array1 记s1遍历,array2 记s2,遍历比较哪个数组元素小,就把哪个数组元素放到新数组中(新数组大小是两数组长度之和),用i记录下一个待放的位置,此数组往后走,在比较两数组,一个数组走完,将另一个数组剩下的元素放到新数组中,
【排序算法】插入、选择、堆排、快排、归并、计数_第14张图片
两有序数组合并代码如下:

public class TestDemo {
	/**
     * 有序数组合并
     * @param array1 有序
     * @param array2 有序
     * @return
     */
    public static int[] mergeArray(int[] array1, int[] array2) {
        int[] merge = new int[array1.length + array2.length];
        int i = 0;
        int s1 = 0;
        int s2 = 0;
        while (s1 < array1.length && s2 < array2.length) {
            if (array1[s1] <= array2[s2]) {
                merge[i++] = array1[s1++];
            } else {
                merge[i++] = array2[s2++];
            }
        }
        while (s1 < array1.length) {
            merge[i++] = array1[s1++];
        }
        while (s2 < array2.length) {
            merge[i++] = array2[s2++];
        }
        return merge;
    }
}

2、实现

public class TestDemo {
	/**
     * 归并排序
     * @param array
     */
    public static void mergeSort(int[] array) {
        mergeSortInternal(array, 0, array.length - 1);
    }

    // 分解
    public static void mergeSortInternal(int[] array, int low, int high) {
        if(low >= high) { // 相遇分解完成
            return;
        }
        int mid = low + ((high - low) >>> 1);
        // 分解左右
        mergeSortInternal(array, low, mid);
        mergeSortInternal(array, mid + 1, high);
        // 合并
        merge(array, low, mid, high);
    }

    // 合并
    private static void merge(int[] array, int low, int mid, int high) {
        int[] tmp = new int[high - low + 1];
        int k = 0;
        int s1 = low;
        int e1 = mid;
        int s2 = mid + 1;
        int e2 = high;
        while (s1 <= e1 && s2 <= e2) {
            if(array[s1] <= array[s2]) { // 去等号就是不稳定
                tmp[k++] = array[s1++];
            }else {
                tmp[k++] = array[s2++];
            }
        }
        while (s1 <= e1) {
            tmp[k++] = array[s1++];
        }
        while (s2 <= e2) {
            tmp[k++] = array[s2++];
        }
        // 拷贝tmp数组的元素 放入原来的数组array当中
        for (int i = 0; i < k; i++) {
            array[i + low] = tmp[i]; // i + low 可以对应后半段位置
        }
    }
}

3、性能分析

稳定性:稳定的

时间复杂度 空间复杂度
O(N*logN) O(n)
数据不敏感 数据不敏感

目前所学的排序,只有三个是稳定的:
冒泡排序,插入排序,归并排序


4、非递归

【排序算法】插入、选择、堆排、快排、归并、计数_第15张图片

代码如下:

public class TestDemo {
	/**
     * 非递归 归并排序
     * @param array
     */
    public static void mergeSort(int[] array) {
        int nums = 1; // 每组的数据个数 从每组1个开始

        while(nums < array.length) { // 组数直到len-1停止
            // 数组每次都要进行遍历
            for (int i = 0; i < array.length; i += nums * 2) {
                int left = i;
                int mid = left + nums - 1;
                if(mid > array.length) { // mid 可能超出范围
                    mid = array.length - 1;
                }
                int right = mid + nums;
                if(right > array.length) { // right 也有可能超出范围
                    right = array.length - 1;
                }
                // 确定下标后 合并
                merge(array, left, mid, right);
            }
            nums *= 2;
        }
    }

	// 合并
    private static void merge(int[] array, int low, int mid, int high) {
        int[] tmp = new int[high - low + 1];
        int k = 0;
        int s1 = low;
        int e1 = mid;
        int s2 = mid + 1;
        int e2 = high;
        while (s1 <= e1 && s2 <= e2) {
            if(array[s1] <= array[s2]) { // 去等号就是不稳定
                tmp[k++] = array[s1++];
            }else {
                tmp[k++] = array[s2++];
            }
        }
        while (s1 <= e1) {
            tmp[k++] = array[s1++];
        }
        while (s2 <= e2) {
            tmp[k++] = array[s2++];
        }
        // 拷贝tmp数组的元素 放入原来的数组array当中
        for (int i = 0; i < k; i++) {
            array[i + low] = tmp[i]; // i + low 可以对应后半段位置
        }
    }
}

5、海量数据的排序问题

外部排序:排序过程需要在磁盘等外部存储进行的排序

前提:内存只有 1G,需要排序的数据有 100G
因为内存中因为无法把所有数据全部放下,所以需要外部排序,而归并排序是最常用的外部排序

  1. 先把文件切分成 200 份,每个 512 M
  2. 分别对 512 M 排序,因为内存已经可以放的下,所以任意排序方式都可以
  3. 进行 200 路归并,同时对 200 份有序文件做归并过程,最终结果就有序了

八、 排序总结

【排序算法】插入、选择、堆排、快排、归并、计数_第16张图片


九、其他非基于比较的排序(了解)

1、基数排序

1.10 基数排序 | 菜鸟教程

10进制的数从小到大排序,取十个桶,
以个位数放入,依次取出,此时个位数已有序
在以十位数排序,以此类推,次数为最大数的位数
【排序算法】插入、选择、堆排、快排、归并、计数_第17张图片


2、桶排序

桶排序 (Bucket sort) 或所谓的箱排序,是一个排序算法,工作的原理是将数组分到有限数量的桶子里每个桶子再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)。桶排序是鸽巢排序的一种归纳结果。当要被排序的数组内的数值是均匀分配的时候,桶排序使用线性时间O(n)。但桶排序并不是 比较排序,他不受到 O(n log n) 下限的影响。

【排序算法】插入、选择、堆排、快排、归并、计数_第18张图片


3、计数排序 countingSort

3.1、原理

排序方法: 申请一个计数数组count[],遍历待排序的数组,对应的下标加一,
遍历完成后,遍历count[],依次获取每一个下标元素不为0的,
如 0下标为2,就是 0 0 ,3下标为1,就是3,遍历完成就是排好序的数组了

适合用: 有n个数,取值范围是 0~n,写出一个排序算法,要求时间复杂度和空间复杂度都是O(n)的

【排序算法】插入、选择、堆排、快排、归并、计数_第19张图片

*问题:
如何确定计数数组大小?
这个计数排序,适合用在0-99范围

解决方法:
1、求数组的最大值maxVal,最小是minVal
maxVal - minVal + 1 = 数组的长度
2、count [array[i]-minVal]


3.2、实现

public class TestDemo {
	/**
     * 计数排序
     * @param array
     */
    public static void countingSort(int[] array) {
        // 找数组最大值和最小值
        int maxVal = array[0];
        int minVal = array[0];
        for (int i = 1; i < array.length; i++) {
            if(array[i] > maxVal) {
                maxVal = array[i];
            }
            if(array[i] < minVal) {
                minVal = array[i];
            }
        }
        // 统计数组每个数据出现的次数
        int[] count = new int[maxVal - minVal + 1];
        for (int i = 0; i < array.length; i++) {
            // 为了空间的合理使用 这里需要index-minVal  防止923-90
            count[array[i] - minVal]++;
        }
        // 因为默认是0 所以不为0的拿出来 把数据写回数组
        int indexArray = 0;
        for (int i = 0; i < count.length; i++) {
            while(count[i] != 0) {
                // 注意 对应回去要加 minVal
                array[indexArray++] = i + minVal; // 注意 下标要向后移动
                count[i]--; // 拷贝一个 次数就少一个
            }
        }
    }
}

3.3、性能分析

稳定性:当前代码不稳定,但本质是稳定的

时间复杂度 空间复杂度
O(n) O(m) m: 和数组范围有关

你可能感兴趣的:(数据结构,排序算法,算法,数据结构,java,二叉树)