【数据结构】6.5 红黑树(C++)

news2024/11/29 11:43:14

【数据结构】——6.5 红黑树

没有学过二叉搜索树(也叫二叉排序树或二叉查找树)的小伙伴们建议先学习一下,这样阅读会更轻松哦 点我学习二叉搜索树

目录

  • 一、红黑树的概念和性质
  • 二、红黑树的存储结构和声明
  • 三、红黑树的构建过程
  • 四、红黑树的实现
    • 1. 默认成员函数
    • 2. 插入Insert
    • 3. 查找Find
    • 4. 中序遍历
    • 5. 检查是否构成红黑树

一、红黑树的概念和性质

红黑树是一种近似平衡的二叉搜索树,它有以下特点:

  1. 每个节点只能是红色或黑色
  2. 根节点为黑色
  3. 红色节点的两个孩子必须是黑色
  4. 对于每个节点,到所有叶子节点的路径中,经过的黑色节点数目是相同的
  5. 叶子节点的两个空指针孩子当作黑节点来看,它被称为NIL节点,在使用时我们也可以忽略它的存在。

在这里插入图片描述

红黑树这些规则有什么用呢?

  1. 因为红色节点的孩子必须是黑色的,所以不可能存在2个连续的红色节点
  2. 因为每个节点到低的每条路径黑色数目相同,且不存在2个连续的的红色节点,所以一条路径最短的情况是每个节点都是黑色节点,最长的情况是一黑一红相间的路径
  3. 因此最长的路径一定不会超过最短路径节点数的2倍,这保证了红黑树不会出现单支树的问题,在一定程度上确保了二叉搜索树的平衡性。

红黑树的最优情况:全为黑色节点或每条路径都是一黑一红

红黑树的最差情况:左子树全黑,右子树一黑一红

红黑树是一个近似平衡搜索二叉树,它确保了没有一条路径会比其他路径长出2倍,解决了普通二叉搜索树因不平衡导致查找效率低的问题。

在这里插入图片描述

AVL树也是一颗近似平衡的二叉搜索树,它们有什么区别?

  1. AVL树中左子树与右子树的高度差不能超过1,红黑树中最长路径不会超过最短路径的2倍。所以从平衡性来讲,AVL树比红黑树更加接近平衡,AVL树的查找效率比红黑树更高
  2. 当插入节点时,AVL树中平衡因子超过2就要进行旋转,而红黑树中出现连续红色节点时才会旋转。这是红黑树的旋转次数比AVL树少,因此红黑树的插入效率比AVL树更高。同理,删除效率也比AVL树更高

虽然选择二叉搜索树作为数据结构去使用是因为它高效率的查找效率,但是在我们经常使用的一些容器中,插入和删除也是我们频繁使用的接口,因此在实际应用中,红黑树的应用比AVL树更广泛。但是对于一些特定场合,使用AVL树也是更不错的选择。

二、红黑树的存储结构和声明

  1. 红黑树的结构依然使用三叉链表来实现
  2. 我们使用enum枚举类型来定义节点的红色和黑色
  3. 我们只实现红黑树的插入,查找和中序遍历,并且提供一个函数检测我们的红黑树是否满足规则
namespace Name
{
    // 颜色定义
    enum Color
	{
		RED,
		BLACK
	};
    
    // 节点声明
    template <class K>
    struct RBTreeNode
    {
        K& _key;					// 键
        RBTreeNode<K> *_left;
        RBTreeNode<K> *_right;
        RBTreeNode<K> *_parent;
        Color _col;					// 颜色

        // 构造函数
        RBTreeNode(const K& key)
            : _key(key)
            , _left(nullptr)
            , _right(nullptr)
            , _parent(nullptr)
            , _col(RED)
        {
            ;
        }
    };
    
    // 红黑树的声明
    template <class K>
	class RBTree
	{
		typedef RBTreeNode<K> Node;		// 将节点类型重命名为Node
        
	public:
        bool Insert(const K& key);		// 插入
        bool Find(const K& key);		// 查找
        void InOrder(void);				// 中序遍历
        bool IsBalance(void);			// 检测是否满足红黑树规则
        
    private:
        Node* _root;	// 根节点指针
    };
}

三、红黑树的构建过程

红黑树通过不断插入元素,构成二叉搜索树,若是破坏了红黑树的规则,则对树进行调整,直到构成红黑树

插入的新节点该是什么颜色呢?

新创建的节点一定是红色,因为新节点是黑色会导致本条路径的黑色节点数量多出一个,破坏红黑树的规则

插入步骤:

  1. 按照二叉搜索树的规则,将新节点插入对应的位置,新节点为红色
  2. 当父亲节点是黑色时,没有破坏红黑树的规则,插入结束
  3. 当父亲节点是红色时,出现连续的红色,需要对红黑树进行调整,使之重新构成红黑树

当出现连续的红色节点时,当前节点为红色父节点为红色祖父节点一定为黑色,所以我们通过叔叔节点来判断当前红黑树的状况。

在这里插入图片描述

新节点插入,父亲节点为红色时,叔叔节点只有2种情况:叔叔节点为红色或不存在

在这里插入图片描述

(1)叔叔节点为红色

当叔叔节点为红色时,需要将父亲节点和叔叔节点变成黑色,祖父节点变成红色,此时整颗子树每条路径的黑色节点仍然是一个,与其他路径的黑色节点数量一致。

在这里插入图片描述

当叔叔节点不存在时插入新节点,我们通过变色的方式调整为红黑树,此时子树的顶端被我们修改为了红色。

  1. 若是子树的父节点为红色,那么就会出现连续的红色节点,我们需要继续向上调整
  2. 若是子树的顶端就是根节点,那么根节点不能被修改为红色

对于根节点问题,我们可以判断子树顶端是否为根节点,如果是则不改变根节点的颜色。也可以在插入完成时将根节点的颜色改为黑色。我们采用第二种方案,因为不需要判断,写起来方便

向上调整时,叔叔节点一定存在,若是叔叔节点还是红色,则继续变色,直到调整到根节点或叔叔节点为黑色

红色方框的意思是,该节点为红色或不存在

在这里插入图片描述

(2)叔叔节点为黑色或不存在

当叔叔节点为黑色或者不存在时,需要对树的形状进行调整,调整的方式就是旋转

根据此时树的形状,将旋转分为4类,分别是左单旋,右单旋,左右双旋,右左双旋

当旋转完毕后要对对应的节点进行变色

黑色方框代表节点为黑色或不存在

<1>右单旋

  1. p是g的左孩子,c是p的左孩子时,进行右单旋
  2. 当旋转完毕后,将父节点变成黑色当前节点和叔叔节点均为红色

右单旋的过程:

  1. 让祖父节点g成为父节点p的右孩子
  2. 若是父节点p有右孩子,则将其交给祖父节点p领养,成为p的左孩子。(图中p没有右孩子)
  3. 此时父节点p在子树的最顶端,将其改为黑色。祖父节点g改为红色

在这里插入图片描述

代码实现

以下代码的实现与AVL树中的代码一样,只是没有平衡因子的更新。还有一个区别是这里的参数传递的是祖父节点,但是操作和AVL树中的父节点一样,可以理解成红黑树中的当前节点比AVL树中的矮一层

// 右单旋
void RotateR(Node* grandpa)
{
    Node* grandpa_p = grandpa->_parent;		// 祖父节点的父节点,用来连接旋转后的子树
    Node* parent = grandpa->_left;			// 父节点
    Node* rightSun = parent->_right;		// 父节点的右孩子

    // 链接孩子节点
    parent->_right = grandpa;
    grandpa->_left = rightSun;
    if (grandpa_p != nullptr)
    {
        // 祖父节点不为根节点
        if (grandpa_p->_left == grandpa)
        {
            grandpa_p->_left = parent;
        }
        else
        {
            grandpa_p->_right = parent;
        }
    }
    else
    {
        // 祖父节点为根节点
        _root = parent;
    }

    // 更新父节点
    parent->_parent = grandpa_p;
    grandpa->_parent = parent;
    if (rightSun != nullptr)
    {
        rightSun->_parent = grandpa;
    }
}

<2>左单旋

  1. p是g的右孩子,c是p的右孩子时,进行左单旋
  2. 当旋转完毕后,将父节点变成黑色当前节点和叔叔节点均为红色

右单旋的过程:

  1. 让祖父节点g成为父节点p的左孩子
  2. 若是父节点p有左孩子,则将其交给祖父节点p领养,成为p的右孩子。(图中p没有左孩子)
  3. 此时父节点p在子树的最顶端,将其改为黑色。祖父节点g改为红色

在这里插入图片描述

代码实现:

// 左单旋
void RotateL(Node* grandpa)
{
    Node* grandpa_p = grandpa->_parent;
    Node* parent = grandpa->_right;
    Node* leftSub = grandpa->_left;

    // 链接孩子节点
    grandpa->_left = grandpa;
    grandpa->_right = leftSub;
    if (grandpa_p != nullptr)
    {
        if (grandpa_p->_left == grandpa)
        {
            grandpa_p->_left = grandpa;
        }
        else
        {
            grandpa_p->_right = grandpa;
        }
    }
    else
    {
        _root = grandpa;
    }

    // 更新父节点
    grandpa->_parent = grandpa_p;
    grandpa->_parent = grandpa;
    if (leftSub != nullptr)
    {
        leftSub->_parent = grandpa;
    }
}

<3>左右双旋

  1. p是g的左孩子,c是p的右孩子时,进行左右双旋
  2. 先以c节点为轴进行左单旋,再以旋转后的c节点为轴进行右单旋
  3. 当旋转完毕后,将父节点变成黑色当前节点和叔叔节点均为红色

在这里插入图片描述

由于红黑树的左右双旋和右左双旋可以直接调用左单旋和右单旋,并且最后只需要更新节点颜色,不需要多余的操作,我们就不将这两个封装成为一个单独的函数了,使用时直接展开书写即可。

<4>右左双旋

  1. p是g的右孩子,c是p的左孩子时,进行右左双旋
  2. 先以c节点为轴进行右单旋,再以旋转后的c节点为轴进行左单旋
  3. 当旋转完毕后,将父节点变成黑色当前节点和叔叔节点均为红色

在这里插入图片描述

这4种旋转方式得到的结果都有一个共同点:

子树的顶端一定是黑色节点,不可能再出现两个连续的红色节点,不需要再向上调整了

四、红黑树的实现

1. 默认成员函数

(1)构造函数

根节点指针_root初始化为空指针即可

RBTree(void)
    : _root(nullptr)
{
    ;
}

(2)析构函数

后序遍历依次释放每个节点,由于要使用递归,参数要传递节点指针,因此我们将递归过程单独封装成为一个函数,让析构函数调用它

// 析构函数
~RBTree(void)
{
    _Destroy(_root);
    _root = nullptr;
}

private:
// 后序遍历释放节点
void _Destroy(Node* root)
{
    if (root == nullptr)
    {
        return;
    }

    _Destroy(root->_left);
    _Destroy(root->_right);
    delete root;
}

(3)拷贝构造

先序遍历依次拷贝每个节点,也将递归过程单独封装为一个函数

// 拷贝构造
RBTree(const AVLTree& t)
{
    _root = _Copy(_root, t._root);
}

private:
// 先序遍历递归拷贝节点
Node* _Copy(Node* root, const Node* src)
{
    if (src == nullptr)
    {
        return nullptr;
    }

    root = new Node(src->_key);
    root->_left = _Copy(root->_left, src->_left);
    root->_right = _Copy(root->_right, src->_right);
    return root;
}

(4)赋值重载

创建临时对象拷贝用来赋值的对象,然后交换自己和它的根节点指针内容,获取临时对象的数据。

// 赋值重载函数
RTree& operator=(const AVLTree& t)
{
    if (this != &t)
    {
        BSTree temp(t);
        std::swap(temp._root, _root);
    }
    return *this;
}

2. 插入Insert

红黑树插入元素的步骤:

  1. 根据二叉搜索树规则将新节点插入到对应的位置
  2. 判断新节点的父节点为黑色,则插入完毕
  3. 如果新节点的父节点为红色,则根据它的叔叔节点做出相应的调整
bool Insert(const pair<K, V>& kv)
{
    // 1.以二叉搜索树的方式插入新节点
    if (_root == nullptr)
    {
        _root = new Node(kv);
        _root->_col = BLACK;		// 根节点为黑色
        return true;
    }

    Node *cur = _root, *parent = nullptr;
    while (cur != nullptr)
    {
        if (kv.first < cur->_kv.first)
        {
            parent = cur;
            cur = cur->_left;
        }
        else if (kv.first > cur->_kv.first)
        {
            parent = cur;
            cur = cur->_right;
        }
        else
        {
            return false;
        }
    }

    cur = new Node(kv);
    if (kv.first < parent->_kv.first)
    {
        parent->_left = cur;
        cur->_parent = parent;
    }
    else
    {
        parent->_right = cur;
        cur->_parent = parent;
    }
    
    // 2.调整为红黑树
    while (parent != nullptr && parent->_col == RED)
    {
        Node* grandpa = parent->_parent;	// 红色节点一定有父亲,所以不用担心空指针
        Node* uncle = grandpa->_left;		// 获取叔叔节点
        if (parent == uncle)
        {
            uncle = grandpa->_right;
        }

        if (uncle != nullptr && uncle->_col == RED)
        {
            // 叔叔节点为红:变色
            parent->_col = BLACK;
            uncle->_col = BLACK;
            grandpa->_col = RED;

            cur = grandpa;
            parent = cur->_parent;
        }
        else
        {
            // 叔叔节点为黑或不存在:旋转+变色
            if (parent == grandpa->_left && cur == parent->_left)
            {
                // 右单旋
                RotateR(grandpa);
                // 变色
                parent->_col = BLACK;
                grandpa->_col = RED;
            }
            else if (parent == grandpa->_right && cur == parent->_right)
            {
                // 左单旋
                RotateL(grandpa);
                // 变色
                parent->_col = BLACK;
                grandpa->_col = RED;
            }
            else if (parent == grandpa->_left && cur == parent->_right)
            {
                // 左右双旋
                RotateL(parent);
                RotateR(grandpa);
                // 变色
                cur->_col = BLACK;
                grandpa->_col = RED;
            }
            else if (parent == grandpa->_right && cur == parent->_left)
            {
                // 右左双旋
                RotateR(parent);
                RotateL(grandpa);
                // 变色
                cur->_col = BLACK;
                grandpa->_col = RED;
            }
            else
            {
                assert(false);		// 不会出现的情况,使用断言处理
            }

            break;	// 旋转之后,顶一定是黑,不需要再判断连续红节点了
        }
    }
    
    _root->_col = BLACK;	// 将根节点置为黑色
    return true;
}

3. 查找Find

就是二叉搜索树的查找方式进行查找

bool Find(const K& key)
{
    Node* cur = _root;
    while (cur != nullptr)
    {
        if (key < cur->_key)
        {
            // 比当前节点小,往左走
            cur = cur->_left;
        }
        else if (key > cur->_key)
        {
            // 比当前节点大,往右走
            cur = cur->_right;
        }
        else
        {
            // 与当前节点相等,返回true
            return true;
        }
    }

    return false;
}

4. 中序遍历

递归实现中序遍历

// 中序遍历
void InOrder(void)
{
    _InOrder(_root);
    std::cout << std::endl;
}

// 中序遍历的递归过程
void _InOrder(Node* root)
{
    if (root == nullptr)
    {
        return;
    }

    _InOrder(root->_left);
    std::cout << root->_key << " ";
    _InOrder(root->_right);
}

5. 检查是否构成红黑树

  1. 先检查空树是红黑树,再判断根节点是否为红色
  2. 为了对比每条路径上的黑色节点数量,我们先获取最左边路径的黑色根节点数量作为参考。若是每条路径的黑色根节点数量与黑色根节点数量一样,则满足红黑树的规则
  3. 使用递归遍历每个节点,并判断是否存在连续的节点
bool IsBalance(void)
{
    // 空树是红黑树
    if (_root == nullptr)
    {
        return true;
    }

    // 根节点为红色不是红黑树
    if (_root->_col != BLACK)
    {
        return false;
    }

    // 获取最左边路径的黑色节点数目
    int ref = 0;
    Node* left = _root;
    while (left != nullptr)
    {
        if (left->_col == BLACK)
        {
            ++ref;
        }
        left = left->_left;
    }

    return _IsBalance(_root, 0, ref);
}

private:
// 递归遍历节点,并判断是否满足红黑树的规则
bool _IsBalance(const Node* root, int blackNum, const int ref) const
{
    if (root == nullptr)
    {
        if (blackNum != ref)
        {
            cout << "路径黑色节点数跟最左路径不相等" << endl;
            return false;
        }
        return true;
    }

    // 判断是否有连续红色节点
    if (root->_col == RED && root->_parent->_col == RED)	// 红色节点一定有父亲,不担心空指针问题
    {
        cout << "error : " << root->_kv.first << " and " << root->_parent->_kv.first << endl;
        return false;
    }

    if (root->_col == BLACK)
    {
        ++blackNum;
    }

    return _isBalance(root->_left, blackNum, ref) 
        && _isBalance(root->_right, blackNum, ref);
}

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

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

相关文章

淘宝客户失率高怎么办?什么因素会影响?

电商干货 商家开淘宝店铺的时候&#xff0c;很怕的是老客户流失了。或者说经常购买的人不买了&#xff0c;这是淘宝店铺的客户流失。 那么当我们遇到淘宝的客户流失率很高的时候该怎么办呢&#xff1f;有什么样的因素会造成影响呢? 淘宝客户流失率高怎么办 1、做好质量营销 质…

Trimble RealWorks处理点云数据(七)之点云导入3dmax

效果 背景 有时候我们需要通过点云数据来逆向建模,而建模软件常用的有3dmax,SketchUp等,那如何将点云数据导入3dmax来建模呢,下面我们来了解下 步骤 1、las导入Trimble RealWorks 2、对点云数据预处理 可以参考这篇文章 TrimbleRealWorks点云数据预处理 3、导出rcp格式…

语音芯片在射击游乐设备上的应用

射击打靶体验馆项目&#xff0c;产品设备仿真程度高、趣闻性强、外观逼真&#xff0c;现场体验是一种集体验&#xff0c;体育竞技为一体且室内外均可使用的游乐&#xff01; 在靶上能够看到击中目标的效果&#xff0c;而且会语音报环靶&#xff0c;通过低音炮&#xff0c;可以…

Pytorch基础 - 6. torch.reshape() 和 torch.view()

目录 1. torch.reshape(shape) 和 torch.view(shape)函数用法 2. 当处理的tensor是连续性的(contiguous) 3. 当处理的tensor是非连续性的(contiguous) 4. PyTorch中的contiguous 在本文开始之前&#xff0c;需要了解最基础的Tensor存储方式&#xff0c;具体见 Tensor数据类…

Spring常见面试题汇总

文章目录在Spring中&#xff0c;Bean的作用域有哪几个&#xff1f;SpringMVC的执行流程你知道吗&#xff1f;谈谈你对Spring IOC的理解&#xff1f;DI又是什么&#xff1f;谈谈你对Spring AOP的理解&#xff1f;Spring Bean的生命周期你能说出多少&#xff1f;Spring如何解决循…

sggJava基础第三天

运算符 运算符是一种特殊的符号&#xff0c;用以表示数据的运算、赋值和比较等。 算术运算符 赋值运算符 比较运算符&#xff08;关系运算符&#xff09; 逻辑运算符 位运算符&#xff08;这个几乎不使用&#xff0c;我们在讲解的时候了解一下即可&#xff0c;只不过…

敏捷团队如何在 PingCode 这类敏捷开发工具中管理 Scrum 开发管理流程

在本教程中&#xff0c;我们将在 PingCode 中介绍如何使用 Scrum 项目、创建产品待办列表和规划迭代、举行 Scrum 会议等详细流程。准备工作&#xff1a;已创建 PingCode 软件帐户 【免费注册通道】 什么是Scrum&#xff1f;Scrum 是国内外最热门的敏捷开发框架之一。Scrum 通…

【SpringBoot2】SpringBoot基础篇

SpringBoot基础篇 JC-1.快速上手SpringBoot ​ 学习任意一项技术&#xff0c;首先要知道这个技术的作用是什么&#xff0c;不然学完以后&#xff0c;你都不知道什么时候使用这个技术&#xff0c;也就是技术对应的应用场景。SpringBoot技术由Pivotal团队研发制作&#xff0c;功…

第三章 word2vec

目录3.1 基于推理的方法和神经网络3.1.1 基于计数的方法的问题3.1.2 基于推理的方法的概要3.1.3 神经网络中单词的处理方法3.2 简单的 word2vec3.2.1 CBOW模型的推理3.2.2 CBOW模型的学习3.2.3 word2vec的权重和分布式表示3.3 学习数据的准备3.3.1 上下文和目标词3.3.2 转化为o…

loki采集k8s日志

前言 loki 是轻量、易用的日志聚合系统。如果你的k8s集群规模并不大&#xff0c;推荐使用grafanaloki的方案来做微服务日志的采集&#xff1b; Loki组成 loki架构很简单&#xff0c;主要由3部分组成&#xff1a; loki&#xff1a;服务端&#xff0c;负责存储日志和处理查询&…

行程器,数显卡尺液晶驱动IC,VK1623段码LCD驱动芯片资料分享,封装LQFP100,QFP100,RAM映射48EGx8COM

永嘉微电/VINKA 型号&#xff1a;VK1623 封装形式&#xff1a;LQFP100/QFP100/DICE/COG KPP2609 概述 VK1623S是一个点阵式存储映射的LCD驱动器&#xff0c;可支持最大384点&#xff08;48EGx8COM&#xff09;的LCD屏。单片机可通过3/4线串行接口配置显示参数和发送显示数据…

Spring Cloud微服务网关Zuul的注解@EnableZuulProxy或@EnableZuulServer做了什么事情

一、Zuul的工作原理 Zuul 1.x的版本是由Servlet以及一系列的Filter组成的&#xff0c;各个组件之间协同合作完成功能&#xff0c;且易于扩展。参看官方的架构图我画了张图&#xff1a; Zuul声明周期&#xff1a; HTTP Request -> DispatcherServlet -> ZuulHandlerMappi…

动力节点老杜Vue3视频笔记——Vue程序初体验

目录 一、Vue程序初体验 1.1 下载并安装vue.js 1.2 第一个Vue程序 1.3 Vue的data配置项 1.4 Vue的template配置项 一、Vue程序初体验 可以先不去了解Vue框架的发展历史、Vue框架有什么特点、Vue是谁开发的&#xff0c;对我们编写Vue程序起不到太大的作用&#xff0c;…

代码随想录_二叉树_leetcode654 617

leetcode654 最大二叉树 654. 最大二叉树 给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建: 创建一个根节点&#xff0c;其值为 nums 中的最大值。递归地在最大值 左边 的 子数组前缀上 构建左子树。递归地在最大值 右边 的 子数组后缀上 …

octave安装使用——吴恩达机器学习

下载octave 解压后双击octave.vbs进行安装 配置 pkg rebuildpkg list 使用基础命令 使用矩阵命令 移动数据 size&#xff1a;矩阵的行和列length&#xff1a;行和列的最大值 读取和存储数据 load&#xff1a;加载文件who&#xff1a;所有变量whos&#xff1a;更详细的变量…

界面开发框架Qt - 组合小部件映射器示例

Qt 是目前最先进、最完整的跨平台C开发工具。它不仅完全实现了一次编写&#xff0c;所有平台无差别运行&#xff0c;更提供了几乎所有开发过程中需要用到的工具。如今&#xff0c;Qt已被运用于超过70个行业、数千家企业&#xff0c;支持数百万设备及应用。 Combo Widget Mappe…

人工智能实验二:约束满足问题

一、实验目的 求解约束满足问题&#xff1b;使用回溯搜索算法求解八皇后问题。 二、实验平台 课程实训平台https://www.educoder.net/paths/369 三、实验内容及步骤 实训内容&#xff1a;2-4 第六章 约束满足问题 1.问题描述 八皇后问题是指在一个 88 的棋盘上摆放八个皇…

Spark运行模式介绍

文章目录1. Local运行模式1.1 基本运行情况介绍1.2 角色划分1.3 Spark 任务提交与解释器对比2. StandAlone运行模式2.1 StandAlone介绍2.2 StandAlone架构2.3 Spark应用架构2.4 StandAlone HA 运行原理3. Spark on YARN3.1 Spark on Yarn 本质3.2 部署模式3.3 两种部署模式运行…

stata变量引用

stata变量引用–潘登同学的stata笔记 文章目录stata变量引用--潘登同学的stata笔记变量生成gen命令通配符&#xff1a;*, ?, -因子变量时间序列变量命名、前缀与标签变量命名、添加前缀通配符与批量重命名变量标签数字-文字对应表CSMAR数据处理查看、查找变量单值、暂元单值暂…

TCP网络连接的书写

TCP网络连接的书写 文章目录TCP网络连接的书写服务器端书写进程sock创建创建bind进行端口绑定(进行bind的初始化)监听socket获取链接用户端创建sock套接字connect进行连接服务器端书写 为TCP是面向连接,所有需要进行对于端口进行监控&#xff0c;另外的UDP的服务器就不需要进行…