欢迎访问我的博客首页。
1. 把字符串转换成整数
1. 初始化列表与构造函数体
使用初始化列表初始化成员变量时,按成员变量的声明顺序初始化,与初始化列表中的顺序无关。在构造函数体内初始化成员变量时,才按初始化语句的顺序初始化。
2. 实现 atoi 函数
题目:实现 atoi 函数。输入的字符串可能包含 ‘0’ 到 ‘9’ 之内的字符和符号字符 ‘+/-’,还可能包含其它无效字符。atoi 函数只处理无效字符前的部分,比如输入字符串 “123ab45”,输出整数 123。如果整数越界,正数输出 32 位有符号最大值,负数输出 32 位有符号最小值。
分析:C++ 中的 atoi 函数的参数是 char * 类型,它是把字符转换成整数。如果字符包含小数点等其它非法字符,它只处理非法字符前面的部分。
bool gb_inputValid = true;
int my_atoi(string str) {
gb_inputValid = true;
// 1.是否为空或只含空格。
if (str.size() == 0 || str.find_first_not_of(" ") == string::npos) {
gb_inputValid = false;
return 0;
}
// 2.去首尾空格。
str.erase(0, str.find_first_not_of(" "));
//str.erase(str.find_last_not_of(" ") + 1);
// 3.符号位。
bool flag = true;
if (str[0] == '-' || str[0] == '+') {
if (str[0] == '-')
flag = false;
str.erase(0, 1);
}
// 4.去掉符号位后面的0。
str.erase(0, str.find_first_not_of('0'));
// 5.截取符号位后面的有效部分。
int len = 0;
for (; len < str.size(); len++) {
if (str[len] < '0' || str[len] > '9')
break;
}
// 6.处理有效部分。
if (len == 0) {
gb_inputValid = false;
return 0;
}
str = str.substr(0, len);
int sum = 0, index = 0;
while (len > 0) {
// 7.x本身是否溢出。
if (index > 9 || (index == 9 && str[len - 1] - '0' > 2)) {
if (flag == true)
return INT_MAX;
return INT_MIN;
}
int x = (str[len - 1] - '0') * pow(10, index);
// 8.加上x是否溢出。
if (flag == true) {
if (x >= INT_MAX - sum)
return INT_MAX;
}
else {
if (x - 1 >= INT_MAX - sum)
return INT_MIN;
}
sum += x;
index++;
len--;
}
// 9.带符号的结果。
if (flag == false) {
sum *= -1;
}
return sum;
}
代码:这道题的题目给出了需要考虑的情况,即使没有给出这些提示,程序员也要考虑到。其次要注意越界情况:第 7 部分首先判断 x 是否溢出,第 8 部分再判断加上 x 后是否溢出,这两处溢出判断不能遗漏。
2. 树中两个节点的最低公共祖先
对三种树中的两个节点求最低公共祖先。
2.1 二叉排序树
当树是二叉排序树时问题很简单,可以根据二叉排序树的性质找两个节点的最低公共祖先。
图
2.1
图 2.1
图2.1
1. 先序遍历法
二叉排序树中两个节点 a 和 b 的最低公共祖先 p 的值介于这两个节点之间。假设 a<b,使用先序遍历找到的第一个满足 a < p < b 的节点 p 就是 a 和 b 的 最低公共祖先。
比如找 a=8 和 b=16 的节点,先序遍历到第一个满足 a < p < b 的节点 p=15 就是 a 和 b 的最低公共祖先。下面是使用递归实现的先序遍历算法:
void pre_rescurse(Node* tree, int a, int b) {
if (tree == nullptr)
return;
if (tree->data > a && tree->data < b) {
cout << tree->data << " ";
return;
}
pre_rescurse(tree->lchild, a, b);
pre_rescurse(tree->rchild, a, b);
}
2. 二叉排序树的查找法
其实只要从树根开始找到第一个满足 a < p < b 的节点 p 就可以,其过程就是从二叉排序树中查找一个满足 a < p < b 的节点 p :
Node* find(Node* tree, int a, int b) {
while (tree != nullptr) {
if (tree->data < a)
tree = tree->rchild;
else if (tree->data>b)
tree = tree->lchild;
else
return tree;
}
return nullptr;
}
2.2 节点带有指向父节点指针的树
如果树的每个节点有一个指向父节点的指针,找最低公共祖先的问题可以转化为找两个链表的第一个交点的问题,也很简单。同时这也不要求树是二叉树。
图
2.2
图 2.2
图2.2
如图 2.2,找节点 D 和 节点 H 的最低公共祖先,等价于找链表 D->B->A 与 链表 H->E->B->A 的第一个交点。找两个链表的第一个交点可以使用栈,还可以使用跑路法。使用栈的方法很容易理解,这里介绍一下跑路法。跑路法就是让两个指针从两个链表头部开始遍历链表,当一个指针到达链表尾部时从另一个链表头部开始继续遍历,直到两个链表所指节点相同。比如指针 1 从 D 节点开始遍历的路径是 D->B->A->H->E->B…,指针 2 从 H 节点开始遍历的路径是 H->E->B->A->D->B…。当它们各遍历 6 个元素时都指向了节点 B,于是节点 B 就是它们的第一个交点,也就是它们的最低公共祖先。
2.3 普通树
图
2.3
图 2.3
图2.3
1. 查找法
还是假设要找节点 D 和节点 H 的最低公共祖先。从树根 A 开始查找节点 D 和节点 H,可以使用任何遍历算法。如果都能查找到,则最低公共祖先在该子树中;如果只包含节点 D 或节点 H 则该节点的父节点就是节点 D 和节点 H 的最低公共祖先。
比如遍历到 A 时,在以 A 为根节点的子树中可以查找到节点 D 和节点 H。然后遍历节点 B,在以 B 为根结点的子树中也可以查找到节点 D 和节点 H,于是无须考虑节点 C。然后遍历节点 D,在以 D 为根节点的树中只能发现节点 D,说明节点 H 必在以节点 D 的兄弟节点为根节点的子树中,于是节点 D 的父节点 B 就是节点 D 和节点 H 的最低公共祖先。
int find_node(treeNode* tree, int a, int b) {
if (tree == nullptr) return 0;
int res_a = 0, res_b = 0;
queue<treeNode*> qu;
qu.push(tree);
while (qu.empty() != true) {
treeNode* temp = qu.front();
qu.pop();
if (temp->data == a) res_a = 1;
if (temp->data == b) res_b = 1;
for (auto it = temp->children.begin(); it != temp->children.end(); it++)
qu.push(*it);
}
return res_a + res_b;
}
treeNode* find_tree(treeNode* tree, int a, int b) {
if (tree == nullptr) return nullptr;
if (find_node(tree, a, b) != 2) return nullptr;
while (tree != nullptr) {
for (auto it = tree->children.begin(); it != tree->children.end(); it++) {
int count = find_node(*it, a, b);
if (count == 2) {
tree = *it;
break;
}
else if (count == 1)
return tree;
}
}
return nullptr;
}
int find_node(treeNode* tree, int a, int b) 函数使用层序遍历,在以 tree 为根节点的树中查找是否包含值为 a, b 的节点。不包含返回 0,包含两者之一返回 1,包含两者返回 2。
treeNode* find_tree(treeNode* tree, int a, int b) 函数用于查找最低公共祖先。首先确保以 tree 为根节点的树中包含值为 a, b 的节点,否则返回空指针。然后在它的子树中继续查找。
2. 求最后一个交点法
上面的查找法对有些节点做了多次遍历,比如以 A 为根节点遍历了一遍树,然后以 B 为根节点又遍历了一遍树,这时以 B 为根节点的树的节点已经被遍历了两次。下面是另一种思路,如图 2.3 的右图。
与 2.2 节求两个链表的第一个交点不同,此时是求两个链表的最后一个交点。求两个链表的最后一个交点是很容易的,关键是怎么从树中找出这两个链表,也就是从树中查找一个节点并把路径保存下来。
bool get_path(treeNode* tree, int node, list<treeNode*>& path) {
if (tree->data == node) return true;
path.push_back(tree);
bool found = false;
for (auto it : tree->children) {
found = get_path(it, node, path);
if (found == true)
break;
}
if (found == false)
path.pop_back();
return found;
}
treeNode* get_last_common_node(list<treeNode*>& path1, list<treeNode*>& path2) {
treeNode *p = nullptr;
for (auto it1 = path1.begin(), it2 = path2.begin(); it1 != path1.end() && it2 != path2.end(); it1++, it2++) {
if (*it1 != *it2) break;
p = *it1;
}
return p;
}
int main() {
treeNode* tree = build_tree();
list<treeNode*> path1;
list<treeNode*> path2;
get_path(tree, 6, path1);
get_path(tree, 5, path2);
treeNode* res = get_last_common_node(path1, path2);
system("pause");
}
函数 get_path 使用递归的方法获取从出发点到它的某个指定的子孙结点的路径。