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

目录

排序算法基本概念

冒泡排序

插入排序

 选择排序

快速排序

希尔排序 

查找算法基本概念

顺序查找算法(Sequential Search Algorithm)

 二分查找算法(Binary Search Algorithm)

分块查找算法(Block Search) 


写这篇文章是因为最近开始很多计算机行业的大学生出来上班找实习了,既然找工作就要经历笔试题,技术面,人事面,一般来说是这三个面(本人当时找工作就是这样)。我也刚出来工作一年,我从事的是嵌入式行业的工作,刚出来投简历和面试过五六家公司,做过笔试题也是有挺多的。如果同学们想看看企业笔试题长什么样的话,可以从我的资源里下载来看看的。(偷拍下来的哈哈)

以我嵌入式行业来说的话,笔试题主要考查的内容还是C语言比较多。

话不多说,进入今天的正题吧,今天带大家了解一下算法。

简单来说,算法是一系列解决问题的步骤,是计算机程序设计的核心组成部分。好的算法可以大大提高计算机程序的执行效率,减少资源消耗。因此,在编程和计算机科学中,对算法的理解、设计和优化是非常重要的。

算法有很多,比如:排序算法查找算法、哈希算法图算法。

今天带同学们学几个面试题或者技术面试时候高频出现的几个排序算法和查找算法。

先讲排序算法。

排序算法基本概念

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

  • 稳定性
    在一组无序数据中,若两个待排序字段一致的数据,在排序前后相对位置不变,则称排序算法是稳定的,否则是不稳定的。

  • 内排序与外排序
    如果待排序数据量不大,可以一次性全部装进内存进行处理,则称为内排序,若数据量大到无法一次性全部装进内存,而需要将数据暂存外存,分批次读入内存进行处理,则称为外排序。

不同的排序算法性能也不同,这句话什么意思呢?就比如有五个保洁阿姨(算法),你派这五个阿姨去打扫房间,每个阿姨需要打扫完房间的时间肯定是不一样的,有些阿姨要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(n^1.3)左右,比普通的插入排序O(n^2)要好很多。但是,由于希尔排序的增量序列选择是一个尚未解决的数学问题,所以希尔排序的实际性能可能会因增量序列的不同而有所差异。在实际应用中,常用的增量序列有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(位运算,更高效)。
      • 比较 arr[mid] 与 target
        • 如果 arr[mid] 等于 target,则找到了目标值,返回 mid
        • 如果 arr[mid] 大于 target,说明目标值可能在 mid 的左侧,因此将 right 更新为 mid - 1
        • 如果 arr[mid] 小于 target,说明目标值可能在 mid 的右侧,因此将 left 更新为 mid + 1
  3. 查找结束
    • 如果循环结束仍未找到目标值,说明 target 不在数组中,返回一个表示未找到的标志值,通常是 -1

二分查找算法的时间复杂度是 O(log n),其中 n 是数组的长度。这是因为每次查找都将数组的范围减半,因此最多需要 log2(n) 次比较就能找到目标值或确定其不存在。这使得二分查找算法在处理大规模有序数组时非常高效。

示例代码:

#include <stdio.h>  
  
// 二分查找函数  
int binarySearch(int arr[], int left, int right, int target) 
{  
    // 当 left 大于 right 时,说明查找区间为空,目标值不存在  
    if (left > right) 
    {  
        return -1; // 返回-1表示未找到目标值  
    }  
      
    // 计算中间位置  
    int mid = left + (right - left) / 2;  
      
    // 检查中间位置的值是否等于目标值  
    if (arr[mid] == target) 
    {  
        return mid; // 如果等于,返回中间位置索引  
    } 
    else if (arr[mid] > target) 
    {  
        // 如果中间位置的值大于目标值,则在左半部分继续查找  
        return binarySearch(arr, left, mid - 1, target);  
    } 
    else 
    {  
        // 如果中间位置的值小于目标值,则在右半部分继续查找  
        return binarySearch(arr, mid + 1, right, target);  
    }  
}  
  
int main() 
{  
    int arr[] = {2, 3, 4, 10, 40}; // 示例有序数组  
    int n = sizeof(arr) / sizeof(arr[0]); // 数组长度  
    int target = 10; // 要查找的目标值  
    int result; // 存储查找结果  
      
    // 调用二分查找函数  
    result = binarySearch(arr, 0, n - 1, target);  
      
    // 输出查找结果  
    if (result == -1) 
    {  
        printf("目标值 %d 不在数组中\n", target);  
    } 
    else 
    {  
        printf("目标值 %d 在数组中的索引为 %d\n", target, result);  
    }  
      
    return 0;  
}

代码中,binarySearch 函数执行二分查找算法。它接受一个有序数组 arr、数组的起始索引 left、数组的结束索引 right 以及目标值 target 作为参数。函数首先检查查找区间是否为空(即 left 是否大于 right),如果是,则返回 -1 表示未找到目标值。然后,它计算中间位置 mid,并检查 arr[mid] 是否等于 target。如果等于,返回 mid;如果不等于,则根据 arr[mid] 与 target 的大小关系,在左半部分或右半部分继续递归查找。在 main 函数中,我们定义了一个有序数组 arr 和一个目标值 target,然后调用 binarySearch 函数进行查找,并输出结果。如果找到目标值,输出其在数组中的索引;否则,输出目标值不在数组中的消息。请注意,为了使用二分查找,数组 arr 必须是有序的。如果数组无序,二分查找将不会按预期工作。

分块查找算法(Block Search) 

分块查找算法是一种结合了顺序查找和二分查找的折中方案,适用于数据量大且部分有序的情况。该算法的基本思路是将数据集合分成若干块(或称为桶),每块内部的数据可以有序也可以无序,但块与块之间需要满足一定的排序规则。同时,建立一个索引表(或称为索引块),用于记录每块的最大值(或其他标识信息)以及块的起始位置。比如: 在一本字典中查找某个汉字的时候,一般是先找目录,在目录中找到对应拼音或笔画,然后直接翻到对应的页面再往下找,很显然这能大大提高查找的效率。

以下是分块查找算法的具体思路:

  1. 数据分块与索引表建立
    • 首先,根据数据的特性(如大小范围、分布情况等)将数据集合划分为多个块。每个块的大小可以根据实际情况进行调整,但通常建议块的大小适中,以便于管理和查找。
    • 对每个块内的数据进行排序(如果未排序),以便在块内部进行快速查找。块与块之间不需要保持有序,但通常要求后一块中的元素不小于前一块中的最大元素,以确保块之间没有重叠。
    • 建立索引表,用于记录每个块的最大值(或其他关键信息)以及块的起始位置。索引表通常按照块的顺序存储,并且需要保持有序,以便进行后续的查找操作。
  2. 查找过程
    • 当需要查找某个目标值时,首先在索引表中进行查找。由于索引表是有序的,可以采用二分查找算法或顺序查找算法来快速定位目标值可能所在的块。
    • 一旦确定了目标值所在的块,就在该块内部进行顺序查找。由于块内的数据已经排序,因此顺序查找的效率会相对较高。
    • 如果在块内部找到了目标值,则返回其位置;如果遍历完整个块仍未找到目标值,则说明目标值不在数据集合中。

分块查找算法的优点在于它结合了顺序查找和二分查找的特点。当数据量很大时,通过分块可以减少每次查找的范围,从而提高查找效率。同时,由于块内数据可以有序也可以无序,因此分块查找算法对数据集合的预处理要求相对较低。然而,分块查找算法的性能仍然受到块大小和索引表建立方式的影响,需要根据具体情况进行优化和调整。

总之,分块查找算法是一种适用于大规模数据集合的查找方法,通过合理划分数据块和建立索引表,可以在保持一定查找效率的同时降低预处理成本。

示例代码:

#include <stdio.h>  
#include <stdlib.h>  
  
// 定义块的大小  
#define BLOCK_SIZE 4  
  
// 假设我们有一个最大索引数组,用于记录每块的最大值  
int maxIndex[3]; // 因为有10个元素,分成3块(最后一块可能不满)  
  
// 假设我们有一个有序的数据数组  
int data[] = {10, 14, 19, 26, 27, 31, 33, 35, 42, 44};  
  
// 分块查找函数  
int blockSearch(int target) 
{  
    int n = sizeof(data) / sizeof(data[0]); // 数组长度  
    int blockNum = n / BLOCK_SIZE; // 块的数量  
    int blockIndex = 0; // 当前块的索引  
    int low, high, mid;  
      
    // 使用二分查找确定目标值可能存在的块  
    low = 0;  
    high = blockNum - 1;  
    while (low <= high) 
    {  
        mid = (low + high) / 2;  
        if (target <= maxIndex[mid]) 
        {  
            blockIndex = mid;  
            high = mid - 1;  
        } 
        else 
        {  
            low = mid + 1;  
        }  
    }  
      
    // 在确定的块中进行顺序查找  
    int start = blockIndex * BLOCK_SIZE;  
    int end = (blockIndex + 1) * BLOCK_SIZE;  
    if (end > n) end = n; // 处理最后一块可能不满的情况  
    for (int i = start; i < end; i++) 
    {  
        if (data[i] == target) 
        {  
            return i; // 找到目标值,返回其索引  
        }  
    }  
      
    return -1; // 没有找到目标值,返回-1  
}  
  
int main() 
{  
    int target;  
    printf("请输入要查找的目标值: ");  
    scanf("%d", &target);  
      
    // 初始化最大索引数组  
    maxIndex[0] = 26; // 第一块的最大值  
    maxIndex[1] = 35; // 第二块的最大值  
    maxIndex[2] = 44; // 第三块(也可能是唯一一块)的最大值  
      
    // 调用分块查找函数  
    int index = blockSearch(target);  
      
    // 输出查找结果  
    if (index != -1) 
    {  
        printf("目标值 %d 在数组中的索引为 %d\n", target, index);  
    } 
    else 
    {  
        printf("目标值 %d 不在数组中\n", target);  
    }  
      
    return 0;  
}

就写到这吧,乏了。如果哪里写错了麻烦请指出,万分感谢!

  • 45
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我不吃辣。

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值