K:樹、二叉樹與森林之間的轉換及其相關代碼實現


相關介紹:

 二叉樹是樹的一種特殊形態,在二叉樹中一個節點至多有左、右兩個子節點,而在樹中一個節點可以包含任意數目的子節點,對於森林,其是多棵樹所組成的一個整體,樹與樹之間彼此相互獨立,互不干擾,但其又是一個整體。樹與二叉樹之間、森林與二叉樹之間可以相互的進行轉換,且這種轉換是一一對應的。樹與森林轉換成二叉樹之后,森林與或樹的相關操作都轉換為二叉樹的操作。在此,將討論樹的存儲結構、樹與森林,二叉樹之間的對應關系與轉換過程及相關代碼。

二叉樹的存儲結構:

 在實際應用中,可根據具體的操作的特點將樹設計成不同的存儲結構。但無論采用何種存儲方式,都要求樹的存儲結構不但能夠存儲各個節點本身的數據域信息,還要求能准確反映樹中各個節點之間的邏輯關系。於此,介紹四種鏈式存儲方式:

1.雙親鏈表存儲結構

 雙親鏈表存儲結構中的每一個節點存放的信息既包含節點本身的數據域信息,又包含指示雙親節點在存儲結構中的位置。所以,這種存儲結構可以設計成:以一組地址連續的存儲單元來存放樹中的各個節點,每一個節點中有兩個域,一個為數據域,用來存儲樹中該節點本身的值;另一個是指針域,用來存儲該節點的雙親節點在存儲結構中的位置信息。下圖演示了該存儲結構:

雙親鏈表存儲結構示意圖

分析: 采用雙親鏈表存儲方式實現查找一個指定節點的雙親節點非常容易,但是要實現查找一個指定節點的孩子節點卻並不容易,需要對整個鏈表掃描一遍

其雙親節點鏈表的代碼描述如下:

相關代碼:

public class Forest
{
	private TreeNode[] tree;
	
	public Forest(int n)
	{
		tree=new TreeNode[n];
	}
	
	class TreeNode
	{
		//數據域對象
		Object data;
		//父對象在數組中的位置
		int parent;
	}
}

2.孩子鏈表存儲結構

 孩子鏈表存儲結構中除了存放節點本身的數據域信息之外,還存放了其所有孩子節點在存儲結構中的位置信息。由於每一個節點的子節點樹不同,則可將一個節點的所有孩子的位置信息按從左到右的順序鏈接成一個單鏈表,稱此單鏈表為該鏈表的孩子鏈表,因此,該存儲結構可設計為:以一組地址連續的存儲單元來存放樹中的各個節點,,每一個節點有兩個域,一個為數據域,用來存儲樹中該節點的值;另一個為指針域,用來存放該節點的孩子鏈表的頭指針。下圖演示了該存儲結構:

孩子鏈表存儲結構示意圖

分析:
這種存儲結構與雙親鏈表存儲結構正好相反,它便於實現查找樹中指定節點的孩子節點,但不便於實現查找樹中指定節點的雙親節點。

其孩子鏈表存儲結構的示例代碼如下:

相關代碼:

package all_in_tree;

import java.util.LinkedList;
import java.util.List;

public class Forest
{
	private TreeNode[] tree;
	
	public Forest(int n)
	{
		tree=new TreeNode[n];
	}
	
	class TreeNode
	{
		//數據域對象
		Object data;
		//孩子鏈表的指針
		List firstChild=new LinkedList();
	}
}

3.雙親孩子鏈表存儲結構

 雙親孩子鏈表存儲結構的設計方法與孩子鏈表存儲結構類似,其主體仍然是一個存儲樹中各個節點信息的數組。只不過數組中的元素含有三個域,比孩子鏈表存儲結構中多了一個存放該節點的雙親節點在數組中位置的指針域。下圖演示了該存儲結構:

雙親孩子鏈表存儲結構示意圖

分析:
這種存儲結構既便於實現查找樹中指定節點的孩子節點,又便於實現查找樹中指定節點的雙親節點。

其雙親孩子鏈表存儲結構的示例代碼如下:

相關代碼:

package all_in_tree;

import java.util.LinkedList;
import java.util.List;

public class Forest
{
	private TreeNode[] tree;
	
	public Forest(int n)
	{
		tree=new TreeNode[n];
	}
	
	class TreeNode
	{
		//數據域對象
		Object data;
		//雙親節點在數組中的位置
		int parent;
		//孩子鏈表的指針
		List firstChild=new LinkedList();
	}
}

4.孩子兄弟鏈表存儲結構

 孩子兄弟鏈表存儲結構又稱為“左孩/右兄”二叉鏈式存儲結構,它類似於二叉樹的二叉鏈式存儲結構,不同點在於鏈表中每個節點的左指針是指向該節點的第一個孩子,而右指針是指向該節點的右鄰兄弟。其本質就是先將一棵二叉樹轉化為一棵二叉樹后存儲在二叉鏈式結構之中。下圖演示了該存儲結構:

孩子兄弟鏈表存儲結構示意圖

分析:
這種存儲結構與樹所對應的二叉樹的二叉鏈式存儲結構相同,一切對於樹的操作都將通過這種方式轉化成對二叉樹的操作。為此,該種存儲方式應用更加廣泛

其孩子兄弟鏈表存儲結構的示例代碼如下:

相關代碼:

package all_in_tree;
public class Forest
{
	private TreeNode root;
	
	class TreeNode
	{
		//數據域對象
		Object data;
		//其左孩子,右兄弟的指針
		TreeNode left,right;
	}
}

樹轉換為二叉樹:

 二叉樹中的節點有左右孩子之分,而在無序樹中節點的各個孩子之間是無次序之分的。為了操作方便,假設樹是一棵有序樹,樹中每個節點的孩子按照從左到右的順序進行編號,依次定義為第一個孩子、第二個孩子、....、第i個孩子。
 將樹轉換為二叉樹的方法可以歸納為“加線”、“刪除”、”旋轉“3個步驟,其具體描述如下:

  1. 加線:將樹中所有相鄰的兄弟之間加一條連線。

  2. 刪線:對樹中的每一個節點,只保留它與第一個孩子節點之間的連線,刪去它與其他孩子節點之間的連線。

  3. 旋轉:以樹的根節點為軸心,將樹平面順時針旋轉一定的角度並適當的進行調整,使得轉化后所得的二叉樹看起來比較規整(該步驟不是必須)

下圖給出了將樹裝換成二叉樹的過程的示意圖:

樹轉換成二叉樹的過程示意圖

由轉換過程可知,樹與由它轉換成的二叉樹是一一對應的,樹中的任意一個節點都對應着二叉樹中的一個節點,樹中每一個節點的第一個孩子節點在二叉樹中是對應節點的左孩子,而樹中每一節點的右鄰兄弟在二叉樹中是對應節點的右孩子(簡而言之,左孩子,右兄弟)。以下,相關的代碼中采用孩子鏈表存儲結構來存儲相關的樹。

 注意到,對於任意一個節點,其孩子鏈表中的第一個節點的值為其當前節點的左孩子節點;孩子鏈表中的任意一個節點為其下一個孩子節點的雙親節點,同時,下一個孩子節點為該節點的右孩子節點。為此,我們可以采用以下的方式,實現樹與二叉樹之間的相互轉換

相關代碼:

package all_in_tree;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;

/**
 * 該類用於演示樹轉化為二叉樹的相關代碼,其中,樹采用孩子鏈表的存儲結構
 * 同時,借助一個Map,用於記錄中間轉化過程,以實現后期對二叉樹的構造
 * @author 學徒
 *
 */
public class Forest
{
	//樹中節點的數目
	private int nodeCount;
	//用於記錄樹中各個存儲節點的指針,便於樹的構造過程
	private Map<Integer,Node> nodeMap=new HashMap<Integer,Node>();
	//用於描述樹的孩子鏈表的存儲結構
	private TreeNode[] tree;
	//用於記錄樹的中間轉化過程中的相關信息
	private MapNode[] map;
	//轉化后的二叉樹的根節點索引
	private Node root;
	/**
	 * 中間記錄轉化過程的節點
	 * @author 學徒
	 *
	 */
	class MapNode
	{
		//存儲相關的數據
		Object data;
		//存儲其左孩子節點在數組中的位置的下標,初始化為-1表示沒有
		int left=-1;
		//存儲其右孩子節點在數組中的位置的下標,初始化為-1表示沒有
		int right=-1;
	}
	/**
	 * 用於創建一棵樹具有n個節點的數
	 * @param n 樹的節點數目
	 * 
	 */
	public Forest(int n)
	{
		//存儲樹的節點的數目
		this.nodeCount=n;
		//用於初始化
		tree=new TreeNode[n];
		map=new MapNode[n];
		for(int i=0;i<n;i++)
		{
			tree[i]=new TreeNode();
			map[i]=new MapNode();
			nodeMap.put(i, new Node());
		}
		root=(Node)nodeMap.get(0);
	}
	/**
	 * 用於設置樹中某個節點的數據域的相關值
	 * @param index 樹中節點在數組中的下標
	 * @param data 樹的數據域
	 */
	public void setTreeData(int index,Object data)
	{
		tree[index].data=data;
		map[index].data=data;
		nodeMap.get(index).data=data;
	}
	/**
	 * 用於增加樹中某個節點的孩子節點
	 * @param index
	 * @param childIndex
	 */
	public void addTreeChild(int index,int childIndex)
	{
		if(tree[index].firstChild==null)
			tree[index].firstChild=new LinkedList<Integer>();
		tree[index].firstChild.add(childIndex);
	}
	
	/**
	 * 用於實現樹轉化為二叉樹的過程,並返回該二叉樹
	 * 
	 */
	public Node  toTranslateBinaryTree()
	{
		//用於遍歷樹的雙親孩子節點存儲結構的數組,同時修改和記錄其中間結果的值,便於后序二叉樹的構造
		for(int i=0 ;i<this.nodeCount;i++)
		{
			//孩子節點的鏈表
			Queue<Integer> childList=tree[i].firstChild;
			//當前孩子節點的下標編號
			int now=-1;
			//上一孩子節點的下標編號,將其默認值設置為當前節點
			int previous=i;
			//當其孩子鏈表存在的時候
			if(childList!=null)
			{
				//當孩子鏈表存在時,由於第一個節點為其當前節點的左孩子節點。為此,用於設置其左孩子節點
				if(!childList.isEmpty())
				{
					now=childList.poll();
					map[previous].left=now;
					previous=now;
				}
				//處理剩下的孩子節點
				while(!childList.isEmpty())
				{
					now=childList.poll();
					map[previous].right=now;
					previous=now;
				}
			}
		}
		//根據記錄的中間結果構造出一棵二叉樹
		for(int i=0;i<this.nodeCount;i++)
		{
			int left=map[i].left;
			int right=map[i].right;
			//用於獲取二叉樹中相關的節點
			Node node=nodeMap.get(i);
			//用於構造該二叉樹
			if(left!=-1)
				node.left=nodeMap.get(left);
			if(right!=-1)
				node.right=nodeMap.get(right);
		}
		root=nodeMap.get(0);
		return root;
	}
}
/**
 * 用於存儲樹中雙親孩子節點的信息
 * @author 學徒
 *
 */
class TreeNode
{
	//存儲相關數據
	Object  data;
/*	//其雙親節點在數組中位置的下標
	int parent;*/
	//用於存儲孩子節點的孩子節點鏈表
	Queue<Integer> firstChild;
	/**
	 * 用於增加孩子節點的方法
	 */
	public void addChild(int child)
	{
		firstChild.add(child);
	}
}
/**
 * 二叉樹中的節點描述類
 * @author 學徒
 */
class Node
{
	Object data;
	Node left;
	Node right;
}
/**
 * 用於測試使用的類,該代碼采用的測試用例即為“樹轉換成二叉樹的過程示意圖”中的樹
 * @author 學徒
 *
 */
class Test
{
	public static void main(String[] args)
	{
		Forest forest=new Forest(8);
		forest.setTreeData(0,"A");
		forest.setTreeData(1,"B");
		forest.setTreeData(2, "C");
		forest.setTreeData(3,"D");
		forest.setTreeData(4,"E");
		forest.setTreeData(5,"F");
		forest.setTreeData(6,"G");
		forest.setTreeData(7, "H");
		forest.addTreeChild(0, 1);
		forest.addTreeChild(0, 2);
		forest.addTreeChild(0, 3);
		forest.addTreeChild(1, 4);
		forest.addTreeChild(1, 5);
		forest.addTreeChild(5, 7);
		forest.addTreeChild(3, 6);
		Node root=forest.toTranslateBinaryTree();
		//得到二叉樹中序遍歷的結果
		System.out.print("中序遍歷的結果 :");
		inRootTraver(root);
		System.out.println();
		//得到二叉樹后序遍歷的結果
		System.out.print("后序遍歷的結果 :");
		postRootTraver(root);
	}
	/**
	 * 用於得到二叉樹中序遍歷的結果
	 */
	public static void inRootTraver(Node root)
	{
		if(root!=null)
		{
			inRootTraver(root.left);
			System.out.print(root.data+"  ");
			inRootTraver(root.right);
		}
	}
	
	/**
	 * 用於得到二叉樹后序遍歷的結果(Node root)
	 */
	public static void postRootTraver(Node root)
	{
		if(root!=null)
		{
			postRootTraver(root.left);
			postRootTraver(root.right);
			System.out.print(root.data+"  ");
		}
	}
}



其運行結果如下:

中序遍歷的結果 :E  H  F  B  C  G  D  A  
后序遍歷的結果 :H  F  E  G  D  C  B  A 

二叉樹轉換成樹:

 二叉樹轉換成樹是由樹轉換成二叉樹的一個逆過程,也就是一個由二叉樹還原成它原來所對應的樹的過程,其步驟如下:

  1. 加線:若某節點是其雙親節點的左孩子,則將該節點沿着右分支向下的所有節點與該節點的雙親節點用線連接

  2. 刪線:將書中所有本來雙親節點與右孩子節點的連線刪除

  3. 旋轉:對經過步驟1、2兩步后所得的樹以根節點為軸心,按逆時針方向旋轉一定的角度並做適當調整,使得轉化后所得的樹看起來比較規整

下圖給出了將二叉樹轉化為樹的過程:

二叉樹轉化為樹的過程示意圖

以下,相關的代碼中采用孩子鏈表存儲結構來存儲相關的樹。
其相關的代碼如下:

相關代碼:

package all_in_tree;

import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;

/**
 * 用於實現將一棵二叉樹轉化為樹的相關操作,其中樹采用孩子鏈表節點的存儲結構
 * 同時,借助一個Map用來記錄二叉樹中節點和樹存儲結構中的數組的下標的編號的對應關系
 * @author 學徒
 *
 */
public class ReverseForest
{
	//用於記錄該樹二叉樹
	private Node root;
	//二叉樹中節點的數目
	private int nodeCount;
	//用於記錄樹中各個存儲節點的指針,便於樹的構造過程
	private Map<Integer,Node> nodeMap=new HashMap<Integer,Node>();
	//用於記錄樹中各個指針對應的存儲節點的編號,便於樹的構造
	private Map<Node,Integer> indexMap=new HashMap<Node,Integer>();
	//用於描述樹的孩子鏈表的存儲結構
	private TreeNode[] tree;
	
	public ReverseForest(Node root)
	{
		this.root=root;
		//對二叉樹采用任意一種遍歷方式,用於計算出其總節點的數目同時初始化其映射Map,此處采用層次遍歷的方式
		Queue<Node> q=new LinkedList<Node>();
		q.add(root);
		nodeMap.put(this.nodeCount,root);
		indexMap.put(root,this.nodeCount++ );
		while(!q.isEmpty())
		{
			Node node = q.poll();
			if(node.left!=null)
			{
				q.add(node.left);
				nodeMap.put( this.nodeCount,node.left);
				indexMap.put(node.left,this.nodeCount++);
			}
			if(node.right!=null)
			{
				q.add(node.right);
				nodeMap.put(this.nodeCount,node.right);
				indexMap.put(node.right, this.nodeCount++);
			}
		}
		//初始化其孩子鏈表的存儲結構
		tree=new TreeNode[this.nodeCount];
		//用於初始化各個樹中的節點
		for(Entry<Integer,Node> node:nodeMap.entrySet())
		{
			int index=node.getKey();
			tree[index]=new TreeNode();
			tree[index].data=node.getValue().data;
		}
	}
	/**
	 * 用於增加樹中某個節點的孩子節點
	 * @param index
	 * @param childIndex
	 */
	public void addTreeChild(int index,int childIndex)
	{
		if(tree[index].firstChild==null)
			tree[index].firstChild=new LinkedList<Integer>();
		tree[index].firstChild.add(childIndex);
	}
	/**
	 * 用於將二叉樹轉化為樹
	 */
	public TreeNode[] toTranslateTree()
	{
		//用於遍歷描述樹的數組
		for(int index=0;index<this.nodeCount;index++)
		{
			//得到該樹節點
			Node node=nodeMap.get(index);
			//得到該樹節點所對應的第一個孩子節點
			node=node.left;
			if(node!=null)
			{
				addTreeChild(index,indexMap.get(node));
				while(node.right!=null)
				{
					addTreeChild(index, indexMap.get(node.right));
					node=node.right;
				}
			}
		}
		return this.tree;
	}
}


class Tests
{
	/**
	 * 測試代碼,用於測試其運行結果
	 * @param args
	 */
	public static void main(String[] args)
	{
		TreeNode[] tree= new ReverseForest(Test.test()).toTranslateTree();
		System.out.println();
		for(int i=0;i<tree.length;i++)
		{
			System.out.print(i+(tree[i].data.toString())+": ");
			Queue<Integer> q=tree[i].firstChild;
			if(q!=null)
			{
				Iterator iterator =q.iterator();
				
				while(iterator.hasNext())
				{
					System.out.print(iterator.next()+"   ");
				}
			}
			else
			{
				System.out.print("無孩子節點");
			}
			System.out.println();
		}
	}
}
/**
 * 用於存儲樹中雙親孩子節點的信息
 * @author 學徒
 *
 */
class TreeNode
{
	//存儲相關數據
	Object  data;
	//用於存儲孩子節點的孩子節點鏈表
	Queue<Integer> firstChild;
	/**
	 * 用於增加孩子節點的方法
	 */
	public void addChild(int child)
	{
		firstChild.add(child);
	}
}
/**
 * 二叉樹中的節點描述類
 * @author 學徒
 */
class Node
{
	Object data;
	Node left;
	Node right;
}
/**
 * 用於測試使用的類,該代碼采用的測試用例即為“樹轉換成二叉樹的過程示意圖”中的樹
 * @author 學徒
 *
 */
class Test
{
	public static Node test()
	{
		Forest forest=new Forest(8);
		forest.setTreeData(0,"A");
		forest.setTreeData(1,"B");
		forest.setTreeData(2, "C");
		forest.setTreeData(3,"D");
		forest.setTreeData(4,"E");
		forest.setTreeData(5,"F");
		forest.setTreeData(6,"G");
		forest.setTreeData(7, "H");
		forest.addTreeChild(0, 1);
		forest.addTreeChild(0, 2);
		forest.addTreeChild(0, 3);
		forest.addTreeChild(1, 4);
		forest.addTreeChild(1, 5);
		forest.addTreeChild(5, 7);
		forest.addTreeChild(3, 6);
		Node root=forest.toTranslateBinaryTree();
		//得到二叉樹中序遍歷的結果
		System.out.print("中序遍歷的結果 :");
		inRootTraver(root);
		System.out.println();
		//得到二叉樹后序遍歷的結果
		System.out.print("后序遍歷的結果 :");
		postRootTraver(root);
		return root;
	}
	/**
	 * 用於得到二叉樹中序遍歷的結果
	 */
	public static void inRootTraver(Node root)
	{
		if(root!=null)
		{
			inRootTraver(root.left);
			System.out.print(root.data+"  ");
			inRootTraver(root.right);
		}
	}
	
	/**
	 * 用於得到二叉樹后序遍歷的結果(Node root)
	 */
	public static void postRootTraver(Node root)
	{
		if(root!=null)
		{
			postRootTraver(root.left);
			postRootTraver(root.right);
			System.out.print(root.data+"  ");
		}
	}
}

運行結果:
中序遍歷的結果 :E  H  F  B  C  G  D  A  
后序遍歷的結果 :H  F  E  G  D  C  B  A  
0A: 1   3   5   
1B: 2   4   
2E: 無孩子節點
3C: 無孩子節點
4F: 6   
5D: 7   
6H: 無孩子節點
7G: 無孩子節點

森林轉換成二叉樹:

 森林是若干棵樹的集合,而任何一棵和樹對應的二叉樹其根節點的右子樹一定為空,因而可得到將森林轉換成二叉樹的方法如下:

  1. 將森林中的每棵樹轉換成相應的二叉樹
  2. 按照森林中樹的先后順序,將一棵二叉樹視為前一棵二叉樹的右子樹依次連接起來,從而構成一棵二叉樹

下圖給出了將森林轉換為二叉樹的過程:

森林轉化為二叉樹的過程示意圖

從中可以看出,森林與二叉樹之間存在着一一對應的關系。森林中第一棵樹的根節點對應着二叉樹的根節點;森林中第一棵樹的根節點的子樹所構成的森林對應着二叉樹的左子樹;森林中除第一棵樹之外的其它樹所構成的森林所對應着二叉樹的右子樹。

 除了將每棵樹轉化為相應的二叉樹,之后按照森林中樹的先后順序,將一棵二叉樹視為前一棵二叉樹的右子樹依次連接起來,構成一棵二叉樹的方法之外,還存在着一種增加一個虛節點,之后將森林轉化成一棵樹,然后再按照將樹轉化成二叉樹的方式轉化成二叉樹的方法

下圖給出了增加一個虛節點將森林轉換成二叉樹的過程:

增加一個虛節點,將森林轉換成二叉樹的示意圖

森林轉化為二叉樹的代碼與樹轉化為二叉樹的代碼基本相同,此處不再重復

二叉樹轉換成森林:

 二叉樹與森林之間存在着一一對應的關系,可以根據以下步驟將一棵二叉樹轉化為森林:

  1. 將二叉樹的根節點的右孩子節點的各個連線之間依次斷開,將一棵二叉樹分解為多棵二叉樹

  2. 依次將每一棵二叉樹通過加線、刪線、旋轉的方式轉換為一棵樹

  3. 各個樹構成的整體即為一個森林

下圖給出了將一棵二叉樹轉化為森林的過程:

二叉樹轉化為森林的過程

二叉樹轉化為森林的代碼與二叉樹轉化為樹的代碼基本相同,此處不再重復

回到目錄|·(工)·)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM