前言
在前面我们其实已经分析过二叉树了,所谓二叉树就是度为2的树。而二叉树中,又有一种特殊的树称之为二叉搜索树
一、二叉搜索树的概念
二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
- 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
- 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
- 它的左右子树也分别为二叉搜索树
为什么我们将这样的树称作二叉搜索树呢?因为这样的树天生就十分适合搜索。是想一下,使用上图中最右边的树,如果我们想找到7的话,首先7比6大,所以一定在右子树,而7小于9,所以一定在左子树。结果发现就找到了。从而我们可以发现,这种树的效率天生就很高。
而且这棵树最多找高度次。也就是说最多找N次(注意不是logN次,因为我们可能碰到下图中右边的树),下图中的两棵树都是二叉搜素树。也就是说时间复杂度为O(N)
普通的二叉搜索树的时间复杂度为O(N),但是我们可以对其进行一些特殊变换,使之成为AVL树,红黑树。这样时间复杂度就可以降为了O(logN)了。
这棵树还有一个名字是二叉排序树,这是因为当我们对这棵树进行中序遍历的时候,结果是升序的。
二、二叉搜索树的实现
1.二叉树的结点定义
对于二叉树的结点,我们类似于list的结点定义一样,使用一个struct类。在这个类中,我们可以有默认构造函数,这个默认构造函数的缺省值为K()。
template<class K> struct BSTreeNode { BSTreeNode(const K& val = K()) :_key(val) ,_left(nullptr) ,_right(nullptr) {} K _key; BSTreeNode<K>* _left; BSTreeNode<K>* _right; };
2.二叉搜索树的结构
为了方便我们后序的结点的定义,我们可以先将结点给typedef一下。然后我们的成员变量其实也就这一个根节点。只要有根节点就可以构建出二叉搜索树了。
private: typedef BSTreeNode<K> Node; Node* _root;
3.二叉搜索树的构造函数
public: BSTree() :_root(nullptr) {}
如上代码所示,对于构造函数其实是比较简答的,我们只需要将二叉树的根结点初始化为空即可
4.二叉搜索树的插入(非递归)
对于插入接口,我们需要考虑的情况有如下几种:
- 二叉树为空的时候,直接new一个结点插入上去即可
- 如果二叉树不为空,那么我们就需要进行比较
- 如果我们想要插入的值大于当前结点的值,那么我们就将当前结点迭代到右孩子
- 如果我们想要插入的值小于当前结点的值,那么我们将当前结点迭代到左孩子
- 如果恰好等于当前结点的值,那么我们可以认为插入失败了,因为此时已经不满足二叉搜索树的定义了。
- 最终我们会发现如此迭代下去,最终的当前结点一定会为空指针,这并非我们所期望的。我们肯定是还需要它的父节点的,这样才能让我目标值插入进去。所以我们还需要一个值来记录当前所迭代位置的父节点。即我们下面代码的parent
- 有了parent以后,我们现在就是new一个新的结点,然后直接parent链接起来。不过此时又分为两种情形,是左孩子还是右孩子,其实并不好确定。于是我们可以使用parent当前的值与目标值进行比对从而判断出是插入左孩子还是右孩子。
public: bool Insert(const K& key) { if (_root == nullptr) { _root = new Node(key); return true; } else { Node* parent = _root; Node* cur = _root; while (cur != nullptr) { if (cur->_key == key) { return false; } else if (cur->_key > key) { parent = cur; cur = cur->_left; } else { parent = cur; cur = cur->_right; } } cur = new Node(key); if (parent->_key > key) { parent->_left = cur; } else if (parent->_key < key) { parent->_right = cur; } return true; } }
5.二叉搜索树的中序遍历(排序)
插入了以后,我们可以使用中序遍历对其进行输出,由于中序遍历就是排序后的情形,所以我们可以很方便的看到我们当前二叉树的结果。
对于排序,我们最好写一个子函数来实现,因为没有子函数的话,参数不好进行控制,会使得代码十分繁琐。
如下代码所示,对于中序遍历我们其实还是比较容易看懂的。无非就是先左子树,然后根,然后右子树即可。我们最好将子函数放在私有的部分中,因为在类外我们并不需要直接使用这个函数
public: void InOrder() { _InOrder(_root); } private: void _InOrder(Node* root) { if (root == nullptr) { return; } _InOrder(root->_left); cout << root->_key << " "; _InOrder(root->_right); }
6.二叉搜索树的查找(非递归)
对于查找的逻辑,是非常简单的,它与插入也是比较相似的,当我们当前结点的值小于目标的时候,迭代到右子树,当大于目标的时候,迭代到左子树。当相等的时候返回true即可。但若当cur都为nullptr了的时候还没有找到,那就是没有找到,返回false即可
public: bool Find(const K& key) { Node* cur = _root; while (cur) { if (cur->_key == key) { return true; } else if (cur->_key > key) { cur = cur->_left; } else if (cur->_key < key) { cur = cur->_right; } } return false; }
7.二叉搜索树的删除(非递归)
对于二叉搜索树的删除操作,其实是比较难的。这里出现了至少十种以上的情形。
- 找目标结点。这里的找目标结点,我们采用与插入法类似的操作,有一个当前结点和一个父结点。一开始自然是cur为根节点,parent为nullptr。然后我们进行迭代, 当cur不为空的时候,如果当前cur里面的值大于目标值,则parent迭代为cur,cur迭代为左孩子。如果cur里面的值小于目标值的话,则parent迭代为cur,cur迭代为右孩子。如果相等的情况下,由于比较复杂,我们在后面在进行讨论。如果迭代下去。只要没有出现相等的情形,迟早会离开循环,离开循环了,那么直接返回false即可
- 如果中间遇到相等的情况,也就是说,我们已经找到了目前要删除的值,这个值就是cur所指向的结点。这个时候,我们需要进一步将其进行分析。
- 这里存在的主要问题就是cur与parent以及cur的孩子之间的关系比较复杂。
- 首先cur的孩子就分为四种情况,分别是没有孩子、只有左孩子、只有右孩子、两个孩子都有。由于我们要删除cur结点,所以孩子的情况的不同,我们所需要的处理方式就不一样。
- 如果没有孩子的话,那么其实我们只需要直接删除该结点,然后将parent所指向cur的位置给置空即可。如下图所示:即当我们想要在下面这棵树中删除13的时候的情形
- 如果cur只有一个左孩子,也就是说右孩子为空。那么我们可以,使用托孤的方式,即将cur的左孩子直接与parent连接起来,然后在直接删除cur即可
- 如果cur只有一个右孩子,也就是说,左孩子为空,那么我们可以和上面一样采用类似的手段,托孤,将 cur的右孩子直接与parent进行连接,然后删除cur
- 如果两个孩子都存在的话,那么此时情形比较复杂,我们采用替代法。替代法的基本思路是,将要删除的该结点视为一棵子树,然后找到这棵树的左子树的最大值,或者右子树的最小值。然后我们与这两个中的任何一个进行替换即可,然后删除原来被替换的结点。如下图所示、
- 其实关于以上的cur与其孩子的四种情形,可以简化为三种情况:即将没有孩子归为只有一个左孩子或者只有一个右孩子的情形,因为我们可以将他们的孩子视为一个nullptr,如果这样思考的话,那么前三种方案都是使用托孤的思路。唯独第四种,我们需要使用替代法进行解决
- 除了cur孩子之间的问题以外,还有parent和cur之间的关系也是存在一些情况的。
- cur为parent的左孩子或右孩子,这种情形是比较简单的,也是前面图中的情形。我们只需要与cur与它的孩子的情况一关联即可。其实就是前面cur与其孩子的变化。
- parent不存在,即cur就是根节点,这种情况我们需要进行特殊处理,因为此时已经不存在父节点了
① 当cur的左孩子或者右孩子任意一个为空的时候,此时我们采用托孤的处理方案,不过此时的托孤需要一些变化,因为cur其实就是_root,所以我们直接让_root赋值为cur->_left或者cur->_right即可。
②当cur的左孩子和右孩子都存在的时候,我们还是使用替代法
如下是我们正常时候的替代,变化还不是很大。
但是当恰好leftMax就是cur的左侧结点的时候,这时候就出现意外了,因为我们是需要进行连接的,所以需要知道leftMax的父节点是谁才可以方便连接,上图是因为leftMax肯定是它的右孩子,而此时变成了左孩子,所以需要特殊处理。(这里的特殊处理在前面的每一次使用替代法中都会出现这种情况)
根据以上的分析,我们已经可以写出以下的删除代码了:
由于这里存在三代人之间的关系,即parent与cur之间的关系可分为三种情况,cur与其孩子的关系可分为三种情况,这里可以不严格的要求先考哪一类情况。我们两种代码都给出
以下是先考虑parent与cur这两代人之间的关系所写的代码
public: bool Erase1(const K& key) { Node* cur = _root; Node* parent = nullptr; while (cur) { if (cur->_key > key) { parent = cur; cur = cur->_left; } else if (cur->_key < key) { parent = cur; cur = cur->_right; } else { if (parent == nullptr) { if (cur->_left == nullptr) { _root = cur->_right; delete cur; return true; } else if (cur->_right == nullptr) { _root = cur->_left; delete cur; return true; } else { Node* leftMaxParent = cur; Node* leftMax = cur->_left; if (leftMax->_right == nullptr) { leftMax->_right = cur->_right; delete cur; _root = leftMax; return true; } while (leftMax->_right) { leftMaxParent = leftMax; leftMax = leftMax->_right; } std::swap(leftMax->_key, cur->_key); leftMaxParent->_right = leftMax->_left; delete leftMax; leftMax = nullptr; return true; } } if (parent->_left == cur) { if (cur->_left == nullptr) { parent->_left = cur->_right; delete cur; return true; } else if (cur->_right == nullptr) { parent->_left = cur->_left; delete cur; return true; } else { Node* leftMaxParent = cur; Node* leftMax = cur->_left; if (leftMax->_right == nullptr) { leftMax->_right = cur->_right; delete cur; parent->_left = leftMax; return true; } while (leftMax->_right) { leftMaxParent = leftMax; leftMax = leftMax->_right; } std::swap(leftMax->_key, cur->_key); leftMaxParent->_right = leftMax->_left; delete leftMax; leftMax = nullptr; return true; } } else { if (cur->_left == nullptr) { parent->_right = cur->_right; delete cur; return true; } else if (cur->_right == nullptr) { parent->_right = cur->_left; delete cur; return true; } else { Node* leftMaxParent = cur; Node* leftMax = cur->_left; if (leftMax->_right == nullptr) { leftMax->_right = cur->_right; delete cur; parent->_right = leftMax; return true; } while (leftMax->_right) { leftMaxParent = leftMax; leftMax = leftMax->_right; } std::swap(leftMax->_key, cur->_key); leftMaxParent->_right = leftMax->_left; delete leftMax; leftMax = nullptr; return true; } } } } return false; }
以下是根据先考虑cur与其孩子之间的关系所写的代码:
public: bool Erase2(const K& key) { Node* cur = _root; Node* parent = nullptr; while (cur) { if (cur->_key > key) { parent = cur; cur = cur->_left; } else if (cur->_key < key) { parent = cur; cur = cur->_right; } else { if (cur->_left == nullptr) { if (parent == nullptr) { _root = cur->_right; } else if (parent->_left == cur) { parent->_left = cur->_right; } else if (parent->_right == cur) { parent->_right = cur->_right; } } else if (cur->_right == nullptr) { if (parent == nullptr) { _root = cur->_left; } else if (parent->_left == cur) { parent->_left = cur->_left; } else if (parent->_right = cur) { parent->_right = cur->_left; } } else { Node* leftMax = cur->_left; Node* leftMaxParent = cur; while (leftMax->_right) { leftMaxParent = leftMax; leftMax = leftMax->_right; } std::swap(cur->_key, leftMax->_key); if (leftMaxParent->_left == leftMax) { leftMaxParent->_left = leftMax->_left; } else { leftMaxParent->_right = leftMax->_left; } cur = leftMax; } delete cur; return true; } } }
可见,虽然无论先写哪一种情形,其实都可以完成这个接口,但是代码量有显著的差异。这里推荐先考虑cur和其后代之间的关系。因为替代法这个出现于cur与其后代之间的关系之中。
8.二叉搜索树的查找(递归)
二叉树其实本身还是比较适合递归的。这里的思路也很简单, 不过我们为了可以控制参数,我们必须要写一个子函数去解决,当根节点为空的时候,直接返回false即可,就是没找到,当当前的结点小于要查找的结点,那么去右子树找,当大于时候,去左子树找即可。
public: bool FindR(const K& key) { return _FindR(_root, key); } private: bool _FindR(Node* root, const K& key) { if (root == nullptr) { return false; } if (root->_key == key) { return true; } else if (root->_key > key) { return _FindR(root->_left, key); } else { return _FindR(root->_right, key); } }
9.二叉搜索树的插入(递归)
这里的递归插入也是比较简单的,我们仍然使用子函数调用,当小于待插入的值的时候则去右子树插入,当大于待插入的值的时候,去左子树插入,但是如果遇到相等了,那么就只能插入失败了。最终我们一定会遇到一个空树,我们可以直接new一个结点来,不过这里的问题是如何连接?其实我们可以使用引用,使用引用的话,此时的root本身就早已跟父节点连接到一块了,所以我们直接给这个结点进行赋值,即可解决问题。
public: bool InsertR(const K& key) { return _InsertR(_root, key); } private: bool _InsertR(Node*& root, const K& key) { if (root == nullptr) { root = new Node(key); return true; } if (root->_key < key) { return _InsertR(root->_right, key); } else if (root->_key > key) { return _InsertR(root->_left, key); } else { return false; } }
10.二叉搜索树的删除(递归)
如下是递归式删除的操作。在这里我们还是使用子函数调用方便控制参数。这里最好使用引用,因为引用的话永远都是这一棵树。比较容易操作,否则就得传入二级指针就比较麻烦了。
如果root为空,就代表这棵树里面根本就没有key,自然直接返回nullptr即可。
如果当前结点的值小于key,那么去右子树删除,如果大于,去左子树删除。
当等于的时候,我们可以这样操作:先将待删除的结点给记录下来,如果该节点左孩子为空,或者右孩子为空的话,我们可以直接覆盖的方式去迭代。这步操作相对而言是比较妙的。
那么我们不妨思考一下:前面的非递归形式,能否使用这种方式来进行迭代呢?其实是不可以的,这里能迭代是因为引用,永远都是那个空间,且由于在不同的栈帧原因导致了引用的指向被巧妙的改变了。而前者他们并没有使用引用,都是使用局部变量来进行迭代的,因为C++中的引用是不可以改变指向的。所以自然不可以。
如下图所示,非递归中,迭代的本质,仅仅只是局部变量的不断更替,不会直接影响root,这里局部变量的修改仅仅只是间接修改堆区已有数据的连接关系。如果直接赋值的话,只是修改了栈区的局部变量的数据,并未修改实际关系。
如果cur的两个孩子都存在的话,我们还是使用替代法,设置一个局部变量leftMax,让其指向cur->_left,然后不断迭代他,找到这颗树的左子树最大结点。然后交换结点,最后我们知道要删除的结点一定在左子树,所以我们使用递归在左子树中找到要删除的结点即可。
注意这里千万不要将leftMax给传入了。这样做看似可以,但是leftMax是一个局部变量,等到离开本层的递归的时候,leftMax被断了,整棵树的逻辑关系全部混乱了。
public: bool EraseR(const K& key) { return _EraseR(_root, key); } private: bool _EraseR(Node*& root, const K& key) { if (root == nullptr) { return false; } if (root->_key < key) { return _EraseR(root->_right, key); } else if (root->_key > key) { return _EraseR(root->_left, key); } else { Node* del = root; if (root->_left == nullptr) { root = root->_right; } else if (root->_right == nullptr) { root = root->_left; } else { Node* leftMax = root->_left; while (leftMax->_right) { leftMax = leftMax->_right; } std::swap(leftMax->_key, root->_key); return _EraseR(root->_left, key); } delete del; return true; } }
11.二叉搜索树的销毁
销毁操作比较简答, 直接按照后序的方式进行销毁即可。最好传引用,因为可以置空root指针
public: ~BSTree() { _Destory(_root); } private: void _Destory(Node*& root) { if (root == nullptr) { return; } _Destory(root->_left); _Destory(root->_right); delete root; root == nullptr; }
12.拷贝构造函数
拷贝构造函数,我们需要实现一个深拷贝,这里我们使用先序的方式进行构造。
public: BSTree(const BSTree<K>& t) { _root = Copy(t._root); } private: Node* Copy(Node* root) { if (root == nullptr) { return nullptr; } Node* Copyroot = new Node(root->_key); Copyroot->_left = Copy(root->_left); Copyroot->_right = Copy(root->_right); return Copyroot; }
13.赋值运算符重载
赋值运算符重载,我们可以使用现代写法,这样做是最方便的
public: BSTree<K>& operator=(BSTree<K> t) { std::swap(_root, t._root); return *this; }
三、二叉搜索树的实现完整代码
#pragma once template<class K> struct BSTreeNode { BSTreeNode(const K& val = K()) :_key(val) ,_left(nullptr) ,_right(nullptr) {} K _key; BSTreeNode<K>* _left; BSTreeNode<K>* _right; }; template<class K> class BSTree { typedef BSTreeNode<K> Node; public: BSTree() :_root(nullptr) {} BSTree(const BSTree<K>& t) { _root = Copy(t._root); } ~BSTree() { _Destory(_root); } BSTree<K>& operator=(BSTree<K> t) { std::swap(_root, t._root); return *this; } bool Insert(const K& key) { if (_root == nullptr) { _root = new Node(key); return true; } else { Node* parent = _root; Node* cur = _root; while (cur != nullptr) { if (cur->_key == key) { return false; } else if (cur->_key > key) { parent = cur; cur = cur->_left; } else { parent = cur; cur = cur->_right; } } cur = new Node(key); if (parent->_key > key) { parent->_left = cur; } else if (parent->_key < key) { parent->_right = cur; } return true; } } void InOrder() { _InOrder(_root); } bool Find(const K& key) { Node* cur = _root; while (cur) { if (cur->_key == key) { return true; } else if (cur->_key > key) { cur = cur->_left; } else if (cur->_key < key) { cur = cur->_right; } } return false; } bool Erase1(const K& key) { Node* cur = _root; Node* parent = nullptr; while (cur) { if (cur->_key > key) { parent = cur; cur = cur->_left; } else if (cur->_key < key) { parent = cur; cur = cur->_right; } else { if (parent == nullptr) { if (cur->_left == nullptr) { _root = cur->_right; delete cur; return true; } else if (cur->_right == nullptr) { _root = cur->_left; delete cur; return true; } else { Node* leftMaxParent = cur; Node* leftMax = cur->_left; if (leftMax->_right == nullptr) { leftMax->_right = cur->_right; delete cur; _root = leftMax; return true; } while (leftMax->_right) { leftMaxParent = leftMax; leftMax = leftMax->_right; } std::swap(leftMax->_key, cur->_key); leftMaxParent->_right = leftMax->_left; delete leftMax; leftMax = nullptr; return true; } } if (parent->_left == cur) { if (cur->_left == nullptr) { parent->_left = cur->_right; delete cur; return true; } else if (cur->_right == nullptr) { parent->_left = cur->_left; delete cur; return true; } else { Node* leftMaxParent = cur; Node* leftMax = cur->_left; if (leftMax->_right == nullptr) { leftMax->_right = cur->_right; delete cur; parent->_left = leftMax; return true; } while (leftMax->_right) { leftMaxParent = leftMax; leftMax = leftMax->_right; } std::swap(leftMax->_key, cur->_key); leftMaxParent->_right = leftMax->_left; delete leftMax; leftMax = nullptr; return true; } } else { if (cur->_left == nullptr) { parent->_right = cur->_right; delete cur; return true; } else if (cur->_right == nullptr) { parent->_right = cur->_left; delete cur; return true; } else { Node* leftMaxParent = cur; Node* leftMax = cur->_left; if (leftMax->_right == nullptr) { leftMax->_right = cur->_right; delete cur; parent->_right = leftMax; return true; } while (leftMax->_right) { leftMaxParent = leftMax; leftMax = leftMax->_right; } std::swap(leftMax->_key, cur->_key); leftMaxParent->_right = leftMax->_left; delete leftMax; leftMax = nullptr; return true; } } } } return false; } bool Erase2(const K& key) { Node* cur = _root; Node* parent = nullptr; while (cur) { if (cur->_key > key) { parent = cur; cur = cur->_left; } else if (cur->_key < key) { parent = cur; cur = cur->_right; } else { if (cur->_left == nullptr) { if (parent == nullptr) { _root = cur->_right; } else if (parent->_left == cur) { parent->_left = cur->_right; } else if (parent->_right == cur) { parent->_right = cur->_right; } } else if (cur->_right == nullptr) { if (parent == nullptr) { _root = cur->_left; } else if (parent->_left == cur) { parent->_left = cur->_left; } else if (parent->_right = cur) { parent->_right = cur->_left; } } else { Node* leftMax = cur->_left; Node* leftMaxParent = cur; while (leftMax->_right) { leftMaxParent = leftMax; leftMax = leftMax->_right; } std::swap(cur->_key, leftMax->_key); if (leftMaxParent->_left == leftMax) { leftMaxParent->_left = leftMax->_left; } else { leftMaxParent->_right = leftMax->_left; } cur = leftMax; } delete cur; return true; } } } bool FindR(const K& key) { return _FindR(_root, key); } bool InsertR(const K& key) { return _InsertR(_root, key); } bool EraseR(const K& key) { return _EraseR(_root, key); } private: Node* Copy(Node* root) { if (root == nullptr) { return nullptr; } Node* Copyroot = new Node(root->_key); Copyroot->_left = Copy(root->_left); Copyroot->_right = Copy(root->_right); return Copyroot; } void _Destory(Node*& root) { if (root == nullptr) { return; } _Destory(root->_left); _Destory(root->_right); delete root; root == nullptr; } bool _EraseR(Node*& root, const K& key) { if (root == nullptr) { return false; } if (root->_key < key) { return _EraseR(root->_right, key); } else if (root->_key > key) { return _EraseR(root->_left, key); } else { Node* del = root; if (root->_left == nullptr) { root = root->_right; } else if (root->_right == nullptr) { root = root->_left; } else { Node* leftMax = root->_left; while (leftMax->_right) { leftMax = leftMax->_right; } std::swap(leftMax->_key, root->_key); return _EraseR(root->_left, key); } delete del; return true; } } bool _InsertR(Node*& root, const K& key) { if (root == nullptr) { root = new Node(key); return true; } if (root->_key < key) { return _InsertR(root->_right, key); } else if (root->_key > key) { return _InsertR(root->_left, key); } else { return false; } } bool _FindR(Node* root, const K& key) { if (root == nullptr) { return false; } if (root->_key == key) { return true; } else if (root->_key > key) { return _FindR(root->_left, key); } else { return _FindR(root->_right, key); } } void _InOrder(Node* root) { if (root == nullptr) { return; } _InOrder(root->_left); cout << root->_key << " "; _InOrder(root->_right); } private: Node* _root; };
对于上面的代码,我们可以使用如下代码进行测试
#include<iostream> #include<algorithm> using namespace std; #include"BinarySearchTree.h" void Test1() { BSTree<int> root; int arr[] = { 8,3,1,10,6,4,7,14,13,5,10,15,20,21,22,32,2 }; for (auto& e : arr) { root.Insert(e); } root.InOrder(); cout << endl; for (auto& e : arr) { root.Erase1(e); root.InOrder(); cout << endl; } BSTree<int> root2; for (auto& e : arr) { root2.Insert(e); } root2.InOrder(); cout << endl; for (auto& e : arr) { root2.Erase2(e); root2.InOrder(); cout << endl; } cout << root2.FindR(1) << endl; } void Test2() { BSTree<int> root; int arr[] = { 8,3,1,10,6,4,7,14,13,5,10,15,20,21,22,32,2 }; for (auto& e : arr) { root.InsertR(e); } root.InOrder(); cout << endl; cout << root.FindR(1) << endl; for (auto& e : arr) { root.EraseR(e); root.InOrder(); cout << endl; } cout << root.FindR(1) << endl; } void Test3() { BSTree<int> root; int arr[] = { 8,3,1,10,6,4,7,14,13,5 }; for (auto& e : arr) { root.InsertR(e); } root.InOrder(); cout << endl; BSTree<int> r1(root); r1.InOrder(); cout << endl; BSTree<int> t2 = r1; t2.InOrder(); } int main() { Test3(); return 0; }
最终结果都是符合我们的预期的。
总结
本文详细介绍了二叉搜索树的概念和实现,二叉搜索树的删除是最麻烦的,因为它的考虑的情况确实是非常多的,也确实是比较难的。希望读者可以好好品味