算法-排序

冒泡排序

从数组起始位置 j 开始遍历数组,每次遍历比较 j 位置与 j+1 位置的值,将较大的数值往 j+1 位置移动(arr[j] > arr[j+1],则进行交换swap(arr,j)),数组长度为 n 。
j = 0,从 0 ~ n-1 位置依次比较并交换,此时 n-1 位置为最大值;
j = 0,从 0 ~ n-2 位置依次比较并交换,此时 n-2、n-1 位置已完成排序;
依次类推,进行排序…;
j = 0,从 0 ~ 1 位置依次比较并交换,此时整个数组完成排序。

package com.example.algorithm.sort;

import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * 冒泡排序,时间复杂度O(N^2),空间复杂度O(1),具备稳定性
 */
@Component
public class BubbleSort {

    /**
     * 从j位置开始,将j和j+1进行比较,大的数往后移动
     * 相邻数据相等时,不进行交换,可以保证稳定性
     *
     * @param arr
     * @author Alan
     * @date 2021/8/31 13:53
     */
    public static void bubbleSort(int[] arr) {
        //每次保证最后第n-1位数字最大,共需要执行的次数为n-1次
        for (int i = 0; i < arr.length; i++) {
            //遍历0~n-i-1位置的数字,比较j和j+1,较大的数往后移动
            for (int j = 0; j < arr.length - 1 - i; j++) {
                if (arr[j] > arr[j + 1]) {
                    swap(arr, j);
                }
            }
            System.out.println(Arrays.toString(arr));
        }
    }

    public static void swap(int[] arr, int j) {
        arr[j] = arr[j] ^ arr[j + 1];
        arr[j + 1] = arr[j] ^ arr[j + 1];
        arr[j] = arr[j] ^ arr[j + 1];
    }
}

选择排序

从数组起始位置 i 开始遍历数组,每次遍历将最小的值放到 i 位置,数组长度为 n 。
i = 0,将 i 位置的值与 1 ~ n-1 位置的值依次比较,将最小值与i位置交换,此时 i 位置的值为最小值;
i = i+1,将 i 位置的值与 2 ~ n-1 位置的值依次比较,将最小值与 i+1 位置交换,此时 i、i+1 位置已完成排序;
依次类推,进行排序…;
i = n-2,将 n-2 位置的值与 n-1 位置的值比较,将最小值与 n-2 位置交换,此时整个数组完成排序。

package com.example.algorithm.sort;

import java.util.Arrays;

/**
 * 选择排序,时间复杂度O(N^2),空间复杂度O(1),不具备稳定性
 */
public class SelectSort {

    /**
     * 从i位置开始,将最小的数放到i位置
     * 不具备稳定性
     *
     * @param arr
     * @author Alan
     * @date 2021/8/31 13:52
     */
    public static void selectSort(int[] arr) {
        //将最小值放到i位置,一共要操作n-1次
        for (int i = 0; i < arr.length; i++) {
            //从i-n-1的数据中记录最小数的位置,并存放到i位置
            int minIndex = i;
            for (int j = i + 1; j < arr.length; j++) {
                if (arr[minIndex] > arr[j]) {
                    minIndex = j;
                }
            }
            //最小值不是本身则进行交换
            if (minIndex != i) {
                swap(arr, i, minIndex);
            }
            System.out.println(Arrays.toString(arr));
        }
    }

    public static void swap(int[] arr, int i, int minIndex) {
        arr[i] = arr[i] ^ arr[minIndex];
        arr[minIndex] = arr[i] ^ arr[minIndex];
        arr[i] = arr[i] ^ arr[minIndex];
    }
}

插入排序

从数组起始位置 i 开始,将 i+1 位置的值依次与 0 ~ i 位置的值进行比较,较小的数往前移动,保证 0 ~ i 是有序的,数组长度为 n 。
i = 0,0 位置本身就是有序的;
i = 1,将 1 位置与 0 位置进行比较,较小是数往前移动,此时 0 ~ 1 的位置是有序的;
i = 2,将 2 位置与 i-1 ~ 0 位置进行依次比较,较小的数往前移动,此时 0 ~ i 的位置是有序的;
依次类推,进行排序…;
i = n-1,将 n-1 位置与 n-2 ~ 0 位置进行依次比较,较小的数往前移动,此时 0 ~ n-1 的位置是有序的。

package com.example.algorithm.sort;

import java.util.Arrays;

/**
 * 插入排序,时间复杂度O(N^2),空间复杂度O(1),具备稳定性
 */
public class InsertSort {

    /**
     * 往前遍历时,相邻数据相等时,不进行交换,可以保证稳定性
     *
     * @param arr
     * @author Alan
     * @date 2021/9/17 13:13
     */
    public static void insertSort(int[] arr) {
        //保证0~i是有序的,第0位置本身就是有序的,共需要遍历n-2次
        for (int i = 0; i < arr.length - 1; i++) {
            //j=i+1位置与前一个数进行比较,小的数往前移动,并且j--后继续和j-1位置数进行比较
            //如果j位置数>j-1位置数,则停止比较,此时数组0-j位置是有序的
            for (int j = i + 1; j > 0; j--) {
                if (arr[j - 1] > arr[j]) {
                    swap(arr, j);
                }
            }
            System.out.println(Arrays.toString(arr));
        }
    }

    public static void swap(int[] arr, int j) {
        arr[j - 1] = arr[j - 1] ^ arr[j];
        arr[j] = arr[j - 1] ^ arr[j];
        arr[j - 1] = arr[j - 1] ^ arr[j];
    }
}

归并排序

使用二分法将数组划分成左右两部分,分别对左右数组进行排序处理,处理完成后合并两个数组数据。使用递归的思路按照上述方法分别对左右数组进行相同处理,就可以完成整个数组的排序。

package com.example.algorithm.sort;

import java.util.Arrays;

/**
 * 递归
 */
public class Recursion {
    /**
     * 归并排序,时间复杂度O(N*logN),空间复杂度O(N),具备稳定性
     *
     * @param arr
     * @author Alan
     * @date 2021/9/3 13:24
     */
    public static void sort(int[] arr) {
        processSort(arr, 0, arr.length - 1);
    }

    /**
     * 递归函数,二分排序
     *
     * @param arr
     * @param l
     * @param r
     * @author Alan
     * @date 2021/9/6 12:52
     */
    private static void processSort(int[] arr, int l, int r) {
        if (l == r) {
            return;
        }
        int mid = l + ((r - l) >> 1);
        processSort(arr, l, mid);
        processSort(arr, mid + 1, r);
        mergeSort(arr, l, mid, r);
    }

    /**
     * 比较在1~m与m+1~r的有序数组,将较小值拷贝到help[]数组中,helpIndex++
     * 数组arr[]在m或r的位置上溢出,则将未溢出的数组arr[]剩下的值拷贝到help[]数组中
     * 将数组help[]拷贝回arr[]数组
     * 左侧和右侧相等时,先拷贝左侧的数据,可以保证稳定性
     *
     * @param arr 数组
     * @param l   数组起始下标位置
     * @param m   二分中点位置,m表示左侧数组结束下标位置,m+1表示右侧数组开始位置
     * @param r   数组结束下标位置
     * @author Alan
     * @date 2021/9/6 12:44
     */
    private static void mergeSort(int[] arr, int l, int m, int r) {
        int[] help = new int[r - l + 1];
        int helpIndex = 0;
        int lIndex = l;
        int rIndex = m + 1;
        while (lIndex <= m && rIndex <= r) {
            help[helpIndex++] = arr[lIndex] <= arr[rIndex] ? arr[lIndex++] : arr[rIndex++];
        }
        while (lIndex <= m) {
            help[helpIndex++] = arr[lIndex++];
        }
        while (rIndex <= r) {
            help[helpIndex++] = arr[rIndex++];
        }
        for (int i = 0; i < help.length; i++) {
            arr[l + i] = help[i];
        }
        System.out.println(Arrays.toString(help));
    }
}

快速排序

从数组中随机选择一个参照数,与数组的最后一个位置数据进行交换。使用参照数计算左右数组的边界,并将小于等于参照数的数据依次放入左边数组,大于参照数的数据依次放入右边数组,最后将参照数放入右边数组的第一个位置。使用递归的思路按照上述方法分别对左右数组进行相同处理,就可以完成整个数组的排序。

package com.example.algorithm.sort;

/**
 * 时间复杂度O(N*logN)~O(N^2)
 * 空间复杂度O(logN)~O(N)
 */
public class QuickSort {
    public static void quickSort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        quickSort(arr, 0, arr.length - 1);
    }

    /**
     * 快排,时间复杂度O(N*logN),空间复杂度O(logN),不具备稳定性
     *
     * @param arr
     * @param l
     * @param r
     * @author Alan
     * @date 2021/9/17 13:18
     */
    public static void quickSort(int[] arr, int l, int r) {
        //数组中随机选择一个数据,作为参照数与数组的最后一个数进行交换
        swap(arr, l + (int) (Math.random() * (r - l + 1)), r);
        int[] p = partition(arr, l, r);
        quickSort(arr, l, p[0]);
        quickSort(arr, p[1] + 1, r);
    }

    /**
     * 不具备稳定性
     *
     * @param arr
     * @param l
     * @param r
     * @return int[] 返回以r为参照数的等于区域的边界值值数组,数组长度为2,分别表示左右边界值
     * @author Alan
     * @date 2021/9/17 13:15
     */
    public static int[] partition(int[] arr, int l, int r) {
        int less = l - 1;
        int more = r;
        while (l < more) {
            //如果当前数小于参照数,则将当前数与小于区域指针less的下一个位置进行交换,当前数指针前移l++
            if (arr[l] < arr[r]) {
                swap(arr, ++less, l++);
            }
            //如果当前数大于参照数,则将当前数与大于区域指针more的下一个位置进行交换,当前数指针不变
            else if (arr[l] > arr[r]) {
                swap(arr, --more, l);
            }
            //如果当前数等于参照数,则当前数的位置不变,当前数指针前移l++
            else {
                l++;
            }
        }
        //遍历到数组最后一位时l=r,将参照数位置r与大于参照数区域的第一个数的位置more进行交换
        swap(arr, more, r);
        return new int[]{less + 1, more};
    }

    public static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

堆排序

数组长度为 n,堆结构中 index 为当前节点,则 (index - 1) / 2 为当前节点的父节点。

构造大根堆,将数组中的数据依次放入堆中,使用 heapInsert 方法来比较并调整数组,使其成为大根堆。
新进入堆中的数据与父节点进行比较,当前节点数据大于父节点( arr[index] > arr[(index - 1) / 2] )则进行交换,交换后当前节点就来到了父节点的位置( index = (index - 1) / 2 ),此时以父节点为当前节点继续往上比较,直到当前节点小于等于父节点或当前节点来到根节点的位置。此时大根堆已经构造完成。

堆排序,使用大根堆根节点即为最大的数的特性对数组进行排序,大根堆构造完成,此时数组的 0 位置即为根节点。
将根节点 0 位置的数据和数组 n-1 位置的数据进行交换,此时数组 n-1 位置已完成排序;
对数组 0 ~ n-2 位置的数据,使用 heapify 方法调整堆,使其再次成为大根堆,将根节点数据与数组 n-2 位置进行交换,此时数组 n-2 ~ n-1 位置已完成排序;
依次类推,进行排序…;
对数组 0 ~ 1 位置的数据,使用 heapify 方法调整堆,使其再次成为大根堆,将根节点数据与数组 1 位置进行交换,此时数组 1 ~ n-1 位置已完成排序,整个数组完成排序。

堆调整,移除堆的根节点,并将堆中的最后一个节点移动到根节点,使用 heapify 方法来调整堆,使其成为大根堆。
从根节点开始,比较当前节点下左右子节点,将较大的子节点与当前节点进行比较,当前节点小于子节点则进行交换,交换后当前节点来到了子节点的位置,此时以子节点为当前节点进行往下比较,直到当前节点大于等左右子节点中较大的节点。此时堆已经重新调整为大根堆。

package com.example.algorithm.sort;

import java.util.Arrays;
import java.util.PriorityQueue;

/**
 * 堆排序
 */
public class HeapSort {

    /**
     * PriorityQueue优先队列底层就是小根堆
     * 每次扩容按照现大小的成倍进行扩容,扩容代价O(logN)
     *
     * @param arr 需要排序的数组
     * @param k   PriorityQueue队列大小
     * @author Alan
     * @date 2021/9/14 13:03
     */
    public static void heapSort(int[] arr, int k) {
        PriorityQueue<Integer> heap = new PriorityQueue<>();
        int index = 0;
        for (; index <= Math.min(arr.length - 1, k); index++) {
            heap.add(arr[index]);
        }
        int i = 0;
        for (; index < arr.length - 1; i++, index++) {
            heap.add(arr[index]);
            arr[i] = heap.poll();
            System.out.println(Arrays.toString(arr));
        }
        while (!heap.isEmpty()) {
            arr[i++] = heap.poll();
            System.out.println(Arrays.toString(arr));
        }
    }

    /**
     * 时间复杂度O(N*logN)额外空间复杂度O(1)
     * 堆排序,不具备稳定性
     *
     * @param arr 需要排序的数组
     * @author Alan
     * @date 2021/9/14 12:42
     */
    public static void heapSort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        for (int i = 0; i < arr.length; i++) {
            heapInsert(arr, i);
        }
        System.out.println(Arrays.toString(arr));
        int heapSize = arr.length;
        swap(arr, 0, --heapSize);
        while (heapSize > 0) {
            heapify(arr, 0, heapSize);
            swap(arr, 0, --heapSize);
            System.out.println(Arrays.toString(arr));
        }
    }

    /**
     * 构造堆O(logN)
     * 新进入的数放置到最后的位置,与父节点比较,若大于父节点的数,则与父节点交换
     *
     * @param arr   需要构造堆的数组
     * @param index 从数组起始位置开始每次+1
     * @author Alan
     * @date 2021/9/10 13:13
     */
    public static void heapInsert(int[] arr, int index) {
        while (arr[index] > arr[(index - 1) / 2]) {
            swap(arr, index, (index - 1) / 2);
            index = (index - 1) / 2;
        }
    }

    /**
     * 移除顶级节点的数后,重新构造堆O(logN)
     * 将最后一个数放到顶级节点位置,比较父节点下的两个孩子,将较大的数与父节点进行比较,若大于父节点的数,则与父节点交换
     *
     * @param arr      堆数组
     * @param index    父节点开始位置
     * @param heapSize 堆大小
     * @author Alan
     * @date 2021/9/10 13:26
     */
    public static void heapify(int[] arr, int index, int heapSize) {
        //左孩子下标
        int left = index * 2 + 1;
        //是否有孩子
        while (left < heapSize) {
            //记录较大数字的下标位置
            int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;
            //和父节点比较
            largest = arr[largest] > arr[index] ? largest : index;

            if (largest == index) {
                break;
            }
            swap(arr, largest, index);
            index = largest;
            left = index * 2 + 1;
        }
    }

    public static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

桶排序

根据当前需要排序数据的进制来设定桶的数量,桶按照单位进制数的最小值~最大值来编号。例如:10进制数,则准备 0 ~ 9 编号的桶。
个位数:取出数组中所有数据的个位数,并将其放入对应数组编号的桶中,按照桶编号顺序从桶中取出数据放回原数组,一个桶中有多个数据的按照先进先出的顺序取出。
十位数:取出数组中所有数据的十位数,并将其放入对应数组编号的桶中,按照桶编号顺序从桶中取出数据放回原数组,一个桶中有多个数据的按照先进先出的顺序取出。
依次类推,进行排序,当数组中的最大数据的最高位被取出处理后,整个数组完成排序。

package com.example.algorithm.sort;

/**
 * 桶排序
 */
public class RadixSort {

    public static void radixSort(int[] arr) {
        radixSort(arr, 0, arr.length - 1, maxBits(arr));
    }

    /**
     * 数组中最大数字的10进制位位数
     *
     * @param arr
     * @return int
     * @author Alan
     * @date 2021/9/17 12:59
     */
    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;
        if (max != 0) {
            res++;
            max /= 10;
        }
        return res;
    }

    /**
     * 桶排序,具备稳定性
     *
     * @param arr   数组
     * @param l     需要排序的起始位置下标
     * @param r     需要排序的结束位置下标
     * @param digit 数组中最大数的10进制位数
     * @author Alan
     * @date 2021/9/17 13:00
     */
    public static void radixSort(int[] arr, int l, int r, int digit) {
        //10进制数准备[0~9]空间的数组
        final int radix = 10;
        //数组数据位置
        int i = 0;
        //数组数据当前位数字的位置=当前位数字的值
        int j = 0;
        int[] bucket = new int[r - l + 1];
        for (int d = 0; d < digit; d++) {
            //d位数数字计数器
            //10进制数准备[0~9]空间的数组
            int[] count = new int[radix];
            //遍历范围内的数组数据,计算d位置的数字在数组count中对应位置的数量
            for (i = l; i <= r; i++) {
                j = getDigit(arr[i], d);
                count[j]++;
            }
            //数据分片,计算数值<i位置的数字在数组中的个数
            for (i = 1; i < radix; i++) {
                count[i] = count[i] + count[i - 1];
            }
            //先进先出,则最后一个数的存放位置为bucket[count[j]-1]
            for (i = r; i >= l; i--) {
                j = getDigit(arr[i], d);
                bucket[count[j] - 1] = arr[i];
                count[j]--;
            }
            //将调整好后的数组拷贝回原数组
            for (i = l, j = 0; i <= r; i++, j++) {
                arr[i] = bucket[j];
            }
        }
    }

    //求一个数d位的数字
    public static int getDigit(int currentNumber, int d) {
        return (currentNumber / (int) Math.pow(10, d - 1)) % 10;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值