算法
算法(Algorithm)是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令。简单来说,算法 就是解决一个问题的具体方法和步骤。在计算机科学中,算法是程序设计的核心,它决定了程序如何执 行特定的任务。
算法的性能评价通常包括时间复杂度和空间复杂度两个方面。时间复杂度衡量了算法执行所需的时间资 源,而空间复杂度则衡量了算法执行所需的存储资源。
1.时间复杂度
它衡量了一个算法执行所需时间的相 对量度。
时间复杂度描述了算法运行时间与输入规模之间的增长关系。
间复杂度并不是算法执行所需的实际时间,因为实际时间会受到很多因素的影响,如处理器速度、编 译器优化、系统负载等。因此,时间复杂度是算法执行时间随输入规模增长而增长的“趋势”或“速率”的度 量。
-
计算方式:
-
常见的时间复杂度有:
-
常数时间复杂度 O(1):算法的执行时间不随输入规模的增长而增长,即无论输入数据有多大,算法的执
行时间都是固定的。(数组访问)
-
线性时间复杂度 O(n):算法的执行时间与输入规模呈线性关系,即算法的执行时间随着输入数据量的增
加而线性增长。(单层循环)
-
对数时间复杂度 O(log n):算法的执行时间与输入规模的对数成正比。(二分查找)
-
线性对数时间复杂度 O(n log n):算法的执行时间随输入规模的增长而线性对数增长。(快速排序、归
并排序)。
-
平方时间复杂度 O(n^2):算法的执行时间与输入规模的平方成正比。(双重循环)。
-
2.空间复杂度
空间复杂度(Space Complexity)是对一个算法在运行过程中临时占用存储空间大小的量度。具体来
说,它表示算法在计算机内存中执行时所需额外空间的度量,记作S(n),其中n是问题的规模(即输入数
据的大小)。这个空间包括算法在执行时所使用的所有额外存储空间,如变量(包括静态变量和动态变量)、递归调用栈、以及输入输出数据所占据的存储空间等。
计算方式类似于时间复杂度.
1 排序
排序算法是计算机程序设计中的一种重要操作,旨在将一组数据元素(或记录)按照某种关键字的大小 顺序,递增或递减地排列起来。排序算法在数据处理、数据库管理、搜索引擎优化等多个领域都有广泛 应用
注意:下文中的排序默认是升序!
1.1冒泡排序
通过重复遍历待排序的序列,从数组一端开始不断比较相邻元素的大小,并在必要时交换它们的位置,
直到没有元素需要交换为止。
示例 时间复杂度O(n^2)
#include <iostream>
using namespace std;
//冒泡排序 时间复杂度O(n^2)
void bubble_sort(int *arr, int size)
{
//每进行一轮排序,最大的元素都会被安排再最右边 size - 1是因为比较左右两个元素,最后一个元
// 素不用比较
int tmp = 0;
for(int i = 0; i < size - 1; i++)
{
// / 对最大元素左边的所有数据重新排序,再找到其中最大的
for(int j = 0; j < size - 1 - i; j++)
{
if(arr[j] > arr[j+1])
{
// 交换 arr[j] 和 arr[j+1]
tmp = arr[j+1];
arr[j+1] = arr[j];
arr[j] = tmp;
}
}
}
}
int main()
{
int arr[5] = {5, 2, 1, 3, 4 };
bubble_sort(arr, 5);
for(int i = 0; i < 5; i++)
{
cout << arr[i] << " ";
}
cout << endl;
return 0;
}
1.2 选择排序
首先在序列中找到最小元素,放到序列的起始位置作为已排序序列;
然后,再从剩余未排序元素中继续寻找最小元素,放到已排序序列的末尾;
重复上述步骤,直到所有元素均排序完成。
示例 时间复杂度O(n^2)
// 选择排序
void select_sort(int *arr, int size)
{
int temp = 0;
// 记录最小索引
for(int i = 0; i < size - 1; i++)
{
//假设当前索引就是i
int MinNum = i;
for(int j = MinNum + 1; j < size; j++)
{
if(arr[j] < arr[MinNum])
{
MinNum = j;
}
}
if(MinNum != i)
{
temp = arr[MinNum];
arr[MinNum] = arr[i];
arr[i] = temp;
}
}
}
int main()
{
int arr1[5] = { 2, 0, 1,3, 4 };
select_sort(arr1, 5);
for(int i = 0; i < 5; i++)
{
cout << arr[i] << " ";
}
cout << endl;
return 0;
}
1.3 插入排序
插入排序的基本思想是将数组分为已排序和未排序两部分,初始时,已排序部分只包含第一个元素,未
排序部分包含其余元素。
然后,依次将未排序部分的元素插入到已排序部分的适当位置,直到未排序部
分为空。
示例 时间复杂度O(n^2)
// 插入排序
void insertionSort(int *arr, int size)
{
//默认第一个元素已经是有序的了
for(int i = 1; i < size ;i++)
{
// 有序位置
int key = arr[i];
// 从后往前
// j = i - 1 是用来从后往前比较的,这里的“后”是相对于当前要插入的元素 arr[i] 的位置而言的
int j = i - 1;
while (j > 0 && arr[j] > key)
{
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
}
int main()
{
int arr2[5] = { 2, 6, 5, 3, 4 };
insertionSort(arr2, 5);
for(int i = 0; i < 5; i++)
{
cout << arr2[i] << " ";
}
cout << endl;
return 0;
}
***1.4 快速排序 (面试会问)
首先从序列中任意选择一个元素,把该元素作为基准(一般是第一个或者最后一个元素)。
然后将小于等于基准的所有元素都移到基准的左 侧,把大于枢轴的元素都移到枢轴的右侧,。基准元素不属于任一 子序列,并且基准元素当前所在位置就是该元素在整个排序完成后的最终位置。
这样一个划分左右子序列的过程就叫做快速排序的一趟排序,或称为一次划分。递归此划分过程,直到 整个序列有序。
算法图解
首先给出一个无序序列[3, 5, 4, 1, 2],选取一个元素为基准元素,一般选择序列第一个元素(或最后一个 元素),以3作为基准,然后设置两个指针,一个left指针指向左侧第一个位置,一个right指针指向右 侧最后一个位置。
首先取出基准元素3,此时left指向的位置留出一个空位。为了好理解说是空位,其实是指向基准值
我们规定,指向**空(基准值)**的指针不移动。
此时应该操作right指针
-
如果right指针指向的元素大于基准元素3,那么right指针左移;
-
如果right指针指向的元素小于基准元素3,那么将right指针指向的元素放到left指针指向的空
位处(保证左边的元素都比基准小,右边元素都基准大),同时left指针右移。
显然,当前right指向的2小于3,所以把2放到left指向的位置,此时right指向为空。
right指针指向空,操作left指针, 对left指针: 如果left指针指向元素小于基准元素3,那么left指针右移
对left指针:
- 如果left指针指向元素小于基准元素3,那么left指针右移;
- 如果left指针指向元素大于基准元素3,那么把left指针指向的元素放到right指向的空位处。
此时,5大于3,元素放到right,同时right指针左移 )
left指针指向空,操作right指针,此时,1小于3,元素放到left,同时left指针右移
!
%BA%8F%5C5.png&pos_id=img-QZQXfVx8-1747289875468)
right指针指向空,操作left指针,此时,4大于3,元素放到right,同时right指针左移
left指针和right指针指向同一个位置,此时将基准元素插入。
最后得到的序列,3左侧全部是小于3的元素,3右侧全部是大于3的元素
然后重复上述步骤,对基准左右两边同时进行快速排序
`示例
时间复杂度O(n logn)`
#include <iostream>
using namespace std;
//冒泡排序 时间复杂度O(n^2)
void bubble_sort(int *arr, int size)
{
//每进行一轮排序,最大的元素都会被安排再最右边 size - 1是因为比较左右两个元素,最后一个元
// 素不用比较
int tmp = 0;
for(int i = 0; i < size - 1; i++)
{
// / 对最大元素左边的所有数据重新排序,再找到其中最大的
for(int j = 0; j < size - 1 - i; j++)
{
if(arr[j] > arr[j+1])
{
// 交换 arr[j] 和 arr[j+1]
tmp = arr[j+1];
arr[j+1] = arr[j];
arr[j] = tmp;
}
}
}
}
// 选择排序
void select_sort(int *arr, int size)
{
int temp = 0;
// 记录最小索引
for(int i = 0; i < size - 1; i++)
{
//假设当前索引就是i
int MinNum = i;
for(int j = MinNum + 1; j < size; j++)
{
if(arr[j] < arr[MinNum])
{
MinNum = j;
}
}
if(MinNum != i)
{
temp = arr[MinNum];
arr[MinNum] = arr[i];
arr[i] = temp;
}
}
}
// 插入排序
void insertionSort(int *arr, int size)
{
//默认第一个元素已经是有序的了
for(int i = 1; i < size ;i++)
{
// 有序位置
int key = arr[i];
// 从后往前
// j = i - 1 是用来从后往前比较的,这里的“后”是相对于当前要插入的元素 arr[i] 的位置而言的
int j = i - 1;
while (j > 0 && arr[j] > key)
{
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
}
// 快速排序
void quick_sort(int *p_num, int size)
{
int base = *p_num; //基准,选在开头
int *left = p_num; //左指针
int *right = p_num + size - 1; //右指针
int temp = 0;
// int *temp == nullptr error
// temp是一个指针,它用于存储内存地址。在你的代码中,你试图用 *temp 来存储交换的值,
// 但 temp 没有指向一个有效的内存地址,所以这种操作是非法的。这就像你有一个指向空房间的门(指针),但你却试图把东西放进这个不存在的房间里
if(size <= 1)
{
return; //递归出口
}
while( left < right)//指针比较
{
if( *left > *right)//指针指向的值比较
{
temp = *left;
*left = *right;
*right = temp;
}
// 等于基准值的指针不移动
// 左指针等于基准右指针前移
if( *left == base)
{
right--;
}
// 右指针等于基准左指针后移
else
{
left++;
}
}
// 递归调用基准值左边
quick_sort(p_num, left - p_num);
quick_sort(right + 1,size - 1 - (left - p_num));
}
int main()
{
int arr[5] = {5, 2, 1, 3, 4 };
bubble_sort(arr, 5);
for(int i = 0; i < 5; i++)
{
cout << arr[i] << " ";
}
cout << endl;
int arr1[5] = { 2, 0, 1,3, 4 };
select_sort(arr1, 5);
for(int i = 0; i < 5; i++)
{
cout << arr1[i] << " ";
}
cout << endl;
int arr2[5] = { 2, 6, 5, 3, 4 };
insertionSort(arr2, 5);
for(int i = 0; i < 5; i++)
{
cout << arr2[i] << " ";
}
cout << endl;
int arr3[5] = { 7, 6, 10, 8, 9 };
quick_sort(arr3, 5);
for(int i = 0; i < 5; i++)
{
cout << arr3[i] << " ";
}
cout << endl;
return 0;
}
-
问题 int *temp == nullptr不能存储元素
temp是一个指针,它用于存储内存地址。在你的代码中,你试图用 *temp 来存储交换的值,
但 temp 没有指向一个有效的内存地址,所以这种操作是非法的。这就像你有一个指向空房间的门(指针),但你却试图把东西放进这个不存在的房间里2 查找
2 查找
查找算法是计算机科学中用于在数据结构中查找特定元素的算法。
-
二分查找
在有序数组中,通过不断将数组分成两半,比较中间元素与目标值的大小,从而确定下一步的查找范 围。
示例 时间复杂度O(log n)
int* half_search(const int *p_num, int size, int num) { // 开始位置 const int *p_start = p_num; // 结束位置 const int* p_end = p_num + size - 1; // 中间位置 const int* p_mid = nullptr; while (p_start <= p_end) { // 中间位置就是开始位置加(结束位置 -开始位置) /2 p_mid = p_start + (p_end - p_start ) / 2 ; if ( *p_mid == num) { return (int*)p_mid; } //中间值比要找的值小 else if( *p_mid < num ) { //在后半部分找 p_start = p_mid + 1; } //中间值比要找的值大 else { //在前半部分找 p_end = p_mid - 1; } } // 遍历完了没有 return nullptr; } int main() { int arr[5] = {1, 2, 3, 4, 5 }; int *p = half_search(arr3, 5 , 4); cout << *p << " "; return 0; }