目录
1.与AVL树进行比较:
2-3树增删操作的次数比AVL树相对较少一点
保证查找效率的前提之下优化增删效率
2.基本原理和插入操作の示意图
①23树有三种节点
2节点 3节点 4节点;4节点只是临时状态
②4节点一旦出现,需要拆分 从下往上
数据进来 先往下沉 再往上升插入元素的流程:
如果是空树,创建一个节点即可。
如果不是空树,插入的情况分为4种:
①.向2-节点中插入元素;
如果未命中查找结束于2节点,直接将2节点替换为3节点,并将待插入元素添加到其中。
②.向一颗只含有一个3节点的树中插入元素;
如果查找结束于3节点,先临时将其成为4节点,把待插入元素添加到其中,然后将4节点转化为3个2节点,中间的节点成为左右节点的父节点。如果之前临时4节点有父节点,就会变成向一个父节点为2节点的3节点中插入元素,中间节点与父节点为2节点的合并。
![]()
③.向一个父节点为2节点的3节点中插入元素;
正常上浮即可。
④.向一个父节点为3节点的3节点中插入元素。
插入元素后一直向上分解临时的4节点,直到遇到2-节点的父节点变成3节点不再分解。如果达到树根节点还是4节点,则进行分解根节点
每次函数进来,
(1)优先处理三种情况:空节点、2节点、3节点(方法:根据该节点在对应位置是否有孩子->有则进行递归调用,无则看大小位置放入即可)
(2)->函数体的第二部分紧接着检查是否产生了4节点(每次递归出了上一层的函数就会进行相应的检查并及时做出处理(所以在前面讨论的时候,是没有对4节点的处理的)
对4节点的处理(最基本要新建两个结点node1、node2存储left和right的值以及指针,然后保存中间的值temp)
->紧接着判断parent是否为nullptr
---是,则memset当前节点的值和指针,将刚才临时保存的temp放入,然后左右指针分别指向刚才开node1和node2即可。
---否,则需要进行上浮的操作(父亲只可能是2节点or3节点)
①若temp需要放在parent的第一个位置(parent最起码得有一个指针!(指向孩子))
此中分情况->parent有无第三个指针(若有代表有第二个孩子,为3节点)即可。然后进行移动与对node1和node2的连接
②若temp需要放在父节点的第二个位置
(第一行判断是2节点,第二行判断是3节点)
仍然像上面一样,看有无第三个指针(有->代表有第二个孩子,为3节点).
③若temp需要放在parent的第三个位置:
最终parent记得count++; 并且释放原来的四节点,delete node。
#pragma once #include using namespace std; //0 空节点 1 2节点 2 3节点 3 4节点 enum state{null_node,two_node,three_node,four_node}; template<class T> class twoThreeTree { struct Node { int count; //标记当前是 2节点还是3节点还是4节点 T data[3]; //存储数据的数组 Node* pArr[4]; //存储指针的数组 Node() { count = null_node; memset(data, 0, sizeof(T) * 3); memset(pArr, 0, sizeof(Node*) * 4); } }; Node* pRoot;//指向根节点 public: twoThreeTree(){ pRoot = NULL; } ~twoThreeTree(){} void insertNode(const T& data); private: void _insertNode(Node* node, Node* pParent, const T& data); }; template<class T> void twoThreeTree::insertNode(const T& data) { if (pRoot) { _insertNode(pRoot, NULL, data); } else {//当前树为空树 pRoot = new Node; pRoot->data[0] = data; pRoot->count = two_node; } } template<class T> void twoThreeTree::_insertNode(Node* node, Node* pParent, const T& data) { if (null_node == node->count) {//当前节点为空 node->data[0] = data; node->count++; return;//注意此处的return } if (two_node == node->count) {//当前节点为2节点 if (data > node->data[0]) {//新数据比当前数据大 往右边插入 if (node->pArr[1]) {//当前节点有右孩子 _insertNode(node->pArr[1], node, data); } else {//当前节点没有左孩子 node->data[1] = data; node->count++; } } else {//往左边插入 if (node->pArr[0]) {//当前节点有左孩子 _insertNode(node->pArr[0], node, data); } else {//当前节点没有孩子 //原来数据往右挪 node->data[1] = node->data[0]; //新数据进来 node->data[0] = data; //节点变化 node->count++; } } } else {//当前节点是3节点 if (data < node->data[0]) {//往最左边插入 图上的情况3 if (node->pArr[0]) {//有孩子 _insertNode(node->pArr[0], node, data); } else{//没有孩子 //中间的放右边 node->data[2] = node->data[1]; //前面的放中间 node->data[1] = node->data[0]; //新数据放前面 node->data[0] = data; //节点变化 node->count++; } } else if (data < node->data[1]) {//往中间插入 图上的情况2 if (node->pArr[1]) {//有孩子 _insertNode(node->pArr[1], node, data); } else {//没有孩子 //中间的放右边 node->data[2] = node->data[1]; //新数据放中间 node->data[1] = data; //节点变化 node->count++; } } else{//往右边插入 图上的情况1 if (node->pArr[2]) {//有孩子 _insertNode(node->pArr[2], node, data); } else{//没有孩子 //新数据放右边 node->data[2] = data; //节点变化 node->count++; } } } //每次递归回溯的时候,都会检查是否产生了4节点 if (four_node == node->count){//如果产生了4节点 //1 创建两个节点 Node* node1 = new Node;//左 Node* node2 = new Node;//右 //node1是左边的 node1->data[0] = node->data[0]; node1->pArr[0] = node->pArr[0]; node1->pArr[1] = node->pArr[1]; node1->count = two_node; //node2是右边的 node2->data[0] = node->data[2]; node2->pArr[0] = node->pArr[2]; node2->pArr[1] = node->pArr[3]; node2->count = two_node; //2 临时存储中间值 T temp = node->data[1]; if (pParent) {//当前节点有父节点 //5 找位置 做插入(分为三个位置) if (temp < pParent->data[0]) {//左边 if (pParent->pArr[2]) {//最右边有孩子 pParent->data[2] = pParent->data[1]; pParent->data[1] = pParent->data[0]; pParent->data[0] = temp; pParent->pArr[3] = pParent->pArr[2]; pParent->pArr[2] = pParent->pArr[1]; pParent->pArr[1] = node2; pParent->pArr[0] = node1; } else if(pParent->pArr[1]) {//最右边没有孩子,中间有孩子 pParent->data[1] = pParent->data[0]; pParent->data[0] = temp; pParent->pArr[2] = pParent->pArr[1]; pParent->pArr[1] = node2; pParent->pArr[0] = node1; } } else if (two_node == pParent->count || //这边利用短路特性 (pParent->count > 1)&&temp < pParent->data[1]) {//temp需要放中间(因为此层的else if已经避开temp小于第一个元素的情况,那么必定是大于第一个元素,而小于第二个元素) if (pParent->pArr[2]) {//最右边有孩子 pParent->data[2] = pParent->data[1]; pParent->data[1] = temp; pParent->pArr[3] = pParent->pArr[2]; pParent->pArr[2] = node2; pParent->pArr[1] = node1; } else if (pParent->pArr[1]) {//3节点的最右边无孩子,中间有孩子 pParent->data[1] = temp; pParent->pArr[2] = node2; pParent->pArr[1] = node1; } } else if (three_node == pParent->count || (pParent->count > 2) && (temp < pParent->data[2])) {//右边 if (pParent->pArr[2]) { pParent->data[2] = temp; pParent->pArr[3] = node2; pParent->pArr[2] = node1; } } pParent->count++; //6 释放内存 delete node; } else {//当前节点没有父节点 //3 当前节点变成2节点 memset(node->data, 0, sizeof(T)* 3);//清空数据 memset(node->pArr, 0, sizeof(Node*)* 4);//清空指针 node->data[0] = temp; node->count = two_node; //4 node1成为当前节点的左孩子 node2成为当前节点的右孩子 node->pArr[0] = node1; node->pArr[1] = node2; } } }
test:
