目录
1.简单查找
如果需要在1~100内找一个数字应该怎么做?最简单的办法就是写一个循环直到找到对应的数字。
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
int main() {
srand(time(0)); // 种随机种子
int n = rand() % 100 + 1;
cout << n << endl;
for (int i = 1; i <= 100; i++) {
if (i == n) {
cout << "对了" << endl;
return 0;
}
else if(i > n){
cout<<" 大了 " << endl;
}
else if (i < n) {
cout << "小了" << endl;
{
}
return 0;
}
这就是最简单的查找,但是也最不方便,每次只能排除一个数字,但是如果从中间开始查找呢,可以看到在上面代码中有判断可以知道i是大于还是小于随机数,所以我们可以用二分查找。
2.二分查找
二分查找,也称为折半查找,是一种高效的查找算法,用于在有序数组或有序列表中查找特定元素的位置。
-
确定查找范围的起始点
left
和结束点right
。初始时,left
指向数组的第一个元素,right
指向数组的最后一个元素。 -
计算中间元素的下标
mid
,公式为mid = (left + right) / 2
。 -
比较中间元素与目标元素的值:
如果中间数50小了就可以取50-100中间的数,直至找到随机数为止,相比于简单查找二分查找更加高效便捷。
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
int main() {
srand(time(0)); // 种随机种子
int n = rand() % 100 + 1;
cout << n << endl;
int left = 1;
int right = 100;
while (left <= right) {
int mid = (left + right) / 2;
cout << mid << " ";
if (mid == n) {
cout << "对了" << endl;
return 0;
}
else if (mid < n) {
cout << "小了" << endl;
left = mid + 1;
}
else if (mid > n) {
cout << "大了" << endl;
right = mid - 1;
}
}
return 0;
}
3.时间复杂度:
时间复杂度是一种用来衡量算法执行时间与输入规模之间关系的度量。它描述了算法执行时间的增长率,通常用大O符号(O)来表示。
在计算时间复杂度时,可以考虑以下几个方面:
-
单个操作的时间复杂度:分析算法中每个基本操作的执行时间,例如赋值、比较、循环等。通常将这些操作的时间复杂度视为常量,记作O(1)。
-
循环结构的时间复杂度:如果算法包含循环结构(例如for循环、while循环),则需要考虑循环的执行次数。如果循环次数与输入规模n成正比,那么循环的时间复杂度就是O(n)。
-
嵌套结构的时间复杂度:如果算法中存在多层嵌套的循环结构,那么需要将各层循环的时间复杂度相乘。
-
递归结构的时间复杂度:对于递归算法,需要考虑递归的深度和每次递归的时间复杂度。递归的时间复杂度可以通过递归公式或递归树来推导。
-
分支结构的时间复杂度:如果算法中存在条件判断语句(例如if语句),那么需要考虑每个分支的时间复杂度,并选择最高的那个作为整个分支结构的时间复杂度。
常见的时间复杂度:
-
O(1):常数时间复杂度
int add(int a, int b) {
return a + b;
}
无论输入的大小是多少,该函数的执行时间都保持不变。
-
O(log n):对数时间复杂度
上述的二分查找时间复杂度就是O(log n)。
-
O(n):线性时间复杂度
void printArray(const vector<int>& arr) {
for (int num : arr) {
cout << num << " ";
}
cout << endl;
}
函数根据数组的大小线性地遍历并打印每个元素。
-
O(n log n):线性对数时间复杂度
最常见的线性对数时间复杂度就是快速排序,后面会详细介绍。
-
O(n^2):平方时间复杂度
void printPairs(const vector<int>& arr) {
for (int i = 0; i < arr.size(); i++) {
for (int j = i + 1; j < arr.size(); j++) {
cout << "(" << arr[i] << ", " << arr[j] << ") ";
}
}
cout << endl;
}
函数打印出数组中所有的元素对。
-
O(2^n):指数时间复杂度
int fibonacci(int n) {
if (n <= 1) {
return n;
}
return fibonacci(n-1) + fibonacci(n-2);
}
这个递归函数根据斐波那契数列的定义,计算第n个数字。但是,该递归实现的时间复杂度是指数级的 ,随着n的增长,计算时间会急剧增加。当n为40时,函数需要执行数十亿次递归调用。
因此,在实际应用中,为了避免指数级的时间复杂度,我们通常会考虑使用更高效的算法来解决类似的问题
在实际应用中,可以根据算法的时间复杂度选择适当的算法。
4.空间复杂度
在编写代码时,我们完全可以用空间来换取时间。比如说,要判断1000以内的素数?
我们可以有两种解决方式:
方法一:每次给一个数,通过计算得到是否是素数的结果。
方法二:先建立一个有1001个元素的数组,然后把所有的数值与下标的数字对应,如果是素数,此数组项的值就是0,如果不是值为1。(桶思想)
对比方法一和方法二,可以发现,所谓的判断某─数值是否是素数,就变成了查找这个数组的某一项的值是多少的问题。此时,我们的运算是最小化了,但是硬盘上或者内存中需要存储这
1001个0和1。
通空间复杂度级别:
-
常数空间复杂度 O(1):算法所需的额外空间是固定的,与输入规模无关,常量级别的空间占用。
-
线性空间复杂度 O(n):算法所需的额外空间随输入规模线性增长,与输入规模成正比。
-
对数空间复杂度 O(log n):算法所需的额外空间随输入规模的对数级别增长,通常是由于递归调用或分治算法的使用。
-
线性对数空间复杂度 O(n log n):算法所需的额外空间随输入规模的对数级别增长,但同时还有线性级别的空间占用。
-
平方空间复杂度 O(n^2):算法所需的额外空间随输入规模的平方级别增长。
空间复杂度通常只考虑算法本身所使用的额外空间,而不包括输入数据占用的空间。
在分析和选择算法时,除了时间复杂度外,空间复杂度也是一个重要的考虑因素。较低的空间复杂度通常意味着更节省内存的算法,尤其是在处理大规模数据时。