什么是数据结构?
在内存中对数据进行管理。
算法效率包括:时间效率 + 空间效率
1、时间效率(时间与环境有关:例如硬件等)
算法中的基本操作的执行次数,为算法的时间复杂度!(抛掉环境因素)
算法的时间复杂度是一个函数!
示例1:计算++count语句共执行多少次?
void Func(int N)
{
int count = 0;
for (int i = 0; i < N; i++)
{
for (int j = 0; j < N; j++)
{
count++;
}
}
for (int k = 0; k < 2 * N; ++k)
{
++count;
}
int M = 10;
while (M--)
{
++count;
}
printf("%d\n", count);
}
Func1的执行次数为F(N) = N2 + 2*N + 10
随着N的变大,后面两项的数值影响越来越小!
F(N)可以简化为F(N) = N2
1.1 大O的渐进表示法:
大O符号:适用于描述函数渐进行为的数学函数。(忽略对结果影响不大的项)
- 用常数1取代运行时间中的所有加法常数;
- 在修改后的运行次数函数中,只保留最高项;
- 如果最高阶项存在且不是1,则去除与这个项目相乘的常数,得到的结果就是大O阶。
例如Func1的时间复杂度:O(N2)
示例2:计算++count语句共执行多少次?
void Func2(int N)
{
int count = 0;
for (int k = 0; k < 2 * N; ++k)
{
count++;
}
int M = 10;
while (M--)
{
++count;
}
printf("%d\n", count);
}
func2的时间复杂度O(N)
示例3:计算++count语句共执行多少次?
void Func3(int N, int M)
{
int count = 0;
for (int k = 0; k < M; ++k)
{
++count;
}
for (int k = 0; k < N; k++)
{
++count;
}
printf("%d\n", count);
}
func3的时间复杂度O(M+N)
如M远远大于N,则时间复杂度为O(M)
示例4:计算Func4的时间复杂度
void Func4(int N)
{
int count = 0;
for (int k = 0; k < 100; k++)
{
++count;
}
printf("%d\n", count);
}
func3的时间复杂度O(1) --- O(1指的是常数次,不是1次)
示例5:计算strchr的时间复杂度
strchr
函数的主要功能是遍历字符串 str
,并查找 character
在其中第一次出现的位置。如果找到了,则返回该位置的指针,如果没有找到,则返回 NULL
。
char* my_strchr(const char* str, int character) {
// 遍历字符串,直到找到指定字符或到达字符串末尾
while (*str != '\0') {
if (*str == (char)character) {
return (char*)str; // 返回指向匹配字符的指针
}
str++; // 移动到下一个字符
}
// 如果没有找到,返回NULL
return NULL;
}
该搜索与字符的个数和位置有关系,因此我们需要考虑最好,最坏和平津三种情况:
- 最坏情况:任意输入规模的最大运行次数(上界)
- 平均情况:任意输入规模的期望运行次数
- 最好情况:任意输入规模的最小运行次数(下界)
一般取最坏情况!
实例6:冒泡排序
void BubbleSort(int* a, int n)
{
assert(a);
for (size_t end = 0; end > 1; --end)
{
int exchange = 0;
for (size_t i = 0; i < end; ++i)
{
if (a[i-1] > a[i])
{
Swap(&a[i - 1], &a[i]);
exchange = 1;
}
}
if (exchange = 0);
break;
}
}
冒泡排序的最好效率为O(N)
最差的效率为O(N2),一般直接取O(N2);不加指示位一定为O(N2)。
实例7:二分查找
int BinarySearch(int* a, int n, int x)
{
assert(a);
int begin = 0;
int end = n - 1;
while(begin <= end)
{
int mid = begin + ((end - begin) >> 1);
if (a[mid] < x)
begin = mid + 1;
else if (a[mid] > x)
end = mid - 1;
else
return mid;
}
return -1;
}
最好为:O(1);最坏为O(log2N)
前提条件为数组有序!O(log2N)效率远远大于O(N)!!! --- 红黑树效率好!
由于对数不好写,在时间复杂度中可以将log2N = logN(只有以2为底才简化)
实例8:计算Fac的时间复杂度
long long Fac(size_t N)
{
if (0 == N)
return 1;
return Fac(N - 1) * N;
}
从Fac(N)到Fac(0)共计算了N + 1次,时间复杂度为O(N)
实例9:计算斐波那契递归Fib的时间复杂度
long long Fib(size_t N)
{
if (N < 3)
return 1;
return Fib(N - 1) + Fib(N - 2);
}
时间复杂度为O(2的n次方)(等比数列 - 缺失的常数)
一般常见的时间复杂度排序:
O(1) > O(logN) > O(N^2) > O(N*logN) > O(N^2) > O(N^3) > O(2^N)
实例10、消失的数字
解题思路:
- 排序 + 二分查找 O(N*logN)
- 异或:a ^ a = 0; a ^ 0 = 0;(异或支持交换律)
- 0 ~ N求和 - 数组的值
接下来使用异或来完成:
int missingNumber(int* nums, int numsSize)
{
int val = 0;
int i = 0;
for (i = 0; i<numsSize; i++)
{
val = val ^ nums[i]; // 取出num[0]~num[n-1]的异或的和
}
for (i = 0; i<=numsSize; i++)
{
val = val ^ i; // 依次从i异或num[0]~num[n-1]
}
return val;
}
时间复杂度为O(N)!
使用求和来计算:
int missingNumber(int* nums, int numsSize)
{
int sum = numsSize * (numsSize+1) / 2;
int i = 0;
for (i = 0; i < numsSize ;i++)
{
sum = sum - nums[i];
}
return sum;
}
时间复杂度也为O(N)!
实例11、转轮数组
如果考虑每次移动一个数,需要移动K次,那么时间复杂度为O(K*N)(下节有)