1.概述

image.png

2.时间复杂度O(n^2)

image.png

2.1 冒泡排序

原始版本:

  1. public static void bubble(int[] arr){
  2. int n=arr.length;
  3. for(int i=0;i<n-1;i++){
  4. //总共要比较n-1轮
  5. for(int j=1;j<n-i;j++){
  6. //采用比较j和j-1,为了不越下界,从j=1开始,同时j的最大值受i的限制
  7. //如果采用比较j和j+1,则就是j=0;j<n-i-1
  8. if(arr[j]<arr[j-1]){
  9. Zswap.swap(arr,j,j-1);
  10. }
  11. }
  12. }
  13. }
  14. public class Zswap {
  15. public static void swap(int[] arr,int i,int j){
  16. int temp=arr[i];
  17. arr[i]=arr[j];
  18. arr[j]=temp;
  19. }
  20. }
  21. public class ZwapMagic {
  22. public static void swap(int[] arr,int i,int j){
  23. /**
  24. 使用按位异或来进行两数的交换
  25. 面试的时候可能会考到
  26. */
  27. arr[i]=arr[i]^arr[j];
  28. arr[j]=arr[i]^arr[j];
  29. arr[i]=arr[i]^arr[j];
  30. }
  31. }

版本2.0

  1. public static void bubbleSort(int[] arr) {
  2. // 初始时 swapped 为 true,否则排序过程无法启动
  3. boolean swapped = true;
  4. for (int i = 0; i < arr.length - 1; i++) {
  5. // 如果没有发生过交换,说明剩余部分已经有序,排序完成
  6. if (!swapped) break;
  7. // 设置 swapped 为 false,如果发生交换,则将其置为 true
  8. swapped = false;
  9. for (int j = 0; j < arr.length - 1 - i; j++) {
  10. if (arr[j] > arr[j + 1]) {
  11. // 如果左边的数大于右边的数,则交换,保证右边的数字最大
  12. swap(arr, j, j + 1);
  13. // 表示发生了交换
  14. swapped = true;
  15. }
  16. }
  17. }
  18. }
  19. // 交换元素
  20. private static void swap(int[] arr, int i, int j) {
  21. int temp = arr[i];
  22. arr[i] = arr[j];
  23. arr[j] = temp;
  24. }

2.0版本的最好时间复杂度为O(n)
即如果数组本身就是有序的话,此时只需要一轮就结束了

时间复杂度和空间复杂度
时间复杂度最坏:n-1,n-2,n-3…1
总和为(n-1+1)*(n-1)/2,故为O(n^2)
时间复杂度最好:n-1,即O(n),这个指的是2.0版本
空间复杂度:O(1)

image.png

2.2 选择排序

选择排序每次在比较当前轮次最小值的时候,
不是每次比较都交换,而是维护一个最小值的索引,到本轮比较结束时再交换

那怎么

3.时间复杂度O(nlogn)

3.1 堆排序

堆排序

堆排序中有一个不易理解的点是递归
就是当一个子节点与父节点进行交换之后,需要保证以新的子节点为根节点的子树为大顶堆
如果不这样最后的排序就会出错;({2,4,3,5,1,7}演示一下如果不交换的话会出现3比4大的情况)
即只要发生了交换,就要递归确保交换后的子树依旧为大顶堆。

在介绍堆排序具体实现之前,我们先要了解完全二叉树的几个性质。将根节点的下标视为 0,则完全二叉树有如下性质:
对于完全二叉树中的第 i 个数,它的左子节点下标:left = 2i + 1
对于完全二叉树中的第 i 个数,它的右子节点下标:right = left + 1
对于有 n 个元素的完全二叉树(n≥2)(n≥2),它的最后一个非叶子结点的下标:n/2 - 1

  1. public static void heapSort(int[] arr) {
  2. // 构建初始大顶堆
  3. buildMaxHeap(arr);
  4. for (int i = arr.length - 1; i > 0; i--) {
  5. // 将最大值交换到数组最后
  6. swap(arr, 0, i);
  7. // 调整剩余数组,使其满足大顶堆
  8. maxHeapify(arr, 0, i);
  9. }
  10. }
  11. // 构建初始大顶堆
  12. private static void buildMaxHeap(int[] arr) {
  13. // 从最后一个非叶子结点开始调整大顶堆,最后一个非叶子结点的下标就是 arr.length / 2-1
  14. for (int i = arr.length / 2 - 1; i >= 0; i--) {
  15. maxHeapify(arr, i, arr.length);
  16. }
  17. }
  18. // 调整大顶堆,第三个参数表示剩余未排序的数字的数量,也就是剩余堆的大小
  19. private static void maxHeapify(int[] arr, int i, int heapSize) {
  20. // 左子结点下标
  21. int l = 2 * i + 1;
  22. // 右子结点下标
  23. int r = l + 1;
  24. // 记录根结点、左子树结点、右子树结点三者中的最大值下标
  25. int largest = i;
  26. // 与左子树结点比较
  27. if (l < heapSize && arr[l] > arr[largest]) {
  28. largest = l;
  29. }
  30. // 与右子树结点比较
  31. if (r < heapSize && arr[r] > arr[largest]) {
  32. largest = r;
  33. }
  34. if (largest != i) {
  35. // 将最大值交换为根结点
  36. swap(arr, i, largest);
  37. // 再次调整交换数字后的大顶堆
  38. maxHeapify(arr, largest, heapSize);
  39. }
  40. }
  41. private static void swap(int[] arr, int i, int j) {
  42. int temp = arr[i];
  43. arr[i] = arr[j];
  44. arr[j] = temp;
  45. }
  46. 作者:力扣 (LeetCode)
  47. 链接:https://leetcode-cn.com/leetbook/read/sort-algorithms/eu7ux3/
  48. 来源:力扣(LeetCode
  49. 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

3.2 快速排序

  1. public static void quickSort(int[] arr) {
  2. quickSort(arr, 0, arr.length - 1);
  3. }
  4. public static void quickSort(int[] arr, int start, int end) {
  5. // 如果区域内的数字少于 2 个,退出递归
  6. if (start >= end) return;
  7. // 将数组分区,并获得中间值的下标
  8. int middle = partition(arr, start, end);
  9. // 对左边区域快速排序
  10. quickSort(arr, start, middle - 1);
  11. // 对右边区域快速排序
  12. quickSort(arr, middle + 1, end);
  13. }
  14. // 将 arr 从 start 到 end 分区,左边区域比基数小,右边区域比基数大,然后返回中间值的下标
  15. public static int partition(int[] arr, int start, int end) {
  16. // 取第一个数为基数
  17. int pivot = arr[start];
  18. // 从第二个数开始分区
  19. int left = start + 1;
  20. // 右边界
  21. int right = end;
  22. // left、right 相遇时退出循环
  23. while (left < right) {
  24. // 找到第一个大于基数的位置
  25. while (left < right && arr[left] <= pivot) left++;
  26. // 交换这两个数,使得左边分区都小于或等于基数,右边分区大于或等于基数
  27. if (left != right) {
  28. exchange(arr, left, right);
  29. right--;
  30. }
  31. }
  32. // 如果 left 和 right 相等,单独比较 arr[right] 和 pivot
  33. // 当arr[right] > pivot时,为了保证返回的right的右边的值都大于right当前的值,right需要--
  34. if (left == right && arr[right] > pivot) right--;
  35. // 将基数和中间数交换
  36. if (right != start) exchange(arr, start, right);
  37. // 返回中间值的下标
  38. return right;
  39. }
  40. private static void exchange(int[] arr, int i, int j) {
  41. int temp = arr[i];
  42. arr[i] = arr[j];
  43. arr[j] = temp;
  44. }
  45. 作者:力扣 (LeetCode)
  46. 链接:https://leetcode-cn.com/leetbook/read/sort-algorithms/eul7hm/
  47. 来源:力扣(LeetCode
  48. 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

习题:

题中说必须在原数组上操作,不能拷贝额外的数组,Then元素交换;

数组变量作为形参是可以影响实参的,就跟引用一样;

递归的终止条件不一定非得用return,如果递归是有条件的,那么不满足条件递归自然就会终止,如堆排序和快速排序中的递归就是如此;

1.把数组排成最小的数

image.png

  1. class Solution {
  2. public String minNumber(int[] nums) {
  3. bubble(nums);
  4. StringBuilder sb=new StringBuilder("");
  5. for(int i=0;i<nums.length;i++){
  6. sb.append(nums[i]);
  7. }
  8. return sb.toString();
  9. }
  10. public void bubble(int[] arr){
  11. int n=arr.length;
  12. for(int i=0;i<n-1;i++){
  13. for(int j=1;j<n-i;j++){
  14. if(((""+arr[j-1]+arr[j]).compareTo(""+arr[j]+arr[j-1]))>0){
  15. int temp=arr[j-1];
  16. arr[j-1]=arr[j];
  17. arr[j]=temp;
  18. }
  19. }
  20. }
  21. }
  22. }

原理就是将数字进行排序,
假设数组中a=3在b=30的左边,
如果ab的字符串330大于ba的字符串303
那么就需要交换ab的位置
定义完上面的排序规则后我们可以采用任意一个排序规则来进行升序排列即可

2.移动零(本题双指针的使用和元素交换很有学习意义)
image.png

  1. class Solution {
  2. public void moveZeroes(int[] nums) {
  3. int left=0;
  4. int right=0;
  5. int n=nums.length;
  6. while(right<n){
  7. if(nums[right]!=0){
  8. swap(nums,left,right);
  9. left++;
  10. }
  11. right++;
  12. }
  13. }
  14. public void swap(int[] arr,int left,int right){
  15. int temp=arr[left];
  16. arr[left]=arr[right];
  17. arr[right]=temp;
  18. }
  19. }

left指的是连续的0的左边第一个,
right的话要么时连续的0的右边第一个,要么是连续的0的右边第一个的再右边一个(此时不是0)
当right指向的不是0时,我们将最左边的0与该位置交换,就能得到另一块连续的0
此时left要向右移一位,指向新的连续的0的左边第一个
一直循环上述操作,知道right超过数组边界