数据结构和算法06 之2-3-4树

简介:

   从第4节的分析中可以看出,二叉搜索树是个很好的数据结构,可以快速地找到一个给定关键字的数据项,并且可以快速地插入和删除数据项。但是二叉搜索树有个很麻烦的问题,如果树中插入的是随机数据,则执行效果很好,但如果插入的是有序或者逆序的数据,那么二叉搜索树的执行速度就变得很慢。因为当插入数值有序时,二叉树就是非平衡的了,它的快速查找、插入和删除指定数据项的能力就丧失了。

    2-3-4树是一个多叉树,它的每个节点最多有四个子节点和三个数据项。2-3-4树和红-黑树一样,也是平衡树,它的效率比红-黑树稍差,但是编程容易。2-3-4树名字中的2、3、4的含义是指一个节点可能含有的子节点的个数。对非叶节点有三种可能的情况:

    ·有一个数据项的节点总是有两个子节点;

    ·有两个数据项的节点总是有三个子节点;

    ·有三个数据项的节点总是有四个字节点。

    简而言之,非叶节点的子节点总是比它含有的数据项多1。如下图所示:


    为了方便起见,用0到2给数据项编号,用0到3给子节点链编号。树的结构中很重要的一点就是它的链与自己数据项的关键字值之间的关系。二叉树所有关键字值比某个节点值小的都在这个节点的左子节点为根的子树上,所有关键字值比某个及诶的那值大的节点都在这个节点右子节点为根的子树上。2-3-4树中规则是一样的,还加上了以下几点:

    ·根是child0的子树的所有子节点的关键字值小于key0;

    ·根是child1的子树的所有子节点的关键字值大于key0并且小于key1;

    ·根是child2的子树的所有子节点的关键字值大于key1并且小于key2;

    ·根是child3的子树的所有子节点的关键字值大于key2。

    这种关系如下图所示,2-3-4树中一般不允许出现重复关键字值,所以不用考虑比较相同的关键字值的情况。


    2-3-4树中插入节点有时比较简单,有时比较复杂。当没有碰到满节点时插入很简单,找到合适的叶节点后,只要把新数据项插入进去即可,插入可能会涉及到在一个节点中移动一个或两个其他的数据项,这样在新的数据项插入后关键字值仍保持正确的顺序。如下图:


    如果往下寻找要插入的位置的路途中,节点已经满了,插入就变得复杂了。这种情况下,节点必须分裂。正是这种分裂过程保证了树的平衡。设要分裂节点中的数据项为A、B、C,下面是分裂时的情况(假设分裂的节点不是根节点):

    ·创建一个新的空节点,它是要分裂节点的兄弟,在要分裂节点的右边;

    ·数据项C移到新节点中;

    ·数据项B移动到要分裂节点的父节点中;

    ·数据项A保留在原来的位置;

    ·最右边的两个子节点从要分裂节点处断开,连接到新节点上。

    下图显示了一个节点分裂的过程。另一种描述节点分裂的方法是说4-节点变成了两个2-节点。


    如果一开始查找插入点时就碰到满根时,插入过程就更复杂一点:

    ·创建新的根。它是要分裂节点的父节点;

    ·创建第二个新的节点。它是要分裂节点的兄弟节点;

    ·数据项C移动到新的兄弟节点中;

    ·数据项B移动到新的根节点中;

    ·数据项A保留在原来的位置上;

    ·要分裂节点最右边的两个子节点断开连接,连接到新的兄弟节点中。

    下图是根分裂的过程。过程中创建新的根,比旧的高一层,因此整个树的高度就增加了一层。


    下面是2-3-4树的代码:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public class Tree234 {  
  2.     private Node2 root = new Node2();  
  3.     public int find(long key) {  
  4.         Node2 currentNode = root;  
  5.         int childNumber;  
  6.         while(true) {  
  7.             if((childNumber = currentNode.findItem(key)) != -1) {  
  8.                 return childNumber;  
  9.             }  
  10.             else if(currentNode.isLeaf()) {  
  11.                 return -1;  
  12.             }  
  13.             else {  
  14.                 currentNode = getNextChild(currentNode, key);  
  15.             }  
  16.         }  
  17.     }  
  18.     //insert a DataItem  
  19.     public void insert(long data) {  
  20.         Node2 currentNode = root;  
  21.         DataItem tempItem = new DataItem(data);  
  22.         while(true) {  
  23.             if(currentNode.isFull()) {  
  24.                 split(currentNode); //if node is full, split it  
  25.                 currentNode = currentNode.getParent();  //back up  
  26.                 currentNode = getNextChild(currentNode, data);  //search once  
  27.             }  
  28.             else if(currentNode.isLeaf()) { //if node if leaf  
  29.                 break;  //go insert  
  30.             }  
  31.             else {  
  32.                 currentNode = getNextChild(currentNode, data);  
  33.             }  
  34.         }  
  35.         currentNode.insertItem(tempItem);  
  36.     }  
  37.     //display tree  
  38.     public void displayTree() {  
  39.         recDisplayTree(root, 0, 0);  
  40.     }  
  41.     public Node2 getNextChild(Node2 currentNode, long key) {  
  42.         int j;  
  43.         //assumes node is not empty, not full and not leaf  
  44.         int numItems = currentNode.getNumItems();  
  45.         for(j = 0; j < numItems; j++) {  
  46.             if(key < currentNode.getItem(j).dData) {  
  47.                 return currentNode.getChild(j);  
  48.             }  
  49.         }  
  50.         return currentNode.getChild(j);  
  51.     }  
  52.     public void split(Node2 currentNode) {  
  53.         //assumes node is full  
  54.         DataItem itemB, itemC;  //存储要分裂节点的后两个DataItem  
  55.         Node2 parent, child2, child3;   //存储要分裂节点的父节点和后两个child  
  56.         int itemIndex;  
  57.         itemC = currentNode.removeItem();  
  58.         itemB = currentNode.removeItem();   //remove items from this node  
  59.         child2 = currentNode.disconnectChild(2);  
  60.         child3 = currentNode.disconnectChild(3); //remove children from this node  
  61.         Node2 newRight = new Node2(); //make a new node  
  62.         if(currentNode == root) {  
  63.             root = new Node2(); //make a new root  
  64.             parent = root;  //root is our parent  
  65.             root.connectChild(0, currentNode);//connect currentNode to parent  
  66.         }  
  67.         else {  
  68.             parent = currentNode.getParent();  
  69.         }  
  70.         //deal with parent  
  71.         itemIndex = parent.insertItem(itemB);   //insert B to parent  
  72.         int n = parent.getNumItems();   //total items  
  73.         for(int j = n-1; j > itemIndex; j--) {  
  74.             Node2 temp = parent.disconnectChild(j);  
  75.             parent.connectChild(j+1, temp);  
  76.         }  
  77.         parent.connectChild(itemIndex+1, newRight);  
  78.         //deal with newRight  
  79.         newRight.insertItem(itemC);  
  80.         newRight.connectChild(0, child2);  
  81.         newRight.connectChild(1, child3);  
  82.     }  
  83.     public void recDisplayTree(Node2 thisNode, int level, int childNumber) {  
  84.         System.out.print("level = " + level + " child = " + childNumber + " ");  
  85.         thisNode.displayNode();  
  86.         //call ourselves for each child of this node  
  87.         int numItems = thisNode.getNumItems();  
  88.         for(int j = 0; j < numItems+1; j++) {  
  89.             Node2 nextNode = thisNode.getChild(j);  
  90.             if(nextNode != null) {  
  91.                 recDisplayTree(nextNode, level+1, j);  
  92.             }  
  93.             else   
  94.                 continue;  
  95.         }  
  96.     }  
  97. }  
  98.   
  99. //数据项  
  100. class DataItem {  
  101.     public long dData;  
  102.     public DataItem(long data) {  
  103.         dData = data;  
  104.     }  
  105.     public void displayItem() {  
  106.         System.out.print("/" + dData);  
  107.     }  
  108. }  
  109. //节点  
  110. class Node2 {  
  111.     private static final int ORDER = 4;  
  112.     private int numItems; //表示该节点存有多少个数据项  
  113.     private Node2 parent;  
  114.     private Node2 childArray[] = new Node2[ORDER]; //存储子节点的数组,最多四个子节点  
  115.     private DataItem itemArray[] = new DataItem[ORDER-1];//该节点中存放数据项的数组,每个节点最多存放三个数据项  
  116.     //连接子节点  
  117.     public void connectChild(int childNum, Node2 child) {  
  118.         childArray[childNum] = child;  
  119.         if(child != null) {  
  120.             child.parent = this;  
  121.         }  
  122.     }  
  123.     //断开与子节点的连接,并返回该子节点  
  124.     public Node2 disconnectChild(int childNum) {  
  125.         Node2 tempNode = childArray[childNum];  
  126.         childArray[childNum] = null;  
  127.         return tempNode;  
  128.     }  
  129.     public Node2 getChild(int childNum) {  
  130.         return childArray[childNum];  
  131.     }  
  132.     public Node2 getParent() {  
  133.         return parent;  
  134.     }  
  135.   
  136.     public boolean isLeaf() {  
  137.         return (childArray[0] == null);  
  138.     }  
  139.     public int getNumItems() {  
  140.         return numItems;  
  141.     }  
  142.     public DataItem getItem(int index) {  
  143.         return itemArray[index];  
  144.     }  
  145.     public boolean isFull() {  
  146.         return (numItems == ORDER-1);  
  147.     }  
  148.     public int findItem(long key) {  
  149.         for(int j = 0; j < ORDER-1; j++) {  
  150.             if(itemArray[j] == null) {  
  151.                 break;  
  152.             }  
  153.             else if(itemArray[j].dData == key) {  
  154.                 return j;  
  155.             }  
  156.         }  
  157.         return -1;  
  158.     }  
  159.     public int insertItem(DataItem newItem) {  
  160.         //assumes node is not full  
  161.         numItems++;  
  162.         long newKey = newItem.dData;  
  163.         for(int j = ORDER-2; j >= 0; j--) {  //start on right  
  164.             if(itemArray[j] == null) {      //item is null  
  165.                 continue;                   //get left one cell  
  166.             }  
  167.             else {                          //not null  
  168.                 long itsKey = itemArray[j].dData;   //get its key  
  169.                 if(newKey < itsKey) {                //if it's bigger  
  170.                     itemArray[j+1] = itemArray[j];  //shift it right  
  171.                 }  
  172.                 else {  
  173.                     itemArray[j+1] = newItem;       //insert new item  
  174.                     return j+1;                     //return index to new item  
  175.                 }  
  176.             }  
  177.         }  
  178.         itemArray[0] = newItem;  
  179.         return 0;  
  180.     }  
  181.     public DataItem removeItem() {  
  182.         //assumes node not empty  
  183.         DataItem tempItem = itemArray[numItems-1];  //save item  
  184.         itemArray[numItems-1] = null;               //disconnect it  
  185.         numItems--;  
  186.         return tempItem;  
  187.     }  
  188.     public void displayNode() {  
  189.         for(int i = 0; i < numItems; i++) {  
  190.             itemArray[i].displayItem();  
  191.         }  
  192.         System.out.println("/");  
  193.     }  
  194. }  

    和红-黑树一样,2-3-4树同样要访问每层的一个节点,但2-3-4树有比相同数据项的红-黑树短(层数少)。更特别的是,2-3-4树中每个节点最多可以有4个子节点,如果每个节点都是满的,树的高度应该和log4N成正比。以2为底的对数和以4为底的对数底数相差2,因此,在所有节点都满的情况下,2-3-4树的高度大致是红-黑树的一般。不过它们不可能都是满的,2-3-4树的高度就大致在log2(N+1)和log2(N+1)/2之间。

    另一方面,每个节点要查看的数据项就更多了,这会增加查找时间。因为节点中用线性搜索来查看数据项,使查找时间增加的倍数和M成正比,即每个节点数据项的平均数量。总的查找时间和M*log4N成正比。有些节点有1个数据项,有些有2个,有些有3个,如果按照平均两个来计算,查找时间和2*log4N成正比。

    因此,2-3-4树中增加每个节点的数据项数量可以抵偿树的高度的减少。2-3-4树中的查找时间与平衡二叉树(如红-黑树)大致相等,都是O(logN)。

    2-3-4树就讨论到这,如果有问题欢迎留言指正~


转载:http://blog.csdn.net/eson_15/article/details/51140009

目录
相关文章
|
11月前
|
存储 机器学习/深度学习 监控
网络管理监控软件的 C# 区间树性能阈值查询算法
针对网络管理监控软件的高效区间查询需求,本文提出基于区间树的优化方案。传统线性遍历效率低,10万条数据查询超800ms,难以满足实时性要求。区间树以平衡二叉搜索树结构,结合节点最大值剪枝策略,将查询复杂度从O(N)降至O(logN+K),显著提升性能。通过C#实现,支持按指标类型分组建树、增量插入与多维度联合查询,在10万记录下查询耗时仅约2.8ms,内存占用降低35%。测试表明,该方案有效解决高负载场景下的响应延迟问题,助力管理员快速定位异常设备,提升运维效率与系统稳定性。
453 4
|
存储 机器学习/深度学习 算法
KMP、Trie树 、AC自动机‌ ,三大算法实现 优雅 过滤 netty 敏感词
KMP、Trie树 、AC自动机‌ ,三大算法实现 优雅 过滤 netty 敏感词
KMP、Trie树 、AC自动机‌ ,三大算法实现 优雅 过滤 netty  敏感词
|
存储 监控 安全
企业上网监控系统中红黑树数据结构的 Python 算法实现与应用研究
企业上网监控系统需高效处理海量数据,传统数据结构存在性能瓶颈。红黑树通过自平衡机制,确保查找、插入、删除操作的时间复杂度稳定在 O(log n),适用于网络记录存储、设备信息维护及安全事件排序等场景。本文分析红黑树的理论基础、应用场景及 Python 实现,并探讨其在企业监控系统中的实践价值,提升系统性能与稳定性。
848 1
|
监控 算法 安全
基于 C# 基数树算法的网络屏幕监控敏感词检测技术研究
随着数字化办公和网络交互迅猛发展,网络屏幕监控成为信息安全的关键。基数树(Trie Tree)凭借高效的字符串处理能力,在敏感词检测中表现出色。结合C#语言,可构建高时效、高准确率的敏感词识别模块,提升网络安全防护能力。
373 2
|
监控 算法 数据处理
基于 C++ 的 KD 树算法在监控局域网屏幕中的理论剖析与工程实践研究
本文探讨了KD树在局域网屏幕监控中的应用,通过C++实现其构建与查询功能,显著提升多维数据处理效率。KD树作为一种二叉空间划分结构,适用于屏幕图像特征匹配、异常画面检测及数据压缩传输优化等场景。相比传统方法,基于KD树的方案检索效率提升2-3个数量级,但高维数据退化和动态更新等问题仍需进一步研究。未来可通过融合其他数据结构、引入深度学习及开发增量式更新算法等方式优化性能。
369 17
|
人工智能 算法 语音技术
Video-T1:视频生成实时手术刀!清华腾讯「帧树算法」终结闪烁抖动
清华大学与腾讯联合推出的Video-T1技术,通过测试时扩展(TTS)和Tree-of-Frames方法,显著提升视频生成的连贯性与文本匹配度,为影视制作、游戏开发等领域带来突破性解决方案。
619 4
Video-T1:视频生成实时手术刀!清华腾讯「帧树算法」终结闪烁抖动
|
存储 监控 算法
局域网上网记录监控的 C# 基数树算法高效检索方案研究
在企业网络管理与信息安全领域,局域网上网记录监控是维护网络安全、规范网络行为的关键举措。随着企业网络数据量呈指数级增长,如何高效存储和检索上网记录数据成为亟待解决的核心问题。基数树(Trie 树)作为一种独特的数据结构,凭借其在字符串处理方面的卓越性能,为局域网上网记录监控提供了创新的解决方案。本文将深入剖析基数树算法的原理,并通过 C# 语言实现的代码示例,阐述其在局域网上网记录监控场景中的具体应用。
325 7
|
存储 监控 算法
基于跳表数据结构的企业局域网监控异常连接实时检测 C++ 算法研究
跳表(Skip List)是一种基于概率的数据结构,适用于企业局域网监控中海量连接记录的高效处理。其通过多层索引机制实现快速查找、插入和删除操作,时间复杂度为 $O(\log n)$,优于链表和平衡树。跳表在异常连接识别、黑名单管理和历史记录溯源等场景中表现出色,具备实现简单、支持范围查询等优势,是企业网络监控中动态数据管理的理想选择。
346 0
|
算法 Java
算法系列之数据结构-Huffman树
Huffman树(哈夫曼树)又称最优二叉树,是一种带权路径长度最短的二叉树,常用于信息传输、数据压缩等方面。它的构造基于字符出现的频率,通过将频率较低的字符组合在一起,最终形成一棵树。在Huffman树中,每个叶节点代表一个字符,而每个字符的编码则是从根节点到叶节点的路径所对应的二进制序列。
526 3
 算法系列之数据结构-Huffman树
|
存储 自然语言处理 数据库
【数据结构进阶】AVL树深度剖析 + 实现(附源码)
在深入探讨了AVL树的原理和实现后,我们不难发现,这种数据结构不仅优雅地解决了传统二叉搜索树可能面临的性能退化问题,还通过其独特的平衡机制,确保了在任何情况下都能提供稳定且高效的查找、插入和删除操作。
1050 19

热门文章

最新文章