2024年运维最新程序员常用的几种算法(建议刚毕业或者在找实习的大学生看看)(2),2024年最新最新整理

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以点击这里获取!

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

排序是处理数据的一种最常见的操作,所谓排序就是将数据按某字段规律排列,所谓的字段就是数据节点的其中一个属性。比如一个班级的学生,其字段就有学号、姓名、班级、分数等等,我们既可以针对学号排序,也可以针对分数排序。

  • 稳定性
    在一组无序数据中,若两个待排序字段一致的数据,在排序前后相对位置不变,则称排序算法是稳定的,否则是不稳定的。
  • 内排序与外排序
    如果待排序数据量不大,可以一次性全部装进内存进行处理,则称为内排序,若数据量大到无法一次性全部装进内存,而需要将数据暂存外存,分批次读入内存进行处理,则称为外排序。

不同的排序算法性能也不同,这句话什么意思呢?就比如有五个保洁阿姨(算法),你派这五个阿姨去打扫房间,每个阿姨需要打扫完房间的时间肯定是不一样的,有些阿姨要20分钟,有些要10分钟,有些要15分钟,这就叫性能。但可能打扫快的阿姨的房间可能不是很干净,那怎么挑选打扫时间又快又干净的阿姨呢?详细性能数据如下表所示。

从表中可以得到一些简单的指导思路:

  1. 选择排序、插入排序和冒泡排序思路简单,但时间效率较差,只适用于数据样本较小的场合,这几种算法的好处是不需要额外开辟空间,空间复杂度是常量。
  2. 希尔排序是插入排序的改进版,在平均情况下时间效率要比直接插入法好很多,也不需要额外开辟空间,要注意的是希尔排序是不稳定排序。
  3. 快速排序是所有排序算法中时间效率最高的,但由于快排是一种递归运算,对内存空间要求较高,当数据量较大时,会消耗较多的内存。

看不懂这个表没关系的,我刚开始学的时候也看不懂。或者你不看这个表也没关系的,只要记住算法的思路就行了。

冒泡排序

这个排序笔试题出现的几率真的很高,实习找工作的大学生必须给我死记硬背下来。

首先引入两个概念:

  • 顺序:如果两个数据的位置符合排序的需要,则称它们是顺序的。
  • 逆序:如果两个数据的位置不符合排序需要,则称它们是逆序的。

冒泡排序基于这样一种简单的思路:从头到尾让每两个相邻的元素进行比较,顺序就保持位置不变,逆序就交换位置。可以预料,经过一轮比较,序列中具有“极值”的数据,将被挪至序列的末端。

以下几个步骤:

  1. 从数组的第一个元素开始,依次比较相邻的两个元素的大小。
  2. 如果前一个元素大于后一个元素,则交换它们的位置。
  3. 继续比较相邻的下一对元素,直到最后一个元素。
  4. 在第一次遍历完成后,最大的数会被放到数组的最后位置。
  5. 在第二次遍历时,由于最后一个元素已经确定是最大值,因此可以跳过它。
  6. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

文字可能有点难懂的话,直接看图!

示例代码:

#include <stdio.h>  // 引入标准输入输出库  
  
// 定义冒泡排序函数  
void bubbleSort(int arr[], int n) 
{  
    int i, j, temp;  // 声明循环变量i, j和临时变量temp  
    for (i = 0; i < n - 1; i++)  // 外层循环,总共需要遍历n-1次  
    { 
        
        for (j = 0; j < n - i - 1; j++) // 内层循环,每次比较相邻元素并交换位置  
        {  
           
            if (arr[j] > arr[j + 1])  // 如果前一个元素大于后一个元素  
            {  
                // 交换两个元素的位置  
                temp = arr[j];  // 将arr[j]的值保存到临时变量temp中  
                arr[j] = arr[j + 1];  // 将arr[j+1]的值赋给arr[j]  
                arr[j + 1] = temp;  // 将临时变量temp中的值赋给arr[j+1]  
            }  
        }  
    }  
}  
  
int main() 
{  
    int arr[] = {64, 34, 25, 12, 22, 11, 90};  // 定义待排序数组  
    int n = sizeof(arr) / sizeof(arr[0]);  // 计算数组长度  
  
    // 打印原始数组  
    printf("原始数组:\n");  
    for (int i = 0; i < n; i++) 
    {  
        printf("%d ", arr[i]);  // 打印数组元素  
    }  
    printf("\n");  // 打印换行符  
  
    // 调用冒泡排序函数  
    bubbleSort(arr, n);  
  
    // 打印排序后的数组  
    printf("排序后的数组:\n");  
    for (int i = 0; i < n; i++) 
    {  
        printf("%d ", arr[i]);  // 打印数组元素  
    }  
    printf("\n");  // 打印换行符  
  
    return 0;  // 程序正常退出  
}
插入排序 (这个也要记住因为面试官可能会问你了解那些排序算法)

插入排序(Insertion Sort)的思路非常直观,它借鉴了我们日常生活中整理扑克牌或列表的方式。其基本思想是:通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

以下是插入排序的详细思路:

  1. 初始化:从数组的第一个元素开始,该元素可以认为已经被排序。
  2. 遍历:从数组的第二个元素开始,依次遍历到最后一个元素。
  3. 插入:对于当前遍历到的元素,假设为arr[i],我们将其视为一个待插入的元素。
  4. 比较与移动:从arr[i-1]开始向前遍历(即从当前元素的前一个元素开始),比较该元素与待插入元素arr[i]的大小。如果arr[i-1]大于arr[i],则将arr[i-1]后移一位,继续向前比较。这个过程一直持续到找到arr[i]应该插入的位置,或者已经遍历到数组的开始。
  5. 插入:找到位置后,将arr[i]插入到该位置。
  6. 重复:对数组的下一个元素重复步骤3至5,直到所有元素都被排序。

下面是一个简单的例子来展示插入排序的过程:

假设我们有以下数组:[4, 3, 2, 10, 12, 1, 5, 6]

  • 初始时,第一个元素4是已排序的。
  • 接下来,我们考虑3,将其与4比较,发现3应该放在4前面,所以交换它们的位置,得到[3, 4, 2, 10, 12, 1, 5, 6]
  • 接下来是2,依次与43比较并交换位置,得到[2, 3, 4, 10, 12, 1, 5, 6]
  • 然后是10,因为它大于它前面的所有元素,所以直接放在末尾,数组变为[2, 3, 4, 10, 12, 1, 5, 6]
  • 对于12,也是直接放在末尾,数组变为[2, 3, 4, 10, 1, 12, 5, 6]
  • 对于1,将其与前面的元素比较并依次交换位置,直到放到正确的位置,得到[1, 2, 3, 10, 4, 12, 5, 6]
  • 以此类推,直到整个数组排序完成。

插入排序对于小规模或部分有序的数据表现较好,时间复杂度在最好情况下为O(n),平均和最坏情况下为O(n^2)。在实际应用中,如果知道数据大部分已经是有序的,或者数据量不大,插入排序是一个简单而有效的选择。然而,对于大规模随机数据,通常选择更高效的排序算法,如快速排序、归并排序或堆排序等。

示例代码:

#include <stdio.h>  
  
// 插入排序函数  
void insertionSort(int arr[], int n) 
{  
    int i, key, j;  
    for (i = 1; i < n; i++) // 从第二个元素开始遍历  
    { 
        key = arr[i]; // 将当前元素保存为key  
        j = i - 1; // 前一个元素的索引  
      
        while (j >= 0 && arr[j] > key)// 如果前一个元素大于key,则将前一个元素后移一位  
        {  
            arr[j + 1] = arr[j];  
            j = j - 1; // 向前移动一位  
        }  
        // 找到key的插入位置,将其插入  
        arr[j + 1] = key;  
    }  
}  
  
int main() 
{  
    int arr[] = {4, 3, 2, 10, 12, 1, 5, 6}; // 待排序数组  
    int n = sizeof(arr) / sizeof(arr[0]); // 数组长度  
  
    // 打印原始数组  
    printf("原始数组:\n");  
    for (int i = 0; i < n; i++) 
    {  
        printf("%d ", arr[i]);  
    }  
    printf("\n");  
  
    // 调用插入排序函数  
    insertionSort(arr, n);  
  
    // 打印排序后的数组  
    printf("排序后的数组:\n");  
    for (int i = 0; i < n; i++) 
    {  
        printf("%d ", arr[i]);  
    }  
    printf("\n");  
  
    return 0;  
}

代码中,insertionSort函数负责执行插入排序算法。对于arr数组的每一个元素,我们从第二个元素开始(索引为1),将其作为key,然后和它之前的元素逐一比较。如果前面的元素比key大,我们就把前面的元素后移一位,直到找到key的正确位置并插入。main函数中,我们定义了待排序的数组arr,并计算了数组的长度n。然后,我们打印出原始数组的元素,调用insertionSort函数进行排序,最后打印出排序后的数组元素。

选择排序

选择排序(Selection Sort)是一种简单直观的排序算法。它的工作原理是首先在未排序序列中找到最小(或最大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(或最大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

以下是选择排序的详细思路:

  1. 初始化:设置两个指针,一个指向已排序部分的末尾(通常初始化为数组的第一个元素的前一个位置),另一个指向未排序部分的开始(通常是数组的第一个元素)。
  2. 查找最小值:从未排序部分的第一个元素开始,遍历到未排序部分的末尾,寻找最小的元素。
  3. 交换:找到最小元素后,将其与未排序部分的第一个元素交换位置。这样,最小元素就被放到了已排序部分的末尾。
  4. 移动指针:将已排序部分的末尾指针向后移动一位,这样下一个位置就用来存放下一个最小元素。
  5. 重复过程:重复第2步到第4步,直到已排序部分的末尾指针到达数组的末尾,此时数组就已经完全排序好了。

需要注意的是,选择排序是不稳定的排序算法,即相等的元素在排序后可能会改变原有的相对顺序。

举个例子,假设有一个数组 [64, 25, 12, 22, 11],按照选择排序的思路,排序过程如下:

  • 初始状态:[64, 25, 12, 22, 11]
  • 第一轮:找到最小元素11,与第一个元素64交换,得到 [11, 25, 12, 22, 64]
  • 第二轮:在未排序部分 [25, 12, 22, 64] 中找到最小元素12,与第二个元素25交换,得到 [11, 12, 25, 22, 64]
  • 第三轮:在未排序部分 [25, 22, 64] 中找到最小元素22,与第三个元素25交换,得到 [11, 12, 22, 25, 64]
  • 第四轮:在未排序部分 [25, 64] 中找到最小元素25,无需交换,因为它已经在正确的位置
  • 第五轮:只剩下一个元素64,无需排序

最终数组变为 [11, 12, 22, 25, 64],完成排序。

选择排序的时间复杂度为 O(n^2),其中 n 是待排序数组的长度。尽管它的效率不是最高的,但由于其实现简单,因此在教学和简单应用中仍然有一定的使用场景。

示例代码:

#include <stdio.h>  
  
// 选择排序函数  
void selectionSort(int arr[], int n) 
{  
    int i, j, minIndex, temp;  
      
    // 遍历所有数组元素  
    for (i = 0; i < n - 1; i++) 
    {  
        // 假设当前索引位置的元素是最小的  
        minIndex = i;  
          
        // 在未排序部分查找最小元素  
        for (j = i + 1; j < n; j++) 
        {  
            // 如果发现更小的元素,更新最小元素的索引  
            if (arr[j] < arr[minIndex]) 
            {  
                minIndex = j;  
            }  
        }  
          
        // 如果找到的最小元素不在当前位置,则交换它们  
        if (minIndex != i) 
        {  
            temp = arr[i];  
            arr[i] = arr[minIndex];  
            arr[minIndex] = temp;  
        }  
    }  
}  
  
int main() 
{  
    int arr[] = {64, 25, 12, 22, 11};  
    int n = sizeof(arr) / sizeof(arr[0]);  
      
    // 打印原始数组  
    printf("原始数组:\n");  
    for (int i = 0; i < n; i++) 
    {  
        printf("%d ", arr[i]);  
    }  
    printf("\n");  
      
    // 调用选择排序函数  
    selectionSort(arr, n);  
      
    // 打印排序后的数组  
    printf("排序后的数组:\n");  
    for (int i = 0; i < n; i++) 
    {  
        printf("%d ", arr[i]);  
    }  
    printf("\n");  
      
    return 0;  
}
快速排序

快排是一种递归思想的排序算法,先比较其他的排序算法,它需要更多内存空间,但快排的语句频度是最低的,理论上时间效率是最高的。

快速排序的基本思路是:在待排序序列中随便选取一个数据,作为所谓“支点”,然后所有其他的数据与之比较,以从小到大排序为例,那么比支点小的统统放在其左边,比支点大的统统放在其右边,全部比完之后,支点将位与两个序列的中间,这叫做一次划分(partition)。

一次划分之后,序列内部也许是无序的,但是序列与支点三者之间,形成了一种基本的有序状态,接下去使用相同的思路,递归地对左右两边的子序列进行排序,直到子序列的长度小于等于1为止。

// 黄色:支点

// 绿色:比支点小的数据

// 紫色:比支点大的数据

// 红色:用来和支点比较大小的(现在比的位置)

// 橙色:已经比较好的数据

注意:快速排序操作复杂,上述gif动图并没有完全将之展示出来

示例代码:

#include <stdio.h>  
  
// 快速排序的函数原型声明  
void quickSort(int arr[], int left, int right);  
  
// 交换数组中两个元素的位置  
void swap(int *a, int *b) 
{  
    int temp = *a;  
    *a = *b;  
    *b = temp;  
}  
  
// 快速排序函数  
void quickSort(int arr[], int left, int right) 
{  
    if (left < right) 
    {  
        // 分割数组,并返回pivot的位置  
        int pivotIndex = partition(arr, left, right);  

        // 对pivot左边的子数组进行递归排序  
        quickSort(arr, left, pivotIndex - 1);  

        // 对pivot右边的子数组进行递归排序  
        quickSort(arr, pivotIndex + 1, right);  
    }  
}  
  
// 分割数组的函数  
int partition(int arr[], int left, int right) 
{  
    // 选择最右侧的元素作为基准值  
    int pivot = arr[right];  
    int i = left - 1; // 指向小于基准值的元素的最后一个位置  
  
    for (int j = left; j < right; j++) 
    {  
        // 如果当前元素小于或等于基准值  
        if (arr[j] <= pivot) 
        {  
            // 将小于基准值的元素移动到左边  
            i++;  
            swap(&arr[i], &arr[j]);  
        }  
    }  
  
    // 将基准值放到正确的位置  
    swap(&arr[i + 1], &arr[right]);  
    return i + 1; // 返回基准值的索引位置  
}  
  
// 主函数  
int main() 
{  
    int arr[] = {10, 7, 8, 9, 1, 5};  
    int n = sizeof(arr) / sizeof(arr[0]);  
  
    printf("原始数组:\n");  
    for (int i = 0; i < n; i++) 
    {  
        printf("%d ", arr[i]);  
    }  
    printf("\n");  
  
    // 对数组进行快速排序  
    quickSort(arr, 0, n - 1);  
  
    printf("排序后的数组:\n");  
    for (int i = 0; i < n; i++) 
    {  
        printf("%d ", arr[i]);  
    }  
    printf("\n");  
  
    return 0;  
}
希尔排序

希尔排序(Shell Sort)是插入排序的一种更高效的改进版本,也称为缩小增量排序。它通过将比较的全部元素分为几个区域来提升插入排序的性能。这样可以让一个元素可以一次性地朝最终位置前进一大步。然后算法再取越来越小的步长进行排序,算法最后一步就是普通的插入排序,但是到了这步,需排序的数据几乎是已经排好了的(只剩少量的数据需要插入到已排好序的序列中),所以速度很快。

希尔排序的基本思路是:

  1. 选择一个增量序列:增量序列的选取对希尔排序的时间性能至关重要。通常,初始增量较大,随后增量逐渐减少,直至增量为1,即最后一次增量排序后序列基本有序。
  2. 分组排序:根据当前的增量值,将待排序的序列分割成若干长度为m的子序列(m为当前增量),然后对每个子序列进行直接插入排序。
  3. 减小增量:每次排序完成后,将增量按照一定的规则减小,通常是除以2或其他小于1的正数。
  4. 重复分组排序:使用新的增量值重复分组排序的步骤,直到增量减小到1。当增量为1时,整个序列已经基本有序,这时进行一次普通的插入排序,即可得到完全有序的序列。

希尔排序在开始时增量较大,这样它可以让元素移动更远,从而更快地使整个序列接近有序。随着增量的逐渐减小,排序的粒度也越来越细,直到增量为1时,完成最后的微调。

希尔排序的时间复杂度依赖于增量序列的选取,对于最优的增量序列,希尔排序的时间复杂度可以达到O(n1.3)左右,比普通的插入排序O(n2)要好很多。但是,由于希尔排序的增量序列选择是一个尚未解决的数学问题,所以希尔排序的实际性能可能会因增量序列的不同而有所差异。在实际应用中,常用的增量序列有Hibbard增量序列、Sedgewick增量序列等。

希尔排序是插入排序的一种优化,它通过将比较的全部元素分为几个区域来提升插入排序的性能,使得算法在数据量大的时候也能有较好的表现。

比如如下图所示,有无无序列:

84、83、88、87、61、50、70、60、80、99

第一遍,先取间隔为(Δ=5Δ=5),即依次对以下5组数据进行排序:

84、83、88、87、61、50、70、60、80、99
84、83、88、87、61、50、70、60、80、99
84、83、88、87、61、50、70、60、80、99
84、83、88、87、61、50、70、60、80、99
84、83、88、87、61、50、70、60、80、99

注意,当对84和50进行排序时,其他的元素就像不存在一样。因此,经过上述间隔为5的一遍排序后,数据如下:

50、83、88、87、61、84、70、60、80、99
50、70、88、87、61、84、83、60、80、99
50、70、60、87、61、84、83、88、80、99
50、70、60、80、61、84、83、88、87、99
50、70、60、80、61、84、83、88、87、99

最终的结果(50、70、60、80、61、84、83、88、87、99)是经过这一遍间隔Δ=5Δ=5的情况下达成的,接下去缩小间隔重复如上过程。例如让间距Δ=3Δ=3:

50、70、60、80、61、84、83、88、87、99
50、70、60、80、61、84、83、88、87、99
50、70、60、80、61、84、83、88、87、99
50、70、60、80、61、84、83、88、87、99

将上述粗体的每一组数据进行排序,得到:

50、70、60、80、61、84、83、88、87、99
50、61、60、80、70、84、83、88、87、99
50、61、60、80、70、84、83、88、87、99
50、61、60、80、70、84、83、88、87、99

最终的结果(50、61、60、80、70、84、83、88、87、99)更加接近完全有序的序列。接下去继续不断减小间隔,最终令Δ=1Δ=1,确保每一个元素都在恰当的位置。动图展示如下:

示例代码:

#include <stdio.h>  
  
void shellSort(int arr[], int n) 
{  
    int gap, i, j, temp;  
      
    // 初始增量设为数组长度的一半,之后每次减半  
    for (gap = n / 2; gap > 0; gap /= 2) 
    {  
        // 对每个子序列进行插入排序  
        for (i = gap; i < n; i++) 
        {  
            temp = arr[i];  
              
            // 从当前元素开始,比较前一个间隔为gap的元素  
            for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) 
            {  
                // 如果前一个元素比当前元素大,则交换它们  
                arr[j] = arr[j - gap];  
            }  
              
            // 找到合适的位置,插入当前元素  


### 最后的话

最近很多小伙伴找我要Linux学习资料,于是我翻箱倒柜,整理了一些优质资源,涵盖视频、电子书、PPT等共享给大家!

### 资料预览

给大家整理的视频资料:

![](https://img-blog.csdnimg.cn/img_convert/f26c3672f21a8819db7034b44b2c58fe.png)

给大家整理的电子书资料:

  

![](https://img-blog.csdnimg.cn/img_convert/19b475a97eec20e295dc831042ddf677.png)



**如果本文对你有帮助,欢迎点赞、收藏、转发给朋友,让我有持续创作的动力!**

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以点击这里获取!](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

 
              
            // 找到合适的位置,插入当前元素  


### 最后的话

最近很多小伙伴找我要Linux学习资料,于是我翻箱倒柜,整理了一些优质资源,涵盖视频、电子书、PPT等共享给大家!

### 资料预览

给大家整理的视频资料:

[外链图片转存中...(img-WGW89LFO-1715290686260)]

给大家整理的电子书资料:

  

[外链图片转存中...(img-Gie8B39L-1715290686260)]



**如果本文对你有帮助,欢迎点赞、收藏、转发给朋友,让我有持续创作的动力!**

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以点击这里获取!](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值