二叉树18:从中序与后序遍历序列构造二叉树

news2024/11/15 23:32:13

主要是我自己刷题的一些记录过程。如果有错可以指出哦,大家一起进步。
转载代码随想录
原文链接:
代码随想录
leetcode链接:344. 反转字符串

题目:

给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。

示例:

示例 1:
在这里插入图片描述

输入:inorder = [9,3,15,20,7], postorder = [9,15,7,20,3]
输出:[3,9,20,null,null,15,7]

示例 2:

输入:inorder = [-1], postorder = [-1]
输出:[-1]

提示:

1 <= inorder.length <= 3000
postorder.length == inorder.length
-3000 <= inorder[i], postorder[i] <= 3000
inorder 和 postorder 都由 不同 的值组成
postorder 中每一个值都在 inorder 中
inorder 保证是树的中序遍历
postorder 保证是树的后序遍历

思路:

其实这个题我看到是一点思路都没有。推荐你们看看卡哥的视频吧。我这边只是做个记录。

首先回忆一下如何根据两个顺序构造一个唯一的二叉树,相信理论知识大家应该都清楚,就是以 后序数组的最后一个元素为切割点,先切中序数组,根据中序数组,反过来再切后序数组。一层一层切下去,每次后序数组最后一个元素就是节点元素。

如果让我们肉眼看两个序列,画一棵二叉树的话,应该分分钟都可以画出来。

流程如图:
在这里插入图片描述那么代码应该怎么写呢?

说到一层一层切割,就应该想到了递归。

来看一下一共分几步:

第一步:如果数组大小为零的话,说明是空节点了。
第二步:如果不为空,那么取后序数组最后一个元素作为节点元素。
第三步:找到后序数组最后一个元素在中序数组的位置,作为切割点
第四步:切割中序数组,切成中序左数组和中序右数组 (顺序别搞反了,一定是先切中序数组)
第五步:切割后序数组,切成后序左数组和后序右数组
第六步:递归处理左区间和右区间

不难写出如下代码:(先把框架写出来)

TreeNode* traversal (vector<int>& inorder, vector<int>& postorder) {

    // 第一步
    if (postorder.size() == 0) return NULL;

    // 第二步:后序遍历数组最后一个元素,就是当前的中间节点
    int rootValue = postorder[postorder.size() - 1];
    TreeNode* root = new TreeNode(rootValue);

    // 叶子节点
    if (postorder.size() == 1) return root;

    // 第三步:找切割点
    int delimiterIndex;
    for (delimiterIndex = 0; delimiterIndex < inorder.size(); delimiterIndex++) {
        if (inorder[delimiterIndex] == rootValue) break;
    }

    // 第四步:切割中序数组,得到 中序左数组和中序右数组
    // 第五步:切割后序数组,得到 后序左数组和后序右数组

    // 第六步
    root->left = traversal(中序左数组, 后序左数组);
    root->right = traversal(中序右数组, 后序右数组);

    return root;
}

难点大家应该发现了,就是如何切割,以及边界值找不好很容易乱套。

此时应该注意确定切割的标准,是左闭右开,还有左开右闭,还是左闭右闭,这个就是不变量,要在递归中保持这个不变量。

在切割的过程中会产生四个区间,把握不好不变量的话,一会左闭右开,一会左闭右闭,必然乱套!

可见循环不变量的重要性,在二分查找以及螺旋矩阵的求解中,坚持循环不变量非常重要,本题也是。

首先要切割中序数组,为什么先切割中序数组呢?

切割点在后序数组的最后一个元素,就是用这个元素来切割中序数组的,所以必要先切割中序数组。

中序数组相对比较好切,找到切割点(后序数组的最后一个元素)在中序数组的位置,然后切割,如下代码中我坚持左闭右开的原则:

// 找到中序遍历的切割点
int delimiterIndex;
for (delimiterIndex = 0; delimiterIndex < inorder.size(); delimiterIndex++) {
    if (inorder[delimiterIndex] == rootValue) break;
}

// 左闭右开区间:[0, delimiterIndex)
vector<int> leftInorder(inorder.begin(), inorder.begin() + delimiterIndex);
// [delimiterIndex + 1, end)
vector<int> rightInorder(inorder.begin() + delimiterIndex + 1, inorder.end() );

接下来就要切割后序数组了。

首先后序数组的最后一个元素指定不能要了,这是切割点 也是 当前二叉树中间节点的元素,已经用了。

后序数组的切割点怎么找?

后序数组没有明确的切割元素来进行左右切割,不像中序数组有明确的切割点,切割点左右分开就可以了。

此时有一个很重的点,就是中序数组大小一定是和后序数组的大小相同的(这是必然)。

中序数组我们都切成了左中序数组和右中序数组了,那么后序数组就可以按照左中序数组的大小来切割,切成左后序数组和右后序数组。

代码如下:

// postorder 舍弃末尾元素,因为这个元素就是中间节点,已经用过了
postorder.resize(postorder.size() - 1);

// 左闭右开,注意这里使用了左中序数组大小作为切割点:[0, leftInorder.size)
vector<int> leftPostorder(postorder.begin(), postorder.begin() + leftInorder.size());
// [leftInorder.size(), end)
vector<int> rightPostorder(postorder.begin() + leftInorder.size(), postorder.end());

此时,中序数组切成了左中序数组和右中序数组,后序数组切割成左后序数组和右后序数组。

接下来可以递归了,代码如下:

root->left = traversal(leftInorder, leftPostorder);
root->right = traversal(rightInorder, rightPostorder);

完整代码如下:
C++完整代码

class Solution {
private:
    TreeNode* traversal (vector<int>& inorder, vector<int>& postorder) {
        if (postorder.size() == 0) return NULL;

        // 后序遍历数组最后一个元素,就是当前的中间节点
        int rootValue = postorder[postorder.size() - 1];
        TreeNode* root = new TreeNode(rootValue);

        // 叶子节点
        if (postorder.size() == 1) return root;

        // 找到中序遍历的切割点
        int delimiterIndex;
        for (delimiterIndex = 0; delimiterIndex < inorder.size(); delimiterIndex++) {
            if (inorder[delimiterIndex] == rootValue) break;
        }

        // 切割中序数组
        // 左闭右开区间:[0, delimiterIndex)
        vector<int> leftInorder(inorder.begin(), inorder.begin() + delimiterIndex);
        // [delimiterIndex + 1, end)
        vector<int> rightInorder(inorder.begin() + delimiterIndex + 1, inorder.end() );

        // postorder 舍弃末尾元素
        postorder.resize(postorder.size() - 1);

        // 切割后序数组
        // 依然左闭右开,注意这里使用了左中序数组大小作为切割点
        // [0, leftInorder.size)
        vector<int> leftPostorder(postorder.begin(), postorder.begin() + leftInorder.size());
        // [leftInorder.size(), end)
        vector<int> rightPostorder(postorder.begin() + leftInorder.size(), postorder.end());

        root->left = traversal(leftInorder, leftPostorder);
        root->right = traversal(rightInorder, rightPostorder);

        return root;
    }
public:
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        if (inorder.size() == 0 || postorder.size() == 0) return NULL;
        return traversal(inorder, postorder);
    }
};

相信大家自己就算是思路清晰, 代码写出来一定是各种问题,所以一定要加日志来调试,看看是不是按照自己思路来切割的,不要大脑模拟,那样越想越糊涂。
加了日志的代码如下:(加了日志的代码不要在leetcode上提交,容易超时)

class Solution {
private:
    TreeNode* traversal (vector<int>& inorder, vector<int>& postorder) {
        if (postorder.size() == 0) return NULL;

        int rootValue = postorder[postorder.size() - 1];
        TreeNode* root = new TreeNode(rootValue);

        if (postorder.size() == 1) return root;

        int delimiterIndex;
        for (delimiterIndex = 0; delimiterIndex < inorder.size(); delimiterIndex++) {
            if (inorder[delimiterIndex] == rootValue) break;
        }

        vector<int> leftInorder(inorder.begin(), inorder.begin() + delimiterIndex);
        vector<int> rightInorder(inorder.begin() + delimiterIndex + 1, inorder.end() );

        postorder.resize(postorder.size() - 1);

        vector<int> leftPostorder(postorder.begin(), postorder.begin() + leftInorder.size());
        vector<int> rightPostorder(postorder.begin() + leftInorder.size(), postorder.end());

        // 以下为日志
        cout << "----------" << endl;

        cout << "leftInorder :";
        for (int i : leftInorder) {
            cout << i << " ";
        }
        cout << endl;

        cout << "rightInorder :";
        for (int i : rightInorder) {
            cout << i << " ";
        }
        cout << endl;

        cout << "leftPostorder :";
        for (int i : leftPostorder) {
            cout << i << " ";
        }
        cout << endl;
         cout << "rightPostorder :";
        for (int i : rightPostorder) {
            cout << i << " ";
        }
        cout << endl;

        root->left = traversal(leftInorder, leftPostorder);
        root->right = traversal(rightInorder, rightPostorder);

        return root;
    }
public:
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        if (inorder.size() == 0 || postorder.size() == 0) return NULL;
        return traversal(inorder, postorder);
    }
};

此时应该发现了,如上的代码性能并不好,因为每层递归定义了新的vector(就是数组),既耗时又耗空间,但上面的代码是最好理解的,为了方便读者理解,所以用如上的代码来讲解。

下面给出用下标索引写出的代码版本:(思路是一样的,只不过不用重复定义vector了,每次用下标索引来分割)
C++优化版本

class Solution {
private:
    // 中序区间:[inorderBegin, inorderEnd),后序区间[postorderBegin, postorderEnd)
    TreeNode* traversal (vector<int>& inorder, int inorderBegin, int inorderEnd, vector<int>& postorder, int postorderBegin, int postorderEnd) {
        if (postorderBegin == postorderEnd) return NULL;

        int rootValue = postorder[postorderEnd - 1];
        TreeNode* root = new TreeNode(rootValue);

        if (postorderEnd - postorderBegin == 1) return root;

        int delimiterIndex;
        for (delimiterIndex = inorderBegin; delimiterIndex < inorderEnd; delimiterIndex++) {
            if (inorder[delimiterIndex] == rootValue) break;
        }
        // 切割中序数组
        // 左中序区间,左闭右开[leftInorderBegin, leftInorderEnd)
        int leftInorderBegin = inorderBegin;
        int leftInorderEnd = delimiterIndex;
        // 右中序区间,左闭右开[rightInorderBegin, rightInorderEnd)
        int rightInorderBegin = delimiterIndex + 1;
        int rightInorderEnd = inorderEnd;

        // 切割后序数组
        // 左后序区间,左闭右开[leftPostorderBegin, leftPostorderEnd)
        int leftPostorderBegin =  postorderBegin;
        int leftPostorderEnd = postorderBegin + delimiterIndex - inorderBegin; // 终止位置是 需要加上 中序区间的大小size
        // 右后序区间,左闭右开[rightPostorderBegin, rightPostorderEnd)
        int rightPostorderBegin = postorderBegin + (delimiterIndex - inorderBegin);
        int rightPostorderEnd = postorderEnd - 1; // 排除最后一个元素,已经作为节点了

        root->left = traversal(inorder, leftInorderBegin, leftInorderEnd,  postorder, leftPostorderBegin, leftPostorderEnd);
        root->right = traversal(inorder, rightInorderBegin, rightInorderEnd, postorder, rightPostorderBegin, rightPostorderEnd);

        return root;
    }
public:
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        if (inorder.size() == 0 || postorder.size() == 0) return NULL;
        // 左闭右开的原则
        return traversal(inorder, 0, inorder.size(), postorder, 0, postorder.size());
    }
};

那么这个版本写出来依然要打日志进行调试,打日志的版本如下:(该版本不要在leetcode上提交,容易超时)

class Solution {
private:
    TreeNode* traversal (vector<int>& inorder, int inorderBegin, int inorderEnd, vector<int>& postorder, int postorderBegin, int postorderEnd) {
        if (postorderBegin == postorderEnd) return NULL;

        int rootValue = postorder[postorderEnd - 1];
        TreeNode* root = new TreeNode(rootValue);

        if (postorderEnd - postorderBegin == 1) return root;

        int delimiterIndex;
        for (delimiterIndex = inorderBegin; delimiterIndex < inorderEnd; delimiterIndex++) {
            if (inorder[delimiterIndex] == rootValue) break;
        }
        // 切割中序数组
        // 左中序区间,左闭右开[leftInorderBegin, leftInorderEnd)
        int leftInorderBegin = inorderBegin;
        int leftInorderEnd = delimiterIndex;
        // 右中序区间,左闭右开[rightInorderBegin, rightInorderEnd)
        int rightInorderBegin = delimiterIndex + 1;
        int rightInorderEnd = inorderEnd;

        // 切割后序数组
        // 左后序区间,左闭右开[leftPostorderBegin, leftPostorderEnd)
        int leftPostorderBegin =  postorderBegin;
        int leftPostorderEnd = postorderBegin + delimiterIndex - inorderBegin; // 终止位置是 需要加上 中序区间的大小size
        // 右后序区间,左闭右开[rightPostorderBegin, rightPostorderEnd)
        int rightPostorderBegin = postorderBegin + (delimiterIndex - inorderBegin);
        int rightPostorderEnd = postorderEnd - 1; // 排除最后一个元素,已经作为节点了

        cout << "----------" << endl;
        cout << "leftInorder :";
        for (int i = leftInorderBegin; i < leftInorderEnd; i++) {
            cout << inorder[i] << " ";
        }
        cout << endl;

        cout << "rightInorder :";
        for (int i = rightInorderBegin; i < rightInorderEnd; i++) {
            cout << inorder[i] << " ";
        }
        cout << endl;

        cout << "leftpostorder :";
        for (int i = leftPostorderBegin; i < leftPostorderEnd; i++) {
            cout << postorder[i] << " ";
        }
        cout << endl;

        cout << "rightpostorder :";
        for (int i = rightPostorderBegin; i < rightPostorderEnd; i++) {
            cout << postorder[i] << " ";
        }
        cout << endl;

        root->left = traversal(inorder, leftInorderBegin, leftInorderEnd,  postorder, leftPostorderBegin, leftPostorderEnd);
        root->right = traversal(inorder, rightInorderBegin, rightInorderEnd, postorder, rightPostorderBegin, rightPostorderEnd);

        return root;
    }
public:
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        if (inorder.size() == 0 || postorder.size() == 0) return NULL;
        return traversal(inorder, 0, inorder.size(), postorder, 0, postorder.size());
    }
};

力扣的代码

感觉官方题解和卡哥的优化代码差不多的思路。官方题解相对来说更简洁。

作者:力扣官方题解
链接:https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal/solutions/426738/cong-zhong-xu-yu-hou-xu-bian-li-xu-lie-gou-zao-14/
来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


class Solution {
	int postIndex;
	unordered_map<int, int>indexMap;//存中序遍历的数组和下标的键值对。

public:
	TreeNode* helper(int inLeft, int inRight, vector<int>& inorder, vector<int>& postorder) {
		// 如果这里没有节点构造二叉树了,就结束
		if (inLeft > inRight) return nullptr;

		// 选择 postIndex 位置的元素作为当前子树根节点
		int root_val = postorder[postIndex];
		TreeNode* root = new TreeNode(root_val);

		// 根据 root 所在位置分成左右两棵子树
		int index = indexMap[root_val];//根节点的下标(中序遍历的)

		// 下标减一
		postIndex--;//指向分隔开的 右子树的根节点的下标(后序遍历的)

		// 构造右子树
		root->right = helper(index + 1, inRight, inorder, postorder);  //左闭右闭
		root->left = helper(inLeft, index - 1, inorder, postorder);    //左闭右闭
		return root;
	}

	TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
		// 从后序遍历的最后一个元素开始
		postIndex = postorder.size() - 1;

		// 建立(元素,下标)键值对的哈希表
		int idx{ 0 };
		for (const auto& val : inorder) {
			indexMap[val] = idx++;
		}

		return helper(0, postIndex, inorder, postorder);
	}
};

105.从前序与中序遍历序列构造二叉树

待续。。。。

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

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

相关文章

最全Go select底层原理,一文学透高频用法

导语 |在日常开发中&#xff0c;select语句被高频使用。但目前&#xff0c;全网分析select在编译期和运行时的完整底层原理资料&#xff0c;非常匮乏。本文基于Go1.18.1版本的源码&#xff0c;讲解select访问Channel在编译期和运行时的底层原理——select编译器优化用到的src/c…

2022年第十二届APMCM亚太杯1月增赛E题发布

2022年亚洲及太平洋地区建模数学竞赛问题E 有多少颗核弹可以摧毁地球? 1945年8月6日&#xff0c;第二次世界大战接近尾声。为了尽快结束战争&#xff0c;美国在日本广岛投下了名为"小男孩"的下一颗原子弹。这样一颗原子弹炸死了广岛的200000人&#xff0c;广岛的所有…

【数据结构Java版】对象的比较之Comparable与Comparator比较器

目录 一、基本类型的比较 二、对象类型的比较 &#xff08;1&#xff09;对象类型比较出现的问题 &#xff08;2&#xff09;重写基类equals方法 &#xff08;3&#xff09;基于Comparable接口的比较 1.实现Comparable接口&#xff0c;重写compareTo方法 &#xff08;4&a…

Flask框架中常规漏洞防范方法

一、前言 Double Fetch是一种条件竞争类型的漏洞&#xff0c;其主要形成的原因是由于用户态与内核态之间的数据在进行交互时存在时间差&#xff0c;我们在先前的学习中有了解到内核在从用户态中获取数据时会使用函数copy_from_user&#xff0c;而如果要拷贝的数据过于复杂的话…

leetcode | 链表

01 链表知识点 1.1 基础知识 线性表&#xff1a;零个或多个相同类型的数据元素的有限序列&#xff0c;一对一关系&#xff0c;所含数据元素个数n称为线性表的长度。 线性表有两种物理结构(存储结构)&#xff1a;顺序存储、链式存储。 线性表的顺序存储结构&#xff1a;用一段…

优化改进YOLOv5算法之添加SE、CBAM、CA模块(超详细)

目录 1 SENet 1.1 SENet原理 1.2 SENet代码(Pytorch) 1.3 YOLOv5中加入SE模块 1.3.1 common.py配置 1.3.2 yolo.py配置 1.3.3 创建添加RepVGG模块的YOLOv5的yaml配置文件 2 CBAM 2.1 CBAM原理 2.2 CBAM代码(Pytorch) 2.3 YOLOv5中加入CBAM模块 2.3.1 common.py配…

敏捷是一种态度:有了敏捷建模,就有了敏捷需求

目 录01 缘起02 敏捷需求5W1H的思考‍‍‍‍‍‍03 关于敏捷需求体系的一些思考‍‍‍‍‍‍04 写在敏捷需求后的话01缘起对研发效能提升的研究&#xff0c;是近年来各家企业技术部门一直在研究的课题。早期&#xff0c;针对敏捷开发的实践&#xff0c;让大多技术管理者尝到…

114.(leaflet之家)leaflet空间判断-点与圆的空间关系

听老人家说:多看美女会长寿 地图之家总目录(订阅之前建议先查看该博客) 文章末尾处提供保证可运行完整代码包,运行如有问题,可“私信”博主。 效果如下所示: 下面献上完整代码,代码重要位置会做相应解释 <!DOCTYPE html> <html>

每个开发人员都应该使用的可扩展和可维护的 React 项目结构

一个好的项目结构可以在理解代码库、灵活性和维护方面对项目的成功产生巨大影响。结构和维护不当的项目很快就会变成一团糟和可怕的遗产&#xff0c;没有人愿意与之共事。我现在将向您展示我在项目中经常使用的结构&#xff0c;并解释其背后的原因。这种结构应该是大规模应用程…

开源代码 | FMCW-MIMO雷达仿真MATLAB

本文编辑&#xff1a;调皮哥的小助理 本程序来源&#xff1a;https://github.com/ekurtgl/FMCW-MIMO-Radar-Simulation&#xff0c;作者是阿拉巴马大学博士生艾库特格尔&#xff0c;研究方向主要是雷达信号处理人类活动识别以及雷达数据的机器学习应用&#xff0c;这份比较新的…

STM32MP157驱动开发——4G通信模块驱动

STM32MP157驱动开发——4G通信模块驱动一、简介二、驱动开发1.高新兴 ME3630 驱动开发驱动修改添加 ECM 支持程序配置 Linux 内核ppp拨号功能测试ECM 联网测试ME3630 4G 模块 GNSS 定位测试2.移远EC20 4G驱动开发驱动修改配置 Linux 内核EC20 ppp 拨号上网移远 GobiNET 驱动移植…

go语言学习(一):Mac环境安装及初始化

​ ​为什么要学习go语言? 1、简洁&#xff0c;快速&#xff0c;安全&#xff1b; ​ ​2、并行&#xff0c;有趣&#xff0c;开源​&#xff1b; 3、内存管理&#xff0c;数据安全&#xff0c;编译迅速 首先&#xff0c;去官网&#xff1a;https://golang.google.cn/dl…

面向对象3(多态、多态调用成员函数的特点、多态的优势和弊端及改进、包、final、权限修饰符、代码块、抽象方法和抽象类、接口、内部类)

1、多态 2、多态调用成员函数的特点 示例如下&#xff1a; 理解&#xff1a; 因为是Animal类型的&#xff0c;所以在输出name时会在父类继承下来的变量里面找 &#xff0c;没有就报错。而一般是先找自己再找父类继承下来的。 3、多态的优势和弊端及改进 优势&#xff1a; 弊端…

Arduino——野火GPS模块

GPS模块 文章目录GPS模块前言一、Arduino代码前言 手上还有一个GPS&#xff0c;用arduino做模块很方便&#xff0c;打算和短信模块结合&#xff0c;短信模块上次已经使用完成了&#xff0c;这次学习一下GPS模块 看模块很容易知道&#xff0c;这个模块用的是串口通信&#xff…

基于STM32智能家居控制系统软件设计及实现

1.1 系统流程图 智能家居控制系统的软件设计主要使用Keil uVision5进行STM32主烧录程序的编写&#xff0c;主程序完成的功能主要为接收并判断语音识别模块传过来的信息&#xff0c;然后根据满足条件的不同进行对应的操作。例如&#xff0c;当语音模块传过来的信息为“打开电视…

top详解--查看cpu及内存使用情况

top详解--查看cpu及内存使用情况 参考链接:http://t.zoukankan.com/guoyu1-p-12237660.html 一、top命令 top命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器。 运行 top 命令后,CPU 使用状态会以全屏的方式显示,…

基于Java+SpringBoot+vue+element实现婚纱摄影网系统

基于JavaSpringBootvueelement实现婚纱摄影网系统 &#x1f345; 作者主页 超级帅帅吴 &#x1f345; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; &#x1f345; 文末获取源码联系方式 &#x1f4dd; 文章目录基于JavaSpringBootvueelement实现婚纱摄影网系统前言介绍&…

Flask中的后端并发思考(以Mysql:too many connections为例)

之前写过一篇《CentOS 下部署NginxGunicornSupervisor部署Flask项目》&#xff0c;最近对该工程的功能进行了完善&#xff0c;基本的功能单元测试也做了。觉得也是时候进行一下压力测试了&#xff0c;所以利用Jmeter对部署到服务器的项目进行了简单的压力测试。在之前的笔记中写…

10个 Python 高效编程小技巧

初识Python语言&#xff0c;觉得python满足了你上学时候对编程语言的所有要求。python语言的高效编程技巧让那些曾经苦逼学了四年c或者c的人&#xff0c;兴奋的不行不行的&#xff0c;终于解脱了。高级语言&#xff0c;如果做不到这样&#xff0c;还扯啥高级呢&#xff1f; 01…

【密码学】HMAC与HS256算法

哈希算法加盐 传统的哈希算法&#xff1a; digest hash(input)因为相同的输入会产生相同的输出&#xff0c;所以想要加盐&#xff0c;加盐的目的就在于&#xff0c;使输入有所变化&#xff1a; digest hash(salt input)这个salt可以看作是一个额外的“认证码”&#xff0…