程序员常用的几种算法(建议刚毕业或者在找实习的大学生看看)

  • 接下来是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];  
            }  
              
            // 找到合适的位置,插入当前元素  
            arr[j] = temp;  
        }  
    }  
}  
  
int main() 
{  
    int arr[] = {84, 83, 88, 87, 61, 50, 70, 60, 80, 99};  
    int n = sizeof(arr) / sizeof(arr[0]);  
      
    printf("原始数组:\n");  
    for (int i = 0; i < n; i++) 
    {  
        printf("%d ", arr[i]);  
    }  
    printf("\n");  
      
    // 调用希尔排序函数  
    shellSort(arr, n);  
      
    printf("排序后的数组:\n");  
    for (int i = 0; i < n; i++) 
    {  
        printf("%d ", arr[i]);  
    }  
    printf("\n");  
      
    return 0;  
}

好,排序算法讲到这,五个算法,应届生起码要记住冒泡和插入。因为排序算法在面试和笔试中出现频率真的高!

查找算法基本概念

在一堆数据中,找到我们想要的那个数据,就是查找,也称为搜索,很容易想到,查找算法的优劣,取决于两个因素:

  • 数据本身存储的特点
  • 查找算法本身的特点

比如,如果数据存储是无序的,那么当我们想要在其中找到某个节点时,一般就只能对它们逐个比对。如果数据存储是有序且存放在一片连续的内存中,那么我们可以考虑从中间开始找(二分法)。因此可以看到,在实际应用中如果需要优化数据的查找(搜索)性能,我们主要从以上两方面入手,当然,有时数据的存储特性是无法更改的,那么此时就只能靠优化算法本身去达到提供程序性能的目的了。

查找算法在面试中出现频率低,但你要是学会一两种查找算法写在简历上,不是很加分吗?

顺序查找算法(Sequential Search Algorithm)

顺序查找算法是一种基本的查找算法,它的基本思路是从列表的一端开始,逐个检查每个元素,直到找到所需的元素或检查完所有元素为止。以下是顺序查找算法的详细思路:

  1. 确定查找范围:首先,需要确定在哪个数据集合或列表中进行查找。这个列表可以是一个数组、链表或其他数据结构。
  2. 初始化指针或索引:在列表的开始位置(通常是第一个元素)设置一个指针或索引。这个指针或索引用于跟踪当前正在检查的元素。
  3. 比较元素:将指针或索引指向的元素与目标值进行比较。如果两者相等,那么查找成功,返回该元素的位置或值。
  4. 移动指针或索引:如果当前元素不等于目标值,则将指针或索引向前移动一位,指向下一个元素。
  5. 重复比较和移动:重复步骤3和4,直到找到目标元素或指针或索引已经移动到列表的末尾。
  6. 判断查找结果:如果遍历完整个列表仍未找到目标元素,那么查找失败,返回相应的结果(如特殊值或错误信息)。

顺序查找算法的时间复杂度是O(n),其中n是列表的长度。这是因为在最坏的情况下,可能需要检查列表中的每个元素。尽管顺序查找在某些情况下可能不是最高效的算法(特别是当数据量很大或数据有序时),但它实现简单,对于小规模数据或特定场景仍然是一个可行的选择。

需要注意的是,顺序查找算法对于无序列表是有效的,但如果列表已经排序,那么可以考虑使用更高效的查找算法,如二分查找。此外,对于某些数据结构(如哈希表),可以直接通过键值快速定位到元素,无需遍历整个列表。

示例代码:

#include <stdio.h>  
  
// 定义顺序查找函数  
int sequentialSearch(int arr[], int n, int target) 
{  
    int i;  
      
    // 遍历数组  
    for (i = 0; i < n; i++) 
    {  
        // 如果找到目标值,返回其索引  
        if (arr[i] == target) 
        {  
            return i;  
        }  
    }  
      
    // 如果没有找到目标值,返回-1  
    return -1;  
}  
  
int main() {  
    // 定义数组  
    int arr[] = {10, 14, 19, 26, 27, 31, 33, 35, 42, 44};  
    int n = sizeof(arr) / sizeof(arr[0]); // 计算数组长度  
    int target; // 目标值  
    int index; // 目标值在数组中的索引  
      
    // 获取用户输入的目标值  
    printf("请输入要查找的目标值: ");  
    scanf("%d", &target);  
      
    // 调用顺序查找函数  
    index = sequentialSearch(arr, n, target);  
      
    // 输出查找结果  
    if (index != -1) 
    {  
        printf("目标值 %d 在数组中的索引为 %d\n", target, index);  
    } 
    else 
    {  
        printf("目标值 %d 不在数组中\n", target);  
    }  
      
    return 0;  
}
二分查找算法(Binary Search Algorithm)

二分查找算法是一种在有序数组中查找特定元素的搜索算法。其核心思想是将数组分成两半,通过比较目标值与中间元素的值,决定在哪一半中继续查找,从而每次都将查找范围减半,直至找到目标值或确定目标值不存在。以下是二分查找算法的详细思路:

  1. 初始化
    • 确定要查找的有序数组 arr 和目标值 target
    • 设置两个指针 left 和 right,分别指向数组的起始位置和结束位置。
  2. 循环查找
    • 当 left 小于等于 right 时,执行以下步骤:
      • 计算中间位置 mid,可以是 (left + right) / 2(整数除法)或者 (left + right) >> 1(位运算,更高效)。

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

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

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

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值