学数据结构与算法不是仅仅学算法本身(经验),而是学习思维(解决问题的能力
)。
数据结构与算法(算法篇)
- 1、算法的性能分析
- 1.1 时间复杂度
- 1.2 空间复杂度
- 1.3 小结
- 2、高精度
- 2.1 高精度加法
- 2.2 高精度减法
- 2.3 高精度乘法
- 2.4 高精度除法
1、算法的性能分析
衡量指标:时间复杂度、空间复杂度、稳定性
1.1 时间复杂度
用O()表示,本质就是:算法的计算次数。
O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < …
#include<iostream>
using namespace std;
void test01()
{
// 时间复杂度:O(n)
int n, ans = 0;
cin >> n;
for (int i = 0; i < n; i++)
{
ans++;
}
// 有效计算次数 / 实际时间复杂度:O(n+3)
// 但当运行次数趋于无穷时候,3可以忽略不计
// 时间复杂度:O(n)
cin >> n;
for (int i = 0; i < n; i++)
{
ans++;
}
ans++;
ans++;
ans++;
}
void test02()
{
// 有效计算次数 / 实际时间复杂度:O(n/2)
// 但当运行次数趋于无穷时候,1/2可以忽略不计
// 时间复杂度:O(n)
int n, ans = 0;
cin >> n;
for (int i = 0; i < n; i+=2)
{
ans++;
}
}
void test02()
{
// 有效计算次数 / 实际时间复杂度:O(n/2)
// 时间复杂度:O(n)
int n, ans = 0;
cin >> n;
for (int i = 0; i < n; i += 2)
{
ans++;
}
}
void test03()
{
int n, ans = 0;
cin >> n;
// 时间复杂度:O(n^2)
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
ans++;
}
}
// 有效计算次数 / 实际时间复杂度:O(n^2+n)
// 但当运行次数趋于无穷时候,n相较于n^2,可以忽略不计
// 时间复杂度:O(n^2)
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
ans++;
}
}
for (int i = 0; i < n; i++)
{
ans++;
}
}
int main()
{
test01();
system("pause");
return 0;
}
1.2 空间复杂度
本质就是:额外
产生的空间(不是数据本身)。
额外
:也就是算法为了处理数据,额外使用的空间。
空间复杂度是和内存
有关。
logn的空间比较少,n^2这种空间的嵌套也比较少。
O(1) < O(n) < O(nlogn)
#include<iostream>
using namespace std;
void test01()
{
// 冒泡排序
// 真实时间复杂度:O((n^2-n) / 2 + 2n)
// 时间复杂度:O(n^2)
// 空间复杂度:O(1)
int a[101];
int n;
cin >> n;
// 这里的a占用n个空间,属于输入,不算额外产生的空间
for (int i = 0; i < n; i++)
{
cin >> a[i];
}
for (int i = n - 1; i > 0; i--)
{
for (int j = 0; j < n - 1; j++)
{
if (a[j] > a[j + 1])
{
// swap底层其实是有第三变量temp交换,但是是有穷的,算O(1)
swap(a[j], a[j + 1]);
}
}
}
for (int i = 0; i < n; i++)
{
cout << a[i] << " ";
}
cout << endl;
}
void test02()
{
int n;
cin >> n;
// 使用了额外的空间,空间复杂度:0(n)
int* arr = new int[n];
for (int i = n - 1; i > 0; i--)
{
for (int j = 0; j < n - 1; j++)
{
if (arr[j] > arr[j + 1])
{
swap(arr[j], arr[j + 1]);
}
}
}
delete[] arr;
}
int main()
{
test01();
system("pause");
return 0;
}
1.3 小结
时间复杂度越高,程序运行的时间就越长。
时间和空间往往是相对的,一般,可能会用时间换空间,用空间换时间。
2、高精度
为什么要接触高精度?
主要就是:解决数据溢出
的问题。
下方的代码,不同的整型是有取值范围的。
int main()
{
// int型就是:-2^31 到 2^32-1
// long long 是:-2^63 到 2^63-1
int a, b;
cin >> a >> b;
cout << a + b << endl;
system("pause");
return 0;
}
还没等输入第二个变量的赋值,程序就结束了。低精度
,就搞不定我们这种需求了。
高精度
的本质就是使用字符
进行计算。
但是,当发现使用字符串
(字符数组)时,能够很好地解决这种数据溢出的问题,但是字符串之间不能够像数值计算那样,+号运算符重载也只是字符串拼接。
int main()
{
string a, b;
cin >> a >> b;
cout << a + b << endl;
system("pause");
return 0;
}
但是,单个字符使用ASCII码,进行存储的,字符’1’是49,使用字符'1' - '0'
就可以巧妙的得到数值1本身。
2.1 高精度加法
注意事项:
1、字符数组局部变量要初始化
,可以写到全局
变量(默认初始化为0)
2、高精度
的本质就是使用字符
进行计算,通过字符 - ‘0’ 转换为数值本身
3、使用两个字符数组相加,存放结果的数组的长度为这两个字符数组中的最长长度+1
。
#include<iostream>
using namespace std;
#include<string>
void string_to_int(string str, int dst[])
{
for (int i = 0; i < str.size(); i++)
{
// "1234"
// 要放置成
// "4321"
// 也就是第0个放在第3个,第1个放在第2个,
// 如果有n个,第i个就是放在第n-i-1个
dst[str.size() - i -1] = str[i] - '0';
}
}
int main()
{
// "1234"
// +"567"
// 个位不能够有效对齐,所以对字符数组取反,再计算
// "4321"
// +"7650"
// "1081"
// 取反不影响计算,最后算完,再反转,得:1801就可以了。
string s1, s2;
// 字符数组局部变量要初始化,可以写到全局变量(默认初始化为0)
int a[101]= { 0 };
int b[101]= { 0 };
int c[101]= { 0 };
cin >> s1 >> s2;
// 1.个位对齐(先反转),字符转整型
string_to_int(s1, a);
string_to_int(s2, b);
for (int i = 0; i < s1.size(); i++)
{
cout << a[i];
}
cout << endl;
for (int i = 0; i < s2.size(); i++)
{
cout << b[i];
}
cout << endl;
// 2.对位相加,放到c数组中,但首先要计算c数组的长度
// "9999" + "1" 是5位。所以两数相加,结果的最高的长度就是最长的数位数+1
int c_len = max(s1.size(), s2.size()) + 1;
// 3.对位相加,放到c数组中
for (int i = 0; i < c_len; i++)
{
c[i] += a[i] + b[i];
// 下一位进位
c[i + 1] = c[i] / 10;
// 两数相加可能会有进位,保留个位
c[i] = c[i] % 10;
}
// 反转输出,比如相加为1801没有进位, 但是这样可能会造成c_len多1个0
// 就会是18010
// 从最后1位逐个判断首位是否是0,逐个去0
while (c[c_len] == 0 && c_len>1)
{
c_len--;
}
// 倒叙打印
for (int i = c_len; i >= 0; i--)
{
cout << c[i];
}
cout << endl;
system("pause");
return 0;
}
2.2 高精度减法
注意事项:
1、最好保证是长
的数 - 短
的数,最后加个负号
就行;长度相同,就比字符串。
2、使用两个字符数组相减,存放结果的数组的长度为这两个字符数组中的最长长度
。
3、在字典序中,比较两个字符串时是逐字符
进行的,每个字符的排序取决于其编码值。即使 s1 和 s2 的长度不同,只要 s1 的第一个字符在字典序中大于 s2 的第一个字符,结果就是 true。
#include<iostream>
using namespace std;
#include<string>
void string_to_int(string str, int dst[])
{
for (int i = 0; i < str.size(); i++)
{
// "1234"
// 要放置成
// "4321"
// 也就是第0个放在第3个,第1个放在第2个,
// 如果有n个,第i个就是放在第n-i-1个
dst[str.size() - i -1] = str[i] - '0';
}
}
bool comStr(string str1, string str2)
{
if (str1.size() != str2.size())
{
return str1.size() >= str2.size();
}
else
{
// 两个字符串长度相同,比大小
return str1 >= str2;
}
}
int main()
{
int a[101] = { 0 };
int b[101] = { 0 };
int c[101] = { 0 };
string s1, s2;
cin >> s1 >> s2;
if (comStr(s1, s2) == false)
{
// 如果s1比s2长度短,或s1 < s2,进行替换,这样就能保证大数减小数
/*string temp = s1;
s1 = s2;
s2 = temp;*/
swap(s1, s2);
}
// 1.反转
string_to_int(s1, a);
string_to_int(s2, b);
// 2.对位相减,放到c数组中,但首先要计算c数组的长度
// "9999" - "1" 是4位。所以两数相减,结果的最高的长度就是最长的数位数
int c_len = max(s1.size(), s2.size());
// 3.对位相减,放到c数组中
// "1234"
//- "567"
// 反转
// "4321"
// - "765"
for (int i = 0; i < c_len; i++)
{
// 要判断个位够不够减
if (a[i] < b[i])
{
a[i] += 10; //借位
a[i + 1]--; // 高位减1
}
c[i] = a[i] - b[i];
}
while (c[c_len] == 0 && c_len > 0)
{
c_len--;
}
// 倒叙打印
for (int i = c_len; i >= 0; i--)
{
cout << c[i];
}
cout << endl;
system("pause");
return 0;
}
2.3 高精度乘法
注意事项:
1、乘法里还是有加法的影子,找通项公式
。
2、使用两个字符数组相乘,存放结果的数组的长度最大为这两个字符数组中的长度之和
。
3、在字典序中,比较两个字符串时是逐字符
进行的,每个字符的排序取决于其编码值。即使 s1 和 s2 的长度不同,只要 s1 的第一个字符在字典序中大于 s2 的第一个字符,结果就是 true。
#include<iostream>
using namespace std;
#include<string>
void string_to_int(string str, int* a)
{
for (int i = 0; i < str.size(); i++)
{
// "1234"
// 要放置成
// "4321"
// 也就是第0个放在第3个,第1个放在第2个,
// 如果有n个,第i个就是放在第n-i-1个
a[str.size() - i - 1] = str[i] - '0';
}
}
int main()
{
int a[201] = { 0 };
int b[201] = { 0 };
int c[201] = { 0 };
string s1, s2;
cin >> s1 >> s2;
// 1.反转
string_to_int(s1, a);
string_to_int(s2, b);
// 2.对位相乘,放到c数组中,但首先要计算c数组的长度
// "1000" * "10" 是5位。所以两数相乘,结果的最高的长度就是最长的数位数
int c_len = s1.size() + s2.size();
// 3.对位相乘,放到c数组中
// a3 a2 a1 a0
//* b1 b0
// a3b0 a2b0 a1b0 a0b0
//+ a3b1 a2b1 a1b1 a0b1
//
//对应 c[4] c[3] c[2] c[1] c[0]
//结合a和b的下标,规律就是:c[i+j] = a[i] + b[j]
// c[3+1] c[3+0] c[2+0] c[1+0] c[0+0]
for (int i = 0; i < s1.size(); i++)
{
for (int j = 0; j < s2.size(); j++)
{
// 这里应该是累加
// 比如:这里c[2] = a3b1 + a2b2 两项
c[i + j] += a[i] * b[j];
// 进位,也是累加,可能是多次进位
c[i + j + 1] += c[i + j] / 10;
// 留下个位
c[i + j] = c[i + j] % 10;
}
}
while (c[c_len] == 0 && c_len > 1)
{
c_len--;
}
// 倒叙打印
for (int i = c_len; i >= 0; i--)
{
cout << c[i];
}
cout << endl;
system("pause");
return 0;
}
2.4 高精度除法
注意事项:
1、不用字符串取反
。
2、只用考虑够除
和余数
就行。
#include<iostream>
using namespace std;
void string_to_int(string str, int* a)
{
for (int i = 0; i < str.size(); i++)
{
// 将每个字符转换成数值
a[i] = str[i] - '0';
}
}
// 高精度除法(高精度 / 低精度)
int main()
{
string s;
int a[201] = { 0 }; // 高精度
long long b; // 低精度
int c[201] = { 0 }; // 存放结果(高精度)
int temp = 0;
cin >> s;
cin >> b;
string_to_int(s, a);
for (int i = 0; i < s.size(); i++)
{
c[i] = (temp * 10 + a[i]) / b;
// 余数
temp = (temp * 10 + a[i]) % b;
}
int lc = 0;
while (c[lc] == 0 && lc < s.size())
{
lc++;
}
for (int i = 0; i < s.size(); i++)
{
cout << c[i];
}
cout << endl;
system("pause");
return 0;
}