十大排序算法

对比指标

  1. 时间复杂度

  2. 空间复杂度

  3. 稳定性

    ​ 排序后,元素的相对位置有没有改变

  4. 是否原地排序

    ​ 排序过程中不需要额外的辅助空间,只需要常数级的空间

排序方法 平均时间复杂度 最坏时间复杂度 最好时间复杂度 空间复杂度 稳定性
插入排序 O(n^2) O(n^2) O(n) O(1) 稳定
选择排序 O(n^2) O(n^2) O(n^2) O(1) 不稳定
冒泡排序 O(n^2) O(n^2) O(n) O(1) 稳定
快速排序 O(n log n) O(n^2) O(n log n) O(log n) 不稳定
归并排序 O(n log n) O(n log n) O(n log n) O(n) 稳定
堆排序 O(n log n) O(n log n) O(n log n) O(1) 不稳定
计数排序 O(n + k) O(n + k) O(n + k) O(k) 稳定
桶排序 O(n + k) O(n^2) O(n) O(n + k) 不稳定
基数排序 O(n * k) O(n * k) O(n * k) O(n + k) 稳定

1.选择排序

  1. **排序思路:**正向遍历,在未排序的元素中子循环遍历寻找最小值,与未排序的第一个元素交换位置,直到排序完成结束遍历。(改进思路:同时寻找最大值,两边一起排序,但是时间复杂度仍然不变)
  2. 时间复杂度:O(n2)
  3. 空间复杂度:O(1)
  4. **稳定性:**不稳定,交换元素会导致原有数组元素位置变化。
  5. **是否原地排序:**无借助其他数组,是原地排序。
  6. 代码实现:
java 复制代码
    //选择排序
    public void selectSort(int[] nums) {
        //遍历元素
        for (int i = 0; i < nums.length - 1; i++) {
            //记录最小值
            int min = i;

            //遍历寻找未排序元素中最小值
            for (int j = i + 1; j < nums.length; j++) {
                if (nums[j] < nums[min]) {
                    min = j;
                }
            }
            //交换最小值
            int temp = nums[i];
            nums[i] = nums[min];
            nums[min] = temp;
        }
    }

2.冒泡排序

  1. **排序思路:**逆向遍历,每次遍历都把逆序对(前元素大于后元素)交换位置,直到排序完成。

  2. 时间复杂度:O(n2)

  3. 空间复杂度:O(1)

  4. **稳定性:**稳定,每次交换位置都只交换逆序对,不影响相等元素的相对位置。

  5. **是否原地排序:**无借助其他数组,是原地排序。

  6. 代码实现:

java 复制代码
    //冒泡排序
    void bubbleSort(int[] nums) {
        //冒泡排序次数判断,一共需要排n-1次
        int sorted = 0;
        while(sorted < nums.length-1) {
            //count用来记本次冒泡排序是否有数据交换,如果没有数据交换说明已经排序完成,直接返回就可以
            int count = 0;
            //开始冒泡,交换逆序列
            for(int j = nums.length - 2; j >= 0; j--) {
                if(nums[j] > nums[j+1]) {
                    int temp = nums[j];
                    nums[j] = nums[j+1];
                    nums[j+1] = temp;
                    count++;
                }
            }
            if(count == 0) {
                break;
            }
            sorted++;
        }
    }

3.插入排序

  1. **排序思路:**在待排序的元素中,假设前n-1个元素已经排列完,将第n个元素插入到已经拍好的序列中,使得前n个元素有序。

  2. 时间复杂度:O(n2)

  3. 空间复杂度:O(1)

  4. **稳定性:**稳定,每次排序交换都是整体移动,不影响元素相对位置。

  5. **是否原地排序:**无借助其他数组,是原地排序。

  6. 代码实现:

java 复制代码
    //插入排序
    void insertSort(int[] nums) {
        //初始化认为第0号元素已经有序,所以从1开始遍历
        for(int i = 1; i < nums.length; i++) {
            //超前遍历,找到他的位置,整体后移
            for(int j = i; j > 0; j--) {
                if(nums[j] < nums[j-1]) {
                    int temp = nums[j];
                    nums[j] = nums[j-1];
                    nums[j-1] = temp;
                }
                else {
                    break;
                }
            }
        }
    }

4.希尔排序

  1. **排序思路:**插入排序的优化版,先选取一个gap进行划分元素,对每组元素分别进行插入排序,然后gap递减,使得元素变得相对有序,再进行插入排序。
  2. 时间复杂度:小于O(n2),但是具体不好算
  3. 空间复杂度:O(1)
  4. **稳定性:**不稳定,分组进行交换的时候有可能对数组元素的相对位置进行打乱。
  5. **是否原地排序:**无借助其他数组,是原地排序。
  6. 代码实现:
java 复制代码
	//希尔排序
    void shellSort(int[] nums) {
        //初始化gap,在gap为1之前都在分组排序
        int gap = nums.length/4;
        while(gap > 0) {
            //与插入排序类似,分组排序
            for(int i = 1; i < nums.length; i++) {
                for(int j = i; j > 0; j=j-gap) {
                    if(j>=gap) {
                        if(nums[j] < nums[j-gap]) {
                            int temp = nums[j];
                            nums[j] = nums[j-gap];
                            nums[j-gap] = temp;
                        }
                    }

                }
            }
            //逐渐减小gap
            gap=gap/2;
        }

    }

5.快速排序

  1. **排序思路:**先排序好一个节点,再递归的排序剩下的节点,使每个节点左边都是小于他的数,右边都是大于他的数。

  2. 时间复杂度:O(nlogn)

  3. 空间复杂度:O(n)

  4. **稳定性:**不稳定,分组进行交换的时候有可能对数组元素的相对位置进行打乱。

  5. **是否原地排序:**借助二叉树排序,不是原地排序。

  6. 代码实现:

    1. 快慢指针法

      1. 选取最左元素left作为待排序元素

        1. 利用fast指针遍历数组,如果该元素大于初始元素,就让slow+1,再交换slow指针对应元素和fast指针对应元素
        2. fast遍历完成数组后,slow元素位置就是该待排序位置的排序位置。
        3. 递归遍历该元素左右区间。
      java 复制代码
      	 //快速排序--快慢指针法
          void quickSort(int[] nums, int left, int right) {
              //如果left==right 说明只包含1个元素,不需要排序
              if(left >= right) {
                  return;
              }
              //每次确定最左边的那个数
              int key = nums[left];
              int slow = left;
              int fast = left;
              //遍历数组,如果fast指针找到比key小的数字,就让slow+1,交换slow与fast的元素
              while(fast <= right) {
                  if(nums[fast] < key) {
                      slow++;
                      int temp = nums[slow];
                      nums[slow] = nums[fast];
                      nums[fast] = temp;
      
                  }
                      fast++;
      
              }
              //将初始数放在他的位置
              int temp = nums[left];
              nums[left] = nums[slow];
              nums[slow] = temp;
              //递归遍历左区间和右区间
              quickSort(nums, left, slow-1);
              quickSort(nums, slow+1, right);
          }
    2. 左右指针法

      1. 选取最左元素left为待排序元素。
      2. 设置begin和end来遍历元素,begin从左向右走,end从右向左走。
      3. end朝左走,若遇到比left小的元素,begin开始向右走,直到begin的元素比left大,交换begin元素值和end元素值。
      4. begin与end相交处就是该待排序元素的排序位置。
      5. 递归遍历该元素的左右区间
      java 复制代码
      	 //快速排序--左右指针法
          void quickSort(int[] nums, int left, int right) {
              //如果left==right 说明只包含1个元素,不需要排序
              if(left >= right) {
                  return;
              }
              //每次确定最左边的那个数
              int key = nums[left];
              int begin = left;
              int end = right;
              //遍历左右指针
              while(begin < end) {
                 while(begin < end && nums[end] >= key) {
                     end--;
                 }
                 while(begin < end && nums[begin] <= key) {
                     begin++;
                 }
                  int temp = nums[begin];
                  nums[begin] = nums[end];
                  nums[end] = temp;
              }
              //将begin位置即初始元素排序后位置
              int temp = nums[begin];
              nums[begin] = nums[left];
              nums[left] = temp;
              //递归遍历左区间和右区间
              quickSort(nums, left, begin-1);
              quickSort(nums, begin+1, right);
          }
    3. 挖坑法

      1. 选取最左元素为待排序元素,存储在变量key中,最左元素为初始化坑位。
      2. 设置begin和end指针,分别指向最左元素和最右元素。
      3. end指针向左走,直到所指元素小于key,将end指针元素放坑位中,end指针处形成新坑位。
      4. begin指针开始向右走,直到所指元素大于key,将begin指针元素放到现在坑位中,begin指针处形成新坑位。
      5. 不断循环3,4,直到begin与end相遇,此时将key的值填入即可,左侧均小于key,右侧均大于key。
      java 复制代码
       	//快速排序--挖坑法
          void quickSort(int[] nums, int left, int right) {
              //如果left==right 说明只包含1个元素,不需要排序
              if(left >= right) {
                  return;
              }
              //每次确定最左边的那个数
              int key = nums[left];
              int begin = left;
              int end = right;
              //挖坑法确定key的位置
              while(begin < end) {
                  //end指针左移,第一个小于key的值为新坑
                  while(end > begin && nums[end] >= key) {
                      end--;
                  }
                  //旧坑位填入值
                  nums[begin] = nums[end];
                  //begin指针右移,第一个大于key的值为新坑
                  while(begin < end && nums[begin] <= key) {
                      begin++;
                  }
                  //旧坑位填入值
                  nums[end] = nums[begin];
              }
              //当前坑位就是key的位置
              nums[begin] = key;
              //递归遍历左区间和右区间
              quickSort(nums, left, begin-1);
              quickSort(nums, begin+1, right);
          }

6.堆排序

  1. 堆的定义完全二叉树 + 数组顺序存储 (下表从1开始:父节点i:左孩子2i,右孩子2i+1)

    大顶堆 :父节点大于等于子节点 小顶堆:父节点小于等于子节点

  2. **排序思路:**选择排序的一种,升序用大顶堆,降序用小顶堆。首先将待排序的数组构造成一个大顶堆,此时整个数组的最大值就是根节点元素。将根节点与末尾交换,此时末尾为最大值,剩余待排序元素为n-1。将剩余n-1个元素再构造成大顶堆,再与第n-1的数交换,不断循环,直到排序完成。

  3. 时间复杂度:O(n+nlogn)

  4. 空间复杂度:O(1)

  5. **稳定性:**不稳定,交换堆顶元素会导致原有数组元素位置变化。

  6. **是否原地排序:**无借助其他数组,是原地排序。

  7. 代码实现:

    1. 建堆:倒着将每个节点为根的子树调整成堆,从最后一个非叶节点开始依次向下调整。如果父节点小于子节点,交换父子节点,继续遍历。

    2. 排序:每轮的堆顶都放在最后,向下调整新的堆顶。(n-1轮)

      java 复制代码
      	//堆排序--大顶堆
          public void heapSort(int[] nums) {
              int len = nums.length;
      
              //构建大顶堆
              for(int i = len/2 - 1; i >= 0; i--) {
              heapAdjust(nums, i, len-1);
              }
      
              //调整排序
             for(int i = 0; i < len-1; i++) {
                 //每次都将根结点放在最后,调整堆
                  int temp = nums[0];
                  nums[0] = nums[len-i-1];
                  nums[len-i-1] = temp;
                  heapAdjust(nums, 0, len-1-i-1);
              }
          }
      
          //向下调整堆
          public void heapAdjust(int[] nums,int begin, int end) {
              int temp = nums[begin];
              for(int i = 2*begin+1; i <= end; i = 2 * i +1) {
                  //存在右孩子且右孩子大于左孩子,将i+1指向右孩子,保证i始终指向的是左右孩子中较大的那个
                  if(i<end && nums[i] <nums[i+1]) {
                      i++;
                  }
                  //如果孩子节点大于父亲节点,交换孩子节点与父亲节点
                  if(nums[i] > temp) {
                      nums[(i-1)/2] = nums[i];
                      nums[i] = temp;
                  }
      
              }
      
          }

7.归并排序

  1. **排序思路:**将数组拆分为长度为1的多个小数组,递归的将相邻两个数组组合,直至归并为已排序数组。

  2. 时间复杂度:O(nlogn)

  3. 空间复杂度:O(n)

  4. **稳定性:**稳定,拆分数组以及数组合并都不改变相等元素的相对位置。

  5. **是否原地排序:**借助其他数组,不是原地排序。

  6. 代码实现:

    1. 拆分数组为多个长度为1的子数组。
    2. 将相邻的子数组合并排序。
    java 复制代码
        //归并排序
        public void mergeSort(int[] nums, int left, int right) {
            //将数组递归拆分并且合并
            if (left < right) {
                //递归拆分数组
                int mid = left + (right - left) / 2;
                mergeSort(nums, left, mid);
                mergeSort(nums, mid + 1, right);
                merge(nums, left, mid, right);
            }
        }
    
        //合并排序数组
        public void merge(int[] nums, int left, int mid, int right) {
            //借助新数组进行帮助排序
            int[] temp = new int[right - left + 1];
            //i为左数组的起点,j为右数组的起点,k为新数组temp的起点
            int i = left;
            int j = mid + 1;
            int k = 0;
            //比较左数组和右数组,谁小谁加入temp
            while (i <= mid && j <= right) {
                if (nums[i] <= nums[j]) {
                    temp[k] = nums[i];
                    k++;
                    i++;
                } else {
                    temp[k] = nums[j];
                    k++;
                    j++;
                }
            }
            //将左数组和右数组的剩余部分全部加入temp数组
            while (i <= mid) {
                temp[k] = nums[i];
                k++;
                i++;
            }
            while (j <= right) {
                temp[k] = nums[j];
                k++;
                j++;
            }
            //将temp数组全部还原到原数组中
            for (i = left, j = 0; i <= right; i++, j++) {
                nums[i] = temp[j];
            }
        }

8.计数排序

  1. **排序思路:**通过计数而不是比较进行排序,记录每个元素出现的次数,再依次填入

  2. 时间复杂度:O(n+k)

  3. 空间复杂度:O(n+k)

  4. **稳定性:**不稳定,会打乱相同元素的相对位置。

  5. **是否原地排序:**非原地排序

  6. 代码实现:

    1. 遍历数组,找到最大元素max,根据max的值创建计数数组来储存各个元素的值。
    2. 遍历数组,将该元素的次数填入计数数组。
    3. 根据计数数组,统计累计数组。
    4. 按照累计数组进行排序。
    java 复制代码
    	//计数数组
        public void countSort(int[] nums) {
            //遍历寻找最大值,确定计数数组的范围
            int max = nums[0];
            for(int i = 1; i < nums.length; i++) {
                if(max < nums[i]) {
                    max = nums[i];
                }
            }
            int[] count = new int[max+1];
            //开始计数
            for(int i = 0; i < nums.length; i++) {
                count[nums[i]]++;
            }
            //根据计数数组,计算累计数组
            for(int i = 1; i < count.length; i++) {
                count[i] = count[i-1]+count[i];
            }
            //根据累计数组进行计数排序,倒序遍历保证排序稳定性
            int[] result = new int[nums.length];
            for(int i = nums.length - 1; i >= 0; i--) {
                result[count[nums[i]]-1]=nums[i];
                count[nums[i]]--;
            }
            //将排序后数组复制到原数组中
            for(int i = 0; i < result.length; i++) {
                nums[i] = result[i];
            }
        }

9.桶排序

  1. **排序思路:**将整个数组分为n个相等大小的子区间(桶),对每个桶内的数进行排序,然后直接遍历桶。
  2. 时间复杂度:O(n+k)
  3. 空间复杂度:O(n+k)
  4. **稳定性:**稳定,按顺序对相等元素进行操作,不改变相对位置。
  5. **是否原地排序:**否,借助多个子区间进行排序
  6. 代码实现:
    1. 设置定量数组作为桶的容量大小。
    2. 遍历数组,将数据放入对应的桶中。
    3. 对非空桶进行排序。
    4. 合并桶中数据。
java 复制代码
    //桶排序
    public void bucketSort(int[] nums) {
        //设置定量数组作为桶的容量大小。
        int bucketSize = 10;
        List<List<Integer>> buckets = new ArrayList<List<Integer>>(bucketSize);
        for (int i = 0; i < bucketSize; i++) {
            buckets.add(new ArrayList<>());
        }
        //遍历数组,将数据放入对应的桶中。
        for (int i = 0; i < nums.length; i++) {
            int index = nums[i] / bucketSize;
            buckets.get(index).add(nums[i]);
        }

        //对非空桶进行排序。
        for (int i = 0; i < bucketSize; i++) {
            Collections.sort(buckets.get(i));
        }
        //按照桶的顺序进行赋值。
        List<Integer> result = new ArrayList<>();
        for (int i = 0; i < bucketSize; i++) {
            for (int j = 0; j < buckets.get(i).size(); j++) {
                result.add(buckets.get(i).get(j));
            }
        }
        //复制List到int[]
        for (int i = 0; i < result.size(); i++) {
            nums[i] = result.get(i);
        }

    }

10.基数排序

  1. **排序思路:**通过逐步处理数字来进行排序,通过位运算比较每个数字的位值来排序。
  2. 时间复杂度:O(d(n + k))
  3. 空间复杂度:O(n+k)
  4. **稳定性:**稳定,因为每次排序都是按顺序进行位运算,不影响想等元素的相对位置。
  5. **是否原地排序:**利用额外数组存储位信息,不是原地排序。
  6. 代码实现:
    1. 计算最大位的长度,判断循环比较次数d。
    2. 从最低位到最高位依次对每一位进行计数排序。
java 复制代码
 	//基数排序
    public void radixSort(int[] nums) {
        //计算最大值max,max的位数就是最高位
        int max = nums[0];
        for (int i = 1; i < nums.length; i++) {
            if (max < nums[i]) {
                max = nums[i];
            }
        }

        //循环处理最高位数字,n/1%10
        for (int exp = 1; max / exp > 0; exp = exp * 10) {
            //计数排序
            int[] count = new int[10];
            for (int i = 0; i < nums.length; i++) {
                count[nums[i] / exp % 10]++;
            }
            //计算累计数组
            for (int i = 1; i < count.length; i++) {
                count[i] = count[i - 1] + count[i];
            }
            //根据累计数组进行排序,注意原数组与位数字的对应关系
            int[] result = new int[nums.length];
            for (int i = result.length - 1; i >= 0; i--) {
                result[count[nums[i] / exp % 10] - 1] = nums[i];
                count[nums[i] / exp % 10]--;
            }

            //复制result到原数组中
            for (int i = 0; i < result.length; i++) {
                nums[i] = result[i];
            }
        }

    }
相关推荐
有梦想的骇客4 小时前
书籍将正方形矩阵顺时针转动90°(8)0605
线性代数·算法·矩阵
有梦想的骇客4 小时前
书籍“之“字形打印矩阵(8)0609
java·算法·矩阵
Chenyu_3105 小时前
12.找到字符串中所有字母异位词
c语言·数据结构·算法·哈希算法
苏三福5 小时前
yolo11-seg ultralytics 部署版本
算法·yolo11
wuqingshun3141598 小时前
蓝桥杯 冶炼金属
算法·职场和发展·蓝桥杯
jndingxin9 小时前
OpenCV CUDA模块光流计算-----实现Farneback光流算法的类cv::cuda::FarnebackOpticalFlow
人工智能·opencv·算法
编程绿豆侠9 小时前
力扣HOT100之栈:394. 字符串解码
java·算法·leetcode
朝朝又沐沐10 小时前
基于算法竞赛的c++编程(18)string类细节问题
开发语言·c++·算法
爱coding的橙子10 小时前
每日算法刷题Day27 6.9:leetcode二分答案2道题,用时1h20min
算法·leetcode·职场和发展