数据结构与算法回顾-2:排序算法小结

Objective-C实现冒泡排序算法的简单示例 简介 冒泡算法是一种基础的排序算法,这种算法会重复的比较数组中相邻的两个元素。如果一个元素比另一个元素大(小),那么就交换这两个元素的位置。重复这一比较直至最后一个元素。这一比较会重复n-1趟,每一趟比较n-j次,j是已经排序好的元素个数。每一趟比较都能找出未排序元素中最大或者最小的那个数字。这就如同水泡从水底逐个飘到水面一样。冒泡排序是一种时间复杂度较高,效率较低的排序方法。其空间复杂度是O(n)。 1, 最差时间复杂度 O(n^2) 2, 平均时间复杂度 O(n^2) 实现思路 1,每一趟比较都比较数组中两个相邻元素的大小 2,如果i元素小于i-1元素,就调换两个元素的位置 3,重复n-1 立即下载

在下面的排序算法中会使用到的方法:

    protected static void exchange(Comparable[] arr, int fromPos, int toPos) {
        Comparable temp = arr[fromPos];
        arr[fromPos] = arr[toPos];
        arr[toPos] = temp;
    }

    protected static boolean less(Comparable cmp1, Comparable cmp2) {
        return cmp1.compareTo(cmp2) < 0;
    }

exchange() 用于在将数组中的两个元素的位置交换;less() 用于比较两个元素的大小,如果cmp1 小于 cmp2 就返回 true. 这里我们将上述两个方法放在一个顶层的名为 Sort 的类中。

排序的稳定性:两个数 a 和 b,它们的大小相等。如果在排序之前,a 排在 b 前面,排序之后,a 仍然排在 b 前面,那么我们称这种排序算法是稳定的。

冒泡排序

   public static void sort(Comparable[] arr) { // 增序
       for (int i=0; i<arr.length-1; i++) {
           for (int j=i+1; j<arr.length; j++) {
               if (less(arr[j], arr[i])) {
                   exchange(arr, j, i);
               }
           }
       }
   }

所谓的冒泡该怎么理解呢?其实就是每次在数组中 i 后面的所有的元素中找到一个比 arr[i] 小的元素和 arr[i] 交换。如果我们将数组从 0 到尾竖着放置的话,那么我们就可以看到,数组中比较小的元素会一个个地被移动到数组的顶端上来,这操作看上去就像气泡向上浮动的过程,因此可以叫做冒泡排序。

不过,本质上来说,这个算法的复杂度和选择排序没有什么不同,不过就是每次从指定的元素后面的元素中选择一个最小的元素放在现在的位置。它的效率甚至比不上选择排序——毕竟交换的次数更多一些。

也难怪上面的排序算法效率如此低下,毕竟不是什么正宗的冒泡排序算法。下面的是正宗的冒泡排序算法:

   public static void sort(Comparable[] arr) { // 增序
       for (int i=0; i<arr.length-1; i++) {
           for (int j=arr.length-1; j>i; j--) {
               if (less(arr[j], arr[j-1])) {
                   exchange(arr, j, j-1);
               }
           }
       }
   }

这个算法和上面相比,看上去改动不是很大。主要是将内部的循环从 j 自增,改成了 j 自减的形式,而且比较和交换不再是针对 i 和 j 处的两个元素,而是 j 和 j-1. 可以这样理解:我们外层的循环从 0 开始至到倒数第二个元素,然后另一个循环从底部向上直到 i 处。每次比较的时候是 j 和 j 的前一个元素进行比较,交换的也是它们。

以上就是冒泡排序算法的图示,也就是在第二层循环中,我们从尾部开始找到一个元素,如果它比上面的元素小,我们就将其与上面的元素交换。这么做的效果是,我们在一次循环结束之后将最小的元素带到了最顶端。比如上面在将 2 带到 1 下面的时候,发现 1 比 2 小,此时就丢掉 2,继续带着 1 向上走。在将 1 移动到最顶端的同时,我们将 2 移动到了第 3 个位置。这也是正宗冒泡排序算法比普通冒泡排序算法高效的原因。

冒泡排序算法的优化

假如要排序的序列是 {2,1,3,4,5,6,7,8,9},也就是除了 2 和 1 都是有序的。如果按照上面的算法,我们在将 2 和 1 交换之后还要继续对循环进行遍历,而实际上这个时候整个序列已经是有序的了。因此,我们可以添加一个布尔类型的变量记录是否进行了交换,如果没有进行交换的话,也就意味着整个数组全部都有序了,因此也就没有必要再进行循环了。

   public static void sort(Comparable[] arr) { // 增序
       boolean exchanged = true; // 默认值为 true
       for (int i=0; i<arr.length-1 && exchanged; i++) { // 将布尔值加入到 for 条件中
           exchanged = false; // 开始循环的时候默认是 false
           for (int j=arr.length-1; j>i; j--) {
               if (less(arr[j], arr[j-1])) {
                   exchange(arr, j, j-1);
                   exchanged = true; // 当进行了交换的时候才设置为 true
               }
           }
       }
   }

冒泡排序算法的时间复杂度是 O(n2).

1、选择排序

1.1 过程

首先找到数组中的第一个元素交换,然后从剩下的元素中选择最小的和数组的第二个元素交换,如此往复,直到将整个数组排序。

1.2 代码

   public class Selection extends Sort{

       public static void sort(Comparable[] arr) { // 增序
           int length = arr.length;
           for (int i=0; i<length-1; i++) {
               int min = i;
               for (int j=i+1; j<length; j++) {
                   if (less(arr[j], arr[min])) {
                       min = j;
                   }
               }
               if (min != i) {
                   exchange(arr, min, i);
               }
           }
       }
   }

在这份代码中应该注意的地方是循环的边界。只要将这里的 less() 方法换成 bigger() 就可以实现按照降序的方式进行排序。

1.3 性能

  1. 运行时间和输入无关,即使数组有序也会一遍遍执行选择排序过程;
  2. 数据移动最少,因为每次排序的时候只交换两个元素的位置;
  3. 选择排序需要 N2/2 次比较和 N 次交换。
  4. 时间复杂度是 O(n2).

2、插入排序

1.1 过程

插入排序遍历的开始位置是下标为 1 的元素,它从下标 1 开始向 0 依次比较相邻的两个元素,如果第 j-1 个元素大于第 j 个元素就交换两者的位置,然后再将位置移动到 2、3…… 按照这样的方式依次进行比较。它能保证每轮比较完毕之后第 i 个元素之前的元素都是按照顺序(这里是增序)的方式排列的。从而保证当比较完毕最后的元素的时候整个数组都是按照增序的方式排列。

下面是插入算法的实现方式:

结合下面的代码来看,这里所谓的“插入”就是靠内部的循环来实现的。它的效果就像是我们将指定位置 i 的元素插入到了指定的位置,而实际上“插入”是通过对比和移动来实现的。

如果我们对一副乱掉的扑克牌进行排序,那我们可能使用的就是插入算法——每次从后面挨个找出比较小的元素插入到前面适合的位置中去。

1.2 代码

   public class Insert extends Sort{
   
       public static void sort(Comparable[] arr) {
           int length = arr.length;
           for (int i=1; i<length; i++) {
               for (int j=i; j>0 && less(arr[j], arr[j-1]); j--) {
                   exchange(arr, j, j-1);
               }
           }
       }
   }

注意循环的边界,以及第二个循环的条件。

1.3 性能

  1. 当倒置(也就是与我们期望顺序相反)很少的时候,插入排序的顺序可以很快,因为它依赖于输入数组中的元素的顺序;
  2. 最坏情况下需要 N2/2 次比较和 N2/2 次交换。
  3. 时间复杂度是 O(n2).

3、希尔排序

3.1 过程

仔细去看希尔排序的时候它的逻辑和插入排序相似,区别在于它不只对相邻的两个元素进行排序,而是先对整个数组的局部进行调整,方式是将指定间隔的元素按照顺序进行排列,然后不断缩小间隔,直到1,最终使用插入排序的方式完成最终的排序。

以下是希尔排序的一个实现过程,这里使用每次取一半的方式来缩小间隔。而在代码中,我们使用每次取3分之一的方式来缩小间隔,但是基本的算法思路是一致的:

3.2 代码

   public class Shell extends Sort{
   
       public static void sort(Comparable[] arr) {
           int length = arr.length;
           int h = 1;
           while (h < length / 3) {
               h = 3 * h + 1; // h的值可以是1,4,13,40……
           }
           while (h >= 1) {
               for (int i=h; i<length; i++) {
                   for (int j=i; j>=h && less(arr[j], arr[j-h]); j-=h) {
                       exchange(arr, j, j-h);
                   }
               }
               h /= 3;
           }
       }
   }

如上面的代码,在循环中我们每次执行 h/=3,这样的效果是先对数组的局部进行排序。比如,如果数组有 16 个元素的话,那么开始循环时 h=13,在循环中达到的效果是将间隔为 13 的元素分别作为一组进行排序;然后,将间隔为 4 的元素分别作为一组元素进行排序;最后将间隔为 1 的元素作为 1 组进行排序,也就达到了插入排序的效果。但是,它能够提升性能的地方就在于,经过之前的局部排序处理之后,整个数组不再是没有顺序的了,因此也就可以提升插入排序的效率。

3.3 性能

  1. 希尔排序可以应用于大型数组,并且数组越大优势越明显(相比于插入排序)
  2. 最坏时间复杂度依然为 O(n2),一些经过优化的增量序列如 Hibbard 经过复杂证明可使得最坏时间复杂度为 O(n3/2)。

4、归并排序

4.1 过程

要将一个数组排序,可以先(递归地)将它分成两半分别排序,然后将结果归并起来。它是基于分治的思想:

从上图中可以看出,它先将一个数组分成两份,然后再将子数组分成两份……最后只需要对两个元素进行排序,并将排序的结果合并不断地起来。最终,它可以得到原数组的排序结果。

4.2 代码

下面是基于 Java 的实现的一份归并排序的算法,这里使用了一个辅助数组 aux:

   public class Merge extends Sort{
   
       private static Comparable[] aux;
   
       public static void sort(Comparable[] arr) {
           aux = new Comparable[arr.length];
           sort(arr, 0, arr.length - 1);
       }
   
       private static void sort(Comparable[] arr, int lo, int hi) {
           if (hi <= lo) {
               return;
           }
           int mid = lo + (hi - lo) / 2;
           sort(arr, lo, mid);
           sort(arr, mid + 1, hi);
           merge(arr, lo, mid, hi);
       }
   
       private static void merge(Comparable[] arr, int lo, int mid, int hi) {
           int i = lo, j = mid + 1;
           System.arraycopy(arr, lo, aux, lo, hi + 1 - lo);
           for (int k=lo;k<=hi;k++) {
               if (i>mid) {
                   arr[k] = aux[j++]; // 如果游标i超过了mid就将游标j的剩余元素赋值给arr即可
               } else if (j > hi) { 
                   arr[k] = aux[i++]; // 如果游标j超过了hi就将游标i的剩余元素赋值给rr即可
               } else if (less(aux[j], aux[i])) {
                   arr[k] = aux[j++]; // 将游标i和游标j中较小的赋值给arr
               } else {
                   arr[k] = aux[i++];
               }
           }
       }
   }

对于 merge() 方法,我们使用如下的一个数组来进行分析:

[i][ ][ ][ ][ ][ ][ ][ ][mid][j][ ][ ][ ][ ][ ][ ][ ]

在每次调用 merge() 方法的时候会先将传入数组 arr 拷贝到辅助数组 aux,这里使用了 System.arraycopy() 方法实现。然后,我们可以将 i 和 j 看成是两个游标,它们在数组中从左向右移动,上面 merge() 方法的功能是:先将下标 i 和下标 j 中较小的元素赋值给 arr,赋值之后游标向右移动。如果两个游标中的哪个超出了范围的上限,那么将另一个游标剩下的元素全部赋值给 arr 即可。所以,这段代码的功能就是将两个数组的所有元素按照从小到大的顺序合并到一个数组中。

在 sort() 方法中,我们使用了递归的方式每次都将数组分成两半进行排序,然后将排序的结果合并成一个数组。因此,上面排序的最终效果是:以长度为 16 的数组 a 为例,最终效果是:

  1. 将 a[0] 和 a[1] 排序,a[2] 和 a[3] 排序,并将结果合并成 a[0…3];
  2. 将 a[4] 和 a[5] 排序,a[6] 和 a[7] 排序,并将结果合并成 a[4…7];
  3. 将 a[0…3] 和 a[4…7] 的结果合并成 a[0…7];
  4. 同理,将 a[8…15] 进行排序;
  5. 将 a[0…7] 和 a[8…15] 的排序结果合并成 a[0…15],即完成了数组的全部排序。

最终的排序都变成了相邻的两个元素之间的排序再合并。

两外,自底向上的排序方法:

   public static void sort(Comparable[] arr) {
       aux = new Comparable[arr.length];
       int length = arr.length;
       for (int sz=1; sz<length; sz*=2) { // sz:子数组的大小
           for (int lo=0; lo<length-sz; lo+=sz*2) { // lo:子数组的索引
               merge(arr, lo, lo+sz-1, Math.min(lo+2*sz-1, length-1));
           }
       }
   }

下面是自底向上排序算法的执行过程:

图 自底向上排序算法的执行过程

所谓的自底向上的意思是先将数组梅两两之间进行比较,然后将比较的结果合并,四四之间合并,八八合并,最终得到整体的排序结果。

4.3 性能

  1. 对于长度为 N 的任意数组,自顶向下的归并排序需要 (NlgN)/2 至 NlgN 次比较;
  2. 归并排序的主要缺点是辅助数组的额外空间和N的大小成正比;
  3. 可以考虑对如上的排序算法进行优化。
  4. 从上文可看出,每次合并操作的平均时间复杂度为 O(n),而完全二叉树的深度为 |log2n|。总的平均时间复杂度为 O(nlogn)。而且,归并排序的最好,最坏,平均时间复杂度均为 O(nlogn)。

5、快速排序

5.1 过程

快速排序是一种分治排序算法,它将一个数组分成两个子数组,将两部分分别进行排序。它和归并排序的区别是:归并排序需要将两个数组归并以合成一个完整的数组,而快速排序只要保证两个子数组都有序,整个数组也就有序了。

5.2 代码

下面是一份使用Java实现的快速排序的算法:

   public class Quick extends Sort{
   
       public static void sort(Comparable[] arr) {
           sort(arr, 0, arr.length - 1);
       }
   
       private static void sort(Comparable[] arr, int lo, int hi) {
           if (hi <= lo) {
               return;
           }
           int j = partition(arr, lo, hi);
           sort(arr, lo, j - 1);
           sort(arr, j + 1, hi);
       }
   
       private static int partition(Comparable[] arr, int lo, int hi) {
           int i = lo, j = hi + 1;
           Comparable v = arr[lo];
           while (true) {
               while (less(arr[++i], v)) { // 找到一个大于v的元素
                   if (i == hi) {
                       break;
                   }
               }
               while (less(v, arr[--j])) { // 找到一个小于v的元素
                   if (j == lo) {
                       break;
                   }
               }
               if (i >= j) { // 两个指针相遇了,退出循环
                   break;
               }
               exchange(arr, i, j); // 交换i和j处的元素的位置,这样可以保证v左小于v,v右都大于v
           }
           exchange(arr, lo, j); // 还要注意最终要将v和a[j]交换
           return j;
       }
   }

5.3 切分

在 partition() 方法中,我们将使用初始值 a[lo] 进行切分,当指针 i 和 j 相遇的时候主循环退出。在循环中,a[i] 小于 v 时,增大i,a[j] 大于 v 时减小 j,然后交换 a[i] 和 a[j] 来保证i左侧的元素都小于 v,j 右侧的元素都大于 v. 当指针相遇时交换 a[lo] 和 a[j],切分结束,这样切分值就留在 a[j] 中了。

https://i-blog.csdnimg.cn/blog_migrate/15a15e81ffebb92f5129c3243a5ad8dc.png

如图所示,这里假设要排序的数组是字符串数组 KRATELEPUIMQCXOS,那么开始的时候 v 为 K,这时将 i 向右移动,直到找到大于 K 的元素,或者达到数组末尾;然后,将 j 向左移动,直到找到小于 K 的元素,或者到达数组开头;如果没有到达数组的两端并且 i 在 j 的左边,我们就将 i 和 j 交换;否则,退出循环,并将 K 和 a[j] 交换位置。这样处理之后 K 左边的元素都小于 K,K 右边的元素都大于 K,那么我们只要再分别将其左侧和右侧的元素进行排序即可,而且排序之后就无需再将两个分数组合并了,因为K左都是小于 K,K 右都是大于 K 的。

注意循环是一直进行的,循环退出的条件有三个:1).游标i达到数组的末尾;2).游标j达到数组的开头;3).游标i和游标j的扫描过的区域有重叠。

5.4 性能

  1. 它的缺点在于在切分不平衡的时候程序的效率可能非常低劣:也就是在上述选择 K 的过程中,如果K是一个非常靠近数组的开头或者结尾位置的元素,那么在排序的时候就没有办法达到分治的目的了。
  2. 快速排序平均时间复杂度也为 O(nlogn) 级。

6、堆排序

6.1 优先队列

普通的队列是一种先进先出的数据结构,元素在队列尾追加,而从队列头删除。在优先队列中,元素被赋予优先级。当访问元素时,具有最高优先级的元素最先删除。优先队列具有最高级先出的行为特征。

堆是具有下列性质的完全二叉树:每个结点的值都大于等于其两个子结点的值,或者每个结点的值都小于等于两个子结点的值,前者被称为大顶堆,后者被称为小顶堆。

  1. 堆有序:当一颗二叉树的每个结点都大于等于它的两个子结点时,它被称为堆有序(堆有序只能保证根大于子结点,无法保证左子树的结点都大于右子树的结点);
  2. 堆的操作会首先进行一些简单的改动,打破堆的状态,然后再遍历堆并按照要求将堆的状态恢复,这个过程叫做堆的有序化;
  3. 上浮:如果二叉堆的有序状态因为某个结点变得比它的父结点大而被打破,那么我们就通过交换它和它的父结点来修复堆,如果交换之后这个结点仍然比父结点大就继续向现在的父结点交换,直到它的父结点比它大,或者达到了堆的顶部;
  4. 下沉:如果二叉堆的有序状态因为某个结点变得比它的子结点小而被打破,那么我们就通过交换它和它的子结点来修复堆,如果交换之后这个结点仍然比子结点小就继续向现在的子结点交换,直到它的子结点比它小,或者达到了堆的底部。
6.1.1 使用二叉堆实现优先队列

二叉堆的元素的存储方式与我们之前提到的二叉树的存储方式相同,比如如下的二叉堆

https://i-blog.csdnimg.cn/blog_migrate/196d17952395acbee53dcdb86ba04bef.png

我们使用一个数组来存储该二叉堆的元素,空出下标为0的元素,所以在数组中以上树的实际存储结果是:- T S R P N O A E I H G.

6.1.2 二叉的堆的代码实现

一下是一份使用Java实现的二叉堆:

   public class MaxPQ<E extends Comparable<E>> {
       private E[] elements;
       private int size;

       public MaxPQ() {}
   
       public MaxPQ(int size) {
           this.elements = (E[]) new Comparable[size + 1]; // 需要使用size+1初始化数组大小,因为第0个元素不使用
       }
   
       public MaxPQ(E[] elements) {
           this.elements = elements;
       }
   
       public void insert(E element) {
           elements[++size] = element;
           swim(size);
       }
   
       public E delMax() {
           E max = elements[1]; // 要被删除的元素位于下标1处
           exchange(1, size--); // 交换1和最后一个元素
           elements[size+1] = null; // 防止结点游离
           sink(1); // 恢复堆的有效性
           return max;
       }
   
       public int size() {
           return size;
       }
   
       public boolean isEmpty() {
           return size == 0;
       }
   
       private boolean less(int i, int j) {
           return elements[i].compareTo(elements[j]) < 0;
       }
   
       private void exchange(int i, int j) {
           E element = elements[i];
           elements[i] = elements[j];
           elements[j] = element;
       }
   
       /**
        * 堆的上浮操作,即将结点k不断和父结点进行比较,如果结点k比父结点k/2大
        * 那么就将其和父结点(k/2)的位置进行交换,直到达到了顶部结点为止
        *
        * @param k 要上浮的结点 */
       private void swim(int k) {
           // while循环中为true时表示,k非根结点,并且k的父结点小于k
           while (k > 1 && less(k / 2, k)) {
               exchange(k / 2, k);
               k /= 2;
           }
       }
   
       /**
        * 堆的下沉操作,将父结点k和两个子结点中较大的那个比较,如果父结点比它小就将父结点
        * 与其进行交换,然后将被交换的那个子结点的位置作为父结点继续进行判断,直达父结点
        * 大于子结点或者达到了堆的末尾的时候退出循环
        *
        * @param k 要进行比较的父结点 */
       private void sink(int k) {
           int j;
           while ((j = 2 * k) < size) {
               // 比较k的左结点和右结点,将j赋值两者中较大的那个的下标
               if (j < size && less(j, j + 1)) { 
                   j++;
               }
               // 将父结点k和子结点j进行比较,如果父结点大于子结点就结束循环
               if (!less(k, j)) { 
                   break;
               }
               exchange(k, j); // 交换父结点和子结点的位置
               k = j; // 将下次要判断的父结点定义为上述中的子结点
           }
       }
   }

在上述代码中我们实现了堆的上浮和下沉的两个方法,在随后的堆排序中会继续使用这两个方法来进行一些操作。

6.2 堆排序

6.2.1 代码
   public class Heap {
   
       public static void sort(Comparable[] pq) {
           int n = pq.length;
           // 注意这里下沉的根结点的选择:先从最后一个子树开始,逐渐到最终的根结点
           // 不断地进行下沉操作
           for (int k = n/2; k >= 1; k--) {
               sink(pq, k, n);
           }
           // 上面执行的操作只是堆有序的操作,它能够给我们的信息只有:
           // 最顶端的结点是最大的元素
           // 下面即用上述信息实现堆排序(也是堆排序的主要逻辑):
           // 这里,每次先交换1和n,然后n-1,这样下沉的时候第n个元素就不参与了,而且
           // 每次我们将n和1交换实际意味着已经得到了最大的元素,然后依次找剩下的树中
           // 的最大元素,这样依次取出最大元素放在后面就达到了排序的目的
           while (n > 1) {
               exch(pq, 1, n--);
               sink(pq, 1, n);
           }
       }
   
       // 将数组pq[]下沉,这里k是下沉的树的根结点,n是该树中结点的总数
       // 后面两个参数的意义很重要!
       private static void sink(Comparable[] pq, int k, int n) {
           int j;
           while ((j = 2 * k) <= n) {
               if (j < n && less(pq, j, j+1)) {
                   j++;
               }
               if (!less(pq, k, j)) {
                   break;
               }
               exch(pq, k, j);
               k = j;
           }
       }
   
       private static boolean less(Comparable[] pq, int i, int j) {
           return pq[i-1].compareTo(pq[j-1]) < 0;
       }
   
       private static void exch(Object[] pq, int i, int j) {
           Object swap = pq[i-1];
           pq[i-1] = pq[j-1];
           pq[j-1] = swap;
       }
   
   }

在上述堆排序代码中的 sort() 方法中,使用 sink() 方法将 a[1] 到 a[N] 的元素进行排序,for 循环构造了堆,然后 while 循环将最大的元素 a[1] 和 a[N] 交换并修复了堆,如此直到堆变空。

上图就是堆排序的完整的实现过程。左侧代表的是堆有序的逻辑,右侧即排序的逻辑。从这里可以看出,在执行完堆有序的逻辑之后,树的最顶端元素已经是最大的。然后我们将其与最末尾元素交换,交换之后排除最末尾的元素,然后再执行下沉逻辑。再次排序之后最顶端又是最大元素,我们再次将其与最末尾元素交换,排除,再排序……这样不断先将最大元素和最末尾元素交换,然后排除末尾的(最大的)元素之后堆排序。这样每次总是将最大元素提取出来,也就达到了排序的效果。

6.2.2 性能

堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为 O(nlogn),它也是不稳定排序。

7、排序算法的性能

abaqus运行报错 WindowsError:[Error 2] abaqus提交job总是提示“WindowsError [Error 2”,报这个错误,可能是由于安装其他软件改变了abaqus软件的路径导致,总结了看到的教程,按照下面的方法可以解决这个问题。 步骤一:找到你的abaqus安装路径(我是按照一个安装视频安装的,安装路径为SIMULI1),然后找到SIMULIA文件夹,点击进入后找到commands文件夹,将这个路径复制下来。 步骤二:我的电脑右键属性,在左侧中找到高级系统设置并... 阅读详情

相关推荐

详解k-means++

一、概述 定义:k-means++是一种为k-means聚类算法选择初始值(或“种子”)的算法。它是NP-hard k-means问题的一种近似算法,它是一种避免标准k-means算法有时发现的较弱聚类的方法。 K-means与K-means++:原始K-means算法最开始随机选取数据集中K个点作为聚类中心,而K-means++按照如下的思想选取K个聚类中心:假设已经选取了n个初始聚类中心(0<n<K),则在选取第n+1个聚类中心时:距离当前n个聚类中心越远的点会有更高的概率被选为第n+1个聚

qq_42364307的博客 3万+

重修c语言,C语言程序设计-重修班-中国大学mooc-题库零氪

第一单元 C语言概述程序设计语言和程序随堂测验1、程序设计语言是用于人和计算机之间进行通信的媒介。2、程序是告诉计算机“做什么”和“怎么做”的一组指令。3、程序设计就是编写程序。简单的C语言程序随堂测验1、以下关于main函数的叙述中,错误的是()。A、main函数是自定义函数B、main函数可以有形式参数,也可以没有形式参数C、main函数定义必须出现在一个源文件的开头D、在一个程序中只能有一个...

weixin_35508482的博客 3311

dify智能体:日志管理与标注功能详解

日志管理与标注功能是优化聊天助手、Agent、文本生成应用、Chatflow 和工作流等工具交互体验的关键。日志管理允许用户查看、修改和标注日志,帮助回顾和优化交互内容。用户可以通过点击👍或👎按钮快速标注满意度,标注后的日志可用于后续优化和分析。标注功能则支持用户对交互内容进行标记和调整,以提升模型输出质量。此外,用户还可以调整界面语言和时区设置,确保在熟悉的环境中操作。这些功能共同作用,显著提升了交互体验和模型性能。

zyx_bx的博客 2710

排序算法分析:冒泡排序、选择排序、插入排序、希尔排序、快速排序

冒泡排序,选择排序,插入排序,希尔排序,快速排序等思路分析,代码实现,以及分析它们的时间复杂度,算法稳定性,空间复杂度

天道酬勤 2549

Debian下Rust环境极速安装指南:国内镜像+避坑配置(附清华/中科大源对比)

本文详细介绍了在Debian系统上快速安装和配置Rust开发环境的完整指南。针对国内网络环境,重点对比评测了清华大学、中国科学技术大学等主流镜像源,提供了详细的安装配置步骤、Cargo优化技巧以及常见问题排查方案,帮助开发者显著提升依赖下载和编译效率。

s8t9u0v1w的博客 1161

Docker+vLLM内网离线部署Qwen3 流程

本文介绍了在CentOS 7系统下进行VLLM容器化部署Qwen3-32B大模型的内网离线方案。首先需准备Nvidia显卡驱动、CUDA 12.4和Docker环境。通过联网机器拉取vllm/vllm-openai镜像并导入内网服务器,同时从魔塔社区下载模型文件。部署时使用docker run命令启动容器,配置了GPU资源、内存共享、端口映射等参数,特别指定了模型路径、并行计算、内存利用率等关键参数。该方案实现了大模型在内网环境的安全高效部署,适用于需要离线运行的AI推理场景。

qq_42881308的博客 1036

冒泡排序的效率

算法描述:从第一个元素开始,将每一个元素与后一位元素进行比较,如果后一个大,则交换彼此位置,否则不做操作,这张扫描一遍,可以将最大的放到数组的最后边。重复上述操作,直到没有数据要被交换位置。       算法效率:冒泡排序要做N*(N-1)/2次比较 ,N*N/4次交换。交换和比较操作都和N*N成正比。由于常数不算在大O表示法中,可以忽略2和4,并且认为冒泡排序运行需要o(N*N)时间级别。  

麦兜猴的专栏 3067

强化学习-马尔可夫模型

前文提到了强化学习源自马尔可夫决策过程,本文从马尔可夫概念入手,讲解和马尔可夫有关的模型和概念。在马尔科夫决策过程中,我们有一个智能体,它在一个马尔科夫过程中进行决策,每次决策都会导致状态的转移和获得即时奖励。智能体的目标是通过选择动作来最大化未来奖励的期望值。通常马尔科夫决策过程是个五元组:SAPRγSAPRγS 表示状态集合,代表智能体可能处于的所有状态。A 表示动作集合,代表智能体可以采取的所有动作。

君子之学必日新,日新者日进也。不日新者必日退,未有不进而不退者 8060

排序算法(二):冒泡排序

原创:https://blog.youkuaiyun.com/u012152619/article/details/47305859

水滴的博客 760

为什么一个“时间片”能折腾 Linux 十年?

时间片扩展不是抢占调度器的控制权,也不是提升优先级。在“非常短暂、非常确定”的用户态临界区内,避免线程被不必要地抢占。用户态自旋锁(user-space spinlock)无系统调用的极短临界区高并发、低延迟的数据结构操作RSEQ(Restartable Sequences)保护区间一旦线程在临界区被抢占,系统整体性能反而下降。线程 A 拿着一个用户态锁线程 B 被调度上来,开始自旋等这个锁但 A 因为时间片用完,被调度器切走CPU 白白空转,所有线程都在浪费时间。

网络技术联盟站 253

【ArcGIS微课1000例】0118:一文讲清楚tif(geotiff)栅格数据格式

TIFF(Tagged Image File Format)是一种常见的图像文件格式,它被广泛用于存储和传输各种类型的图像数据。图像存储:TIFF格式可以存储多通道的位图图像,包括灰度图、彩色图和透明度图等。它支持不同的像素深度,可以是8位、16位、24位或32位等。因此,TIFF格式非常适合存储高质量的图像数据,包括照片、卫星图像、医学影像等。无损压缩:TIFF格式支持无损压缩,这意味着图像可以被压缩而无需损失任何像素信息。这种压缩方式可以减小文件大小,而不会降低图像的质量。

智慧空间实验室 2816

PTA8

1.下面程序的输出结果是正确的是

m0_73925458的博客 421

GEE土地分类:一文读懂精度评定—混淆矩阵confusion matrix、整体精度OA、用户者精度、生产者精度、kappa系数和F1score系数含案例

本文介绍了如何在 Google Earth Engine (GEE) 中构建和分析混淆矩阵,以评估分类模型的性能。首先,通过 ee.Array 构建混淆矩阵,并展示了如何指定自定义类标签。接着,计算了多种性能指标,包括准确率、Kappa 值、F1-score、消费者准确率和生产者准确率。此外,还演示了如何计算不同类别的 F1-score。最后,通过一个实际案例,展示了如何利用 GEE 进行土地分类,并评估分类器的性能,包括训练和验证样本的准确率、Kappa 值等指标。这些步骤帮助用户理解模型的优缺点,并进行

此星光明博客 442

Notes: 排序算法小结 (Sorting)

最近两天,复习了几个经典的排序算法。在这里,写一份小结,粗略回顾。 1)Bubble Sort 顾名思义,就是把最小的数如冒泡一样,在每次的遍历当中,移到最顶的位置。 public void BubbleSort(int[] input){ for(int i=0;i<input.length;i++){ for(int j=input.length-1;j>i;j--){

Steven Li's Zone 615

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

[数据结构]七种排序算法小结

冒泡排序 选择排序 插入排序 归并排序 快速排序 堆排序 希尔排序眼看着就要实习,为了巩固基础,回顾并总结排序算法。参考自:http://www.nowcoder.com/courses/1/1/1冒泡排序时间复杂度o(n2)o(n^2)。下面以一个例子来看什么是冒泡排序。 例: 第一次取区间[0,7],通过比较第i个和第i+1个的大小,如果第i+1个数字小于第i个,则互换。这样位置7就是最

让勤奋成为习惯 5654

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

数据结构与算法:合集1.0版

课程知识结构体系——这门课程是什么? 数据结构的重要性——这门课程重要吗?数据结构是计算机软件相关专业的专业基础课。 在教学计划种的地位:核心、承上启下的课程。 数据结构是介于数学、计算机硬件和计算软件三者之间的一门核心课程。 类似于武术中的“练功”科目:“练武不练功,到头一场空” 考研:必考专业课,四门专业课,共150分,《数据结构和算法》占45分(更有很多多学校只考数据结构和算法)。 找工作:面试时最主要核心内容。早期,计算机主要用于数值计算。 例1、求解梁架结构中的应力。 例2、预报人口增长情况。

uxiang_blog的博客 1546

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

【算法】十大排序算法总结(Java代码实现)

最近遇到了一个排序问题,却发现自己十大经典排序算法记不清了。我本来以为十大排序算法这么经典的东西,网上肯定会有含有测试的 Java 代码实现,应该很容易就能找到。不过出乎意料的是,现在的这些能汇集全部排序算法的总结篇都或多或少的有点问题。所以我还是自己全部写一遍,正好复习一下。代码在结尾,并且包含测试用例。 这篇博客中所提到的所有排序算法均以从小到大排序为例,其他情况请自行类比。本文只适合已经学过排序并想再回顾一遍的人,很多地方只是提一下排序的思路。如果完全没有学过,建议还是先去系统地学习一下。 .

qq_38374633的博客 459

算法设计和数据结构学习_2(常见排序算法思想)

   一般约定我们是按照从小到大的顺序排序。 按照向量的形式来参考排序中数组元素的相对位置,行向量中最左边为数组的第一个元素,且称为前面。列向量中最上面的元素为数组的第一个元素,也是称为前边。 对常见的排序算法的思想要有个认识,比如说:冒泡排序,选择排序,插入排序,快速排序,shell排序,堆排序,归并排序,基排序。 算法的稳定性是看原先数据中相同的元素值的顺序在排序后是否改变了?如果...

weixin_33737774的博客 126

11.11 小结

假设最差情况,一直为一半长度,那么最终的递归深度就是 log⁡𝑛。在该方法下,输入元素全部相等的数组,仅一轮哨兵划分即可完成排序。回顾原始的快速排序,我们有可能会连续地递归长度较大的数组,最差情况下为 𝑛、𝑛−1、…、2、1 ,递归深度为 𝑛。不行,当我们以最左端元素为基准数时,必须先“从右往左查找”再“从左往右查找”。也就是说,此时最后一步交换操作会把一个比基准数更大的元素交换至数组最左端,导致哨兵划分失败。可以发现,学生 D 和 C 的位置发生了交换,姓名的有序性被破坏了,而这是我们不希望看到的。

让学习成为一种习惯 928

算法学习<2>---选择排序

引言 数据结构和算法对于程序员来说相当重要,我最近打算学习这一门课程,并以博客的形式记录自己的学习过程和心得,目前暂时从两本书入手,一本是《大话数据结构》,一本书《算法图解》,我先从《算法图解》,这本手开始学习吧~。如果你最近也在学习,可以关注一起学习,一起交流哦~ 选择排序 学习选择排序算法之前先回顾一下数组和链表的特点: 数组擅长随机读取,而链表擅长插入和删除。 下面是常见数组和链表操作的运行时间。 数组 链表 读取 O(1) O(n) 插入 O(n) O(1) 删除 O(n

哆哆jarvis的博客 239

排序算法小结3——快速排序

快速排序的介绍 快速排序(Quicksort)是对冒泡排序的一种改进。基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列 快速排序的思路图 快速排序的过程和思路其实很简单,假设我们现在对“6 1 2 7 9 3 4 5 10 8”这个10个...

interesting_code的博客 293

小灰的算法之旅python篇_《【正版 】漫画算法:小灰的算法之旅(Python篇)》【摘要 书评 在线阅读】-苏宁易购图书...

商品参数出版社:《养生指南》杂志社版次:1ISBN:9787121382789版权提供:《养生指南》杂志社 ^_^:8c956c965ee961bc61d0c5c7d2ae8f38本书通过虚拟的主人公小灰的心路历程,用漫画的形式讲述了算法和数据结构的基础知识、复杂多变的算法面试题目及算法的实际应用场景。第1章 介绍了算法和数据结构的相关概念,告诉大家算法是什么,数据结构又是什么,它们有哪些用途,如...

weixin_39622138的博客 342

2021----算法小结

回顾 算法是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令。 算法是解决问题的一种特殊方法,不是问题本身的答案,而是经过准确定义的、以获得问题解的过程。 算法是问题的程序化解决方案,是一系列解决问题的清晰指令,对于符合规范的输入,能够在有限的时间内获得所需要的输出。 算法分析是对计算机性能和资源利用的研究 有穷性,确切性,输入项,输出项,可行性 表达方式 流程图 伪代码 PAD图 伪代码中选择了高级编程语言,如Python,Java和C++中共有的编程语言构件 表达式:标准数学符号和布尔表达

可及的小屋 1060

2020新东方视频面试 问题小结

1.自我介绍 实习与学习经历 2.大学学哪些专业课? 3.进程与线程的区别? 4.线程的同步方式? 5.java中创建线程同步的方式有哪些? 6.java中如何创建带有返回值的线程? 7.线程池的种类有哪些? 8.线程池的工作原理? 9.操作系统中发生死锁的的条件有哪些? 4个必要条件 10.网络中 osi七层模型和tcp/IP模型简单的介绍? 11.每一层对应的有哪些协议? 12.传输层TCP与...

qq_30693057的博客 1073

第一阶段工作小结

第一阶段工作小结   说起来感觉时间过的很快,如果从寒假留校那时候开始,到现在为止大概是接近4个月了。这4个月的时间,我从对java的一无所知,到现在两本java教材基本 看完,开始看java Web篇的内容,说起来不得不说时间过的很快。不过今天写的这篇小结主要是写从3月31号,我正式成为实习生开始,到现在的一个工作上的 小结吧。   下面写的内容就是我一边回忆过去所学知识

水晶之夜的专栏 708

算法+数据结构总结系列(三)

排序快速排序快排过程与实现时间复杂度堆排序堆排序前传——二叉树的基础知识堆排序前传——堆和堆的向下调整堆排序传——堆排序过程堆排序传——堆排序实现堆排序后传——topk问题归并排序 快速排序 快排过程与实现 时间复杂度 def partition(li, left, right): tag = li[left] # 锁定目标 while left < right: while left < right and li[right] >= tag: # 从右边找比tag小的数并避免一直

weixin_41644810的博客 483
上一篇: 数据结构与算法回顾-1:算法的度量和基本数据结构
下一篇: 数据结构与算法回顾-3:二叉查找树
开发者如是说
博客等级 码龄10年 67粉丝 163原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值