数据结构和算法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树的代码:

public class Tree234 {
	private Node2 root = new Node2();
	public int find(long key) {
		Node2 currentNode = root;
		int childNumber;
		while(true) {
			if((childNumber = currentNode.findItem(key)) != -1) {
				return childNumber;
			}
			else if(currentNode.isLeaf()) {
				return -1;
			}
			else {
				currentNode = getNextChild(currentNode, key);
			}
		}
	}
	//insert a DataItem
	public void insert(long data) {
		Node2 currentNode = root;
		DataItem tempItem = new DataItem(data);
		while(true) {
			if(currentNode.isFull()) {
				split(currentNode);	//if node is full, split it
				currentNode = currentNode.getParent();	//back up
				currentNode = getNextChild(currentNode, data);	//search once
			}
			else if(currentNode.isLeaf()) { //if node if leaf
				break;	//go insert
			}
			else {
				currentNode = getNextChild(currentNode, data);
			}
		}
		currentNode.insertItem(tempItem);
	}
	//display tree
	public void displayTree() {
		recDisplayTree(root, 0, 0);
	}
	public Node2 getNextChild(Node2 currentNode, long key) {
		int j;
		//assumes node is not empty, not full and not leaf
		int numItems = currentNode.getNumItems();
		for(j = 0; j < numItems; j++) {
			if(key < currentNode.getItem(j).dData) {
				return currentNode.getChild(j);
			}
		}
		return currentNode.getChild(j);
	}
	public void split(Node2 currentNode) {
		//assumes node is full
		DataItem itemB, itemC;	//存储要分裂节点的后两个DataItem
		Node2 parent, child2, child3;	//存储要分裂节点的父节点和后两个child
		int itemIndex;
		itemC = currentNode.removeItem();
		itemB = currentNode.removeItem();	//remove items from this node
		child2 = currentNode.disconnectChild(2);
		child3 = currentNode.disconnectChild(3); //remove children from this node
		Node2 newRight = new Node2(); //make a new node
		if(currentNode == root) {
			root = new Node2(); //make a new root
			parent = root;	//root is our parent
			root.connectChild(0, currentNode);//connect currentNode to parent
		}
		else {
			parent = currentNode.getParent();
		}
		//deal with parent
		itemIndex = parent.insertItem(itemB);	//insert B to parent
		int n = parent.getNumItems();	//total items
		for(int j = n-1; j > itemIndex; j--) {
			Node2 temp = parent.disconnectChild(j);
			parent.connectChild(j+1, temp);
		}
		parent.connectChild(itemIndex+1, newRight);
		//deal with newRight
		newRight.insertItem(itemC);
		newRight.connectChild(0, child2);
		newRight.connectChild(1, child3);
	}
	public void recDisplayTree(Node2 thisNode, int level, int childNumber) {
		System.out.print("level = " + level + " child = " + childNumber + " ");
		thisNode.displayNode();
		//call ourselves for each child of this node
		int numItems = thisNode.getNumItems();
		for(int j = 0; j < numItems+1; j++) {
			Node2 nextNode = thisNode.getChild(j);
			if(nextNode != null) {
				recDisplayTree(nextNode, level+1, j);
			}
			else
				continue;
		}
	}
}

//数据项
class DataItem {
	public long dData;
	public DataItem(long data) {
		dData = data;
	}
	public void displayItem() {
		System.out.print("/" + dData);
	}
}
//节点
class Node2 {
	private static final int ORDER = 4;
	private int numItems; //表示该节点存有多少个数据项
	private Node2 parent;
	private Node2 childArray[] = new Node2[ORDER]; //存储子节点的数组,最多四个子节点
	private DataItem itemArray[] = new DataItem[ORDER-1];//该节点中存放数据项的数组,每个节点最多存放三个数据项
	//连接子节点
	public void connectChild(int childNum, Node2 child) {
		childArray[childNum] = child;
		if(child != null) {
			child.parent = this;
		}
	}
	//断开与子节点的连接,并返回该子节点
	public Node2 disconnectChild(int childNum) {
		Node2 tempNode = childArray[childNum];
		childArray[childNum] = null;
		return tempNode;
	}
	public Node2 getChild(int childNum) {
		return childArray[childNum];
	}
	public Node2 getParent() {
		return parent;
	}

	public boolean isLeaf() {
		return (childArray[0] == null);
	}
	public int getNumItems() {
		return numItems;
	}
	public DataItem getItem(int index) {
		return itemArray[index];
	}
	public boolean isFull() {
		return (numItems == ORDER-1);
	}
	public int findItem(long key) {
		for(int j = 0; j < ORDER-1; j++) {
			if(itemArray[j] == null) {
				break;
			}
			else if(itemArray[j].dData == key) {
				return j;
			}
		}
		return -1;
	}
	public int insertItem(DataItem newItem) {
		//assumes node is not full
		numItems++;
		long newKey = newItem.dData;
		for(int j = ORDER-2; j >= 0; j--) { 	//start on right
			if(itemArray[j] == null) { 		//item is null
				continue; 					//get left one cell
			}
			else {							//not null
				long itsKey = itemArray[j].dData;	//get its key
				if(newKey < itsKey) {				//if it's bigger
					itemArray[j+1] = itemArray[j]; 	//shift it right
				}
				else {
					itemArray[j+1] = newItem; 		//insert new item
					return j+1;						//return index to new item
				}
			}
		}
		itemArray[0] = newItem;
		return 0;
	}
	public DataItem removeItem() {
		//assumes node not empty
		DataItem tempItem = itemArray[numItems-1];	//save item
		itemArray[numItems-1] = null;				//disconnect it
		numItems--;
		return tempItem;
	}
	public void displayNode() {
		for(int i = 0; i < numItems; i++) {
			itemArray[i].displayItem();
		}
		System.out.println("/");
	}
}

和红-黑树一样,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树就讨论到这,如果有错误请留言指正,如果喜欢,别忘了点个赞哟~

时间: 2024-12-28 21:00:11

数据结构和算法06 之2-3-4树的相关文章

java数据结构和算法06(红黑树)

这一篇我们来看看红黑树,首先说一下我啃红黑树的一点想法,刚开始的时候比较蒙,what?这到底是什么鬼啊?还有这种操作?有好久的时间我都缓不过来,直到我玩了两把王者之后回头一看,好像有点儿意思,所以有的时候碰到一个问题困扰了很久可以先让自己的头脑放松一下,哈哈! 不瞎扯咳,开始今天的正题: 前提:看红黑树之前一定要先会搜索二叉树 1.红黑树的概念 红黑树到底是个什么鬼呢?我最开始也在想这个问题,你说前面的搜索二叉树多牛,各种操作效率也不错,用起来很爽啊,为什么突然又冒出来了红黑树啊? 确实,搜索二

研磨数据结构与算法-06递归的应用

一,简单Demo public class Recursion { public static void main(String[] args) { test(100); } public static void test(int n) { if(n == 0) { return; } System.out.println(n); test2(n - 1); } } 二,三角数字 public class Triangle { public static int getNumber(int n)

Java数据结构和算法(八)--红黑树与2-3树

红黑树规则: 1.根节点与叶节点都是黑色节点 2.每个红色节点的两个子节点都是黑色节点,反之,不做要求,换句话说就是不能有连续两个红色节点 3.从根节点到所有叶子节点上的黑色节点数量是相同的 一般对红黑树的讲述都是先给出这样的定义,这样想对不太容易理解的,而在算法4一书中,直接跳过这些规则,而讲述了红黑树与2-3树的等价性 如果我们先了解2-3树,理解了红黑树与2-3树之间的关系,回过头就会发现红黑树不难 2-3树: 2-3树满足二分搜索树的基本性质,但是不是二叉树 2-3树节点可以存放一个元素

Java数据结构和算法(十二)——2-3-4树

通过前面的介绍,我们知道在二叉树中,每个节点只有一个数据项,最多有两个子节点.如果允许每个节点可以有更多的数据项和更多的子节点,就是多叉树.本篇博客我们将介绍的——2-3-4树,它是一种多叉树,它的每个节点最多有四个子节点和三个数据项. 1.2-3-4 树介绍 2-3-4树每个节点最多有四个字节点和三个数据项,名字中 2,3,4 的数字含义是指一个节点可能含有的子节点的个数.对于非叶节点有三种可能的情况: ①.有一个数据项的节点总是有两个子节点: ②.有二个数据项的节点总是有三个子节点: ③.有

数据结构与算法 3:二叉树,遍历,创建,释放,拷贝,求高度,面试,线索树

[本文谢绝转载,原文来自http://990487026.blog.51cto.com] 树 数据结构与算法 3:二叉树,遍历,创建,释放,拷贝,求高度,面试,线索树 二叉树的创建,关系建立 二叉树的创建,关系建立2 三叉链表法 双亲链表: 二叉树的遍历 遍历的分析PPT 计算二叉树中叶子节点的数目:使用全局变量计数器 计算二叉树中叶子节点的数目:不使用全局变量计数器 无论是先序遍历,中序遍历,后序遍历,求叶子的数字都不变;因为本质都是一样的,任何一个节点都会遍历3趟 求二叉树的高度 二叉树的拷

数据结构与算法20170804

本文介绍数据结构与算法的知识,相信很多人在学校都学习过,同时为了贴近实际,文章直接附上编译通过可直接使用的源码. 一.数据结构 1.线性表: 1)带头结点的链表 1 /***************************************************************************** 2 * Copyright (C) 2017-2018 Hanson Yu All rights reserved. 3 ---------------------------

《Java数据结构和算法》- 哈希表

Q: 如何快速地存取员工的信息? A: 假设现在要写一个程序,存取一个公司的员工记录,这个小公司大约有1000个员工,每个员工记录需要1024个字节的存储空间,因此整个数据库的大小约为1MB.一般的计算机内存都可以满足. 为了尽可能地存取每个员工的记录,使用工号从1(公司创业者)到1000(最近雇佣的工人).将工号作为关键字(事实上,用其他作为关键字完全没有必要).即使员工离职不在公司,他们的记录也是要保存在数据库中以供参考,在这种情况下需要使用什么数据结构呢? A: 一种可能使用数组,每个员工

数据结构与算法C++描述学习笔记1、辗转相除——欧几里得算法

前面学了一个星期的C++,以前阅读C++代码有些困难,现在好一些了.做了一些NOI的题目,这也是一个长期的目标中的一环.做到动态规划的相关题目时发现很多问题思考不通透,所以开始系统学习.学习的第一本是<数据结构与算法C++描述>第三版,边学边做一些笔记.所以这些笔记中的代码有很多将会非常简单,甚至可能只有一个记录或者结论. 辗转相除法用来求两个整数的最大公约数,即能同时整除两个数的最大整数.程序如下: int gdc(int m,int n){ int rem; while(n!=0){ //

数据结构与算法之线性表

前言 上一篇<数据结构和算法之时间复杂度和空间复杂度>中介绍了时间复杂度的概念和常见的时间复杂度,并分别举例子进行了一一说明.这一篇主要介绍线性表. 线性表属于数据结构中逻辑结构中的线性结构.回忆一下,数据结构分为物理结构和逻辑结构,逻辑结构分为线性结构.几何结构.树形结构和图形结构四大结构.其中,线性表就属于线性结构.剩余的三大逻辑结构今后会一一介绍. 线性表 基本概念 线性表(List):由零个或多个数据元素组成的有限序列. 注意: 1.线性表是一个序列. 2.0个元素构成的线性表是空表.