C++【搜索二叉树】

news2024/11/20 21:31:15

目录

一、什么是搜索二叉树

二、搜索二叉树如何删除数据 

删除的是叶子结点的情况

删除的结点下面仅有一个子节点(托孤)(要删除的结点只有一个孩子)

替换法删除  (要删除的结点有两个个孩子)

三、写一棵搜索二叉树

递归版本的插入

测试代码

四、查找某一个数是否存在于我们的二叉搜索树中

测试代码

递归版本的查找

测试代码

 五、二叉搜索树中删除某一个结点

测试代码

递归版本

测试代码

六、拷贝构造

析构函数

拷贝构造(深拷贝)

测试代码​​​​​​​

赋值

 测试代码

七、搜索二叉树的分析

八、Key的模型和Key/Value模型

1.英文翻译为中文 

 测试代码(简单英翻译中词典)

2.统计出现次数


一、什么是搜索二叉树

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
它的左右子树也分别为二叉搜索树

(每一棵子树都满足上面的特征)

(这样我们的搜索二叉树就非常利于我们的搜索)

比方说我们查找8,8比6大,找右子树,8比9小,找9的左子树,7比8小,找7的右子树,找到了。 

最多的查找次数是树的高度! 

这样就不是暴力查找了,而是利用了二叉树的特性 

二叉搜索树又被称为是二叉排序树或二叉查找树。  

二、搜索二叉树如何删除数据 

删除的是叶子结点的情况

对于上面的二叉树,如果我们想要删除0,2,4,7,13,其实都是可以直接删除的,不会破坏我们的搜索二叉树的性质。

删除的结点下面仅有一个子节点(托孤)(要删除的结点只有一个孩子)

假如我们要删除14,由于我们的14下面只有一个13,所以我们其实可以将13和14的结点中的数据相互交换,此时我们的14也就被换到了叶子结点的位置,然后我们再直接将当前14的这个结点删除掉就可以了。

(删除的结点左为空,让父亲结点指向我的右子树,删除结点右为空,让父亲结点指向我的左子树)

其实我们上一种情况也可以归纳到我们当前这种情况,也就是我们要删除的是叶子结点的话,比方说我们要删除14下面的13,因为13的左子树为空,我们就直接将13的右子树挂载到我们的14的左子树下面,也就是nullptr,那么我们也是能够将13删掉的

替换法删除  (要删除的结点有两个个孩子)

如果我们想要删除3

(3的位置的数要满足比左子树的最大值大,比右子树的最小值小)

1.左子树的最大节点--2(左子树最右结点)

2.或者右子树的最小节点--4(右子树的最左节点)

与3进行替换,然后将3删除。

替换结点赋值给删除结点后,删除替换节点,替换节点要么没有孩子,要么只有一个孩子(如果我们上面的二叉树的4的右子树中还有一个5),可以直接删除。

三、写一棵搜索二叉树

下面是按照我们上面两个部分的思路写出来的二叉搜索树的主要实现的代码

namespace zhuyuan
{
    template<class K>
    struct BSTreeNode
    {
        BSTreeNode<K>* _left;
        BSTreeNode<K>* _right;
        K _key;

        BSTreeNode(const K& key)
                :_left(nullptr)
                ,_right(nullptr)
                ,_key(key)
        {}
    };
//这里的k,因为我们在搜索二叉树中将我们判断的依据称之为key
    template<class K>
    class BSTree
    {
        typedef BSTreeNode<K> Node;
    public:
        bool Insert(const K& key)
        {
            //表示当前的树一个结点都没有
            if(_root== nullptr)
            {
                _root=new Node(key);
                return true;
            }

            Node *parent= nullptr;
            Node *cur=_root;
            //先查找到要插入的位置
            while(cur)
            {
                if(cur->_key<key)
                {
                    //parent为cur的父节点,为了将我们的cur结点挂载到我们指定的父节点下面
                    //这里我们在cur结点往下走之前,及时更新我们的父节点
                    parent=cur;
                    cur=cur->_right;
                }
                else if(cur->_key>key)
                {
                    parent=cur;
                    cur=cur->_left;
                }
                else
                {
                    //如果这个插入的值在这棵树里面已经存在了,就会插入失败。
                    return false;
                }
            }
            //cur只是一个局部变量,处理这个作用域就消失了
            //所以我们先应该将其父节点挂载当前结点。所以我们在上面的代码中加入了cur的父节点,同步向下移动
            cur=new Node(key);
            if(parent->_key<key)
            {
                parent->_right=cur;
            }
            else
            {
                parent->_left=cur;
            }
            return true;
        }

        //套一层无参的去调用有参的,这样我们的_root就不会暴露给外部了。
        //这里我们的类私有的函数一般都是在名称前面加上“_”,然后如果是共有的函数的话就不加
        //比方是我们瞎买的呢InOrder()就是共有的,外部可以调用
        //但是下面的_InOrder就是私有的,外部不可以调用
        void InOrder()
        {
            _InOrder(_root);
        }

    private:
        //递归必须要显式地传子树,但是我们的这里的_root又是私有的
        //所以我们这里将其设置为是私有的,然后我们类内部再给出一个方法去调用中序遍历
        void _InOrder(Node* root)
        {
            if(root== nullptr)
            {
                return;
            }
            _InOrder(root->_left);
            cout<<root->_key<<" ";
            _InOrder(root->_right);
        }
    private:
        Node * _root= nullptr;
    };

}

测试代码

void TestBSTree1()
    {
        BSTree<int>  t;
        int a[]={8,3,1,10,6,4,7,14,13,4,3,4,4};
        for(auto e:a)
        {
            t.Insert(e);
        }
        //排序+去重
        t.InOrder();
    }

调用测试代码

#include <iostream>
#include <string>
using namespace std;
#include "BinarySearchTree.h"

int main()
{
    zhuyuan::TestBSTree1();
    return 0;
}

我们观察到我们的二叉搜索树不但中序是有序的,并且还帮助我们完成了去重 

 

递归版本的插入

    //封装一层,人为将带_的方法放在private属性里面,用不带_的去调用
    //下面带_其实是在private里面的
    bool InsertR(const K& key)
        {
            return _InsertR(_root, key);
        }


    //思考这里的root前为啥要加上&
    bool _InsertR(Node*& root, const K& key)
        {
            //找到了插入的位置,将我们的结点插入
            if (root == nullptr)
            {
                //这个root仅仅是一个局部变量,是一个形参,并没有帮助我们完成插入
                //还是需要将其和其父亲结点链接在一起
                //但是我们这里没有父亲怎么办?
                //是将这里的父亲随着二叉的链条一直往下传。
                
                //但是我们只要在我们上面的Node*& root这里加一个引用,
                //这里取引用的意义就是假设我们现在root的值是空
                //但是这个root同时也是我们上一层的父节点的子节点的别名
                //也就是我们直接修改的就是上一层父节点的子节点
                //所以就成功实现了插入
                root = new Node(key);
                return true;
            }
            //要插入的结点的值要是比我们当前的结点的key值小,就转换到我们当前结点的左子树去插入
            if (root->_key < key)
                return _InsertR(root->_right, key);
            //要插入的结点的值要是比我们当前的结点的key值大,就转换到我们当前结点的右子树去插入
            else if (root->_key > key)
                return _InsertR(root->_left, key);
            //如果要插入的数据已经存在了,就不插入了,插入失败
            else
                return false;
        }

测试代码

    void TestBSTree1()
    {
        BSTree<int>  t;
        int a[]={8,3,1,10,6,4,7,14,13,4,3,4,4};
        for(auto e:a)
        {
            t.InsertR(e);
        }
        t.InOrder();
        cout<<endl;
    }

四、查找某一个数是否存在于我们的二叉搜索树中

只要当前的值比我们的根节点数值小,那么我们就查找当前根节点的左子树,如果比我们的根节点的数值大,那么我们就查找当前根节点的右子树。

如果查找到最后找到null结点,那么就是找不到了。

    bool Find(const K& key)
        {
            Node* cur = _root;
            while (cur)
            {
                if (cur->_key < key)
                {
                    cur = cur->_right;
                }
                else if (cur->_key > key)
                {
                    cur = cur->_left;
                }
                else
                {
                    return true;
                }
            }
            //表示找不到了
            return false;
        }

测试代码

    void TestBSTree1()
    {
        BSTree<int>  t;
        int a[]={8,3,1,10,6,4,7,14,13,4,3,4,4};
        for(auto e:a)
        {
            t.Insert(e);
        }

//        //排序+去重
//        t.InOrder();
        cout<<t.Find(10)<<endl;
        cout<<t.Find(11)<<endl;
    }

我们的二叉树中10是有的,11是没有的,所以分别为1和0 

递归版本的查找

    //封装一层,人为将带_的方法放在private属性里面,用不带_的去调用
    //下面带_其实是在private里面的
    bool FindR(const K& key)
        {
            return _FindR(_root, key);
        }
    
    bool _FindR(Node* root, const K& key)
        {
            //查找不到返回false
            if (root == nullptr)
                return false;
            
            //如果我们当前root的_key小于我们的key值,我们就到我们当前结点的右子树中去查找
            if (root->_key < key)
                return _FindR(root->_right, key);
            //如果我们当前root的_key大于我们的key值,我们就到我们当前结点的左子树中去查找
            else if (root->_key > key)
                return _FindR(root->_left, key);
            //找到了我们的想要查找的结点
            else
                return true;
        }

测试代码

    void TestBSTree1()
    {
        BSTree<int>  t;
        int a[]={8,3,1,10,6,4,7,14,13,4,3,4,4};
        for(auto e:a)
        {
            t.Insert(e);
        }

        cout<<t.FindR(3)<<endl;
        cout<<t.FindR(9)<<endl;
    }

 

这里的我们的时间复杂度为O(h),这里的h为树的高度 

 五、二叉搜索树中删除某一个结点

这里的删除时按照我们的二中的方法进行操作的

    bool Erase(const K& key)
        {
            Node* parent = nullptr;
            Node* cur = _root;

            //查找到要删除的结点
            while (cur)
            {
                if (cur->_key < key)
                {
                    parent = cur;
                    cur = cur->_right;
                }
                else if (cur->_key > key)
                {
                    parent = cur;
                    cur = cur->_left;
                }
                //这里的情况就是cur->_key==key
                //找到了,开始尝试删除
                else
                {
                    // 开始删除
                    // 1、左为空
                    // 2、右为空
                    // 3、左右都不为空

                    //要删除的结点左为空
                    if (cur->_left == nullptr)
                    {
                        //如果要删除的是根节点
                        //当前的根节点的左子树是nullptr
                        //所以我们就将根节点变成我们的原先的根节点的右子树
                        if (cur == _root)
                        {
                            _root = cur->_right;
                        }
                        else
                        {
                            //如果我要删除的结点是父亲的左节点(要删除的结点左为空)
                            //那么我们就将要删除的结点的右挂载到父亲的左节点
                            if (cur == parent->_left)
                            {
                                parent->_left = cur->_right;
                            }
                            else
                            {
                                parent->_right = cur->_right;
                            }
                        }

                        delete cur;
                        cur = nullptr;
                    }

                    //如果要删除的结点右为空
                    else if (cur->_right == nullptr)
                    {
                        //如果要删除的是根节点
                        //当前的根节点的右子树是nullptr
                        //所以我们就将根节点变成我们的原先的根节点的左子树
                        if (_root == cur)
                        {
                            _root = cur->_left;
                        }
                        else
                        {
                            //如果我要删除的结点为父亲的左节点
                            //将要删除的结点的左节点挂载到父亲的左节点
                            if (cur == parent->_left)
                            {
                                parent->_left = cur->_left;
                            }
                            else
                            {
                                //将要删除的结点的左节点挂载到父亲的右节点
                                parent->_right = cur->_left;
                            }
                        }

                        delete cur;
                        cur = nullptr;
                    }

                    //如果要删除的结点左右都不为空
                    //替换法删除
                    //用右子树的最小结点
                    else
                    {
                        // 找到右子树最小节点进行替换
                        //类似于上面的写法
                        //我们的minParent是我们的min的父节点
                        //为了防止我们删除的是根节点的情况,所以我们的minParent不能初始化为nullptr
                        //应该初始化为cur
                        Node* minParent = cur;
                        Node* min = cur->_right;
                        while (min->_left)
                        {
                            minParent = min;
                            min = min->_left;
                        }
                        //将当前的要删除的位置的结点的值和右子树的最大值进行交换
                        swap(cur->_key, min->_key);
                        //如果我们右子树的最小值的结点是其父节点的左结点
                        //那么我们这里就直接进行(托孤)操作

                        //因为我们这里min已经是当前右子树中数值最小的结点了,那么其不可能还有左子树,因为左子树的结点会比我们的根节点要小!
                        //所以我们的min只可能是右子树还有结点

                        //和我们上面的操作方案一样,就是将min结点的右子树挂载到我们的父节点左结点上
                        if (minParent->_left == min)
                            minParent->_left = min->_right;
                        //如果我们的min结点是其父节点的右结点,那么我们就将我们min的右子树挂载到我们的父节点的右子树上

                        else
                            minParent->_right = min->_right;

                        //将我们的min结点删除掉。
                        delete min;
                    }

                    //删除成功
                    return true;
                }
            }

            //删除失败
            return false;
        }

测试代码

    void TestBSTree1()
    {
        BSTree<int>  t;
        int a[]={8,3,1,10,6,4,7,14,13,4,3,4,4};
        for(auto e:a)
        {
            t.Insert(e);
        }

//        //排序+去重
        t.InOrder();
        cout<<endl;
        t.Erase(8);
        t.InOrder();
        cout<<endl;
        t.Erase(3);
        t.InOrder();
        cout<<endl;
    }

递归版本

        bool EraseR(const K& key)
        {
            return _EraseR(_root, key);
        }

//递归版本的删除
        //思考这里的root前为啥要加上引用
        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
            {
                //root就是我们要删除的结点
                Node* del = root;
                //root的左子树为空,就将当前root的右子树挂载到上层的root的子树上
                if (root->_left == nullptr)
                    //由于我们上面的定义的时候我们的root前有&,所以我们这里
                    //这的root是上一层root的孩子指针的别名
                    //所以修改的话,就可以直接修改
                    //不需要再像我们的非递归版本一样再设置一个父节点用来挂载子节点
                    root = root->_right;
                //root的右子树为空,就将当前root的左子树挂载到上层的root的子树上
                else if (root->_right == nullptr)
                    root = root->_left;
                else
                {
                    // 找右树的最左节点替换删除
                    Node* min = root->_right;
                    while (min->_left)
                    {
                        min = min->_left;
                    }
                    //交换要删除的节点的值和其右子树的值最小结点的值
                    swap(root->_key, min->_key);
                    //下面这种写法是错的
                    //return EraseR(key);  错的
                    //因为我们在交换过之后,我们原先的整一棵搜索二叉树已经不满足搜索二叉树的结构了,很可能这里就找不到了!
                    //(我们当前要删除的结点的值已经大于其右子树的值最小的结点的值了!)

                    //递归删除我们的交换过之后的结点
                    //因为我们当前的要删掉的位置是我们的交换过之后的原先要被删除的结点的右子树当中的最小值的结点,
                    //右子树的最小值的结点是不可能有左子树的,因为左子树一定会比当前的位置的节点的值小
                    //所以我们这里将右子树传进去
                    //然后这个key也就是我们原先要删除的结点的值
                    //交换过之后我们的这个key值也被交换到了我们的原先的结点的右子树的值最小的结点

                    //并且这里我们递归调用的话,我们root->right的这棵子树还是满足二叉搜索树的结构的!
                    //因为我们的要删除的结点原本就是要比其右子树中的值最小的结点小的!
                    //所以我们将要删除的结点值和其右子树最小的值进行交换值的话,我们的要删除的结点的右子树依旧满足搜索二叉树的结构!

                    //并且这里我们由于引用的原因,我们不用再设置一个父节点去挂载我们的子节点
                    //所以我们这里直接按照下面这样写就可以了
                    return _EraseR(root->_right, key);
                }

                delete del;
                return true;
            }
        }

测试代码

void TestBSTree1()
    {
        BSTree<int>  t;
        int a[]={8,3,1,10,6,4,7,14,13,4,3,4,4};
        for(auto e:a)
        {
            t.Insert(e);
        }
        t.InOrder();
        cout<<endl;
        t.EraseR(14);
        t.InOrder();
        cout<<endl;
        t.EraseR(13);
        t.InOrder();
        cout<<endl;
    }

 我们已经写完了增删查,但是我们的当前模型的二叉搜索树不支持改,因为一旦改了就可能不是搜索二叉树的模型了。

六、拷贝构造

void TestBSTree1()
    {
        BSTree<int>  t;
        int a[]={8,3,1,10,6,4,7,14,13,4,3,4,4};
        for(auto e:a)
        {
            t.Insert(e);
        }
        t.InOrder();
        cout<<endl;

        BSTree<int> copy=t;
        copy.InOrder();
    }

这里其实默认是浅拷贝。

但是我们没有写析构函数。

一旦写了析构函数就会崩溃。

析构函数

    ~BSTree()
		{
			_Destory(_root);
		}


    void _Destory(Node*& root)
        {
            if (root == nullptr)
            {
                return;
            }
            
//    先销毁左节点,然后销毁右节点,最后销毁root结点,后序
            _Destory(root->_left);
            _Destory(root->_right);
            delete root;
            root = nullptr;
        }

由于两棵树在析构的时候会对同一块地址进行多次析构,所以我们的程序就会发生崩溃 

 

拷贝构造(深拷贝)

    // C++的用法:强制编译器生成默认的构造
        BSTree() = default;

        BSTree(const BSTree<K>& t)
        {
            _root = _Copy(t._root);
        }


        //按照树形结构去拷贝
        Node* _Copy(Node* root)
        {
            //如果是空,就直接return空
            if (root == nullptr)
            {
                return nullptr;
            }

            //插入同样的值,顺序不同我们的树的形状就会发生变化
            //所以我们这里需要采用先序遍历的形式去拷贝我们的搜索二叉树

            //为copy树创建新的结点
            //遇到左子树拷贝左子树,遇到右子树拷贝左子树
            Node* copyRoot = new Node(root->_key);
            copyRoot->_left = _Copy(root->_left);
            copyRoot->_right = _Copy(root->_right);
            //返回拷贝的结点
            return copyRoot;
        }

你不写拷贝构造编译器会帮你生成默认的拷贝构造

但是你如果写了构造,那么编译器就不会再帮你生成默认的拷贝构造了!

所以我们上面代码中需要写 

或者

BSTree()
{}

如果不写就会产生下面的报错

测试代码

    void TestBSTree1()
    {
        BSTree<int> t;
        int a[]={8,3,1,10,6,4,7,14,13,4,3,4,4};
        for(auto e:a)
        {
            t.Insert(e);
        }
        t.InOrder();
        cout<<endl;

        BSTree<int> copy=t;
        copy.InOrder();
    }

 

赋值

现代式的写法,我们直接使用传值传参,t1传给t,这里的t就是t1的拷贝

我们直接将t的_root交换给我们当前this指向的t2的root就可以了

然后这个临时拷贝的t还会帮助我们去析构

(只要写完拷贝构造,我们的赋值就可以这么操作)

        // t2 = t1
        BSTree<K>& operator=(BSTree<K> t)
        {
            swap(_root, t._root);
            return *this;
        }

 测试代码

    void TestBSTree1()
    {
        BSTree<int> t;
        int a[]={8,3,1,10,6,4,7,14,13,4,3,4,4};
        for(auto e:a)
        {
            t.Insert(e);
        }
        t.InOrder();
        cout<<endl;

        BSTree<int> t1;
        t1.Insert(3);
        t1.Insert(1);
        t1.Insert(2);
        t1=t;
        t1.InOrder();
        cout<<endl;
    }

七、搜索二叉树的分析

搜索二叉树的时间复杂度:O(h) h是树的高度

最坏的情况下h是N(如果我们的树都是单支的,长得就跟一根单链表一样)

所以我们的搜索二叉树还是有缺陷的!

只有接近满二叉树的形式,我们的搜索二叉树才能达到最佳的效果,也就是将我们树的高度降低到最小!  

我们就需要采用AVL树来将我们的树的高度保持在 log_{2}^{n} 的高度 

八、Key的模型和Key/Value模型

Key的模型:判断关键字在不在,也就是我们上面的模型

比方说 

1、刷卡进宿舍楼

        用一个文件,将这栋楼的所有的学生的学号都记录在里面

        刷卡的时候,看看这个文件中有没有这个学生的学号。

        这里查找的时候我们就可以是由搜索树(排序+去重)

2. 检查一篇英文文档中的单词拼写是否正确

        单词是没有规律的,所以我们需要词库

        我们这里就需要将词库中的单词都插入到一棵搜索树中

 Key/Value的模型--通过key去找value

(允许修改这里的Value) 

(比较大小还是以Key去比较的,查找的时候也是按照Key去查找的,Value不参数树的比较关系)

1.英文翻译为中文 

比方说

1、简单的英文翻译中文程序

        通过一个值要找到另外一个 

        我们的树中既要存英文,也要存中文

//在树里面将K和V绑定存到一起去
    template<class K, class V>
    struct BSTreeNode
    {
        BSTreeNode<K, V>* _left;
        BSTreeNode<K, V>* _right;
        K _key;
        V _value;

        BSTreeNode(const K& key, const V& value)
                :_left(nullptr)
                , _right(nullptr)
                , _key(key)
                , _value(value)
        {}
    };

    template<class K, class V>
    class BSTree
    {
        typedef BSTreeNode<K, V> Node;
    public:
        bool Insert(const K& key, const V& value)
        {
            if (_root == nullptr)
            {
                _root = new Node(key, value);
                return true;
            }

            Node* parent = nullptr;
            Node* cur = _root;
            while (cur)
            {
                if (cur->_key < key)
                {
                    parent = cur;
                    cur = cur->_right;
                }
                else if (cur->_key > key)
                {
                    parent = cur;
                    cur = cur->_left;
                }
                else
                {
                    return false;
                }
            }

            cur = new Node(key, value);
            if (parent->_key < key)
            {
                parent->_right = cur;
            }
            else
            {
                parent->_left = cur;
            }

            return true;
        }

        Node* Find(const K& key)
        {
            Node* cur = _root;
            while (cur)
            {
                if (cur->_key < key)
                {
                    cur = cur->_right;
                }
                else if (cur->_key > key)
                {
                    cur = cur->_left;
                }
                else
                {
                    return cur;
                }
            }

            return nullptr;
        }

        bool Erase(const K& key)
        {
            //...

            return true;
        }

        void InOrder()
        {
            _InOrder(_root);
            cout << endl;
        }
    private:

        void _InOrder(Node* root)
        {
            if (root == nullptr)
            {
                return;
            }

            _InOrder(root->_left);
            cout << root->_key << ":" << root->_value << endl;
            _InOrder(root->_right);
        }
    private:
        Node* _root = nullptr;
    };

 测试代码(简单英翻译中词典)

void TestBSTree1()
    {
        //string对象也是支持比较大小的,就是按照字符串的ASC码进行比较
        BSTree<string, string> dict;
        dict.Insert("sort", "排序");
        dict.Insert("left", "左边");
        dict.Insert("right", "右边");
        dict.Insert("string", "字符串");
        dict.Insert("insert", "插入");
        dict.Insert("erase", "删除");
        string str;
        //输入一个单词进行查找
        //用英文查找中文

        //只要有输入就会执行
        while (cin >> str)
        {
            BSTreeNode<string, string>* ret = dict.Find(str);
            if (ret)
            {
                cout << "对应的中文:" << ret->_value << endl;
            }
            else
            {
                cout << "对应的中文->无此单词" << endl;
            }
        }
    }

如何停止这里的while

①(ctrl+z+换行)

②(ctrl+c)杀掉进程

2.统计出现次数

比方说我们统计水果出现的个数

如果只是使用计数排序的话,我们不能找到字符串

但是我们之前写的KV模型的树可以做到!

void TestBSTree2()
    {
        string arr[] = { "香蕉", "苹果", "香蕉", "草莓", "香蕉", "苹果", "苹果", "苹果" };

        BSTree<string, int> countTree;
        for (auto& str : arr)
        {
            //BSTreeNode<string, int>* ret = countTree.Find(str);
            auto ret = countTree.Find(str);
            if (ret)
            {
                ret->_value++;
            }
            else
            {
                countTree.Insert(str, 1);
            }
        }

        countTree.InOrder();
    }

 

 这里的排序是按照苹果,草莓,香蕉去排序的。

我们链表相交和复杂链表的复制就能有新的策略去做

链表相交:

我们想要去查找最先的公共节点的话,我们就先将L1遍历一遍,然后将其地址放入一棵二叉搜索树当中,然后遍历L2的时候,每经过一个结点就查找一个在不在L1中,最先找到的就是我们的最先的公共节点。 

复杂链表的赋值:

也就是我们的每一个链表的结点都是有一个随机指针结点,指向不同的链表位置的。

然后我们原先的写法就是在每一个链表结点后面都挂载一个拷贝的结点,然后通过一定的方式在拷贝完成之后将其剪下来。

但是有了搜索二叉树了之后,我们就可以存储原结点地址和拷贝结点的映射的Key/Value模型。

不再像以前那么繁琐了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/252.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

腾讯前辈熬夜肝了一个月整理的《Linux内核学习笔记》,啃完受益匪浅不走弯路

小编热衷于收集整理资源&#xff0c;记录踩坑到爬坑的过程。希望能把自己所学&#xff0c;实际工作中使用的技术、学习方法、心得及踩过的一些坑&#xff0c;记录下来。也希望想做Linux内核高级工程师的你一样&#xff0c;通过我的分享可以少走一些弯路&#xff0c;可以形成一套…

【数据结构】常见七大排序总结

目录 一、插入排序&#xff1a;直接插入排序【稳定排序方法】 二、插入排序&#xff1a;希尔排序【不稳定排序方法】 三、选择排序&#xff1a;直接选择排序【不稳定排序方法】 四、选择排序&#xff1a;堆排序【不稳定排序方法】 五、交换排序&#xff1a;冒泡排序【稳定…

基于单片机MC9S12XS128的两轮自平衡小车设计

目 录 1.绪论 1 1.1研究背景与意义 1 1.2两轮自平衡车的关键技术 2 1.2.1系统设计 2 1.2.2数学建模 2 1.2.3姿态检测系统 2 1.2.4控制算法 3 1.3本文主要研究目标与内容 3 1.4论文章节安排 3 2.系统原理分析 5 2.1控制系统要求分析 5 2.2平衡控制原理分析 5 2.3自平衡小车数学…

总结一下flex布局

flex布局 传统布局方案是基于盒状模型&#xff0c;依赖 display position float 方式来实现&#xff0c;灵活性较差&#xff1b;Flex是Flexible Box的缩写&#xff0c;意为”弹性布局”。Flex可以简便、完整、响应式地实现多种页面布局 CSS3 弹性盒子是一种一维的布局&…

[Spring MVC3]MyBatis详解

本章重点讲述了MyBatis映射器&#xff0c;对数据层进行的操作&#xff0c;建议本篇文章和Spring Boot的持久层相互比较来看会更加收获颇多Spring Boot持久层技术 本文需要使用到MVC第一讲的模板格式与配置情况&#xff0c;全部代码已经放在此博客中&#xff0c;Spring MVC1 目…

修改设备管理器的COM端口名称

Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Class{4d36e978-e325-11ce-bfc1-08002be10318}\0001] “InfPath”“oem53.inf” “InfSection”“CH341SER_Inst.NTamd64” “ProviderName”“wch.cn” “DriverDateData”hex:00,00…

计算机算法(二)——进入计算机世界

作者介绍&#xff1a; &#x1f4de;作者&#xff1a;小刘在C站 &#x1f4c0;每天分享课堂笔记 &#x1f339;夕阳下&#xff0c;是最美的绽放 瑞士著名的科学家Niklaus Wirth教授曾提出&#xff1a;数据结构算法程序。 数据结构是程序的骨 算法是程序的灵魂。 在生活…

【 C++11 】lambda表达式

目录 1、lambda表达式的引入 2、lambda表达式 lambda表达式的语法 lambda表达式捕捉列表说明 使用lambda表达式排序自定义类型 lambda表达式的底层原理 1、lambda表达式的引入 在C98中&#xff0c;如果想要对一个数据集合中的元素进行排序&#xff0c;可以使用std::sort方法&…

asp.net图书馆借阅归还系统

图书管理系统既是完整的知识定位系统&#xff0c;又是面向未来互联网发展的信息管理模式。图书管理系统&#xff0c;是一套利用计算机信息网络技术&#xff0c;实现对引用、注释和参考图书的自动化处理和规范化管理&#xff0c;服务于教师&#xff0c;学生及各类科研人员的集成…

【C语言进阶考试】你是否真正学懂了C语言

目录 前言 &#x1f392;选择题【全面深度剖析】 &#x1f4d7;考点一&#xff1a;无符号数unsigned的理解与应用 &#x1f4d5;考点二&#xff1a;字符ASCII计算与转换的理解和应用 &#x1f4d9;考点三&#xff1a;对位操作符的理解与应用 &#x1f4d8;考点四&#xf…

docker mysql 主从配置

准备&#xff1a;一台装有docker的虚拟机或者服务器 拉取mysql镜像&#xff1a; docker pull mysql:5.6 启动两个mysql容器 master docker run -p 1006:3306 --name mysql_master -v F:/mysql/mysql_master/conf:/etc/mysql -v F:/mysql/mysql_master/logs:/logs -v F:/mys…

【1024 | 程序员节】浅谈前端开发中常用的设计模式——适配器模式、工厂模式、单例模式等

前言 博主主页&#x1f449;&#x1f3fb;蜡笔雏田学代码 专栏链接&#x1f449;&#x1f3fb;【前端面试专栏】 今天学习前端面试题相关的知识&#xff01; 感兴趣的小伙伴一起来看看吧~&#x1f91e; 文章目录设计模式设计模式分类工厂模式什么是工厂模式工厂模式好处单例模式…

I2C知识大全系列三 —— I2C驱动之单片机中的I2C

两种方式 单片机中的I2C驱动有两种方式。一种方式是用专用硬件I2C控制器实现&#xff0c;这种方式简单易行&#xff0c;品质也容易控制&#xff0c;只是会增加硬件成本方面的压力。另一种方式是用纯软件方式实现&#xff0c;这种方式几乎无硬件成本方面的考虑。 主要对比&…

网页图片采集-网页图片采集软件免费

一款免费的网页图片采集软件可以采集网页上的各种图片&#xff0c;每个人都可以采集到各种高清图源。支持任意格式的图片采集&#xff0c;只需要导入链接即可批量采集图片。 还有更多的采集方式&#xff1a;输入关键词全网图片采集/任意网站所有图片采集&#xff01;不仅可以采…

【C++】STL——vector(万字详解)

&#x1f387;C学习历程&#xff1a;入门 博客主页&#xff1a;一起去看日落吗持续分享博主的C学习历程博主的能力有限&#xff0c;出现错误希望大家不吝赐教分享给大家一句我很喜欢的话&#xff1a; 也许你现在做的事情&#xff0c;暂时看不到成果&#xff0c;但不要忘记&…

记首次参加网络安全比赛(初赛-知识竞赛,决赛-CTF夺旗赛-解题模式)

网络安全相关的方向很多&#xff0c;几乎IT相关的安全内容都可以涵盖在内。笔者本身的知识体系更偏向于编程语言和Web应用&#xff0c;本次参赛可谓极具挑战&#xff0c;但是就是喜欢这种感觉&#xff1a;&#xff09; 赛程安排 9月16日接到通知 9月26日初赛 10月15日决赛 …

计算机网络习题答案

1、校园网属于&#xff08;局域网LAN &#xff09; 2、在下列传输介质中&#xff0c;(光缆 )传输介质的抗电磁干扰性最好。 3、光纤上采用的多路复用技术为&#xff08;WDM&#xff09; 4、计算机网络的交换方式不包括 无线交换 5、网络体系结构模型OSI模型和TCP/IP模型…

区块链实训教程(6)--开发、编译、部署、调用HelloWorld合约

文章目录1. 任务背景2. 任务目标3. 相关知识点4. 任务实操4.1 新建合约文件4.2 编写合约代码4.3 保存、编译、部署合约4.4 调用合约5. 任务总结1. 任务背景 FISCO BCOS运用智能合约进行归纳资产管理、规则定义和价值交换等操作&#xff0c;所以我们需要学习如何使用智能合约。…

aws ec2 配置jenkins和gitlab

环境搭建 下载jenkins的war包&#xff0c;启动jenkisn nohup java -jar jenkins.war --httpPort8091 > jenkins.log 2>&1 &docker安装gitlab 默认情况下&#xff0c;Omnibus GitLab 会自动为初始管理员用户账号 (root) 生成密码&#xff0c;并将其存储到 /etc…

HarmonyOS系统中内核实现温湿度采集方法

大家好&#xff0c;今天主要来聊一聊&#xff0c;如何使用鸿蒙系统中的温湿度传感器方法。 第一&#xff1a;温湿度传感器基本原理 大部分的传感器是在环境温度变化后会产生一个相应的延伸&#xff0c;因此传感器可以以不同的方式对这种反应进行信号转换。常见的大部分是电阻…