当前位置: 首页 > news >正文

【C语言实战(40)】C语言查找算法:从基础到实战的效率进阶

目录

  • 一、引言
  • 二、线性查找
    • 2.1 基本原理
    • 2.2 代码实现
    • 2.3 实战:学生信息查找
  • 三、二分查找
    • 3.1 基本原理
    • 3.2 代码实现
    • 3.3 实战:有序数组查找
  • 四、算法优化实战
    • 4.1 效率对比
    • 4.2 先排序再二分查找
    • 4.3 应用场景总结
  • 五、总结


一、引言

在 C 语言编程的广阔领域中,查找算法占据着举足轻重的地位。无论是在数据处理、数据库操作,还是在各种应用程序的开发中,我们常常需要从大量的数据中快速准确地找到特定的元素,而查找算法正是实现这一目标的关键工具。它就像是一把精准的 “数据探测器”,能够在复杂的数据集合中迅速定位到我们需要的信息,极大地提高了程序的效率和实用性。

接下来,我们将深入探讨两种经典的查找算法 —— 线性查找和二分查找。它们各具特色,适用于不同的场景,掌握它们将为我们的 C 语言编程能力增添强大的助力。

二、线性查找

2.1 基本原理

线性查找,也被称为顺序查找,是一种最为基础和直观的查找算法 。它的核心工作方式就是对数组进行从头到尾的遍历,将数组中的每一个元素都与我们设定的目标值进行逐一比较。一旦发现某个元素与目标值相等,就意味着成功找到了目标元素,此时算法会立即返回该元素在数组中的索引位置;若遍历完整个数组后,仍然没有找到与目标值匹配的元素,那就说明目标元素不在这个数组中,算法会返回一个特定的值,比如 - 1,以此来表示查找失败。

从时间复杂度的角度来看,在最坏的情况下,也就是目标元素位于数组的末尾或者根本不存在于数组中时,线性查找需要遍历整个数组,所以它的时间复杂度为 O (n),其中 n 代表数组中元素的数量。不过在最好的情况下,目标元素恰好是数组的第一个元素,那么只需要进行一次比较就能找到,此时时间复杂度为 O (1)。而在平均情况下,线性查找大约需要遍历一半的数组元素,时间复杂度同样为 O (n)。从空间复杂度上说,线性查找在执行过程中只需要使用几个额外的变量,比如用于记录当前遍历位置的索引变量和用于存储目标值的变量等,这些变量所占用的空间并不会随着数组规模的增大而增加,所以它的空间复杂度为 O (1),属于原地查找算法。 这种简单直接的查找方式,虽然在面对大规模数据时效率欠佳,但因其实现简单,并且无需对数据进行排序等预处理操作,在处理小规模数据或者对数据顺序没有要求的场景中,仍然有着广泛的应用。

2.2 代码实现

以下是使用 C 语言实现线性查找的代码示例,分别展示如何在整型数组和结构体数组中查找目标元素。

查找整型数组中的目标元素

#include <stdio.h>// 线性查找函数,在整型数组中查找目标元素
int linearSearchInt(int arr[], int size, int target) {for (int i = 0; i < size; i++) {if (arr[i] == target) {return i;  // 找到目标元素,返回其索引}}return -1;  // 未找到目标元素,返回-1
}int main() {int myArray[] = {10, 20, 30, 40, 50};int arraySize = sizeof(myArray) / sizeof(myArray[0]);int targetNumber = 30;int result = linearSearchInt(myArray, arraySize, targetNumber);if (result != -1) {printf("Element found at position %d\n", result);} else {printf("Element not found in the array.\n");}return 0;
}

在这段代码中,linearSearchInt函数接受三个参数:整型数组arr、数组的大小size以及要查找的目标元素target。函数通过for循环遍历数组,将数组中的每个元素与目标元素进行比较。如果找到匹配的元素,就返回该元素的索引;如果遍历完整个数组都没有找到匹配的元素,则返回 - 1。在main函数中,我们定义了一个整型数组myArray,并调用linearSearchInt函数来查找目标元素targetNumber,根据返回结果输出相应的信息。

查找结构体数组中的目标元素

假设我们有一个学生结构体,包含学号和姓名,现在要在结构体数组中查找指定学号的学生。

#include <stdio.h>
#include <string.h>// 定义学生结构体
typedef struct {int id;char name[50];
} Student;// 线性查找函数,在学生结构体数组中查找指定学号的学生
Student* linearSearchStudent(Student students[], int size, int targetId) {for (int i = 0; i < size; i++) {if (students[i].id == targetId) {return &students[i];  // 找到目标学生,返回其指针}}return NULL;  // 未找到目标学生,返回NULL
}int main() {Student students[] = {{1, "Alice"},{2, "Bob"},{3, "Charlie"}};int numStudents = sizeof(students) / sizeof(students[0]);int targetId = 2;Student* result = linearSearchStudent(students, numStudents, targetId);if (result != NULL) {printf("Student found: ID = %d, Name = %s\n", result->id, result->name);} else {printf("Student not found.\n");}return 0;
}

在这个示例中,我们首先定义了一个Student结构体,包含id和name两个成员。linearSearchStudent函数接受一个学生结构体数组students、数组的大小size以及要查找的目标学号targetId。函数通过循环遍历结构体数组,比较每个学生的学号与目标学号是否相等。如果找到匹配的学生,就返回该学生的指针;如果遍历完整个数组都没有找到匹配的学生,则返回NULL。在main函数中,我们创建了一个学生结构体数组students,并调用linearSearchStudent函数来查找指定学号的学生,根据返回结果输出相应的信息。

2.3 实战:学生信息查找

在实际应用中,我们经常需要在学生信息数组中查找指定学号的学生。下面我们通过一个完整的示例来展示如何实现这一功能。

假设我们有一个包含多个学生信息的数组,每个学生信息包括学号、姓名和成绩。现在我们要编写一个程序,能够根据用户输入的学号,查找并输出对应的学生信息。

#include <stdio.h>
#include <string.h>// 定义学生结构体
typedef struct {int id;char name[50];float score;
} Student;// 线性查找函数,在学生结构体数组中查找指定学号的学生
Student* linearSearchStudent(Student students[], int size, int targetId) {for (int i = 0; i < size; i++) {if (students[i].id == targetId) {return &students[i];  // 找到目标学生,返回其指针}}return NULL;  // 未找到目标学生,返回NULL
}int main() {Student students[] = {{1, "Alice", 85.5},{2, "Bob", 90.0},{3, "Charlie", 78.0},{4, "David", 88.5}};int numStudents = sizeof(students) / sizeof(students[0]);int targetId;printf("请输入要查找的学号: ");scanf("%d", &targetId);Student* result = linearSearchStudent(students, numStudents, targetId);if (result != NULL) {printf("学号: %d\n", result->id);printf("姓名: %s\n", result->name);printf("成绩: %.2f\n", result->score);} else {printf("未找到学号为 %d 的学生。\n", targetId);}return 0;
}

在这个程序中,linearSearchStudent函数的实现与前面的示例相同,用于在学生结构体数组中查找指定学号的学生。在main函数中,我们首先定义了一个包含多个学生信息的数组students,然后提示用户输入要查找的学号。接着,调用linearSearchStudent函数进行查找,并根据返回结果输出相应的学生信息。如果未找到指定学号的学生,则输出提示信息。通过这个示例,我们可以看到线性查找在实际场景中的具体应用,它能够帮助我们快速地从大量的学生信息中找到所需的记录。

三、二分查找

3.1 基本原理

二分查找,也称作折半查找,是一种高效的查找算法,但它有着一个严格的前提条件,那就是只能应用于有序数组。它的核心原理是基于分治思想,每次都将数组从中间分成两部分,然后通过比较中间元素与目标值的大小关系,来决定下一步在左半部分还是右半部分继续查找,从而不断地折半缩小查找范围。

具体来说,在执行二分查找时,首先会设定两个指针,一个指向数组的起始位置(left),另一个指向数组的结束位置(right)。接着,计算出中间位置(mid),其计算公式通常为mid = left + (right - left) / 2,这种计算方式能够避免left + right可能产生的整数溢出问题。然后,将中间位置的元素与目标值进行比较:如果中间元素恰好等于目标值,那就意味着找到了目标,直接返回中间位置的索引即可;如果中间元素大于目标值,说明目标值应该在数组的左半部分,于是将右指针right更新为mid - 1,继续在左半部分查找;如果中间元素小于目标值,表明目标值在数组的右半部分,此时将左指针left更新为mid + 1,在右半部分继续查找。不断重复这个过程,直到找到目标值或者left大于right(表示目标值不在数组中)为止。

从时间复杂度来看,由于每次查找都能将数组的查找范围缩小一半,所以二分查找的时间复杂度为 O (log n),其中 n 是数组中元素的数量。这使得二分查找在处理大规模数据时,效率远远高于线性查找 。不过,二分查找也有其局限性,它要求数组必须是有序的,如果数组无序,就需要先对数组进行排序才能使用二分查找算法,而排序本身也需要一定的时间和空间开销。

3.2 代码实现

在 C 语言中,二分查找可以通过递归和非递归两种方式来实现,下面我们分别展示这两种实现方式的代码。

递归版二分查找

#include <stdio.h>// 递归版二分查找函数
int binarySearchRecursive(int arr[], int left, int right, int target) {if (left > right) {return -1;  // 搜索范围为空,未找到目标值}int mid = left + (right - left) / 2;  // 计算中间位置if (arr[mid] == target) {return mid;  // 找到目标值,返回其位置} else if (arr[mid] < target) {// 目标值在右半部分,递归在右半部分查找return binarySearchRecursive(arr, mid + 1, right, target); } else {// 目标值在左半部分,递归在左半部分查找return binarySearchRecursive(arr, left, mid - 1, target); }
}int main() {int myArray[] = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100};int arraySize = sizeof(myArray) / sizeof(myArray[0]);int targetNumber = 50;int result = binarySearchRecursive(myArray, 0, arraySize - 1, targetNumber);if (result != -1) {printf("Element found at position %d\n", result);} else {printf("Element not found in the array.\n");}return 0;
}

递归版二分查找的优点是代码简洁,逻辑清晰,很好地体现了二分查找的递归思想 。但它也存在一些缺点,由于递归调用需要在栈中保存函数的参数、局部变量等信息,所以会占用较多的栈空间,当递归深度较大时,可能会导致栈溢出。而且递归调用还存在一定的函数调用开销,在性能上可能不如非递归实现。

非递归版二分查找

#include <stdio.h>// 非递归版二分查找函数
int binarySearchIterative(int arr[], int size, int target) {int left = 0;int right = size - 1;while (left <= right) {int mid = left + (right - left) / 2;  // 计算中间位置if (arr[mid] == target) {return mid;  // 找到目标值,返回其位置} else if (arr[mid] < target) {left = mid + 1;  // 目标值在右半部分,更新左边界} else {right = mid - 1;  // 目标值在左半部分,更新右边界}}return -1;  // 未找到目标值
}int main() {int myArray[] = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100};int arraySize = sizeof(myArray) / sizeof(myArray[0]);int targetNumber = 50;int result = binarySearchIterative(myArray, arraySize, targetNumber);if (result != -1) {printf("Element found at position %d\n", result);} else {printf("Element not found in the array.\n");}return 0;
}

非递归版二分查找使用循环来替代递归调用,避免了递归带来的栈溢出风险和函数调用开销,在性能上相对更优,尤其是在处理大规模数据时。同时,它的代码结构也更加直观,对于不熟悉递归的开发者来说更容易理解和维护。

3.3 实战:有序数组查找

在实际应用中,二分查找常用于在有序数组中快速查找目标值。下面我们通过一个具体的示例,展示如何在有序整型数组中查找目标值,并返回其索引位置。

#include <stdio.h>// 非递归版二分查找函数
int binarySearch(int arr[], int size, int target) {int left = 0;int right = size - 1;while (left <= right) {int mid = left + (right - left) / 2;  // 计算中间位置if (arr[mid] == target) {return mid;  // 找到目标值,返回其位置} else if (arr[mid] < target) {left = mid + 1;  // 目标值在右半部分,更新左边界} else {right = mid - 1;  // 目标值在左半部分,更新右边界}}return -1;  // 未找到目标值
}int main() {int myArray[] = {1, 3, 5, 7, 9, 11, 13, 15, 17, 19};int arraySize = sizeof(myArray) / sizeof(myArray[0]);int targetNumber;printf("请输入要查找的目标值: ");scanf("%d", &targetNumber);int result = binarySearch(myArray, arraySize, targetNumber);if (result != -1) {printf("目标值 %d 在数组中的索引位置是 %d\n", targetNumber, result);} else {printf("目标值 %d 不在数组中\n", targetNumber);}return 0;
}

在这个程序中,binarySearch函数实现了非递归版的二分查找算法。在main函数中,我们首先定义了一个有序整型数组myArray,然后提示用户输入要查找的目标值。接着,调用binarySearch函数在数组中查找目标值,并根据返回结果输出相应的信息。如果找到目标值,就输出其在数组中的索引位置;如果未找到,就输出提示信息表明目标值不在数组中。通过这个实战示例,我们可以更加深入地理解二分查找在实际编程中的应用。

四、算法优化实战

4.1 效率对比

在实际应用中,了解不同查找算法的效率差异至关重要。为了更直观地对比线性查找与二分查找的效率,我们可以通过编写测试代码,在大数据量的情况下,统计它们查找目标元素所耗费的时间。

下面是一段用于对比线性查找和二分查找效率的 C 语言代码:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <stdbool.h>// 线性查找函数
int linearSearch(int arr[], int size, int target) {for (int i = 0; i < size; i++) {if (arr[i] == target) {return i;}}return -1;
}// 非递归二分查找函数
int binarySearch(int arr[], int size, int target) {int left = 0;int right = size - 1;while (left <= right) {int mid = left + (right - left) / 2;if (arr[mid] == target) {return mid;} else if (arr[mid] < target) {left = mid + 1;} else {right = mid - 1;}}return -1;
}// 生成有序数组
void generateSortedArray(int arr[], int size) {for (int i = 0; i < size; i++) {arr[i] = i + 1;}
}int main() {const int size = 1000000;int *array = (int *)malloc(size * sizeof(int));if (array == NULL) {printf("内存分配失败\n");return 1;}generateSortedArray(array, size);int target = rand() % size + 1;  // 随机生成一个目标值clock_t start, end;double cpu_time_used;// 测试线性查找的时间start = clock();linearSearch(array, size, target);end = clock();cpu_time_used = ((double) (end - start)) / CLOCKS_PER_SEC;printf("线性查找耗时: %f 秒\n", cpu_time_used);// 测试二分查找的时间start = clock();binarySearch(array, size, target);end = clock();cpu_time_used = ((double) (end - start)) / CLOCKS_PER_SEC;printf("二分查找耗时: %f 秒\n", cpu_time_used);free(array);return 0;
}

在这段代码中,我们首先定义了线性查找和二分查找的函数,然后通过generateSortedArray函数生成一个包含 1000000 个元素的有序数组。接着,使用clock函数来记录查找操作前后的时间,通过计算时间差来得到每种查找算法的耗时。在实际运行中,多次执行这段代码,可以发现,在大数据量下,二分查找的耗时明显低于线性查找 。这是因为线性查找的时间复杂度为 O (n),随着数据量的增加,查找时间会线性增长;而二分查找的时间复杂度为 O (log n),增长速度远远慢于线性查找,所以在处理大规模数据时,二分查找具有更高的效率。

4.2 先排序再二分查找

当面对无序数组时,我们可以先对数组进行排序,然后再使用二分查找,以此来提高查找效率。这种方法的核心思路是利用二分查找在有序数组上的高效性,通过排序将无序数组转化为有序数组,从而适用二分查找算法。

以下是一个实现先排序再二分查找的 C 语言示例代码,使用快速排序对数组进行排序:

#include <stdio.h>
#include <stdlib.h>// 快速排序函数
void quickSort(int arr[], int low, int high) {if (low < high) {int pi = low;int pivot = arr[high];int i;for (i = low; i < high; i++) {if (arr[i] < pivot) {int temp = arr[i];arr[i] = arr[pi];arr[pi] = temp;pi++;}}int temp = arr[pi];arr[pi] = arr[high];arr[high] = temp;quickSort(arr, low, pi - 1);quickSort(arr, pi + 1, high);}
}// 二分查找函数
int binarySearch(int arr[], int low, int high, int target) {while (low <= high) {int mid = low + (high - low) / 2;if (arr[mid] == target) {return mid;} else if (arr[mid] < target) {low = mid + 1;} else {high = mid - 1;}}return -1;
}int main() {int arr[] = {64, 34, 25, 12, 22, 11, 90};int n = sizeof(arr) / sizeof(arr[0]);int target = 22;printf("原始数组: ");for (int i = 0; i < n; i++) {printf("%d ", arr[i]);}printf("\n");quickSort(arr, 0, n - 1);printf("排序后的数组: ");for (int i = 0; i < n; i++) {printf("%d ", arr[i]);}printf("\n");int result = binarySearch(arr, 0, n - 1, target);if (result != -1) {printf("目标值 %d 找到,索引为 %d\n", target, result);} else {printf("目标值 %d 未找到\n", target);}return 0;
}

在这个示例中,quickSort函数实现了快速排序算法,将无序数组arr进行排序。然后,binarySearch函数在排序后的数组上执行二分查找,查找目标值target。从性能角度分析,快速排序的平均时间复杂度为 O (n log n),二分查找的时间复杂度为 O (log n),因此这种先排序再二分查找的整体时间复杂度主要取决于排序操作,为 O (n log n)。虽然这种方法在查找效率上优于直接使用线性查找(线性查找时间复杂度为 O (n)),但需要注意的是,排序操作本身需要额外的时间和空间开销 。如果数组规模较小,或者查找操作执行次数较少,直接使用线性查找可能更为简单高效;而当数组规模较大且需要频繁查找时,先排序再二分查找的优势就会更加明显。

4.3 应用场景总结

在实际编程中,选择合适的查找算法对于程序的性能至关重要。根据前面的分析,我们可以总结出以下查找算法的应用场景:

  • 有序数组:当数组已经有序时,二分查找是首选的算法。它的时间复杂度为 O (log n),能够在大规模数据中快速定位目标元素,例如在数据库索引、有序数据集合的查找等场景中,二分查找都能发挥出高效的特性。
  • 无序数组
    • 数据量较小或查找操作不频繁:直接使用线性查找即可。线性查找实现简单,无需对数据进行预处理,在小规模数据或者偶尔进行查找的情况下,能够满足需求,并且不会引入额外的排序开销。
    • 数据量较大且查找操作频繁:先对数组进行排序,然后使用二分查找。虽然排序操作会带来 O (n log n) 的时间复杂度,但后续频繁的查找操作可以利用二分查找的高效性(时间复杂度为 O (log n)),从整体上提高程序的性能。不过,如果数组的顺序在查找过程中经常发生变化,每次查找都重新排序可能会导致性能下降,此时需要考虑其他更合适的数据结构或算法。

在选择查找算法时,需要综合考虑数组的规模、数据的有序性以及查找操作的频率等因素,权衡各种算法的优缺点,从而选择最适合当前场景的算法,以实现高效的数据查找和处理。

五、总结

线性查找作为一种基础且直观的查找算法,无需数据有序,实现简单,适用于小规模数据或对数据顺序无要求的场景,但其时间复杂度为 O (n),在大数据量下效率较低。二分查找则依赖于有序数组,利用分治思想不断折半缩小查找范围,时间复杂度为 O (log n),在处理大规模有序数据时具有显著的效率优势。

在实际编程中,算法优化至关重要。通过对比线性查找和二分查找在大数据量下的耗时差异,我们清晰地看到了不同算法的效率特性 。对于无序数组,先排序再二分查找的策略在一定程度上提高了查找效率,但需综合考虑排序开销。选择合适的查找算法,要依据数组的有序性、数据规模以及查找操作的频率等多方面因素。

算法的选择与优化是提升程序性能的关键,它贯穿于编程的各个领域,从简单的数据处理到复杂的系统开发,都离不开对算法的深入理解和灵活运用。在今后的编程实践中,我们应根据具体问题的需求,精准地选择和优化算法,以实现高效、优质的程序设计。

http://www.dtcms.com/a/511728.html

相关文章:

  • 洛谷 P2949 [USACO09OPEN] Work Scheduling G
  • 建站公司杭州南宁制作网站服务商
  • Deepseek-ocr论文精读
  • 【完整源码+数据集+部署教程】【文件&发票】发票信息提取系统源码&数据集全套:改进yolo11-ContextGuided
  • SpringBoot+Shiro+mybatis教务管理系统源码
  • 佛山个人制作网站公司手机百度下载免费安装
  • Git 项目开发核心指南:聚焦常用语法与完整流程
  • 【图像处理基石】遥感多光谱图像处理入门:从概念到实战(附Python代码)
  • Spring Boot项目中使用线程池并发插入6万条数据的线程池参数设置指南
  • 网站建设网站设计哪家专业东莞展馆设计公司
  • Docker Swarm:打造高效、可扩展的容器编排引擎,引领微服务新纪元(上)
  • 第15章:Spring AI Alibaba — 认识Graph框架
  • [Dify 实战] 构建一个自动发送邮件的插件:从 OpenAPI 到自动化通知
  • 基于Chrome140的FB账号自动化(关键词浏览)——脚本撰写(二)
  • CICD实战(8) - 使用Arbess+GitLab实现React.js项目自动化部署
  • 小程序uview actionSheet 内容过多高度设置
  • 基于.net的个人网站开发实录哪个网站建站比较好
  • 徐州做网站公司哪家好湘建网
  • 做头发个人网站制作素材专业网站设计制作服务
  • Linux初识进程
  • c#using Oracle.ManagedDataAccess.Client 批量保存数据
  • 人大金仓数据库kingbase8创建表示例
  • oracle包编译错误
  • 函数指针 指针函数 数组指针 指针数组 常量指针 指针常量
  • sqoop采集完成后导致hdfs数据与Oracle数据量不符的问题。怎么解决?
  • 洛阳有做网站开发的吗平台网站建设源码
  • 从零开始的C++学习生活 12:AVL树全面解析
  • Spring Boot 启动慢?启动过程深度解析与优化策略
  • telnet工具使用详解
  • YOLOv4:目标检测界的 “集大成者”