数据结构——二叉搜索树(附带C++实现版本)

文章目录

  • 二叉搜索树
    • 概念
  • 二叉树的实际应用
  • 二叉树模拟实现
    • 存储结构
    • 二叉搜索树构成
    • 二叉搜索树的查找
    • 插入操作
    • 中序遍历
    • 二叉树的删除
      • 循环(利用左子树最右节点)
      • 递归(利用右子树根节点)
    • 二叉树拷贝
    • 二叉树资源的销毁
  • 二叉树实现完整代码
  • 总结

二叉搜索树

概念

二叉搜索树又叫二叉排序树,二叉搜索树也是一种树形结构。
它是一课满足以下性质的搜索树:

  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 它的左右子树也分别是二叉搜索树

注意,二叉搜索树对于值相同的节点只能存储一个

在这里插入图片描述

优点:这样的结构能够平均用logn的时间复杂度找到我们想要的值,但是其最坏时间复杂度是O(n), 这是由于搜索二叉树不一定是平衡的,如下图所示:
在这里插入图片描述
这个结构也满足二叉搜索树的性质,但是其查找所需要的复杂度是O(n)

二叉树的实际应用

  1. K模型:K模型只以key为关键码,结构中只需要存储K即可,关键码即为需要搜索道的值。

例如: 给一个单词word,判断该单词是否拼写正确,具体方法如下:

  • 以词库中的所有单词集合中的每一个单词作为key,构建一颗二叉搜索树。
  • 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误
  • KV模型: 每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方 式在现实生活中非常常见:
  • 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对
  • 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出 现次数就是<word, count>就构成一种键值对

二叉树模拟实现

在这里为大家以kv模型为例模拟实现二叉搜索树,只要稍作修改即可变成k模型。

存储结构

首先,二叉搜索树中存储的是节点,所以我们需要定义一个表示节点的结构体,如下:

	template<typename K, typename V>struct BSTree_node{K _key = K();V _value = V();BSTree_node* _left = nullptr;BSTree_node* _right = nullptr;BSTree_node() {}BSTree_node(const K& key, const V& value):_key(key), _value(value){}};

K模型和K,V模型差别就在k,v模型里面还存储了键所对应的值的内容,而k模型没有

二叉搜索树构成

对于二叉搜索树来说,我们想要找到其中的全部成员,和二叉树一样,我们只需要存储它的根节点即可。

template<typename K, typename V>
class BSTree
{//typedef减少代码长度typedef BSTree_node<K, V> node;
private:node* _root = nullptr;

二叉搜索树的查找

由于二叉搜索树的性质,想要从中查找就很简单了。
a. 从根开始比较查找,比根大往右走,比根小往左走。
b. 最多查找高度次,如果走到空还没找到,则这个值不存在

//循环操作
node* find(const K& key)
{//find不需要查找值,只需要查找键node* cur = _root;while (cur){if (cur->_key > key) cur = cur->_left;else if (cur->_key < key) cur = cur->_right;else return cur;}return nullptr;
}
//递归的方式
//由于递归需要传递this指针来找到根节点,而方法不能在外面使用this指针,因此我们需要写一个子函数来完成任务
private:node* _findR(node* root, const K& key){if (!root) return nullptr;if (root->_key < key) return _findR(root->_right, key);else if (root->_key > key) return _findR(root->_left, key);else return root;}
public:node* findR(const K& key){return _findR(_root,key);}

插入操作

插入操作同样两个要点:
a. 如果树为空,则直接新增节点,赋值给root指针
b. 树不空,按二叉搜索树的位置不断进行比较找到插入位置,如果找到相同值的节点则插入失败,否则插入新节点
同样提供递归和循环两种方法:

//循环
//对于循环,由于当找到空节点的时候并不知道其在其双亲的左侧还是右侧,所以每次变换时需要记录其是在左侧还是右侧//成功插入返回true,插入失败返回falsebool insert(const K& key, const V& value){node* tmp = new node(key, value);if (!_root){_root = tmp;return true;}//需要存储parent,还要存储所在的方向node* parent = nullptr;node* cur = _root;bool is_right = false;while (cur){if (cur->_key < key) {is_right = true;parent = cur;cur = cur->_right;}else if (cur->_key > key) {is_right = false;parent = cur;cur = cur->_left;}else return false;}//出循环时,cur已经指向空指针//如果直接对cur赋值,是在对该拷贝赋值,并没有修改其双亲的指向if (is_right) parent->_right = tmp;else parent->_left = tmp;return true;}//递归//同样,我们需要一个子函数来传递this指针private://这里使用引用是为了解决循环中无法知道新节点在其双亲的左边还是右边的问题bool _insertR(node*& root, const K& key, const V& value)
{//引用解决了找不到父亲的问题if (!root){//由于使用的是引用,这里其实是在修改双亲的指向root = new node(key, value);return true;}if (root->_key < key) return _insertR(root->_right, key, value);else if (root->_key > key) return _insertR(root->_left, key, value);else return false;
}		

中序遍历

由于二叉树的特殊性质,其中序遍历一定是有序的,因此我们可以写一个inorder函数输出存储结果用于检验我们操作是否正确实现

private:void _inorder(node* root){if (!root) return;_inorder(root->_left);std::cout << root->_key << ":" << root->_value << std::endl;_inorder(root->_right);	}
public:void inorder(){_inorder(_root);std::cout << std::endl;}

二叉树的删除

  1. 首先查找元素是否在二叉树中,如果不存在,则返回
  2. 如果存在,则删除节点还需要分以下几种情况:
  • 要删除的节点无左孩子
  • 要删除的节点只有左孩子节点
  • 要删除的节点只有右孩子节点
  • 要删除的节点有左,右孩子节点

在实际删除过程中,可以将情况1和情况2或情况3合并起来,删除过程如下:

  • 情况1: 删除该节点且使被删除节点的双亲节点指向被删除节点的左孩子——直接删除
    情况2: 删除该节点且使被删除节点的双亲指向被删除节点的右孩子
    情况3: 寻找和节点的值最相近的节点(左子树最右节点或者该节点右子树的根节点),用它的值填补道被删除节点,然后再来处理该节点的删除问题。

节点的删除操作实现是二叉搜索树中最困难的一个,由于其的细节很多,这里同样还是给出递归和循环各一种方法。

循环(利用左子树最右节点)

对于循环,博主选择了和左子树的最右节点进行交换的方法,这是由于只要找到了左子树的最右节点,和此时的根节点交换,然后就只需要进行左右都没孩子的删除操作即可。
但是,和插入有着同样的问题,我们在查找待删除节点的时候并不知道它在左侧还是右侧,所以我们仍然要保存其双亲节点的位置,但是,还有例外。
先看情况一和情况二
在这里插入图片描述
对于这种情况来说,如果要删除的是根节点,那么其双亲节点的指针就是nullptr,如果不加以判断,就会出错,当待删除节点为根节点时,我们直接将树的_root节点改成_root->left/_root->right即可
情况三:
对于情况三来说,虽然我们需要找的是左子树的最右节点,但是一定不要认为左子树最右节点一定在其双亲的右边,有一种情况是例外的。如下:
在这里插入图片描述
如果此时要删除的是根节点,那么其左子树的最右节点就在其双亲的左边,所以我们仍然需要一个标记来判断该节点是在双亲的左边还是右边。

对于二叉树的删除操作来说,循环需要考虑的细节较多,递归虽然也有细节,但是相对更简单一些,但是循环的好处就是不会爆栈,因此在数据量非常大的时候还是使用循环更合适。
循环模拟代码如下:

bool erase(const K& key)
{//可以分为两种大情况//无子节点,有一个子节点 -> 采用托孤处理//托孤要注意删根节点的情况//两个子节点都有 -> 将其与左树最大节点或者右数最小节点相交换,然后删除//第一步先找到要删除的值的位置node* parent = nullptr;node* cur = _root;//保存节点位于其双亲的位置bool is_right = false;while (cur){if (cur->_key < key){is_right = true;parent = cur;cur = cur->_right;}else if (cur->_key > key){is_right = false;parent = cur;cur = cur->_left;}else break;}if (!cur) return false;node* del = cur;//这里需要找到父节点的本质原因是引用不能改变指向if (!cur->_left){//特殊情况,删除的是根节点if (!parent) _root = _root->_right;else{if (is_right) parent->_right = cur->_right;else parent->_left = cur->_right;}}else if (!cur->_right){//同理if (!parent) _root = _root->_left;else{if (is_right) parent->_right = cur->_left;else parent->_left = cur->_left;}}else{//左右两边都不为空//这里循环找左边最大更方便node* leftMax = cur->_left;//bool is_up = true;//出现 特殊情况的本质是下面这个循环没有生效while (leftMax->_right){parent = leftMax;leftMax = leftMax->_right;//is_up = false;}std::swap(leftMax->_key, cur->_key);//如果左子树最大节点就是初始的leftMax,则将待删除节点的左指向leftMax的左//if(is_up)if (leftMax == cur->_left) cur->_left = leftMax->_left;else parent->_right = leftMax->_left;//转换待释放的节点del = leftMax;}delete del;return true;
}

递归(利用右子树根节点)

对于递归来说,如果我们选择右子树的根节点进行操作,整个删除过程就可以变成子问题解决。

首先,由于递归可以使用引用作为参数,我们不需要纠结双亲以及其位于双亲左还是右的问题,因此对于循环中情况1,2删除根节点的问题就不需要考虑了
另外,对于情况3来说,选择右子树的根节点也使得情况简单了许多,因为将右子树节点与待删除节点的值交换后,就变成了删除其右子树的根的子问题,完美符合递归的逻辑,直到根只有一个孩子的时侯就变成情况1了,不需要考虑其他特殊情况。
代码如下:

private:bool _erase(node*& root, const K& key)
{if (!root) return false;if (root->_key < key) return _erase(root->_right, key);else if (root->_key > key) return _erase(root->_left, key);else{//用引用就不需要考虑父亲指向的问题了if (!root->_left) {root = root->_right;return true;}else if (!root->_right){root = root->_left;return true;}else{swap(root->_key, root->_right->_key);return _erase(root->_right, key);}}
}
public:bool eraseR(const K& key){_erase(_root, key);}

二叉树拷贝

二叉树的拷贝构造也可以利用递归的性质来实现,先拷贝根节点,然后拷贝左子树,最后拷贝右子树,拷贝左子树和右子树的逻辑与主逻辑相同。
代码:

private:node* _copy(node*& root, node* copy){if (!copy) return nullptr;root = new node(copy->_key, copy->_value);root->_left = _copy(root->_left, copy->_left);root->_right = _copy(root->_right, copy->_right);return root;}
public:BSTree(const BSTree& t){_copy(_root, t._root);}

二叉树资源的销毁

由于二叉树的节点都是new出来的节点,所以我们在结束使用时也需要释放资源,否则就会导致内存泄漏的问题,对于释放资源,我们可以放在析构函数解决,运用递归后序遍历的思路,先释放左子树的资源,然后释放右子树的资源,最后释放根节点的资源,代码如下:

private:void _destroy(node* root){if (!root) return;_destroy(root->_left);_destroy(root->_right);delete root;}public:~BSTree(){_destroy(_root);}

二叉树实现完整代码

#pragma once
#include<iostream>
#include<algorithm>
#include<string>
using namespace std;namespace key_value
{template<typename K, typename V>struct BSTree_node{K _key = K();V _value = V();BSTree_node* _left = nullptr;BSTree_node* _right = nullptr;BSTree_node() {}BSTree_node(const K& key, const V& value):_key(key), _value(value){}};template<typename K, typename V>class BSTree{typedef BSTree_node<K, V> node;private:node* _root = nullptr;void _inorder(node* root){if (!root) return;_inorder(root->_left);std::cout << root->_key << ":" << root->_value << std::endl;_inorder(root->_right);	}bool _insertR(node*& root, const K& key, const V& value){//引用解决了找不到父亲的问题if (!root){root = new node(key, value);return true;}if (root->_key < key) return _insertR(root->_right, key, value);else if (root->_key > key) return _insertR(root->_left, key, value);else return false;}node* _findR(node* root, const K& key){if (!root) return nullptr;if (root->_key < key) return _findR(root->_right, key);else if (root->_key > key) return _findR(root->_left, key);else return root;}//同理,这里要修改的不是局部变量,而是上一个指针的指向,所以要使用引用bool _erase(node*& root, const K& key){if (!root) return false;if (root->_key < key) return _erase(root->_right, key);else if (root->_key > key) return _erase(root->_left, key);else{//用引用就不需要考虑父亲指向的问题了if (!root->_left) {root = root->_right;return true;}else if (!root->_right){root = root->_left;return true;}else{swap(root->_key, root->_right->_key);return _erase(root->_right, key);}}}void _destroy(node* root){if (!root) return;_destroy(root->_left);_destroy(root->_right);delete root;}node* _copy(node*& root, node* copy){if (!copy) return nullptr;root = new node(copy->_key, copy->_value);root->_left = _copy(root->_left, copy->_left);root->_right = _copy(root->_right, copy->_right);return root;}public:BSTree() {}//循环///插入,如果已经存在就不用插入bool insert(const K& key, const V& value){node* tmp = new node(key, value);if (!_root){_root = tmp;return true;}//需要存储parent,还要存储所在的方向node* parent = nullptr;node* cur = _root;bool is_right = false;while (cur){if (cur->_key < key) {is_right = true;parent = cur;cur = cur->_right;}else if (cur->_key > key) {is_right = false;parent = cur;cur = cur->_left;}else return false;}//出循环时,cur已经指向空指针if (is_right) parent->_right = tmp;else parent->_left = tmp;return true;}node* find(const K& key){//find不需要查找值,只需要查找键node* cur = _root;while (cur){if (cur->_key > key) cur = cur->_left;else if (cur->_key < key) cur = cur->_right;else return cur;}return nullptr;}bool erase(const K& key){//可以分为两种大情况//无子节点,有一个子节点 -> 采用托孤处理//托孤要注意删根节点的情况//两个子节点都有 -> 将其与左树最大节点或者右数最小节点相交换,然后删除//第一步先找到要删除的值的位置node* parent = nullptr;node* cur = _root;//保存节点位于其双亲的位置bool is_right = false;while (cur){if (cur->_key < key){is_right = true;parent = cur;cur = cur->_right;}else if (cur->_key > key){is_right = false;parent = cur;cur = cur->_left;}else break;}if (!cur) return false;node* del = cur;//这里需要找到父节点的本质原因是引用不能改变指向if (!cur->_left){//特殊情况,删除的是根节点if (!parent) _root = _root->_right;else{if (is_right) parent->_right = cur->_right;else parent->_left = cur->_right;}}else if (!cur->_right){//同理if (!parent) _root = _root->_left;else{if (is_right) parent->_right = cur->_left;else parent->_left = cur->_left;}}else{//左右两边都不为空//这里循环找左边最大更方便node* leftMax = cur->_left;//bool is_up = true;//出现 特殊情况的本质是下面这个循环没有生效while (leftMax->_right){parent = leftMax;leftMax = leftMax->_right;//is_up = false;}std::swap(leftMax->_key, cur->_key);//如果左子树最大节点就是初始的leftMax,则将待删除节点的左指向leftMax的左//if(is_up)if (leftMax == cur->_left) cur->_left = leftMax->_left;else parent->_right = leftMax->_left;//转换待释放的节点del = leftMax;}delete del;return true;}//递归///中序遍历void inorder(){_inorder(_root);std::cout << std::endl;}bool insertR(const K& key, const V& value){return _insertR(_root, key, value);}node* findR(const K& key) { return _findR(_root, key); }bool eraseR(const K& key){_erase(_root, key);}~BSTree(){_destroy(_root);}BSTree(const BSTree& t){_copy(_root,t._root);}};//测试用例1void test_BSTree(){int a[] = { 8,3,1,10,6,4,7,14,13 };BSTree<int, int> bst;for (auto e : a) bst.insert(e,e);std::cout << bst.find(8)->_key << std::endl;std::cout << bst.find(13)->_key << std::endl;//std::cout << bst.find(18)->_key << std::endl;BSTree<int, int> t1(bst);t1.inorder();bst.erase(4);bst.inorder();bst.erase(6);bst.inorder();bst.erase(7);bst.inorder();bst.erase(3);bst.inorder();for (auto e : a){bst.erase(e);}bst.inorder();}//测试用例2void TestBSTree(){BSTree<string, string> dict;dict.insertR("insert", "插入");dict.insertR("erase", "删除");dict.insertR("left", "左边");dict.insertR("string", "字符串");string str;while (cin >> str){auto ret = dict.findR(str);if (ret){cout << str << ":" << ret->_value << endl;}else{cout << "单词拼写错误" << endl;}}string strs[] = { "苹果", "西瓜", "苹果", "樱桃", "苹果", "樱桃", "苹果", "樱桃", "苹果" };// 统计水果出现的次BSTree<string, int> countTree;for (auto str : strs){auto ret = countTree.findR(str);if (ret == nullptr){countTree.insertR(str, 1);}else{ret->_value++;}}BSTree<string, int> t1(countTree);countTree.inorder();t1.inorder();}
}

总结

前面提到了对于二叉查找树来说,

  • 最好情况下二叉树为完全二叉树(或接近完全二叉树),其平均比较次数为: l o g 2 N log_2 N log2N
  • 最坏情况下,二叉搜索树有可能退化成单支树(或类似单支),其平均比较次数为: N 2 \frac{N}{2} 2N

那么就有一个问题了,如果退化成单支树,二叉搜索树的性能就消失了,那么是否能够改进,不论按什么次数插入关键码,二叉搜索树的性能都能达到最优呢?
那么就需要用到AVL树和红黑树了,这两种树都是特殊的搜索二叉树,但是底层相对于普通的二叉搜索树又复杂了许多,加入了翻转二叉树等操作来达到最有优效率!
STL中的mapset底层就是用红黑树实现的,其做到了查找最坏时间复杂度为 O ( l o g 2 N ) O(log_2 N) O(log2N),这样大家就感受到红黑树的强大了把!

对于AVL树和红黑树的知识,博主会在之后的博客中讲解,大家敬请期待!


以上就是二叉树实现的增删查改的相关知识内容,完整代码以及其作用和性能分析的总结了,希望大家看完能够有所收获!如果对博主的内容有疑惑或者博主内容有误的话,欢迎评论区指出!

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PHPStudy 安装tp8 php8.2.9

一、PhpStudy升级PHP版本&#xff0c;安装PHP8.2操作步骤 1.1、官网下载最新的php版本 打开Windows版的官网下载&#xff0c;地址&#xff1a;https://windows.php.net/download/ 页面上有不同的PHP版本&#xff0c;这里我们下载的是64位nts版的PHP8.2.9。 1.2、解压下载的文…

2023.8 - java - 泛型

泛型问题的引出&#xff1a; jdk 1.5 引出泛型 // package 泛型; public class index {public static void main (String[] args){test t new test();t.setContent("aaa");int a (int) t.getContent();System.out.println(a);} }class test{Object content;publi…

国内常见的几款可视化Web组态软件

组态软件是一种用于控制和监控各种设备的软件&#xff0c;也是指在自动控制系统监控层一级的软件平台和开发环境。这类软件实际上也是一种通过灵活的组态方式&#xff0c;为用户提供快速构建工业自动控制系统监控功能的、通用层次的软件工具。通常用于工业控制&#xff0c;自动…

[SWPUCTF 2022 新生赛]ez_ez_php

这段代码是一个简单的PHP文件处理脚本。让我们逐行进行分析&#xff1a; error_reporting(0); - 这行代码设置了错误报告的级别为0&#xff0c;意味着不显示任何错误。 if (isset($_GET[file])) { - 这行代码检查是否存在一个名为"file"的GET参数。 if ( substr($_…

无涯教程-Perl - wantarray函数

描述 如果当前正在执行的函数的context正在寻找列表值,则此函数返回true。在标量context中返回false。 语法 以下是此函数的简单语法- wantarray返回值 如果没有context,则此函数返回undef&#xff1b;如果lvalue需要标量,则该函数返回0。 例 以下是显示其基本用法的示例…

记录Taro巨坑,找不到sass类型定义文件

问题 taronutuisassts项目里tsconfig.json一直报红报错。 找不到“sass”的类型定义文件。 程序包含该文件是因为: 隐式类型库 “sass” 的入口点 其实正常人想的肯定是装上 types/sass试试。开始我试过了&#xff0c;没用。删了依赖重装也没用。后面在issue中找到答案了 解决…

Mybatis的SqlSource SqlNode BoundSql

学习链接 MyBatis SqlSource解析 【Mybatis】Mybatis源码之SqlSource#getBoundSql获取预编译SQL Mybatis中SqlSource解析流程详解 Mybatis TypeHandler解析 图解 Mybatis的SqlSource&SqlNode - processon DynamicSqlSource public class DynamicSqlSource implement…

05-微信小程序常用组件-表单组件

05-微信小程序常用组件-表单组件 文章目录 表单组件button 按钮案例代码 form 表单案例代码 image 图片支持长按识别的码案例代码 微信小程序包含了六大组件&#xff1a; 视图容器、 基础内容、 导航、 表单、 互动和 导航。这些组件可以通过WXML和WXSS进行布局和样式设…

800V高压电驱动系统架构分析

需要电驱竞品样件请联&#xff1a;shbinzer &#xff08;拆车邦&#xff09; 过去一年是新能源汽车市场爆发的一年&#xff0c;据中汽协数据&#xff0c;2021年新能源汽车销售352万辆&#xff0c;同比大幅增长157.5%。新能源汽车技术发展迅速&#xff0c;畅销车辆在动力性能…

【Python原创设计】基于Python Flask的全国气象数据采集及可视化系统-附下载方式以及项目参考论文,原创项目其他均为抄袭

基于Python Flask的全国气象数据采集及可视化系统 一、项目简介二、项目技术三、项目功能四、运行截图五、分类说明六、实现代码七、数据库结构八、源码下载 一、项目简介 本项目是一个基于Web技术的实时气象数据可视化系统。通过爬取中国天气网的各个城市气象数据&#xff0c…

Nginx高可用集群

目录 一.简介二.案例1.实现思路2.配置文件修改3.实现效果故障转移机制 一.简介 以提高应用系统的可靠性&#xff0c;尽可能地减少中断时间为目标&#xff0c;确保服务的连续性&#xff0c;达到高可用的容错效果。例如“故障切换”、“双机热备”、“多机热备”等都属于高可用集…

ceph集群的扩容缩容

文章目录 集群扩容添加osd使用ceph-deploy工具手动添加 添加节点新节点前期准备新节点安装ceph&#xff0c;出现版本冲突 ceph-deploy增加节点 集群缩容删除osd删除节点 添加monitor节点删除monitor节点使用ceph-deploy卸载集群 实验所用虚拟机均为Centos 7.6系统&#xff0c;8…

windows系统丢失mfc120u.dll的解决方法

1.mfc120u.dll是什么 mfc120u.dll是Windows操作系统中的一个动态链接库&#xff08;Dynamic Link Library&#xff0c;简称DLL&#xff09;文件。它包含了一些用于运行C程序的函数和其他资源。这个特定的DLL文件是Microsoft Foundation Classes&#xff08;MFC&#xff09;库的…

【数据结构OJ题】相交链表

原题链接&#xff1a;https://leetcode.cn/problems/intersection-of-two-linked-lists/description/ 目录 1. 题目描述 2. 思路分析 3. 代码实现 1. 题目描述 2. 思路分析 看到这道题&#xff0c;很容易想到的方法就是暴力求解&#xff0c;就是将一个链表的每个结点的地址…

曲线救国 | 双非渣硕的秋招路

作者 | 带带大兄弟 面试锦囊之面经分享系列&#xff0c;持续更新中 欢迎后台回复"面试"加入讨论组交流噢 一篇旧文&#xff0c;可以参考~ 写在前面 双非渣硕&#xff0c;0实习&#xff0c;3篇水文&#xff0c;三个给老板当打工仔的nlp横向项目&#xff0c;八月份开…

文本三剑客之sed编辑器

sed 一、sed简介1.1 什么是sed&#xff1f;1.2 sed原理1.3 sed核心功能 二、sed命令格式详解2.1 命令格式2.2 常用选项2.3 sed脚本语法2.3.1 基本语法结构2.3.2 地址部分-----指定匹配范围2.3.3 命令部分-----要执行的命令 三、sed查找替换3.1 基本语法3.2 分组后向引用3.3 变量…

【面试八股文】每日一题:谈谈你对线程的理解

每日一题-Java核心-谈谈你对线程的理解【面试八股文】 Java线程是Java程序中的执行单元。一个Java程序可以同时运行多个线程&#xff0c;每个线程可以独立执行不同的任务。线程的执行是并发的&#xff0c;即多个线程可以同时执行。 1. 线程的特点 Java中的线程有如下的特点 轻…

[LeetCode - Python]844. 比较;含退格的字符串(Easy);415. 字符串相加(Easy)

1.题目 844. 比较含退格的字符串&#xff08;Easy&#xff09; 1.代码&#xff1a; class Solution:def backspaceCompare(self, s: str, t: str) -> bool:# 暴力法s list(s)t list(t)M 0N 0for i in range(len(s)):i -M if s[i] # :if i > 0 :s.pop(i)s.pop(i-…

LION AI 大模型落地,首搭星纪元 ES

自新能源汽车蓬勃发展以来&#xff0c;随着潮流不断进步和变革的“四大件”有着明显变化。其中有&#xff1a;平台、智能驾驶、配置、以及车机。方方面面都有着不同程度的革新。 而车机方面&#xff0c;从以前老旧的媒体机、 CD 机发展至如今具有拓展性、开放性、智能化的车机…

Docker部署php运行环境(php-fpm+nginx)

前言 如果使用docker去部署一套php的运行环境&#xff0c;我们需要构建出nginx、php-fpm两个容器&#xff0c;nginx通过fast_cgi协议去转发php-fpm中的端口&#xff0c;从而实现web server的搭建&#xff0c;接下来以php的laravel框架为演示例子。 部署php-fpm 第一步 编写ph…