算法作业4 2-3-4树
一、算法要求:
- 构建2-3-4树
- 从一个数据文件中依次读入关键字序列构建树,测试建树时间;
- 测试遍历产生顺序输出序列需要的时间;
- 测试构建完成树后删除指定关键字集合的时间。
二、算法思路:
2-3-4树是一种阶为4的B树。它是一种自平衡的数据结构,可以在O(logN)的时间内查找、插入和删除,这里的n是树中元素的数目。
2-3-4树是这样一种数据结构,满足如下性质:
1) 每个节点有1、2或3个key,分别称为2-node,3-node,4-node。
2) 每个节点的keys把区间进行了划分,以4-nde为例,key1、key2、key3分别夹在subtree1, subtree2和subtree2, subtree3和subtree3, subtree4之间。
要求1: 构建2-3-4树
从文件中读取数据,把每个数据通过2-3-4树的插入操作,插入树中,完成建树,插入操作间要求2。
要求2: 插入操作,插入新元素newkey
2-3-4树的插入,从root向叶子节点按照search规律遍历。如果发现newkey已经在树中,则算法结束。
如果newkey不在树上,这里分两种情况讨论。
一,如果待插入的叶节点只有1个或者2个元素,则直接把newkey放进叶节点的空闲位置,算法结束。
图1 插入11图示
二,待插入的叶节点已经有3个元素,根据2-3-4树的规则,每个节点的元素个数在1、2、3之间,故当newkey要加入到这个叶子时,就违反了2-3-4树的定义。这时就需要对该叶子节点进行分裂,将叶子以中间节点为界,分成两个包含1个元素的子节点,同时把中间节点提升到该叶子的父节点中,如果这样使得父节点的元素个数超过3,则要继续向上分裂,以此类推。若已经到了根节点,根节点的分裂,使得树加高一层。
我们发现,如果按照上述思路,程序需要在插入时回溯。为了避免回溯,可以以另一种思路完成插入,即在从root开始往下搜索的过程中,一旦遇到已满的节点(即元素个数为3),里面对该节点进行分裂。这样的好处是,能保证在叶子节点需要分裂时,其父节点一定是非满的,从而不需要再向上回溯。
图2 插入12图示
要求3: 删除操作,删除元素key
和插入操作类似,在删除2-3-4树节点时,为了避免回溯,当遇到需要合并的节点时就立即执行合并。2-3-4树的删除思路如下:
从root向叶子节点按照search规律遍历,分三种情况讨论:
一,如果key在叶节点leafnode中,则直接从leafnode中删除key。算法结束。
图3 删除11图示
情况二和三能保证当在叶子节点找到key时,肯定能从兄弟处借节点,或合并成功而不会引起父节点的元素个数少于3。
二,如果key在分支节点node中:
A)如果node的左分支节点left至少包含2个元素,则找出key值的直接前驱(即找出left子树的最右的元素prev),替换key,并在left子树中递归删除prev。
图4 删除9图示
B)如果node的右分支节点right至少包含2个关键字,则找出key值的直接后继(即找出right子树的最左的元素next),替换key,并在right子树中递归删除next。
图5 删除7图示
C)最坏的情况是,如果left和right都只有1个关键字,则将key与right的那个元素合并到left中,使得left有3个元素,再从left子树中递归删除key。
图6 删除5图示
三,如果key不在分支节点node中,则必然在node的某个分支节点subtree[i]中,如果subtree[i]节点只有1个元素,则:
A)如果subtree[i]的有左兄弟,且左兄弟subtree[i - 1]拥有至少2个关键字,则将node中夹在subtree[i]和subtree[i - 1]中间的元素node->key[i - 1]降至subtree[i]中,将subtree[i - 1]的最大元素上升至node->key[i - 1]中。
B)如果subtree[i]的没有左兄弟,则一定有右兄弟。若右兄弟subtree[i + 1]拥有至少2个关键字,则将node中夹在subtree[i]和subtree[i + 1]中间的元素node->key[i]降至subtree[i]中,将subtree[i + 1]的最大元素上升至node->key[i]中。
图7 删除4图示
C)如果subtree[i - 1]与subtree[i + 1]都只拥有1个元素,则将subtree[i]与其中一个兄弟合并,将node的一个元素降至合并的节点中,成为中间元素。
图8 删除4图示
三、算法伪代码:
1. 插入操作伪代码:
1 //插入元素到2-3-4树 2 //root为树根节点,key为待插入的元素 3 Insert(root, key) 4 begin 5 If root is empy 6 new a root and insert key into it 7 return 8 if root->keynum == 3 9 new a root 10 newroot->subtree[0] ← root 11 split children 12 insert to nonfull(newroot, key) 13 return 14 else 15 insert to nonfull(root, key) 16 end
1 //插入到未满元素 2 //node为当前节点,key为待插入元素 3 Insert to nonfull(node, key) 4 begin 5 if key is exist 6 return 7 if node is leaf 8 insert key into node 9 else 10 if node subtree->keynum == 3 11 split children 12 insert nonfull(node subtree, key) 13 end
2. 删除操作伪代码:
1 //从2-3-4树中删除元素 2 //root为树根节点,key为待删除元素 3 delete(root, key) 4 begin 5 if root is 2-node 6 if root->subtree[0] and root->subtree[1] are 2-node 7 merge children 8 delete root 9 delete_nonone(root, key) 10 return root->subtree[0] 11 else 12 delete_nonone(root, key) 13 return root 14 else 15 delete_nonone(root, key) 16 end
1 //删除未空元素 2 //node为当前节点,key为待删除元素 3 delete_nonone(node, key) 4 begin 5 if node is leaf 6 remove key from node 7 else 8 if key is in internal node 9 if node have left subtree 10 replace key by its predecessor 11 delete_nonone(node’s left subtree, predecessor) 12 else if node have right subtree 13 replace key by its successor 14 delete_nonone(node’s right subtree, successor) 15 else 16 merge children 17 delete(node’s left subtree, key) 18 else 19 if node[i]->keynum == 1 20 if node’s left subtree->keynum == 1 21 shift to right children 22 else if node’s right subtree->keynum == 1 23 shift to left children 24 else 25 merge children 26 delete_nonone(node’s subtree, key) 27 else 28 delete_nonone(node’s subtree, key) 29 end
3. 顺序遍历伪代码:
1 OrdeTravel(root) 2 Begin 3 If root == NULL return 4 If root->type == TwoNode 5 OrdeTravel (rt->subtree[0]) 6 Print key[0] 7 OrdeTravel (rt->subtree[1]) 8 else if root->type == ThreeNode 9 OrdeTravel (rt->subtree[0]) 10 print(key[0]) 11 OrdeTravel (rt->subtree[1]) 12 print(key[1]) 13 OrdeTravel (rt->subtree[2]) 14 else 15 OrdeTravel (rt->subtree[0]) 16 print(key[0]) 17 OrdeTravel (rt->subtree[1]) 18 print(key[1]) 19 OrdeTravel (rt->subtree[2]) 20 print(key[2]) 21 OrdeTravel (rt->subtree[3]) 22 end
四、程序效率分析:
2-3-4树的自平衡树,所有叶子节点都在同一层上,所以插入、删除和查找的时间复杂度都是树的高度,即T(N) = O(lgN)。
以下是测试数据:
数据量 |
建树时间 |
删除时间 |
顺序遍历时间 |
10K |
0.022s |
0s |
0.181s |
100K |
0.283s |
0s |
1.761s |
1M |
3.626s |
0s |
16.801s |
10M |
41.418s |
0.015s |
168.596s |
服务器一亿 |
172.86s |
21.28s(删除m个) |
- |
五、程序运行截图:
A) 简单测试数据:
B)建树测试:
C)简单删除测试: