排序算法代码速查【面试前速看】

一、冒泡排序

代码实现🎆

public int[] bubbleSort(int[] nums) {
  // 未排序数据的右边界
  int j = nums.length - 1;
  do {
    //定义一个变量 x 记录未排序区域的右边界
    int x = 0;
    for (int i = 0; i < j; i++) {
      if (nums[i] > nums[i + 1]) { // 相邻元素中前面的元素大则交换
        int t = nums[i];
        nums[i] = nums[i + 1];
        nums[i + 1] = t;
        // 每次交换后,更新变量x,最后一次交换后x的值为经过一轮排序后新的右边界,优化代码
        x = i;
      }
    }
    j = x;
  } while (j != 0);
  return nums;
}
public void bubbleSort(int[] nums, int j) {
    if (j == 0) {
        return;
    }
    //定义一个变量 x 记录未排序区域的右边界
    int x = 0;
    for (int i = 0; i < j; i++) {
        if (nums[i] > nums[i + 1]) { // 相邻元素中前面的元素大则交换
            int t = nums[i];
            nums[i] = nums[i + 1];
            nums[i + 1] = t;
            // 每次交换以后,更新x <未排序数据的右边界> 的值
            x = i;
        }
    }
    bubbleSort(nums, x);
}
public static int[] bubbleSort(int[] arr) {
    // 数组长度如果为5,则需要进行4轮冒泡
    for (int i = 1; i < arr.length; i++) {
        boolean flag = true;
        for (int j = 0; j < arr.length - i; j++) {
            if (arr[j] > arr[j + 1]) {// 相邻元素中前面的元素大则交换
                int tmp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = tmp;
                // Change flag 只要一轮循环发生了交换,则把flag置为false
                flag = false;
            }
        }
        // 一轮都没有发生交换,没必要继续,直接退出循环
        if (flag) {
            break;
        }
    }
    return arr;
}

此处对代码做了一个小优化,加入了 is_sorted Flag,目的是将算法的最佳时间复杂度优化为 O(n),即当原输入序列就是排序好的情况下,该算法的时间复杂度就是 O(n)。

二、选择排序

代码实现🎆

public void selectSort(int[] nums) {
    // 1. 选择轮数:nums.length - 1
    // 2. 交换的索引right初始化为nums.length - 1,每次递减,将选择到的最大值与right位置交换
    for (int right = nums.length - 1; right > 0; right--) {
        int max = right;
        for (int i = 0; i < right; i++) {
            if (nums[i] > nums[max]) {
                max = i;
            }
        }
        // max 值发生了变化才交换,否则不需要交换
        if (max != right) {
            // 每一轮循环结束时,将选择到的最大的的值交换到最右侧
            int t = nums[max];
            nums[max] = nums[right];
            nums[right] = t;
        }
    }
}
public void selectionSort(int[] arr) {
    // 每一轮循环结束时,将选择到的最小的的值交换到最左侧 同上
    for (int left = 0; left < arr.length - 1; left++) {
        int minIndex = left;
        for (int j = left + 1; j < arr.length; j++) {
            if (arr[j] < arr[minIndex]) {
                minIndex = j;
            }
        }
        if (minIndex != left) {
            int tmp = arr[left];
            arr[left] = arr[minIndex];
            arr[minIndex] = tmp;
        }
    }
}

三、插入排序

代码实现🎆

public void insertionSort(int[] nums) {
    // 定义一个low指针,指向未排序区域的第一个数,默认从1开始,认为第一个数已经有序
    for (int notSortedFirstIndex = 1; notSortedFirstIndex < nums.length; notSortedFirstIndex++) {
        // 定义一个指针指向已排序区域的最后一个指针
        int sortedLastIndex = notSortedFirstIndex - 1;
        // 记录未排序区域的第一个值,用于后序插入
        int current = nums[notSortedFirstIndex];
        // 如果以排序区域的最后一个值大于未排序区域的第一个值,则将已排序区域的最后一个值向后移动
        while (sortedLastIndex >= 0 && nums[sortedLastIndex] > current) {
            nums[sortedLastIndex + 1] = nums[sortedLastIndex];
            sortedLastIndex--;
        }
        // 循环结束,说明已经找到插入位置
        if (sortedLastIndex != notSortedFirstIndex - 1) {
            // 上面的i是经过--以后不满足while条件退出循环,因此在插入时需要在i+1位置插入
            nums[sortedLastIndex + 1] = current;
        }
    }
}
/* 插入排序 */
void insertionSort(int[] nums) {
    // i=>未排序区域的第一个指针
    // j=>以排序区域的最后一个指针
    // base=>暂存未排序区域中选择的基准元素
    // 外循环:已排序区间为 [0, i-1]
    for (int i = 1; i < nums.length; i++) {
        int base = nums[i], j = i - 1;
        // 内循环:将 base 插入到已排序区间 [0, i-1] 中的正确位置
        while (j >= 0 && nums[j] > base) {
            nums[j + 1] = nums[j]; // 将 nums[j] 向右移动一位
            j--;
        }
        nums[j + 1] = base;        // 将 base 赋值到正确位置
    }
}

四、希尔排序

代码实现🎆

public void shellSort(int[] nums) {
    // 定义分组间隙 gap ,初始为数组长度的一半,每次循环右位移一位(除以二)
    for (int gap = nums.length >> 1; gap >= 1; gap = gap >> 1) {
        // notSortedFirstIndex 未排序数组的第一个指针
        for (int notSortedFirstIndex = gap; notSortedFirstIndex < nums.length; notSortedFirstIndex++) {
            // 未排序区域的第一个元素值
            int current = nums[notSortedFirstIndex];
            int sortedLastIndex = notSortedFirstIndex - gap; // 排序数组的最后一个数据指针
            // 插入排序的过程
            while (sortedLastIndex >= 0 && nums[sortedLastIndex] > current) {
                nums[sortedLastIndex + gap] = nums[sortedLastIndex];
                sortedLastIndex -= gap;
            }
            if (sortedLastIndex != notSortedFirstIndex - gap) {
                nums[sortedLastIndex + gap] = current;
            }
        }
    }
}
public void shellSort(int[] arr) {
    // 定义分组间隙 gap ,初始为数组长度的一半
    int gap = arr.length >> 1;
    while (gap > 0) {
        for (int i = gap; i < arr.length; i++) {
            // 未排序区域的第一个元素值
            int current = arr[i];
            // 已排序区域的最后一个元素指针
            int preIndex = i - gap;
            // Insertion sort
            while (preIndex >= 0 && arr[preIndex] > current) {
                // 后移
                arr[preIndex + gap] = arr[preIndex];
                preIndex -= gap;
            }
            arr[preIndex + gap] = current;

        }
        // 取下一个希尔增量
        gap = gap >> 1;
    }
}

五、归并排序

代码实现🎆

public void mergeSort(int[] nums) {
    int[] arr = new int[nums.length];
    /*
         * 参数一:待排序数组
         * 参数二:待排序数组的区域左标
         * 参数二:待排序数组的区域右标
         * 参数三:临时数组,用来合并数组时使用
         */
    split(nums, 0, nums.length - 1, arr);
}

private void split(int[] nums, int left, int right, int[] arr) {
    // 治
    if (left == right) {
        return;
    }
    // 分
    int mid = (left + right) >>> 1;
    split(nums, left, mid, arr);
    split(nums, mid + 1, right, arr);
    // 合
    merge(nums, left, mid, mid + 1, right, arr);
    System.arraycopy(arr, left, nums, left, right - left + 1);
}

/**
     * 合并有序数组
     *
     * @param nums 原始数组
     * @param i  iEnd 第一个有序范围
     * @param j  jEnd 第二个有序范围
     * @param arr 临时数组
     */
private void merge(int[] nums, int i, int iEnd, int j, int jEnd, int[] arr) {
    // 定义一个指针k用于构建临时数组
    int k = i;
    while (i <= iEnd && j <= jEnd) {
        // 每次将 i ~ iEnd 或者 j ~ jEnd 中更小的数加入到临时数组,并将指针后移
        if (nums[i] < nums[j]) {
            arr[k] = nums[i];
            i++;
        } else {
            arr[k] = nums[j];
            j++;
        }
        k++;
    }
    // 当 i ~ iEnd 或 j ~ jEnd 其中一个区域的数据全部被加入到临时数组,则把另一个区域的数据拷贝到临时数组中
    if (i > iEnd) {
        System.arraycopy(nums, j, arr, k, jEnd - j + 1);
    }
    if (j > jEnd) {
        System.arraycopy(nums, i, arr, k, iEnd - i + 1);
    }
}
public void mergeSort(int[] nums) {
    int len = nums.length;
    int[] arr = new int[len];
    // width 代表有序区间的宽度,取值依次为 1,2,4 ...
    for (int width = 1; width < len; width *= 2) {
        // [left,right] 分别代表合并区间的左右边界
        // 每次合并两个有序区间,因此左边界每次循环 + 2 * width
        for (int left = 0; left < len; left += 2 * width) {
            //右边界为下次左边界 - 1
            int right = Math.min(len - 1, left + 2 * width - 1);
            //中间值mid为左边界 + 一个宽度width - 1
            int mid = Math.min(len - 1, left + width - 1);
            merge(nums, left, mid, mid + 1, right, arr);
        }
        System.arraycopy(arr, 0, nums, 0, len);
    }
}
    /**
     * 合并有序数组
     *
     * @param nums 原始数组
     * @param i  iEnd 第一个有序范围
     * @param j  jEnd 第二个有序范围
     * @param arr 临时数组
     */
private void merge(int[] nums, int i, int iEnd, int j, int jEnd, int[] arr) {
    // 定义一个k操作临时数组
    int k = i;
    //如果i和j都在有效范围内
    while (i <= iEnd && j <= jEnd) {
        //比较i和j处索引的数组的值,并把较小的值加入到临时数组a2中
        if (nums[i] < nums[j]) {
            arr[k] = nums[i];
            i++;
        } else {
            arr[k] = nums[j];
            j++;
        }
        //更新操作临时数组的指针
        k++;
    }
    // 当i > iEnd说明第一个有序范围内的元素已经全部迭代,将第二范围内没有被迭代的元素拷贝到arr数组即可
    if (i > iEnd) {
        System.arraycopy(nums, j, arr, k, jEnd - j + 1);
    }
    // 当j > jEnd说明第二个有序范围内的元素已经全部迭代,将第一范围内没有被迭代的元素拷贝到arr数组即可
    if (j > jEnd) {
        System.arraycopy(nums, i, arr, k, iEnd - i + 1);
    }
}
public static void mergeInsertionSort(int[] a) {
    int[] a2 = new int[a.length];
    split(a, 0, a.length - 1, a2);
}

//归并排序分区方法
private static void split(int[] a, int left, int right, int[] a2) {
    // 治:当分区范围 <= 32后,采用插入排序实现有序
    // 传统的归并排序必须等到 left == right 即分区范围内只有一个元素时才视为有序
    // 通过整合插入排序,可以对小范围内(<=32)的元素直接采用插入排序后即可视为有序
    // 不用一直递归调用到分区内只有一个元素,提高效率和性能
    if (right - left <= 32) {
        //插入排序
        insertionSort(a, left, right);
        return;
    }
    // 分
    int mid = (right - left) >>> 1;
    split(a, left, mid, a2);
    split(a, mid + 1, right, a2);
    // 合
    merge(a, left, mid, mid + 1, right, a2);
    System.arraycopy(a2, left, a, left, right - left + 1);
}

/**
     * 插入排序
     */
private static void insertionSort(int[] a, int left, int right) {
    for (int low = left + 1; low <= right; low++) {
        //定义一个变量t记录未排序区域的第一个值
        int t = a[low];
        //定义一个指针 i 为已排序区域的最后一个值的指针
        int i = low - 1;
        while (i >= left && a[i] > t) {
            a[i + 1] = a[i];
            i--;
        }
        //循环结束,说明找到了插入位置,插入即可
        if (i != low - 1) {
            a[i + 1] = t;
        }
    }
}

/**
     * 合并有序数组
     *
     * @param a1 原始数组
     * @param i  iEnd 第一个有序范围
     * @param j  jEnd 第二个有序范围
     * @param a2 临时数组
     */
private static void merge(int[] a1, int i, int iEnd, int j, int jEnd, int[] a2) {
    //定义变量k为操作临时数组的指针
    int k = i;
    //如果i和j都在有效范围内
    while (i <= iEnd && j <= jEnd) {
        //比较i和j处索引的数组的值,并把较小的值加入到临时数组a2中
        if (a1[i] < a1[j]) {
            a2[k] = a1[i];
            i++;
        } else {
            a2[k] = a1[j];
            j++;
        }
        //更新操作临时数组的指针
        k++;
    }
    // 当i > iEnd说明第一个有序范围内的元素已经全部迭代,将第二范围内没有被迭代的元素拷贝到a1数组即可
    if (i > iEnd) {
        System.arraycopy(a1, j, a2, k, jEnd - j + 1);
    }
    // 当j > jEnd说明第二个有序范围内的元素已经全部迭代,将第一范围内没有被迭代的元素拷贝到a1数组即可
    if (j > jEnd) {
        System.arraycopy(a1, i, a2, k, iEnd - i + 1);
    }
}

六、快速排序

代码实现🎆

public void quickSort(int[] nums) {
    doQuickSort(nums, 0, nums.length - 1);
}

private void doQuickSort(int[] nums, int left, int right) {
    if (left >= right) {
        return;
    }
    // 分区
    int p = partition(nums, left, right);
    doQuickSort(nums, left, p - 1);
    doQuickSort(nums, p + 1, right);
}

/**
     * 单边循环快排(lomuto 洛穆托分区方案)
     * 核心思想:每轮找到一个基准点元素,把比它小的放到它左边,比它大的放到它右边,这称为分区
     * 
     * 	1.选择最右元素作为基准点元素
     * 	2.j 找比基准点小的,i 找比基准点大的,一旦找到,二者进行交换
     * 		· 交换时机:j 找到小的,且与 i 不相等
     * 		· i 找到 >= 基准点元素后,不应自增 
     * 	3.最后基准点与 i 交换,i 即为基准点最终索引
     * 
     */
private int partition(int[] nums, int left, int right) {
    int pv = nums[right]; // 选择最右元素作为基准点元素
    int i = left; // i找比基准点大的值,如果找到大于等于基准点的值,不再移动
    int j = left; // j找比基准点小的值
    while (j < right) {
        if (nums[j] < pv) { // j找到小的了
            if (j != i) {
                swap(nums, i, j); // i和j不一样,则交换
            }
            // 只有找到的值比基准点小,i才++,如果大于基准点的值,i不变,j++
            // 等到下次找到比基准点小的值,i和j不相等,进行交换
            i++;
        }
        j++;
    }
    swap(nums, right, i);
    return i;
}

private void swap(int[] nums, int i, int j) {
    int t = nums[i];
    nums[i] = nums[j];
    nums[j] = t;
}
public void quickSort(int[] nums) {
    doQuickSort(nums, 0, nums.length - 1);
}

private void doQuickSort(int[] nums, int left, int right) {
    if (left >= right) {
        return;
    }
    int p = partition(nums, left, right);
    doQuickSort(nums, left, p - 1);
    doQuickSort(nums, p + 1, right);
}

/**
     * 双边循环快排
     * 1.选择最左元素作为基准点元素 
     * 2.j 指针负责从右向左找比基准点小或等的元素,i 指针负责从左向右找比基准点大的元素,
                                                     一旦找到二者交换,直至 i,j 相交
     * 3.最后基准点与 i(此时 i 与 j 相等)交换,i 即为分区位置
     */
private int partition(int[] nums, int left, int right) {
    /***************************随机元素作为基准点***************************/
    int index = ThreadLocalRandom.current().nextInt(right - left + 1) + left;
    swap(nums,index,left);
    /***************************随机元素作为基准点***************************/
    int pv = nums[left]; // 选择最左侧元素作为基准点
    int i = left; // i 指针从左到右找大于基准点的值
    int j = right; // j 指针从右到左找小于基准点的值
    while (i < j) {
        // 必须先处理j指针再处理i指针
        // 1. j 从右向左找小(等)的
        while (i < j && nums[j] > pv) {
            j--;
        }
        // 2. i 从左向右找大的
        while (i < j && nums[i] <= pv) {
            i++;
        }
        // 3. 交换位置
        swap(nums, i, j);
    }
    swap(nums, left, i);
    return i;
}

private void swap(int[] nums, int i, int j) {
    int t = nums[i];
    nums[i] = nums[j];
    nums[j] = t;
}
public void quickSort(int[] nums) {
    doQuickSort(nums, 0, nums.length - 1);
}

private void doQuickSort(int[] nums, int left, int right) {
    if (left >= right) {
        return;
    }
    int p = partition(nums, left, right);
    doQuickSort(nums, left, p - 1);
    doQuickSort(nums, p + 1, right);
}


/*
核心思想是
* 改进前,i 只找大于的,j 会找小于等于的。一个不找等于、一个找等于,势必导致等于的值分布不平衡
* 改进后,二者都会找等于的交换,等于的值会平衡分布在基准点两边
     */
private int partition(int[] nums, int left, int right) {
    // 随机基准点
    int random = ThreadLocalRandom.current().nextInt(right - left + 1) + left;
    swap(nums, left, random);
    // 基准点
    int pv = nums[left];
    int i = left + 1; // 在处理重复值时,i要从left + 1开始
    int j = right;
    while (i <= j) {
        // i 指针和 j 指针的先后处理方式不再重要
        // 处理重复值,i需要从左到右找大于等于基准点的值
        while (i <= j && nums[i] < pv) {
            i++;
        }
        // 处理重复值,j需要从右向左找小于等于基准点的值
        while (i <= j && nums[j] > pv) {
            j--;
        }
        if (i <= j) {
            swap(nums, i, j);
            i++;
            j--;
        }
    }
    swap(nums, j, left);
    return j;
}

private void swap(int[] nums, int i, int j) {
    int t = nums[i];
    nums[i] = nums[j];
    nums[j] = t;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值