线索化二叉树(先序,中序,后序)+线索化二叉树的遍历【java详解】

目录

线索化二叉树的基本介绍:

 举个栗子:

二叉树的中序线索化:

创建HeroNode类,表示节点信息:

编写中序线索化方法代码:

中序线索化遍历代码:

测试代码:

测试结果:

 二叉树的先序线索化:

编写先序线索化代码:

 先序线索化遍历代码:

测试结果:

二叉树前序和中序线索化完整代码:

二叉树的后续线索化:

定义一个PostTreadBinaryTree 来表示节点信息:

编写createBinaryTree方法来实现二叉树的创建:

编写后续线索化方法代码: 

后序线索化遍历:

代码详解:

 测试代码:

测试结果:

最后,后序线索化的详细代码,有需要可以自己查看:

小结:


写在之前:在学习线索化二叉树之前,要对二叉树的前中后序遍历有一定的了解,如果还不会的小伙伴可以看看我的这篇博客:二叉树-------前,中,后序遍历 + 前,中,后序查找+删除节点 (java详解)-CSDN博客

由于网上以及书上对线索化二叉树的前序和后序及其遍历介绍的较少,这里统一整理了一下,希望对各位有所帮助。欢迎点赞加关注支持博主~

------------------------------------------------------------------------------------------------------------------------------- 

线索化二叉树的基本介绍:

  • n个结点的二叉链表中含有n+1  【公式 2n-(n-1)=n+1】 个空指针域。利用二叉链表中的空指针域,存放指向该结点在某种遍历次序下的前驱和后继结点的指针(这种附加的指针称为"线索")
  • 这种加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树(Threaded BinaryTree)。根据线索性质的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树三种
  • 一个结点的前一个结点,称为前驱结点
  • 一个结点的后一个结点,称为后继结点

 举个栗子:

将一颗普通的二叉树如下图所示(它是一个含有6个节点的二叉树,按照上述公式计算其空指针域为7【n+1】):

中序线索化后,得到如图所示的二叉树(中序线索化后):

 这实际上是一个将二叉树的指针域充分利用的过程(即充分利用各个节点的左右指针),那么,上述的过程究竟是如何实现的呢?看到这里,你想必还是一脸懵圈,别急,然我们再举个栗子(以中序线索化为例):

我们先将上述数字转化为一个数组,当然这个数组不是随便写的,是通过中序遍历得到的,这个数组是:8,3,10,1,14,6  ,接着我们只需要结合原图,将左右指针为空的节点进行连线,具体如下:

说明:数字8左右指针都为空,理应联两条线,但根据其中序遍历的结果,它的左边为空,所以上述图中数字8只连了一根线,对应到3节点。同理数字10连两根线对应3,1;数字14连2根线,对应1,6;6连一根线,对应空(null),实际在图中表示为不连线---》连线的根数是根据节点的左右指针个数决定的,如数字8,节点左右指针为空,连两条线(对应左右指针的方向),而数字6右指针为空,所以连一条线

实际线索化后的二叉树(通常null省略罢了 ):

 通过上面的一些描述,想必你对二叉树的线索化有了一定理解,那么我们接着开始进入正题:

二叉树的中序线索化:

说明:当线索化二叉树后,Node节点的属性left和right有如下情况:

1).left指向的是左子树,也可能是指向前驱节点,比如①节点left指向左子树,二⑩节点的left指向的就是前驱节点

2).right指向的是右子树,也可能是指向后继节点,比如①节点right指向左=右子树,二⑩节点的right指向的就是后继节点

同时,为了方便表示线索后的二叉树,我们这样定义:
1. 如果leftType == 0 表示指向的是左子树, 如果 1 则表示指向前驱结点
2. 如果rightType == 0 表示指向是右子树, 如果 1表示指向后继结点

创建HeroNode类,表示节点信息:

//先创建HeroNode 结点class HeroNode {private int no;private String name;private HeroNode left; //默认nullprivate HeroNode right; //默认null//说明//1. 如果leftType == 0 表示指向的是左子树, 如果 1 则表示指向前驱结点//2. 如果rightType == 0 表示指向是右子树, 如果 1表示指向后继结点private int leftType;private int rightType;public int getLeftType() {return leftType;}public void setLeftType(int leftType) {this.leftType = leftType;}public int getRightType() {return rightType;}public void setRightType(int rightType) {this.rightType = rightType;}public HeroNode(int no, String name) {this.no = no;this.name = name;}public int getNo() {return no;}public void setNo(int no) {this.no = no;}public String getName() {return name;}public void setName(String name) {this.name = name;}public HeroNode getLeft() {return left;}public void setLeft(HeroNode left) {this.left = left;}public HeroNode getRight() {return right;}public void setRight(HeroNode right) {this.right = right;}@Overridepublic String toString() {return "HeroNode [no=" + no + ", name=" + name + "]";}}*/

通过上面的中序线索化构造的栗子,我们可以按照先

(一)先线索化左子树---》(二)线索化当前结点---》(三)在线索化右子树

编写中序线索化方法代码:

public void threadedNodes(HeroNode node) {//如果node==null, 不能线索化if (node == null) {return;}//(一)先线索化左子树threadedNodes(node.getLeft());//(二)线索化当前结点//处理当前结点的前驱结点//以8结点来理解//8结点的.left = null , 8结点的.leftType = 1if (node.getLeft() == null) {//让当前结点的左指针指向前驱结点node.setLeft(pre);//修改当前结点的左指针的类型,指向前驱结点node.setLeftType(1);}//处理后继结点if (pre != null && pre.getRight() == null) {//让前驱结点的右指针指向当前结点pre.setRight(node);//修改前驱结点的右指针类型pre.setRightType(1);}//!!! 每处理一个结点后,让当前结点是下一个结点的前驱结点pre = node;//(三)在线索化右子树threadedNodes(node.getRight());}

中序线索化遍历代码:

/遍历线索化二叉树的方法public void threadedList() {//定义一个变量,存储当前遍历的结点,从root开始HeroNode node = root;while (node != null) {//循环的找到leftType == 1的结点,第一个找到就是8结点//后面随着遍历而变化,因为当leftType==1时,说明该结点是按照线索化//处理后的有效结点while (node.getLeftType() == 0) {node = node.getLeft();}//打印当前这个结点System.out.println(node);//如果当前结点的右指针指向的是后继结点,就一直输出while (node.getRightType() == 1) {//获取到当前结点的后继结点node = node.getRight();System.out.println(node);}//替换这个遍历的结点node = node.getRight();}}

测试代码:

public class ThreadedBinaryTreeDemo {public static void main(String[] args) {//测试一把中序线索二叉树的功能HeroNode root = new HeroNode(1, "tom");HeroNode node2 = new HeroNode(3, "jack");HeroNode node3 = new HeroNode(6, "smith");HeroNode node4 = new HeroNode(8, "mary");HeroNode node5 = new HeroNode(10, "king");HeroNode node6 = new HeroNode(14, "dim");HeroNode node7 = new HeroNode(1, "xiao");//二叉树,后面我们要递归创建, 现在简单处理使用手动创建root.setLeft(node2);root.setRight(node3);node2.setLeft(node4);node2.setRight(node5);node3.setLeft(node6);//测试中序线索化ThreadedBinaryTree threadedBinaryTree = new ThreadedBinaryTree();threadedBinaryTree.setRoot(root);threadedBinaryTree.threadedNodes();//测试: 以10号节点测试HeroNode leftNode = node5.getLeft();HeroNode rightNode = node5.getRight();System.out.println("10号结点的前驱结点是 ="  + leftNode); //3System.out.println("10号结点的后继结点是="  + rightNode); //1//当线索化二叉树后,能在使用原来的遍历方法//threadedBinaryTree.infixOrder();System.out.println("使用线索化的方式遍历 线索化二叉树");threadedBinaryTree.threadedList(); // 8, 3, 10, 1, 14, 6}
}

 

接着我们测试一下就以节点⑩为例(如上图),线索化后其前驱节点为③,其后继节点为①

同时,遍历的结果与中序遍历保持一致,即 8,3,10,1,14,6

测试结果:

 二叉树的先序线索化:

按照中序线索化的方法,我给出线索化的图解:

 

 

HeroNode类与上面基本一致,不做过多说明

按照  线索化当前节点---》线索化左子树---》线索化右子树  过程进行

编写先序线索化代码:

  //前序线索化public void pretreadedNodes(HeroNode node){if(node == null){return ;}if(node.getLeft() == null){node.setLeft(pre);node.setLeftType(1);}if(pre != null && pre.getRight() == null){pre.setRight(node);pre.setRightType(1);}pre = node;if(node.getLeftType() == 0) {pretreadedNodes(node.getLeft());}if(node.getRightType() == 0) {pretreadedNodes(node.getRight());}}

 先序线索化遍历代码:

//遍历前序线索化二叉树public void pretreadedList(){HeroNode node = root;while(node != null){while(node.getLeftType() == 0){System.out.println(node);node = node.getLeft();}System.out.println(node);while(node.getRightType() == 1){node = node.getRight();System.out.println(node);}node = node.getRight();}}

 

接着我们测试一下就以节点⑩为例(如上图),线索化后其前驱节点为⑧,其后继节点为⑥

同时,先序线索化遍历的结果与先序遍历保持一致

测试结果:

二叉树前序和中序线索化完整代码:

//测试
public class ThreadedBinaryTreeDemo {public static void main(String[] args){HeroNode root = new HeroNode(1, "tom");HeroNode node2 = new HeroNode(3, "jack");HeroNode node3 = new HeroNode(6, "smith");HeroNode node4 = new HeroNode(8, "mary");HeroNode node5 = new HeroNode(10, "king");HeroNode node6 = new HeroNode(14, "dim");root.setLeft(node2);root.setRight(node3);node2.setLeft(node4);node2.setRight(node5);node3.setLeft(node6);TreadedBinaryTree treadedBinaryTree = new TreadedBinaryTree(root);//treadedBinaryTree.intreadedNodes();treadedBinaryTree.pretreadedNodes();HeroNode leftNode = node5.getLeft();HeroNode rightNode = node5.getRight();System.out.println("10号节点的前驱节点是:"+leftNode);System.out.println("10号节点的后继节点是:"+rightNode);System.out.println("使用线索化二叉树的遍历:");treadedBinaryTree.pretreadedList();//treadedBinaryTree.intreadedList();}
}class TreadedBinaryTree{private HeroNode root;private HeroNode pre = null;public TreadedBinaryTree(HeroNode root) {this.root = root;}public void posttreadedNodes(){this.posttreadedNodes(root);}public void intreadedNodes(){this.intreadedNodes(root);}public void pretreadedNodes(){this.pretreadedNodes(root);}//遍历中序线索化二叉树public void intreadedList(){HeroNode node =root;while(node != null){while(node.getLeftType() == 0){node = node.getLeft();}System.out.println(node);while(node.getRightType() == 1){node = node.getRight();System.out.println(node);}node = node.getRight();}}//遍历前序线索化二叉树public void pretreadedList(){HeroNode node = root;while(node != null){while(node.getLeftType() == 0){System.out.println(node);node = node.getLeft();}System.out.println(node);while(node.getRightType() == 1){node = node.getRight();System.out.println(node);}node = node.getRight();}}//中序线索化public void intreadedNodes(HeroNode node){if(node == null){return ;}intreadedNodes(node.getLeft());if(node.getLeft() == null){node.setLeft(pre);node.setLeftType(1);}if(pre != null && pre.getRight() == null){pre.setRight(node);pre.setRightType(1);}pre = node;intreadedNodes(node.getRight());}//前序线索化public void pretreadedNodes(HeroNode node){if(node == null){return ;}if(node.getLeft() == null){node.setLeft(pre);node.setLeftType(1);}if(pre != null && pre.getRight() == null){pre.setRight(node);pre.setRightType(1);}pre = node;if(node.getLeftType() == 0) {pretreadedNodes(node.getLeft());}if(node.getRightType() == 0) {pretreadedNodes(node.getRight());}}//后续线索化二叉树public void posttreadedNodes(HeroNode node){if(node == null){return ;}posttreadedNodes(node.getLeft());posttreadedNodes(node.getRight());if(node.getLeft() == null){node.setLeft(pre);node.setLeftType(1);}if(pre != null && pre.getRight() == null){pre.setRight(node);pre.setRightType(1);}pre = node;}}class HeroNode{private int no;private String name;private HeroNode left;//默认nullprivate HeroNode right;//默认null//说明//1. 如果leftType == 0 表示指向的是左子树, 如果 1 则表示指向前驱结点//2. 如果rightType == 0 表示指向是右子树, 如果 1表示指向后继结点private int leftType;private int rightType;public HeroNode() {}public HeroNode(int no, String name) {this.no = no;this.name = name;}/*** 获取* @return no*/public int getNo() {return no;}/*** 设置* @param no*/public void setNo(int no) {this.no = no;}/*** 获取* @return name*/public String getName() {return name;}/*** 设置* @param name*/public void setName(String name) {this.name = name;}/*** 获取* @return left*/public HeroNode getLeft() {return left;}/*** 设置* @param left*/public void setLeft(HeroNode left) {this.left = left;}/*** 获取* @return right*/public HeroNode getRight() {return right;}/*** 设置* @param right*/public void setRight(HeroNode right) {this.right = right;}/*** 获取* @return leftType*/public int getLeftType() {return leftType;}/*** 设置* @param leftType*/public void setLeftType(int leftType) {this.leftType = leftType;}/*** 获取* @return rightType*/public int getRightType() {return rightType;}/*** 设置* @param rightType*/public void setRightType(int rightType) {this.rightType = rightType;}public String toString() {return "HeroNode{no = " + no + ", name = " + name + "}";}
}

二叉树的后续线索化:

(后序相对前序与中序复杂,单独拎出来讲---》主要是遍历过程复杂)

按照上述中序线索化的过程,我给出后续线索化的图解:

 

定义一个PostTreadBinaryTree 来表示节点信息:

public class PostTreadBinaryTree {private Node preNode;   //线索化时记录前一个节点//节点存储结构static class Node {String data;        //数据域Node left;          //左指针域Node right;         //右指针域Node parent;        //父节点的指针(为了后序线索化使用)boolean isLeftThread = false;   //左指针域类型  false:指向子节点、true:前驱或后继线索boolean isRightThread = false;  //右指针域类型  false:指向子节点、true:前驱或后继线索Node(String data) {this.data = data;}}

编写createBinaryTree方法来实现二叉树的创建:

 static Node createBinaryTree(String[] array, int index) {Node node = null;if(index < array.length) {node = new Node(array[index]);node.left = createBinaryTree(array, index * 2 + 1);node.right = createBinaryTree(array, index * 2 + 2);//记录节点的父节点(后序线索化遍历时使用)if(node.left != null) {node.left.parent = node;}if(node.right != null) {node.right.parent = node;}}return node;}

编写后续线索化方法代码: 

void postThreadOrder(Node node) {if(node == null) {return;}//处理左子树postThreadOrder(node.left);//处理右子树postThreadOrder(node.right);//左指针为空,将左指针指向前驱节点if(node.left == null) {node.left = preNode;node.isLeftThread = true;}//前一个节点的后继节点指向当前节点if(preNode != null && preNode.right == null) {preNode.right = node;preNode.isRightThread = true;}preNode = node;}

后序线索化遍历:

后序遍历线索化二叉树是一种对二叉树进行线索化的方法,使得在遍历二叉树时可以更高效地找到前驱和后继节点。下面是后序遍历线索化二叉树的思路:

  1. 首先,定义一个辅助指针pre,用于记录当前节点的前驱节点。
  2. 对于二叉树的每个节点,按照后序遍历的顺序进行处理。
  3. 如果当前节点的左子树不为空,就将当前节点的左子树线索化。具体步骤如下:
    • 找到当前节点的左子树的最右节点,即左子树中最后一个被访问的节点。
    • 将该最右节点的右指针指向当前节点,并将其线索化标记设置为1,表示指向后继节点。
  4. 如果当前节点的右子树不为空,就将当前节点的右子树线索化。具体步骤如下:
    • 找到当前节点的右子树的最左节点,即右子树中第一个被访问的节点。
    • 将该最左节点的左指针指向当前节点,并将其线索化标记设置为1,表示指向前驱节点。
  5. 更新pre指针为当前节点,以便在下一次迭代时使用。
  6. 重复步骤2-5,直到遍历完整个二叉树。

这样,通过后序遍历线索化二叉树,我们可以在不使用递归或栈的情况下,高效地找到任意节点的前驱和后继节点。

代码详解:

 void postThreadList(Node root) {//1、找后序遍历方式开始的节点Node node = root;while(node != null && !node.isLeftThread) {node = node.left;}Node preNode = null;while(node != null) {//右节点是线索if(node.isRightThread) {System.out.print(node.data + ", ");preNode = node;node = node.right;} else {//如果上个处理的节点是当前节点的右节点if(node.right == preNode) {System.out.print(node.data + ", ");if(node == root) {return;}preNode = node;node = node.parent;} else {    //如果从左节点的进入则找到有子树的最左节点node = node.right;while(node != null && !node.isLeftThread) {node = node.left;}}}}}

 测试代码:

 public static void main(String[] args) {String[] array = {"1","3","6","8","10","14"};Node root = createBinaryTree(array, 0);PostTreadBinaryTree tree = new PostTreadBinaryTree();tree.postThreadOrder(root);System.out.println("后序按后继节点遍历线索二叉树结果:");tree.postThreadList(root);}

该测试结果(遍历测试结果)与后序遍历的结果 一致

测试结果:

最后,后序线索化的详细代码,有需要可以自己查看:

//后序线索化
public class PostTreadBinaryTree {private Node preNode;   //线索化时记录前一个节点//节点存储结构static class Node {String data;        //数据域Node left;          //左指针域Node right;         //右指针域Node parent;        //父节点的指针(为了后序线索化使用)boolean isLeftThread = false;   //左指针域类型  false:指向子节点、true:前驱或后继线索boolean isRightThread = false;  //右指针域类型  false:指向子节点、true:前驱或后继线索Node(String data) {this.data = data;}}/*** 通过数组构造一个二叉树(完全二叉树)* @param array* @param index* @return*/static Node createBinaryTree(String[] array, int index) {Node node = null;if(index < array.length) {node = new Node(array[index]);node.left = createBinaryTree(array, index * 2 + 1);node.right = createBinaryTree(array, index * 2 + 2);//记录节点的父节点(后序线索化遍历时使用)if(node.left != null) {node.left.parent = node;}if(node.right != null) {node.right.parent = node;}}return node;}/*** 后序线索化二叉树* @param node  节点*/void postThreadOrder(Node node) {if(node == null) {return;}//处理左子树postThreadOrder(node.left);//处理右子树postThreadOrder(node.right);//左指针为空,将左指针指向前驱节点if(node.left == null) {node.left = preNode;node.isLeftThread = true;}//前一个节点的后继节点指向当前节点if(preNode != null && preNode.right == null) {preNode.right = node;preNode.isRightThread = true;}preNode = node;}/*** 后续遍历线索二叉树,按照后继方式遍历(思路:后序遍历开始节点是最左节点)* @param*/void postThreadList(Node root) {//1、找后序遍历方式开始的节点Node node = root;while(node != null && !node.isLeftThread) {node = node.left;}Node preNode = null;while(node != null) {//右节点是线索if(node.isRightThread) {System.out.print(node.data + ", ");preNode = node;node = node.right;} else {//如果上个处理的节点是当前节点的右节点if(node.right == preNode) {System.out.print(node.data + ", ");if(node == root) {return;}preNode = node;node = node.parent;} else {    //如果从左节点的进入则找到有子树的最左节点node = node.right;while(node != null && !node.isLeftThread) {node = node.left;}}}}}public static void main(String[] args) {String[] array = {"1","3","6","8","10","14"};Node root = createBinaryTree(array, 0);PostTreadBinaryTree tree = new PostTreadBinaryTree();tree.postThreadOrder(root);System.out.println("后序按后继节点遍历线索二叉树结果:");tree.postThreadList(root);}
}

小结:

1. 前序线索化二叉树遍历相对最容易理解,实现起来也比较简单。由于前序遍历的顺序是:根左右,所以从根节点开始,沿着左子树进行处理,当子节点的left指针类型是线索时,说明到了最左子节点,然后处理子节点的right指针指向的节点,可能是右子树,也可能是后继节点,无论是哪种类型继续按照上面的方式(先沿着左子树处理,找到子树的最左子节点,然后处理right指针指向),以此类推,直到节点的right指针为空,说明是最后一个,遍历完成。 
2. 中序线索化二叉树的网上相关介绍最多。中序遍历的顺序是:左根右,因此第一个节点一定是最左子节点,先找到最左子节点,依次沿着right指针指向进行处理(无论是指向子节点还是指向后继节点),直到节点的right指针为空,说明是最后一个,遍历完成。 
3. 后序遍历线索化二叉树最为复杂,通用的二叉树数节点存储结构不能够满足后序线索化,因此我们扩展了节点的数据结构,增加了父节点的指针。后序的遍历顺序是:左右根,先找到最左子节点,沿着right后继指针处理,当right不是后继指针时,并且上一个处理节点是当前节点的右节点,则处理当前节点的右子树,遍历终止条件是:当前节点是root节点,并且上一个处理的节点是root的right节点。

小结转载自:后序线索化二叉树(Java版)_线索二叉树的后序遍历-CSDN博客
 

博客到这里也是结束了,制作不易,喜欢的小伙伴可以点赞加关注支持下博主,这对我真的很重要~~

 

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

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

相关文章

OpenHarmony系统解决方案 - 配置屏幕方向导致开机动画和Launcher显示异常

问题环境 系统版本&#xff1a;OpenHarmony-3.2-Release 问题现象 配置设备默认方向&#xff0c;例如修改为横屏显示&#xff0c;修改文件display_manager_config.xml的buildInDefaultOrientation参数值为2(Orientation::HORIZONTAL)。 源码中文件位于foundation/window/win…

在 Geoserver 中添加自定义的室内坐标系

要在 Geoserver 中添加自定义的室内坐标系&#xff0c;您需要在数据目录中的 user_projections 文件夹下创建或编辑一个 epsg.properties 文件&#xff0c;然后在文件末尾添加您的坐标系的定义&#xff0c;使用 WKT&#xff08;Well-Known Text&#xff09;格式。您还需要为您的…

WordPress站点成功升级后的介绍页地址是什么?

我们一般在WordPress站点后台 >> 仪表盘 >> 更新中成功升级WordPress的话&#xff0c;最后打开的就是升级之后的版本介绍页。比如boke112百科前两天升级到WordPress 6.4.2后显示的介绍页如下图所示&#xff1a; 该介绍除了介绍当前版本修复了多少个问题及修补了多少…

ABC341 A-G

Toyota Programming Contest 2024#2&#xff08;AtCoder Beginner Contest 341&#xff09; - AtCoder B读不懂题卡了&#xff0c;F读假题卡了&#xff0c;开题开慢了rank了 A - Print 341 题意&#xff1a; 打印一串交替出现的包含N个0&#xff0c;N1个1的01串 代码&…

2024免费人像摄影后期处理工具Portraiture4.1

Portraiture作为一款智能磨皮插件&#xff0c;确实为Photoshop和Lightroom用户带来了极大的便利。通过其先进的人工智能算法&#xff0c;它能够自动识别并处理照片中的人物皮肤、头发和眉毛等部位&#xff0c;实现一键式的磨皮美化效果&#xff0c;极大地简化了后期处理的过程。…

Switch开关(antd-design组件库)简单使用

1.Switch开关 开关选择器。 2.何时使用 需要表示开关状态/两种状态之间的切换时&#xff1b; 和 checkbox 的区别是&#xff0c;切换 switch 会直接触发状态改变&#xff0c;而 checkbox 一般用于状态标记&#xff0c;需要和提交操作配合。 组件代码来自&#xff1a; 开关 Swit…

【leetcode题解C++】51.N皇后 and 76.最小覆盖子串

51. N皇后 按照国际象棋的规则&#xff0c;皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。 n 皇后问题 研究的是如何将 n 个皇后放置在 nn 的棋盘上&#xff0c;并且使皇后彼此之间不能相互攻击。 给你一个整数 n &#xff0c;返回所有不同的 n 皇后问题 的解决方…

用Python和OpenCV搭建自己的一维码和QRCode扫描仪(步骤 + 源码)

导 读 本文主要介绍使用Python和OpenCV搭建自己的一维码和QRCode扫描仪&#xff08;步骤 源码&#xff09;。 项目简介 本文我们将创建一个程序来扫描图像中的二维码和条形码。对于这个程序&#xff0c;我们需要三个包&#xff0c;分别是OpenCV、NumPy和pyzbar。大多数 Pyth…

linux kernel 内存踩踏之KASAN_HW_TAGS(MTE)(三)

一、背景 linux kernel 内存踩踏之KASAN&#xff08;一&#xff09;_kasan版本跟hasan版本区别-CSDN博客 linux kernel 内存踩踏之KASAN_SW_TAGS&#xff08;二&#xff09;-CSDN博客 最后来介绍一下KASAN_HW_TAGS&#xff0c;ARM64上就是MTE&#xff0c;这个特性在ARMv8.5支…

C++数据结构与算法——栈与队列

C第二阶段——数据结构和算法&#xff0c;之前学过一点点数据结构&#xff0c;当时是基于Python来学习的&#xff0c;现在基于C查漏补缺&#xff0c;尤其是树的部分。这一部分计划一个月&#xff0c;主要利用代码随想录来学习&#xff0c;刷题使用力扣网站&#xff0c;不定时更…

电商+支付双系统项目------支付系统的构思

本篇文章会讲讲支付的一些相关的名词概念以及怎么去设计支付系统&#xff0c;通过理解支付的这些名词概念和支付系统的架构&#xff0c;为接下来写支付系统的代码做好准备。 目录 支付------支付资质 支付------支付场景 微信 付款码支付 Native支付 支付宝 条码付 扫码…

问卷设计初探:题目类型概览与注意事项梳理

问卷法常被人们应用于社会调查中&#xff0c;它能反馈出最真实的社会信息。所以&#xff0c;很多企业为了最大程度地了解市场&#xff0c;也经常使用问卷调查法进行研究。不过&#xff0c;想要发挥出问卷法的最大用处&#xff0c;前提是要将问卷设计规范并且可量化。 想要设计…

在JavaScript中的防抖函数 - 通过在React中构建自动完成功能来解释

当你将一个新应用推向生产环境时&#xff0c;你希望确保它用户友好。网站的性能是用户体验的关键部分。每个用户都希望网站及其内容能够快速加载。每一秒都是宝贵的&#xff0c;可能导致用户再也不会访问你的网站。 在本指南中&#xff0c;我们将了解JavaScript中一个非常重要…

2024.2.15 模拟实现 RabbitMQ —— 消息持久化

目录 引言 约定存储方式 消息序列化 重点理解 针对 MessageFileManager 单元测试 小结 统一硬盘操作​​​​​​​ 引言 问题&#xff1a; 关于 Message&#xff08;消息&#xff09;为啥在硬盘上存储&#xff1f; 回答&#xff1a; 消息操作并不涉及到复杂的增删查改消…

人工智能学习与实训笔记(十四):Langchain之Agent

人工智能专栏文章汇总&#xff1a;人工智能学习专栏文章汇总-CSDN博客 本篇目录 0、概要 1、Agent整体架构 2、langchain中agent实现 3、Agent业务实现逻辑 0、概要 Agent是干什么的&#xff1f; Agent的核心思想是使用语言模型&#xff08;LLM&#xff09;作为推理的大脑…

redis为什么使用跳跃表而不是树

Redis中支持五种数据类型中有序集合Sorted Set的底层数据结构使用的跳跃表&#xff0c;为何不使用其他的如平衡二叉树、b树等数据结构呢&#xff1f; 1&#xff0c;redis的设计目标、性能需求&#xff1a; redis是高性能的非关系型&#xff08;NoSQL&#xff09;内存键值数据…

【51单片机实验笔记】开关篇(二) 矩阵按键

目录 前言原理图分析矩阵按键扫描算法 软件实现1. 矩阵键盘检测2. 简易计算器实现 总结 前言 本节内容&#xff0c;我们学习一下矩阵按键&#xff0c;它是独立按键的阵列形式&#xff0c;常见的应用即键盘。 本节涉及到的封装源文件可在《模块功能封装汇总》中找到。 本节完…

websocket数据帧格式

客户端、服务端数据的交换&#xff0c;离不开数据帧格式的定义。因此&#xff0c;在实际讲解数据交换之前&#xff0c;我们先来看下WebSocket的数据帧格式。 WebSocket客户端、服务端通信的最小单位是帧&#xff08;frame&#xff09;&#xff0c;由1个或多个帧组成一条完整的消…

基于协同过滤的时尚穿搭推荐系统

项目&#xff1a;基于协同过滤的时尚穿搭推荐系统 摘 要 基于协同过滤的时尚穿搭推荐系统是一种能自动从网络上收集信息的工具&#xff0c;可根据用户的需求定向采集特定数据信息的工具&#xff0c;本项目通过研究服饰流行的分析和预测的分析和预测信息可视化时尚穿搭推荐系统…

C++中的volatile:穿越编译器的屏障

C中的volatile&#xff1a;穿越编译器的屏障 在C编程中&#xff0c;我们经常会遇到需要与硬件交互或多线程环境下访问共享数据的情况。为了确保程序的正确性和可预测性&#xff0c;C提供了关键字volatile来修饰变量。本文将深入解析C中的volatile关键字&#xff0c;介绍其作用、…