[ Java算法 ] - 排序(Sort Algorithm)

常用排序算法总览

插入排序法直接插入排序希尔排序
交换排序冒泡排序快速排序
选择排序直接选择排序堆排序
归并排序基数排序

cite: https://github.com/hustcc/JS-Sorting-Algorithm


时间复杂度基础知识

注意:通常时间复杂度的有小到大排序依次为: O ( 1 ) < O ( log ⁡ 2 n ) < O ( n ) < O ( n log ⁡ 2 n ) < O ( n 2 ) < O ( n 3 ) < O ( n k ) < O ( 2 n ) < ( n ! ) {O\left( 1 \right) < O\left( {{{\log }_2}n} \right) < O\left( n \right) < O\left( {n{{\log }_2}n} \right) < O\left( {{n^2}} \right) < O\left( {{n^3}} \right) < O\left( {{n^k}} \right) < O\left( {{2^n}} \right) < \left( {n!} \right)} O(1)<O(log2n)<O(n)<O(nlog2n)<O(n2)<O(n3)<O(nk)<O(2n)<(n!)

  • O ( 1 ) {O\left( 1 \right) } O(1):常数阶,无论代码执行多少行,只要没有循环体就是常数阶。消耗的时间并不随着变量的增长而增长。
  • O ( log ⁡ 2 n ) {O\left( {{{\log }_2}n} \right)} O(log2n):对数阶,循环条件中,判断变量以二倍变化逼近结束条件。
  • O ( n ) {O\left( n \right)} O(n):线性阶,只循环n次,如一个for循环。
  • O ( n log ⁡ 2 n ) {O\left( {n{{\log }_2}n} \right) } O(nlog2n):线性对数阶,将对数阶循环n次。
  • O ( n 2 ) {O\left( {{n^2}} \right)} O(n2):平方阶,两个线性阶嵌套。

通常优先讨论时间复杂度,使用空间换取时间。


交换方法

  • 数组类的排序通常把指定的位置交换的方法单独定义,这样可以提高代码的阅读性
	public static void swap(int[] arr, int i, int j) {
		int tmp = arr[i];
		arr[i] = arr[j];
		arr[j] = tmp;
	}
  • 当 i 严格保证与 j 不相同时,可以使用异或运算。
	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];
	}


1. 插入排序

1.1 直接插入排序(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);
			}
		}
	}	

1.2 希尔排序(Shell Sort)

1.2.1 交换法(速度较慢,仅供思想学习)

    public static void shellSort(int[] nums) {
        for (int gap = nums.length / 2; gap > 0; gap /= 2) {
            for (int i = gap; i < nums.length; i++) {
                int j = i;
                int tmp = nums[j];
                if (nums[j] < nums[j - gap]) {
                    while (j - gap >= 0 && tmp < nums[j - gap]) {
                        nums[j] = nums[j - gap];
                        j -= gap;
                    }
                    nums[j] = tmp;
                }
            }
        }
    }	

1.2.2 移动法(速度更快)

	public static void shellSortMoving(int[] nums) {
        for (int gap = nums.length / 2; gap > 0; gap /= 2) {
            for (int i = gap; i < nums.length; i++) {
                int j = i;
                int tmp = nums[j];
                while (j - gap >= 0 && tmp < nums[j - gap]) {
                    nums[j] = nums[j - gap];
                    j -= gap;
                }
                nums[j] = tmp;
            }
        }
    }


2. 交换排序

2.1 冒泡排序(Bubble Sort)

cite: https://github.com/hustcc/JS-Sorting-Algorithm

2.1.1 基础版本

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

2.1.2 优化版本1

  • 冒泡可能存在经过n轮排序后已经完成排序。这时需要在外层循环设置一个标记位,如果内层循环没有发生交换,则说明此时序列已经有序。
	public static void bubbleSort(int[] arr){
	    if (arr == null || arr.length < 2) {
	    	return;
	    }
	    for (int i = 0; i < arr.length - 1; i++) {
	        //是否已经有序的标记,默认有序
	        boolean isSorted = true;
	        for (int j = 0; j < arr.length - 1 -i; j++) {
	            if (arr[j] > arr[j + 1]){
	                swap(arr, j, j+1);
	                //发生元素交换,序列仍是无序状态
	                isSorted = false;
	            }
	        }
	        if (isSorted){
	            break;
	        }
	    }
	}

2.1.3 优化版本2

	public static void bubbleSort(int[] arr){
	    if (arr == null || arr.length < 2) {
	    	return;
	    }
	    //记录记录下来最后一次元素交换的位置
	    int lastExchangeIndex = 0;
	    //无序数列的边界,每次比较只需要比到这里为止
	    int sortBorder = arr.length-1;
	    for (int i = 0; i < arr.length - 1; i++) {
	        //是否已经有序的标记,默认有序
	        boolean isSorted = true;
	        for (int j = 0; j < sortBorder; j++) {
	            int tmp = 0;
	            //升序排序>,降序排序<
	            if (arr[j] > arr[j + 1]){
	                swap(arr, j, j+1);
	                //发生元素交换,序列仍是无序状态
	                isSorted = false;
	                //更新为最后一次交换元素的位置
	                lastExchangeIndex = j;
	            }
	        }
	        //更新无序数列的边界
	        sortBorder = lastExchangeIndex;
	        if (isSorted){
	            break;
	        }
	    }
	}

2.2 快速排序(Quick Sort)

cite: https://github.com/hustcc/JS-Sorting-Algorithm

2.2.1基础版本

	public void quickSort(int[] nums, int low, int hight) {
        if (low < hight) {
            int pLow = low;
            int pHight = hight;
            int tmp = nums[pLow];
            while (pLow < pHight) {
                while (pLow < pHight && nums[pHight] > tmp) pHight--;
                if (pLow < pHight) nums[pLow++] = nums[pHight];
                while (pLow < pHight && nums[pLow] < tmp) pLow++;
                if (pLow < pHight) nums[pHight--] = nums[pLow];
            }
            nums[pLow] = tmp;
            quickSort(nums, low, pLow - 1);
            quickSort(nums, pLow + 1, hight);
        }
    }	


3.选择排序

https://github.com/hustcc/JS-Sorting-Algorithm

3.1 直接选择排序(Section Sort)

	public static void selectionSort(int[] arr) {
			// 排除数组为空和只用一个数的情况
			if (arr == null || arr.length < 2) {
				return;
			}
			// 0 ~ N-1  找到最小值放到 0 位置
			// 1 ~ N-1  找到最小值放到 1 位置
			// 2 ~ N-1  找到最小值放到 2 位置
			for (int i = 0; i < arr.length - 1; i++) {
				int minIndex = i;
				for (int j = i + 1; j < arr.length; j++) { // i ~ N-1 上找最小值的下标 
					minIndex = arr[j] < arr[minIndex] ? j : minIndex;
				}
				swap(arr, i, minIndex);
			}
	}

3.2 堆排序(Heap Sort)

https://github.com/hustcc/JS-Sorting-Algorithm

	// 待补充


4.归并排序(Merge Sort)

  • 归并排序(Merge Sort)是利用经典的分治(Divide-and-Conquer)策略(分治法将问题分(Divide)成一些小的问题然后递归求解,而治(Conquer)的阶段则将分的阶段得到的各答案合并在一起,即分而治之)。
    cite: https://github.com/hustcc/JS-Sorting-Algorithm
	/**
     * 
     * @param arr   需要排序的数组
     * @param left  数组最左边的索引
     * @param right 数组最右边的索引
     * @param tmp   做中转的临时数组,长度与需要排序的数组等长
     */
    public static void mergeSort(int[] arr, int left, int right, int[] tmp) {
        if (left < right) {
            int mid = (left + right) / 2;   // 获取中间 索引
            // 向左递归进行分解
            mergeSort(arr, left, mid, tmp);
            // 向右递归进行分解
            mergeSort(arr, mid+1, right, tmp);
            merge(arr, left, mid, right, tmp);
        }
    }

    /**
     * @param arr   需要排序的数组
     * @param left  数组最左边的索引
     * @param mid   中数组中间的索引
     * @param right 数组最右边的索引
     * @param tmp   做中转的临时数组
     */
    public static void merge(int[] arr, int left, int mid, int right, int[] tmp) {
        // 初始化pLeftIndex,左边有序序列的初始索引
        int pLeftIndex = left;
        // 初始化pRightIndex,右边有序序列的初始索引
        int pRightIndex = mid + 1;
        // 指向tmp临时数组的当前索引
        int tmpIndex = 0;

        // 先报左右两边(有序)数据按照规则填充到tmp数组,直到左右两边的有序序列其中一边完毕为止
        while (pLeftIndex <= mid && pRightIndex <= right) {
            // 如果左边有序序列的当前元素小于等于右边有序序列的当前元素
            // 即将左边的当前元素拷贝到tmp数组,让后t和i自加
            if (arr[pLeftIndex] <= arr[pRightIndex]) {
                tmp[tmpIndex++] = arr[pLeftIndex++];
            } else {
                tmp[tmpIndex++] = arr[pRightIndex++];
            }
        }
        // 把有剩余数据的一边的数据依次全部填充到tmp数组
        while (pLeftIndex <= mid) {
            tmp[tmpIndex++] = arr[pLeftIndex++];
        }

        while (pRightIndex <= right) {
            tmp[tmpIndex++] = arr[pRightIndex++];
        }
        // 将tmp数组的元素拷贝到arr中
        tmpIndex=0;
        int tempLeft = left;
        while (tempLeft <= right) {
            arr[tempLeft++] = tmp[tmpIndex++];
        }
    }


5. 基数排序(Radix Sort)

  • 经典的使用空间换时间的算法
    cite: https://github.com/hustcc/JS-Sorting-Algorithm
	public static void radixSort(int[] arr) {
        // 得到数组中最大的位数
        int maxNum = arr[0];
        for (int i = 1; i < arr.length; i++) {
            if (arr[i] > maxNum) {
                maxNum = arr[i];
            }
        }
        // 得到最大数的位数
        int maxLength = (maxNum + "").length();
        // 定义一个二维数组表示10个桶
        int[][] bucket = new int[10][arr.length];
        // 为了记录每个桶实际存放多少个数据,定义一个一维数组记录各个桶每次放入的数据
        int[] bucketElementCounts = new int[10];
        // 按照最大位数循环
        for (int epoch = 0, n = 1; epoch < maxLength; epoch++, n *= 10) {
            // 对每个元素对应位进行排序
            for (int j = 0; j < arr.length; j++) {
                // 记录每个元素个位的值
                int digitOfElement = arr[j] / n % 10;
                // 放到对应的桶中
                bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
                bucketElementCounts[digitOfElement]++;
            }
            // 按照桶的顺序,依次取出放入原来数组
            int arrIndex = 0;
            for (int k = 0; k < bucketElementCounts.length; k++) {
                // 如果桶中有数据,则放入原始数组中
                if (bucketElementCounts[k] != 0) {
                    // 循环该桶第j个桶,放入
                    for (int l = 0; l < bucketElementCounts[k]; l++) {
                        arr[arrIndex++] = bucket[k][l];
                    }
                }
                // 置零
                bucketElementCounts[k] = 0;
            }
        }
    }


插图引用:
https://github.com/hustcc/JS-Sorting-Algorithm
https://gz101h.shinyapps.io/sortingAlgos/

posted @ 2022-03-08 19:39  E-CorE  阅读(71)  评论(0)    收藏  举报