数据结构中的七大基本排序算法

在开始叙述排序之前, 我想先说明一个概念:
稳定性: 稳定性是评判一个排序方法的重要指标. 两个相等的数据, 如果经过排序后, 其相对位置不会发生改变, 那么我们称这个排序算法就是稳定的排序算法.
在这里插入图片描述
如图所示, 这就是一个稳定的排序.

1. 直接插入排序

每次选择无序区间的第一个元素, 在有序区间内选择合适的位置插入.
简单来说, 就像我们在打牌的时候, 在揭牌过程中整理牌的道理是一样的.

直接插入排序代码如下:

public static void insertSort(int[] array) {
       // i 表示当前需要进行排序的元素
       for (int i = 1; i < array.length; i++) {
           int tmp = array[i];
           // j 表示 i 之前的元素
           int j = i - 1;
           for (; j >= 0; j--) {
               if(array[j] > tmp) {
                   array[j + 1] = array[j];
               } else {
                   /*array[j + 1] = tmp;*/
                   break;
               }
           }
           // 如果 j < 0
           array[j + 1] = tmp;
       }
}

性能分析:

最好时间复杂度最坏时间复杂度空间复杂度稳定性
O(n)O(n^2)O(1)稳定的排序

对于直接插入排序而言, 数据越接近有序, 排序速度越快, 效率越高

2. 希尔排序

希尔排序是采用了分组的思想, 将原数组序列分为多组, 然后进行组内插入排序. 这也是直接插入排序的一种优化排序.
关于分组, 需要注意的是: 分组必须以素数的形式进行分组, 且最后一次分组只能为1组.
希尔排序代码如下:

public static void shellSort(int[] array, int gap) { // gap表示的是所分的组数
        // 希尔排序在组内其实就是一个直接插入排序
        for (int i = gap; i < array.length; i++) {
            int tmp = array[i];
            int j = i - gap;
            for (; j >= 0; j -= gap) {
                if(array[j] > tmp) {
                    array[j + gap] = array[j];
                } else {
                    break;
                }
            }
            array[j + gap] = tmp;
        }
}
public static void main1(String[] args) {
     int[] array = {12, 5, 9, 34, 6, 8, 33, 56, 89, 0, 7,  4, 22, 55, 77};
     System.out.println(Arrays.toString(array));
     int[] drr = {5, 3, 1};
     for (int i = 0; i < drr.length; i++) {
         shellSort(array, drr[i]);
     }
     System.out.println(Arrays.toString(array));
}

性能分析:

最好时间复杂度平均时间复杂度最坏时间复杂度空间复杂度稳定性
O(n)O(n^1.3) ~ O(n ^ 1.5)O(n^2)O(1)不稳定排序

3. 选择排序

每次从无序数据元素中选出最小(最大)的一个元素, 存放在无序数据元素的最前(后), 直到全部待排序的数据元素排完.
选择排序的代码如下:

public static void selectSort(int[] array) {
        for (int i = 0; i < array.length; i++) {
            for (int j = i + 1; j < array.length; j++) {
                if(array[j] < array[i]) {
                    int tmp = array[i];
                    array[i] = array[j];
                    array[j] = tmp;
                }
            }
        }
 }

性能分析:

时间复杂度空间复杂度稳定性
O(n^2)O(1)不稳定排序

4. 堆排序

其基本原理也是选择排序, 只不过通过堆来进行选择最大数/最小数.
注意: 要得到一个升序的序列就要建立一个大根堆, 得到一个降序的序列就要建立一个小根堆.
进行堆排序时需要做如下步骤: 1. 建堆. 2.调整堆(向上调整堆, 向下调整堆)
堆排序代码如下:

 // 向下调整
    public static void adjustDown(int[] array, int root, int len) {
        int parent = root;
        int child = parent * 2 + 1;
        // 说明最起码有左孩子
        while (child < len) {
            // 取左右孩子中值最大的孩子节点
            if(child + 1 < len && array[child] < array[child + 1]) {
                child = child + 1;
            }
            if(array[parent] < array[child]) {
                // 如果父节点的值小于孩子节点的值, 交换
                int tmp = array[parent];
                array[parent] = array[child];
                array[child] = tmp;
                // 需要进行进一步的比较
                parent = child;
                child = 2 * parent + 1;
            } else {
                break;
            }
        }
 }
    // 创建堆
 public static void createHeap(int[] array) {
      // 从最后一个不为叶子节点的节点开始进行向下调整
      for (int i = (array.length - 1 - 1) / 2 ; i >= 0; i--) {
          adjustDown(array, i, array.length);
      }
 }

性能分析:

时间复杂度空间复杂度稳定性
O(n*logn)O(1)不稳定排序

5. 冒泡排序

在无序区间间, 将相邻两数进行比较, 将最大的数冒泡到无序区间的最后, 持续这个过程, 直到数组整体有序
冒泡排序代码如下:

public static void bubbleSort(int[] array) {
        // 用 i 来控制冒泡排序的趟数
        for (int i = 0; i < array.length; i++) {
            // 用 j 来控制每趟冒泡比较的次数
            for (int j = 0; j < array.length - 1 - i; j++) {
                if(array[j] > array[j + 1]) {
                    int tmp = array[j];
                    array[j] = array[j + 1];
                    array[j + 1] = tmp;
                }
            }
        }
 }

性能分析:

最好时间复杂度平均时间复杂度最坏时间复杂度空间复杂度稳定性
O(n)O(n^2)O(n^2)O(1)稳定排序

6. 快速排序(重要)

在这里插入图片描述
思路:

  1. 先确认最左边(left)的值为基准值, 然后将它拿出来放进tmp中, 然后从最右边(right)的值开始与tmp的值进行比较, 若 < tmp 的值,就将其挪到左边空缺处.然后再转从左边(left)的值开始与 tmp 的值进行比较, 若> tmp 的值, 就将其挪到右边空缺处, 直到 left 与 right 相遇, 将 tmp 中的值放入相遇位置, 一次快速排序就结束了.
  2. 进行递归快排左边的数据和右边的数据
    快速排序代码如下:
// 找到基准位置par
   public static int partition(int[] array, int low, int high) {
       int tmp = array[low];
       while (low < high) {
           while (low < high && array[high] >= tmp) {
               high--;
           }
           array[low] = array[high];
           while (low < high && array[low] <= tmp) {
               low++;
           }
           array[high] = array[low];
       }
       // low == high
       array[high] = tmp;
       return high;
   }
   
   // 通过递归进行快速排序
   public static void quick(int[] array, int left, int right) {
       // 递归的结束条件
       if(left >= right) {
           return;
       }
       int par = partition(array, left, right);
       // 递归par左边的数组
       quick(array, left, par - 1);
       // 递归par右边的数组
       quick(array, par + 1, right);
   }
   // 快速排序
   public static void quickSort(int[] array) {
       quick(array, 0, array.length - 1);
}

快速排序的两种优化方式:

  1. 当数据元素逐渐趋于有序时, 就可以使用直接插入排序
  2. 快速排序的最坏情况是, 数据元素已经有序: 1 2 3 4 5 / 5 4 3 2 1, 这时可以采用三数取中的方法对最坏情况进行优化处理.
public static void swap(int[] array, int i, int j) {
       int tmp = array[i];
       array[i] = array[j];
       array[j] = tmp;
   }
   // 三数取中
   public static void three_nums_mid(int[] array, int left, int right) {
       // array[mid] <= array[left] <= array[right]
       int mid = (left + right) / 2;
       if(array[mid] > array[left]) {
           swap(array, mid, left);
       }
       if(array[mid] > array[right]) {
           swap(array, mid, right);
       }
       if(array[left] > array[right]) {
           swap(array, right, left);
       }
   }
 // 通过递归进行快速排序
   public static void quick(int[] array, int left, int right) {
       // 递归的结束条件
       if(left >= right) {
           return;
       }

       // 优化方式1: 当待排序序列中的数据个数小于一定的值之后, 就使用直接插入排序
       // right 和 left 之间相差 right - left + 1 个数
       if(right - left + 1 <= 100) {
           // 使用直接插入排序
           insertSort(array, left, right);
           return;
       }

       // 优化方式2: 三数取中方法
       three_nums_mid(array, left, right);


       int par = partition(array, left, right);
       // 递归par左边的数组
       quick(array, left, par - 1);
       // 递归par右边的数组
       quick(array, par + 1, right);
   }

性能分析:
在这里插入图片描述
稳定性: 不稳定排序

7. 归并排序

在这里插入图片描述
归并排序采用的也是分治法.
分解:
将数据元素序列从中间分为两部分, 每一部分也按照同样的方法进行分解, 直到每部分中只有一个数据元素即可, 这时每个部分都是有序的
合并:
将拆分出的每个有序数据部分进行合并, 由于数据都是有序的 , 因此合并出来的数据也是有序的, 合并到只有一个部分为止.
归并排序的代码如下:

    // 合并
    public static void merge(int[] array, int left, int mid, int right) {
        int s1 = left;
        int s2 = mid + 1;
        int len = right - left + 1;
        // 创建一个临时空间, 用于存放合并之后的元素分类
        int[] ret = new int[len];
        int i = 0; // i 表示 ret 数组的下标
        // 当两个分类都有元素时
        while (s1 <= mid && s2 <= right) {
            if(array[s1] <= array[s2]) {
                ret[i++] = array[s1++];
            } else {
                ret[i++] = array[s2++];
            }
        }
        while (s1 <= mid) {
            ret[i++] = array[s1++];
        }
        while (s2 <= right) {
            ret[i++] = array[s2++];
        }

        // 合并好的元素全部存放在 ret 数组中, 但是最后打印的却是 array 数组
        // 因此, 需要将 ret 数组中的元素拷贝到 array 数组中, 但是需要注意 ret 数组和 array 数组的下标
        for (int j = 0; j < ret.length; j++) {
            array[j + left] = ret[j];
        }
    }

    public static void merageSortInternal(int[] array, int left, int right) {
        // 递归的结束条件
        if(left >= right) {
            return;
        }
        // 1. 分解
        int mid = (left + right) >>> 1;
        // 左递归
        merageSortInternal(array, left, mid);
        // 右递归
        merageSortInternal(array, mid + 1, right);

        // 2. 合并
        merge(array, left, mid, right);
    }

    public static void mergeSort(int[] array) {
        merageSortInternal(array, 0, array.length -1);
    }

性能分析:

时间复杂度空间复杂度稳定性
O(n * logn)O(n)稳定排序

总结

在这七种基本的排序中
稳定的排序有: 直接插入排序, 冒泡排序, 归并排序
不稳定的排序有: 快速排序, 希尔排序, 堆排序, 选择排序.
最常用的排序为: 快速排序, 归并排序, 堆排序

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值