十大排序算法

(前言:嗯,之前是学过一点排序算法的,比如说快排,归并,插入排序,冒泡排序什么的,但是有好多学校也没教,自己学的也不扎实,这次自己一边慢慢写,一边教自己一边,努力把这些搞懂,但我肯定一天是写不完的,所以要多等)

1 算法分类

1.1 比较排序

1.2 非比较排序

复杂度

2023年08月30日 16:50:20 续

2 十大排序算法

2.1 冒泡排序

就不搞那些虚的了,写的前面那些也要不都是百度贴的
冒泡排序,我的印象里的就是两两一次对比换顺序,时间复杂度好高,思想倒是挺简单的,但是代码要让我自己写也不一定写多好,但别的文章里的图都挺好的,还总出现,所以可以直接引用一波
啊,对时间复杂度还是要说一下的
时间复杂度: 最佳情况:T(n) = O(n) 最差情况:T(n) = O(n2) 平均情况:T(n) = O(n2)

//啊,如我所说,很简单
   public static int[] bubbleSort(int[] array) {
       if (array.length == 0)
           return array;
       for (int i = 0; i < array.length; i++)
           for (int j = 0; j < array.length - 1 - i; j++)
               if (array[j + 1] < array[j]) {
                   int temp = array[j + 1];
                   array[j + 1] = array[j];
                   array[j] = temp;
               }
       return array;
   }

2.2 选择排序

先贴个图

2.2.1原理

遍历一遍找到未排序的最小的,再从剩余的里遍历找最小的放到起始位置(第二遍的起始位置),之后同理

2.2.2复杂度

时间复杂度最佳情况:T(n) = O(n2) 最差情况:T(n) = O(n2) 平均情况:T(n) = O(n2)

2.2.3代码

代码都差不多,我就粘一下

   public static int[] selectionSort(int[] array) {
        //数组长度为零情况
       if (array.length == 0)
           return array;
        //遍历找最小
       for (int i = 0; i < array.length; i++) {
           int minIndex = i;
            //依次向前
           for (int j = i; j < array.length; j++) {
               if (array[j] < array[minIndex]) //找到最小的数
                   minIndex = j; //将最小数的索引保存
           }
           int temp = array[minIndex];
           array[minIndex] = array[i];
           array[i] = temp;
       }
       return array;
   }

2023年08月31日 19:09:29

2.3 插入排序

2.3.1 原理

举例:整理桥牌,将每一张牌插入到其他已经有序的牌的适当位置,为了给要插入的元素腾出空间,需要将其余所有元素再插入前都右移动一位
个人理解*:就是从第一个开始,往前遍历,找到自己第一个大于的,然后把这个位置之后的元素后移一位,把自己插入进来,嗯

2.3.2 复杂度

最佳情况:T(n) = O(n) 最坏情况:T(n) = O(n2) 平均情况:T(n) = O(n2)

2023年09月01日 14:54:12

2.3.3 代码

public class Insertion{
  public static void main(String[]args){
    int N = a.length;
    for(int i=0; i < N; i++){
      for(int j=i; j>0 && less(a[j], a[j-1]); j--){
        exch(a, j, j-1);
      }
    }    
  }
  private static boolean less(Comparable v, Comparable w){
    return v.compareTo(w) < 0;
  }
  private static boolean exch(Comparable[] a, int i, int j){
    Comparable t=a[i]; a[i]=a[j]; a[j]=t;
  }
}

再来个简洁版本

public class Insertion {
    public static void main(String[] args) {
        Comparable[] a = {9, 5, 2, 7, 1};
        int N = a.length;
        
        for (int i = 0; i < N; i++) {
            Comparable temp = a[i];
            int j = i;
            while (j > 0 && temp < a[j - 1]) {
                a[j] = a[j - 1];
                j--;
            }
            a[j] = temp;
        }
        for (int i = 0; i < a.length; i++) {
          Comparable num = a[i];
          System.out.print(num + " ");
        }
    }
}

2.3.3.1 关于代码的一些问题

  1. 当使用.compareTo()进行比较时,它会返回一个整数值,用于表示两个对象之间的关系:如果a.compareTo(b)的结果小于0,表示a小于b。如果a.compareTo(b)的结果等于0,表示a等于b。如果a.compareTo(b)的结果大于0,表示a大于b。
  2. Comparable num : a 这是什么意思? Comparable num : a 是 Java 中的增强 for 循环语法,用于遍历数组 a 中的元素,并将每个元素赋值给变量 num。跟for循环差不多。
  3. 为什么要用Comparable呢? 使用 Comparable 是为了实现通用的比较功能。Comparable 是一个接口,它定义了一个 compareTo 方法,用于比较两个对象的大小关系。 通过使用 Comparable,我们可以在排序算法中比较不同类型的对象,而不仅仅局限于整数或字符串。这使得代码更加灵活和通用,可以适用于各种类型的数据。 在这段代码中,数组 a 中的元素被声明为 Comparable 类型,这意味着数组中的元素必须实现 Comparable 接口,并提供 compareTo 方法的实现。这样,我们可以使用 compareTo 方法来比较数组中的元素,并根据比较结果进行排序。

插入排序总结

熟悉原理之后还是挺简单的嘛,但是可能是java基础不好,书里的一些例子方法我都不懂,而且懂了原理代码实现的能力也比较弱。加油吧

2.4 希尔排序

前言:哇,这个我要么是没听过,要么是忘了,反正没印象。。。

2.4.1 原理&思想

1959年Shell发明,第一个突破O(n2)的排序算法,是简单插入排序的改进版。它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫缩小增量排序(我就直接复制了,这个写的挺好)
思想是使数组中任意间隔为h的元素都是有序的,这样的h数组被称为h有序数组。换句话说,一个有序数组就是h个相互独立的有序数组编织起来组成的一个数组。


(图肯定是偷(借用)的)

2.4.2 实现步骤

  1. 确定数组长度n,并决定分组长度n/2
  2. 进行分组的插入排序
  3. 在(n/2)/2上进行插入排序,在进行/2直到为1为止

2.4.3 复杂度

复杂度*:最佳情况:T(n) = O(nlog2 n) 最坏情况:T(n) = O(nlog2 n) 平均情况:T(n) =O(nlog2n)

2.4.4 代码

public class shell{
   public static int[] ShellSort(int[] array) {
       int len = array.length;
       int temp, gap = len / 2;
       while (gap > 0) {
           for (int i = gap; i < len; i++) {
               temp = array[i];
               int preIndex = i - gap;
               while (preIndex >= 0 && array[preIndex] > temp) {
                   array[preIndex + gap] = array[preIndex];
                   preIndex -= gap;
               }
               array[preIndex + gap] = temp;
           }
           gap /= 2;
       }
       return array;
   }
}

2.4.4.1 对于代码的解释

  1. 首先,获取待排序数组的长度,并初始化一个变量 gap,表示步长,初始值为数组长度的一半。
  2. 进入循环,当步长大于0时进行排序。
  3. 在每一次排序中,从步长 gap 开始,遍历数组。
  4. 将当前位置的元素保存到临时变量 temp 中。
  5. 定义一个变量 preIndex,表示当前元素的前一个元素的索引位置(初始值为 i - gap)。
  6. 在内部循环中,判断 preIndex 的值是否大于等于0,且当前位置 array[preIndex] 的值是否大于 temp。如果满足条件,说明前一个元素比当前元素大,需要将前一个元素后移 gap 个位置。
  7. 将前一个元素后移 gap 个位置。
  8. 更新 preIndex 的值,继续向前查找。
  9. 将 temp 插入到正确的位置,即 preIndex + gap。
  10. 完成一次子序列的插入排序后,继续减小步长 gap。
  11. 重复步骤3-10,直到步长 gap 为1,即最后一次进行完整的插入排序。
  12. 返回排序后的数组。

实例*
假设我们有一个待排序的数组 [9, 5, 2, 7, 1]

  1. 初始时,步长 gap 为数组长度的一半,即 2。
  2. 第一次排序,按照步长 2 进行分组,得到 [9, 2] 和 [5, 7] 和 [1] 这三个子序列。
  3. 对每个子序列进行插入排序,得到 [2, 9] 和 [5, 7] 和 [1]。
  4. 此时,数组变为 [2, 5, 9, 7, 1]
  5. 第二次排序,步长 gap 减半,变为 1。
  6. 对整个数组进行插入排序,得到 [1, 2, 5, 7, 9]。
  7. 最终,数组排序完成。

2023年09月02日 21:08:02·

2.5 归并排序

分而治之

2.5.1 原理&思想

放个图先


(没错,图是借用的)
要将一个数组排序,可以先(递归的)将它分为两半分别排序,然后归并结合起来

优点:能够将任意长度为N的数组排序所需时间和NlogN成正比
缺点:所需的额外空间和N成正比

2.5.2 复杂度

最佳情况:T(n) = O(n) 最差情况:T(n) = O(nlogn) 平均情况:T(n) = O(nlogn)

2.5.3 代码

   public static int[] MergeSort(int[] array) {
       if (array.length < 2) return array;
       int mid = array.length / 2;
       int[] left = Arrays.copyOfRange(array, 0, mid);
       int[] right = Arrays.copyOfRange(array, mid, array.length);
       return merge(MergeSort(left), MergeSort(right));
   }
  
    // 归并排序——将两段排序好的数组结合成一个排序数组
 
   public static int[] merge(int[] left, int[] right) {
       int[] result = new int[left.length + right.length];
       for (int index = 0, i = 0, j = 0; index < result.length; index++) {
           if (i >= left.length)
               result[index] = right[j++];
           else if (j >= right.length)
               result[index] = left[i++];
           else if (left[i] > right[j])
               result[index] = right[j++];
           else
               result[index] = left[i++];
       }
       return result;
   }

还没怎么看完,看书上还有原地归并,自顶向下的归并,自底向上的归并

顺便想到分治,把涉及到分治的提一嘴

二分搜索,大整数乘法,Strassen矩阵乘法,棋盘覆盖,合并排序,快速排序,线性时间选择,最接近点对问题,循环赛日程表,汉诺塔
(脖子有点不得劲,明天上课摸鱼写一写)

2023年09月04日 10:06:20
我觉得应该举个实例

假设输入的数组为[4, 2, 7, 1, 9, 5],首先调用MergeSort方法。
第一次递归:
array为[4, 2, 7],将其分成两个子数组left为[4],right为[2, 7]。
继续递归调用MergeSort方法,对left和right进行排序。
left为[4],直接返回。
right为[2, 7],继续递归调用MergeSort方法。
left为[2],直接返回。
right为[7],直接返回。
调用merge方法,将left和right合并为[2, 7],返回结果。
第二次递归:
array为[1, 9, 5],将其分成两个子数组left为[1],right为[9, 5]。
继续递归调用MergeSort方法,对left和right进行排序。
left为[1],直接返回。
right为[9, 5],继续递归调用MergeSort方法。
left为[9],直接返回。
right为[5],直接返回。
调用merge方法,将left和right合并为[5, 9],返回结果。
最后一次递归:
leftSorted为[2, 7],rightSorted为[5, 9]。
调用merge方法,将leftSorted和rightSorted合并为[2, 5, 7, 9],返回结果。
最终,MergeSort方法返回的排序数组为[1, 2, 4, 5, 7, 9]。

以下是一个简单的示例:
假设我们有两个已排序的子数组:
前一个数组:[1, 3, 5]
后一个数组:[2, 4, 6]
在合并时,我们将比较数组的第一个元素:
1 (来自前一个数组) < 2 (来自后一个数组)
因此,我们将 1 插入合并后的数组,并将前一个数组的下标向后移动一个位置:
合并后的数组:[1]
前一个数组:[3, 5]
后一个数组:[2, 4, 6]
我们继续比较数组的第一个元素:
3 (来自前一个数组) > 2 (来自后一个数组)
在这种情况下,我们交换这两个元素的位置:
合并后的数组:[1, 2]
前一个数组:[3, 5]
后一个数组:[3, 4, 6]
然后我们继续比较:
3 (来自前一个数组) < 3 (来自后一个数组)
相等的情况下,我们将元素按顺序插入合并后的数组,并将两个数组的下标都向后移动一个位置:
合并后的数组:[1, 2, 3]
前一个数组:[5]
后一个数组:[4, 6]
然后我们比较:
5 (来自前一个数组) < 4 (来自后一个数组)
合并后的数组:[1, 2, 3, 4]
前一个数组:[5]
后一个数组:[6]
比较:
5 (来自前一个数组) < 6 (来自后一个数组)
合并后的数组:[1, 2, 3, 4, 5]
前一个数组:[]
后一个数组:[6]
最后我们将剩下的元素直接插入合并后的数组中:
合并后的数组:[1, 2, 3, 4, 5, 6]
在每次合并时,我们始终比较两个数组的第一个元素,并根据大小进行合适的操作,以保持排序的正确性。

啊哈哈,举了两个,能理解就行,反正我是理解了~
哦哦,之后我还会在练习的时候把练习题的链接贴过来,nice

2.6 快速排序

2.6.1 原理

快速排序是一种分而治之的排序方法。它的工作原理是将数组分成两部分,然后独立地对部分进行排序。

快速排序很受欢迎,因为它不难实现,适用于各种不同类型的输入数据,并且比典型应用程序中的任何其他排序方法都要快得多。它是就地的(仅使用一个小的辅助堆栈),平均需要与 N log N 成比例的时间才能对 N 个项目进行排序,并且具有极短的内部循环。

上图!

该方法的关键是分区过程,该过程重新排列数组以使以下三个条件成立:

  • 条目 a[j] 在数组中的最终位置,对于某些 j
  • a[lo] 到 a[j-1] 中的任何条目都不大于 a[j]
  • 在 a[j+1] 到 a[hi] 中,没有条目小于 a[j]

2.6.2 步骤

  • 从数组中台哦出一个元素作为基准(pivot)
  • 重排数组,以pivot为基准,小的在其前,大的在其后,此操作名为分区
  • 递归地(recursive)把小于基准(pivot)元素的子数列和大于基准值元素的子数列排序。

2.6.3 复杂度

复杂度: 最佳情况:T(n) = O(nlogn) 最差情况:T(n) = O(n2) 平均情况:T(n) = O(nlogn)

2.6.4 代码

     /**
     * 快速排序
     * @param nums
     */
    public static void quickSort(int[] nums) {
        quickSort(nums,0,nums.length-1);
    }

    /**
     * 快速排序
     * @param nums
     * @param low
     * @param hight
     */
    private static void quickSort(int[] nums, int low, int hight) {
        if(low>hight){
            return;
        }
        int i=low;
        int j =hight;
        int temp =nums[i];
        while (i<j){
            //先排右部分数组
            while (nums[j]>=temp && i<j){
                j--;
            }
            //比基准小的放左边
            nums[i] = nums[j];
            //后排左部分数组
            while (nums[i]<=temp && i<j){
                i++;
            }
            //比基准大的放右边
            nums[j] = nums[i];

        }
        nums[i] = temp;
        quickSort (nums,low,j-1);
        quickSort (nums,j+1,hight);
    }

快下课了,等下午再仔细研究研究,顺便做点题

啊·~~~,下午好困。。。。。想摆,等我再想想

2023年09月07日 15:01:09

贴个快排的c++模板

void quick_sort(int q[], int l, int r)
{
    if (l >= r) return;

    int i = l - 1, j = r + 1, x = q[l + r >> 1];
    while (i < j)
    {
        do i ++ ; while (q[i] < x);
        do j -- ; while (q[j] > x);
        if (i < j) swap(q[i], q[j]);
    }
    quick_sort(q, l, j), quick_sort(q, j + 1, r);
}

2.6.5 hoare分区

2023年09月12日 09:19:08
先看了看代码,然后越看越不对劲,我就想,快排不是找privot嘛?这个算法怎么没有嘞?然后去了解了一波,原来这个东西叫Hoare分区算法,接下来我们在了解了解这个算法
为方便理解,我们举个栗子:假设我们有随机数组:[7, 2, 1, 6, 8, 5, 3, 4]
首先,选择数组的第一个元素 7 作为 pivot。然后,设置两个指针 i 和 j 分别指向数组的左边和右边:

i                  j
↓                  ↓
[7, 2, 1, 6, 8, 5, 3, 4]

接下来,j 开始向左移动,直到找到一个小于等于 pivot 的元素:

i              j
↓              ↓
[7, 2, 1, 6, 8, 5, 3, 4]

此时,j 停在了元素 3 上。然后,i 开始向右移动,直到找到一个大于等于 pivot 的元素:

     i           j
     ↓           ↓
[7, 2, 1, 6, 8, 5, 3, 4]

此时,i 停在了元素 6 上。接着交换 i 和 j 指向的元素:

     i           j
     ↓           ↓
[7, 2, 1, 3, 8, 5, 6, 4]

然后,j 再次向左移动,i 向右移动,继续找到需要交换的元素:

        i     j
        ↓     ↓
[7, 2, 1, 3, 8, 5, 6, 4]

再次进行交换:

        i     j
        ↓     ↓
[7, 2, 1, 3, 4, 5, 6, 8]

重复上述步骤,直到 i 和 j 相遇。在这个例子中,相遇的位置是 i = j = 4。此时,我们将 pivot 移动到相遇位置的地方:

             i/j
              ↓
[4, 2, 1, 3, 7, 5, 6, 8]

数组被 pivot 分成了两部分,左边的元素都小于等于 pivot,右边的元素都大于等于 pivot。
接下来,对左右两个子序列分别使用同样的 Hoare 分区算法进行递归排序。这里只展示左边的子序列的排序过程,右边的子序列同理:

         i    j
         ↓    ↓
[1, 2, 3, 4, 7, 5, 6, 8]

继续递归排序左边的子序列,直到排序完成。最终得到排序后的结果为:
[1, 2, 3, 4, 5, 6, 7, 8]
这样就完成了 Hoare 分区算法的示例演示。通过将数组分成两个部分并使用递归,我们可以在每个子序列上重复这个过程,从而实现整个数组的排序。
仔细看了看,上面打的不太好,再来个图方便理解*


好吧。。。。有这个图我也没怎么看懂,到时候再研究研究。。。。

接下来到哪个算法了?

2.7 堆排序

2.7.1 原理

  1. 堆结构就是用数组实现的完全二叉树结构
  2. 完全二叉树中如果每棵子树的最大值都在顶部就是大根堆
  3. 反之为小根堆
  4. 堆结构的heapinsert与heapify操作
    • heapinsert:新进入的元素都要去跟自己的父元素比较,如果大,就交换。时间复杂度和高度一致,O(logN)
    • heapify:取出最大值时,将最后一个元素放到根节点,然后将heapSize-1,将父节点与左右孩子比较,大的放在父节点,然后周而复始。时间复杂度和高度一致,O(logN)
    • 如果改变了堆中的一个值,先heapinsert然后heapify无脑完事
  5. 堆结构的增大与减小
  6. 优先级队列结构就是堆结构

2.7.1.1 堆结构的位置怎么找

名称 计算方法
左孩子 2*i+1
右孩子 2*i+2
父节点 (i-1)*2

2.7.2 基本思想&步骤

利用大顶堆(小顶堆)堆顶记录的是最大关键字(最小关键字)这一特性,使得每次从无序中选择最大记录(最小记录)变得简单。

  • 将待排序的序列构造成一个最大堆,此时序列的最大值为根节点
  • 依次将根节点与待排序序列的最后一个元素交换
  • 再维护从根节点到该元素的前一个节点为最大堆,如此往复,最终得到一个递增序列

才发现忘记上图

2.7.2.1 大顶堆与小顶堆

如他的名字一样

大顶堆就是大的在上,小顶堆就是小的在上

2.7.3 复杂度

时间复杂度:最佳情况:T(n) = O(nlogn) 最差情况:T(n) = O(nlogn) 平均情况:T(n) = O(nlogn)

2.7.4 代码

//声明全局变量,用于记录数组array的长度;
static int len;
   public static int[] HeapSort(int[] array) {
       len = array.length;
       if (len < 1) return array;
       //1.构建一个最大堆
       buildMaxHeap(array);
       //2.循环将堆首位(最大值)与末位交换,然后在重新调整最大堆
       while (len > 0) {
           swap(array, 0, len - 1);
           len--;
           adjustHeap(array, 0);
       }
       return array;
   }

   public static void buildMaxHeap(int[] array) {
       //从最后一个非叶子节点开始向上构造最大堆
       for (int i = (len - 1) / 2; i >= 0; i--) {
           adjustHeap(array, i);
       }
   }

   public static void adjustHeap(int[] array, int i) {
       int maxIndex = i;
       //如果有左子树,且左子树大于父节点,则将最大指针指向左子树
       if (i * 2+1 < len && array[i * 2+1] > array[maxIndex])
           maxIndex = i * 2+1;
       //如果有右子树,且右子树大于父节点,则将最大指针指向右子树
       if (i * 2 + 2 < len && array[i * 2 + 2] > array[maxIndex])
           maxIndex = i * 2 + 2;
       //如果父节点不是最大值,则将父节点与最大值交换,并且递归调整与父节点交换的位置。
       if (maxIndex != i) {
           swap(array, maxIndex, i);
           adjustHeap(array, maxIndex);
       }
   }

posted @   无聊的飞熊  阅读(77)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示