【面试必备】手撕代码,你怕不怕? (4)

很简单,直接上代码:

/** * 冒泡排序 * * @param nums 待排序的数组 */ public void bubbleSort(int[] nums) { // 正确性判断 if (null == nums || nums.length <= 1) { return; } // 从小到大排序 for (int i = 0; i < nums.length - 1; i++) { for (int j = i + 1; j < nums.length; j++) { if (nums[i] > nums[j]) { nums[i] = nums[i] + nums[j]; nums[j] = nums[i] - nums[j]; nums[i] = nums[i] - nums[j]; } } } }

这里需要注意:加上正确性判断;(讨论:其实我看大多数地方都是没有这个的,不知道有没有加上的必要...求讨论)

另外光写完实现冒泡排序的算法是不算完的,还要养成良好的习惯去写测试单元用例,而且尽可能要考虑到多的点,例如这里的负数、多个相同的数之类的特殊情况,我就大概写一个吧,也欢迎指正:

@Test public void bubbleSortTester() { // 测试用例1:验证负数是否满足要求 int[] nums = {1, 4, 2, -2, 5, 11, -7, 0}; bubbleSort(nums); // 输出测试结果 for (int i = 0; i < nums.length; i++) { System.out.print(nums[i] + ", "); } System.out.println(); // 测试用例2:验证多个相同的数是否满足要求 nums = new int[]{1, 1, 5, 7, 7, 3, 1}; bubbleSort(nums); // 输出测试结果 for (int i = 0; i < nums.length; i++) { System.out.print(nums[i] + ", "); } } 冒泡排序优化

想象一个这样的场景:如果该数组基本有序,或者在数组的后半段基本有序,上面的算法就会浪费许多的时间开销,所以我们不再使用双重嵌套去比较每两个元素的值,而只是不断比较数组每前后两个数值,让大的那个数不断“冒”到数组的最后,然后缩小尾边界的范围,并且增加一个标志位,表示这一趟是否发生了交换,如果没有那么证明该数组已经有序则完成了排序了:

/** * 改进的冒泡排序 * * @param nums 待排序的数组 */ public void bubbleSort2(int[] nums) { // 正确性判断 if (null == nums || nums.length <= 1) { return; } // 使用一个数来记录尾边界 int length = nums.length; boolean flag = true;// 发生了交换就为true, 没发生就为false,第一次判断时必须标志位true。 while (flag) { flag = false;// 每次开始排序前,都设置flag为未排序过 for (int i = 1; i < length; i++) { if (nums[i - 1] > nums[i]) {// 前面的数字大于后面的数字就交换 int temp; temp = nums[i - 1]; nums[i - 1] = nums[i]; nums[i] = temp; // 表示交换过数据; flag = true; } } length--; // 减小一次排序的尾边界 } }

同样的记得写单元测试函数;

冒泡排序进一步优化

顺着这个思路,我们进一步想象一个场景:现在有一个包含 1000 个数的数组,仅有前面 100 个数无序,后面的 900 个数都比前面的 100 个数更大并且已经排好序,那么上面优化的方法又会造成一定的时间浪费,所以我们进一步增加一个变量记录最后发生交换的元素的位置,也就是排序的尾边界了:

/** * 冒泡算法最优解 * * @param nums 待排序的数组 */ public static void bubbleSort3(int[] nums) { int j, k; int flag = nums.length;// flag来记录最后交换的位置,也就是排序的尾边界 while (flag > 0) {// 排序未结束标志 k = flag;// k 来记录遍历的尾边界 flag = 0; for (j = 1; j < k; j++) { if (nums[j - 1] > nums[j]) {// 前面的数字大于后面的数字就交换 // 交换a[j-1]和a[j] int temp; temp = nums[j - 1]; nums[j - 1] = nums[j]; nums[j] = temp; // 表示交换过数据; flag = j;// 记录最新的尾边界. } } } }

这应该是最优的冒泡排序了,同时也别忘记了最后要写测试单元用例代码;

快速排序

快排也是一种很经典的算法,它使用了一种分治的思想,基本思想是:通过一趟排序将待排序的数组分成两个部分,其中一部分记录的是比关键字更小的,另一部分是比关键字更大的,然后再分别对着两部分继续进行排序,直到整个序列有序;

基础实现

非常经典的代码,直接上吧:

public static void quickSort(int[] arr) { qsort(arr, 0, arr.length - 1); } private static void qsort(int[] arr, int low, int high) { if (low < high) { int pivot = partition(arr, low, high); // 将数组分为两部分 qsort(arr, low, pivot - 1); // 递归排序左子数组 qsort(arr, pivot + 1, high); // 递归排序右子数组 } } private static int partition(int[] arr, int low, int high) { int pivot = arr[low]; // 枢轴记录 while (low < high) { while (low < high && arr[high] >= pivot) --high; arr[low] = arr[high]; // 交换比枢轴小的记录到左端 while (low < high && arr[low] <= pivot) ++low; arr[high] = arr[low]; // 交换比枢轴小的记录到右端 } // 扫描完成,枢轴到位 arr[low] = pivot; // 返回的是枢轴的位置 return low; }

当然,在手撕的时候需要注意函数上的 Java Doc 格式的注释,这里省略掉是为了节省篇幅,另外别忘了测试单元用例代码

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpdpfy.html