树与二叉树21~~25

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

提示:这里可以添加本文要记录的大概内容:

例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。


提示:以下是本篇文章正文内容,下面案例可供参考

Day21——二叉树的深度遍历的递归实现

树——一对多结构

树的概念

1.有一个特殊的结点,称为根结点,根结点没有前驱结点
2.除根结点外,其余结点被分成M(M>0)个互不相交的集合T1、T2…Tm,其中每一个集合Ti(1<=i<=m)又是一颗结构与树类似的子树.每棵子树的根结点有且只有一个前驱,可以有0个或多个后继,因此,树是递归定义的.

数据结构知识复习:

在这里插入图片描述

结点:由两部分组成,数据和指向其他子树的指针
结点的度:结点拥有的子树个数
叶结点度为0的结点,也称终端结点,
分支结点:度不为0的结点
祖先结点:从根节点到该结点所经分支上的所有结点
树的度:树中所有结点的度的最大值
树的深度:树中所有结点的层次的最大值

二叉树

二叉树的存储

二叉树可以选用顺序表和链表进行存储,再用顺序表存储时以将二叉树按照逐层编号,然后存储依照编号存储于顺序表当中。但是,在遇到存储非完全二叉树时,会造成存储空间的极大浪费。
故链表反而是存储二叉树的最佳方案。、
链表在存储空间上是离散的,结点与结点间通过域形成逻辑连接。将其应用到二叉树的存储上,不难推断出,我们需要再增加一个域,这样使一个结点的域可以分别指向其左右孩子

	/**
	 * The value in char.
	 */
	char value;
	
	/**
	 * The left child.
	 */
	BinaryCharTree leftChild;
 
	/**
	 * The right child.
	 */
	BinaryCharTree rightChild;
 
	/**
	 *********************
	 * The first constructor
	 * 
	 * @param paraName The value.
	 *********************
	 */
	public BinaryCharTree(char paraName) {
		value = paraName;
		leftChild = null;
		rightChild = null;
	}// Of the constructor

求二叉树的结点数目

Sum = 1 + getLeftChild() + getRightChild()

	/**
	 *********************
	 * Get the number of nodes.
	 * 
	 * @return The number of nodes.
	 *********************
	 */
	public int getNumNodes() {
		// It is a leaf.
		if ((leftChild == null) && (rightChild == null)) {
			return 1;
		} // Of if
 
		// The number of nodes of the left child.
		int tempLeftNodes = 0;
		if (leftChild != null) {
			tempLeftNodes = leftChild.getNumNodes();
		} // Of if
 
		// The number of nodes of the right child.
		int tempRightNodes = 0;
		if (rightChild != null) {
			tempRightNodes = rightChild.getNumNodes();
		} // Of if
 
		// The total number of nodes.
		return tempLeftNodes + tempRightNodes + 1;
	}

求二叉树的深度

Sum = 1 + MAX(getLeftChild() + getRightChild())

	/**
	 *********************
	 * Get the depth of the binary tree.
	 * 
	 * @return The depth. It is 1 if there is only one node, i.e., the root.
	 *********************
	 */
	public int getDepth() {
		// It is a leaf.
		if ((leftChild == null) && (rightChild == null)) {
			return 1;
		} // Of if
 
		// The depth of the left child.
		int tempLeftDepth = 0;
		if (leftChild != null) {
			tempLeftDepth = leftChild.getDepth();
		} // Of if
 
		// The depth of the right child.
		int tempRightDepth = 0;
		if (rightChild != null) {
			tempRightDepth = rightChild.getDepth();
		} // Of if
 
		// The depth should increment by 1.
		if (tempLeftDepth >= tempRightDepth) {
			return tempLeftDepth + 1;
		} else {
			return tempRightDepth + 1;
		} // Of if
	}// Of getDepth

二叉树的遍历

主要方法:前序遍历、中序遍历、后续遍历、层次遍历

前序遍历的实现

方法:“根节点 - 左子树 - 右子树”

	/**
	 *********************
	 * Pre-order visit.
	 *********************
	 */
	public void preOrderVisit() {
		System.out.print("" + value + " ");
 
		if (leftChild != null) {
			leftChild.preOrderVisit();
		} // Of if
 
		if (rightChild != null) {
			rightChild.preOrderVisit();
		} // Of if
	}// Of preOrderVisit

中序遍历

方法:左子树 - 根节点 - 右子树

	/**
	 *********************
	 * In-order visit.
	 *********************
	 */
	public void inOrderVisit() {
		if (leftChild != null) {
			leftChild.inOrderVisit();
		} // Of if
 
		System.out.print("" + value + " ");
 
		if (rightChild != null) {
			rightChild.inOrderVisit();
		} // Of if
	}// Of inOrderVisit

后序遍历

	/**
	 *********************
	 * Post-order visit.
	 *********************
	 */
	public void postOrderVisit() {
		if (leftChild != null) {
			leftChild.postOrderVisit();
		} // Of if
 
		if (rightChild != null) {
			rightChild.postOrderVisit();
		} // Of if
 
		System.out.print("" + value + " ");
	}// Of postOrderVisit

数据模拟

	/**
	 *********************
	 * Mannually construct a tree. Only for testing.
	 *********************
	 */
	public static BinaryCharTree manualConstructTree() {
		// Step 1. Construuct a tree with only one node.
		BinaryCharTree resultTree = new BinaryCharTree('a');
 
		// Step 2. Construct all nodes. The first node is the root.
		BinaryCharTree tempTreeB = new BinaryCharTree('b');
		BinaryCharTree tempTreeC = new BinaryCharTree('c');
		BinaryCharTree tempTreeD = new BinaryCharTree('d');
		BinaryCharTree tempTreeE = new BinaryCharTree('e');
		BinaryCharTree tempTreeF = new BinaryCharTree('f');
		BinaryCharTree tempTreeG = new BinaryCharTree('g');
 
		// Step 3. Link all nodes.
		resultTree.leftChild = tempTreeB;
		resultTree.rightChild = tempTreeC;
		tempTreeB.rightChild = tempTreeD;
		tempTreeC.leftChild = tempTreeE;
		tempTreeD.leftChild = tempTreeF;
		tempTreeD.rightChild = tempTreeG;
 
		return resultTree;
	}// Of manualConstructTree

	/**
	 *********************
	 * The entrance of the program.
	 * 
	 * @param args Not used now.
	 *********************
	 */
	public static void main(String args[]) {
		BinaryCharTree tempTree = manualConstructTree();
		System.out.println("\r\nPreorder visit:");
		tempTree.preOrderVisit();
		System.out.println("\r\nIn-order visit:");
		tempTree.inOrderVisit();
		System.out.println("\r\nPost-order visit:");
		tempTree.postOrderVisit();
 
		System.out.println("\r\n\r\nThe depth is: " + tempTree.getDepth());
		System.out.println("The number of nodes is: " + tempTree.getNumNodes());
	}// Of main

在这里插入图片描述

Day22——二叉树的存储

我们知道由中序遍历和前或后序遍历中的任意一个可以得到一个二叉树。但是在二叉树的实际存储中运用这样的方法会比较麻烦,逻辑理解上会比较困难,不符合代码的简洁性。
本小节引出对二叉树的常用存储方法。

一个向量存储数据的冗杂

1.用一个向量存储如下二叉树
在这里插入图片描述
如果我们用顺序表的层次遍历表示就是:[a, b, c, 0, d, e, 0, 0, 0, f, g]。用起来非常的浪费存储空间。

压缩存储的优势

压缩存储方式, 即将节点的位置和值均存储. 可表示为两个向量:
[0, 1, 2, 4, 5, 9, 10]
[a, b, c, d, e, f, g]
利用记录下标来对结点在树中的存储结构的映射,在实际应用上减小了冗余。对于有n个结点的树,转储的空间开销就被缩小为2n。
对一个结点,若按照层次遍历序号为其编序,某个结点的标号为i(从0开始),那么他的左孩子下标就为2i+1(若存在),右孩子下标为2i+2(若存在)
在这里插入图片描述

广度优先搜索

其别名又叫BFS,属于一种盲目搜寻法,目的是系统地展开并检查图中的所有节点,以找寻结果。换句话说,它并不考虑结果的可能位置,彻底地搜索整张图,直到找到结果为止。

BFS的使用过程必须要利用队列最为中间存储,并且全程遍历都已队列的状况为遍历的核心。

数组的声明与队列创建

    /**
	 * The values of nodes according to breadth first treversal.
	 */
	char[] valuesArray;
 
	/**
	 * The indices in the complete binary tree.
	 */
	int[] indicesArray;

        // Initialize arrays.
		int tempLength = getNumNodes();
 
		valuesArray = new char[tempLength];
		indicesArray = new int[tempLength];
		int i = 0;
 
		// Traverse and convert at the same time.
		CircleObjectQueue tempQueue = new CircleObjectQueue();
		tempQueue.enqueue(this);
		CircleIntQueue tempIntQueue = new CircleIntQueue();
		tempIntQueue.enqueue(0);

初始化了BFS需要用到的两个循环队列:记录下标的整型队列,记录链表树的每个结点的类引用的队列。(this是当前类的引用,可以用this访问当前类属性与方法)
this反映了这个方法所在类所构造的对象,也就链树中的某个结点,因为我们转储的起点是根节点,所以这个操作就是就是认定此结点为根节点并向下层次遍历,从而进行转储。

核心代码

       public void toDataArrays() {
		// Initialize arrays.
		int tempLength = getNumNodes();
 
		valuesArray = new char[tempLength];
		indicesArray = new int[tempLength];
		int i = 0;
 
		// Traverse and convert at the same time.
		CircleObjectQueue tempQueue = new CircleObjectQueue();
		tempQueue.enqueue(this);
		CircleIntQueue tempIntQueue = new CircleIntQueue();
		tempIntQueue.enqueue(0);
 
		BinaryCharTree tempTree = (BinaryCharTree) tempQueue.dequeue();
		int tempIndex = tempIntQueue.dequeue();
		while (tempTree != null) {
			valuesArray[i] = tempTree.value;
			indicesArray[i] = tempIndex;
			i++;
 
			if (tempTree.leftChild != null) {
				tempQueue.enqueue(tempTree.leftChild);
				tempIntQueue.enqueue(tempIndex * 2 + 1);
			} // Of if
 
			if (tempTree.rightChild != null) {
				tempQueue.enqueue(tempTree.rightChild);
				tempIntQueue.enqueue(tempIndex * 2 + 2);
			} // Of if
 
			tempTree = (BinaryCharTree) tempQueue.dequeue();
			tempIndex = tempIntQueue.dequeue();
		} // Of while

BFS可以总结为下面的步骤:

1.取出队列中的元素作为父级(出队)
2.父级是否合法?若不合法说明队列已经空,程序结束。若合法,进入下一步
3.遍历当前与父级相邻的结点,若其合法,将其纳入队列(入队)
三个步骤中,我们需要引入对于数据的操作。可以在第2步到第3步之间加入一个2.5步,执行对于父级的操作 ,当然这个操作也可以放在第三步,在入队前对子元素进行操作操作是什么视目标而定,如果是打印结点,那么最终就会得到一个层次遍历结果。
核心代码中,将“操作” 放到2.5步了,并且“操作” 的内容是转储:将结点引用与下标存放到我们初始化的转储数组中。这个过程中我们不断维护两个队列,其中入队操作会因为查看父级有关的邻边的操作不同而不同,如果是结点引用的话,那么父级就是利用leftChild / rightChild属性访问子元素;如果是下标的话,那么父级就是利用i2+1和i2+2属性访问子元素

数据模拟

	/**
	 *********************
	 * The entrance of the program.
	 * 
	 * @param args Not used now.
	 *********************
	 */
	public static void main(String args[]) {
		BinaryCharTree tempTree = manualConstructTree();
		System.out.println("\r\nPreorder visit:");
		tempTree.preOrderVisit();
		System.out.println("\r\nIn-order visit:");
		tempTree.inOrderVisit();
		System.out.println("\r\nPost-order visit:");
		tempTree.postOrderVisit();
 
		System.out.println("\r\n\r\nThe depth is: " + tempTree.getDepth());
		System.out.println("The number of nodes is: " + tempTree.getNumNodes());
 
		tempTree.toDataArrays();
		System.out.println("The values are: " + Arrays.toString(tempTree.valuesArray));
		System.out.println("The indices are: " + Arrays.toString(tempTree.indicesArray));
 
	}// Of main

在这里插入图片描述
测试数据成功实现了对树的存储,树如下
在这里插入图片描述

Day23——使用具有通用性的队列

object类

1.object类类位于 java.lang 包中,是所有类组成的网络的树根
2.一个类的父类若没有明确说明,那么他就会自动继承object类。
3.一切正常定义的类都隐式继承了Object类

用object类构建循环队列

package datastructure.queue;
 
/**
 * The usage of the if statement.
 * 
 * @author Xingyi Zhang 1328365276@qq.com
 */
 
public class CircleObjectQueue {
 
	/**
	 * The total space. One space can never be used.
	 */
	public static final int TOTAL_SPACE = 10;
 
	/**
	 * The data.
	 */
	Object[] data;
 
	/**
	 * The index of the head.
	 */
	int head;
 
	/**
	 * The index of the tail.
	 */
	int tail;
 
	/**
	 ******************* 
	 * The constructor
	 ******************* 
	 */
	public CircleObjectQueue() {
		data = new Object[TOTAL_SPACE];
		head = 0;
		tail = 0;
	}// Of the first constructor
 
	/**
	 *********************
	 * Enqueue.
	 * 
	 * @param paraValue The value of the new node.
	 *********************
	 */
	public void enqueue(Object paraValue) {
		if ((tail + 1) % TOTAL_SPACE == head) {
			System.out.println("Queue full.");
			return;
		} // Of if
 
		data[tail % TOTAL_SPACE] = paraValue;
		tail++;
	}// Of enqueue
 
	/**
	 *********************
	 * Dequeue.
	 * 
	 * @return The value at the head.
	 *********************
	 */
	public Object dequeue() {
		if (head == tail) {
			// System.out.println("No element in the queue");
			return null;
		} // Of if
 
		Object resultValue = data[head % TOTAL_SPACE];
 
		head++;
 
		return resultValue;
	}// Of dequeue
 
	/**
	 *********************
	 * Overrides the method claimed in Object, the superclass of any class.
	 *********************
	 */
	public String toString() {
		String resultString = "";
 
		if (head == tail) {
			return "empty";
		} // Of if
 
		for (int i = head; i < tail; i++) {
			resultString += data[i % TOTAL_SPACE] + ", ";
		} // Of for i
 
		return resultString;
	}// Of toString
 
	/**
	 *********************
	 * The entrance of the program.
	 * 
	 * @param args Not used now.
	 *********************
	 */
	public static void main(String args[]) {
		CircleObjectQueue tempQueue = new CircleObjectQueue();
	}// Of main
 
}// Of CircleObjectQueue

对比以前学习过的循环队列,本次的代码只是将char改为了object。要知道object类有自动类型转换功能,且object类支持null的返回

Integer类

定义:integer类是 int 基本数据类型的封装类
和int的区别:
①、Integer 是 int 包装类,int 是八大基本数据类型之一

②、Integer 是类,默认值为null,int是基本数据类型,默认值为0;

③、Integer 表示的是对象,用一个引用指向这个对象,而int是基本数据类型,直接存储数值。

Integer 的自动拆箱和装箱

①、自动装箱

Integer a = 128;

通过反编译可以发现生成的class文件为

Integer a = Integer.valueOf(128);

自动装箱规范要求 byte<= 127、char<=127、-128<=short <=127、-128<=int <=127都被包装到固定的对象中(缓存)
②、自动拆箱
 我们将 Integer 类表示的数据赋值给基本数据类型int,就执行了自动拆箱。

Integer a = new Integer(128);
int m = a;

反编译后

Integer a = new Integer(128);
int m = a.intValue();
简单来讲:自动装箱就是Integer.valueOf(int i);自动拆箱就是 i.intValue();

一个例子如下:

    Integer i = 10;
    Integer j = 10;
    System.out.println(i == j);
    }

打印结果为true
对于 i == j ,我们知道这是两个Integer类,他们比较应该是用equals,这里用==比较的是地址,那么结果肯定为false,但是实际上结果为true,这是为什么?

我们进入到Integer 类的valueOf()方法:
  在这里插入图片描述
分析源码我们可以知道在i >= -128 并且 i <= 127的时候,第一次声明会将 i 的值放入缓存中,第二次直接取缓存里面的数据,而不是重新创建一个Ingeter 对象。那么第一个打印结果因为 i = 10 在缓存表示范围内,所以为 true。

day22代码重构

    // Initialize arrays.
	int tempLength = getNumNodes();
 
	valuesArray = new char[tempLength];
	indicesArray = new int[tempLength];
	int i = 0;


	    // Traverse and convert at the same time.
	CircleObjectQueue tempQueue = new CircleObjectQueue();
	tempQueue.enqueue(this);
	CircleObjectQueue tempIntQueue = new CircleObjectQueue();
	Integer tempIndexInteger = Integer.valueOf(0);
	//初始化了一个值为0的Integer对象
	tempIntQueue.enqueue(tempIndexInteger);

核心代码部分逻辑上一致,但需注意Object类型要变为可以在BFS的循环体中可以使用的逻辑主体,需要利用强制转换将其变为特定的子类

BinaryCharTree tempTree = (BinaryCharTree) tempQueue.dequeue();
int tempIndex = (Integer) tempIntQueue.dequeue();
//由于integer类有自动拆箱功能,故第二句是简化后的样子

Day24——顺序表建立二叉树

前提

顺序表构建的完全二叉树的特点:若孩子结点存在,则可以用当前节点的下标索引通过2 * i + 1直接访问其左孩子节点,2 * i + 2访问其右孩子节点。

堆排序( Heap Sort )算法简介:

1.堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
2. 算法描述

代码实现

用队列存储所有的结点

    // Step 1. Use a sequential list to store all nodes.
	int tempNumNodes = paraDataArray.length;
	BinaryCharTree[] tempAllNodes = new BinaryCharTree[tempNumNodes];
	for (int i = 0; i < tempNumNodes; i++) {
		tempAllNodes[i] = new BinaryCharTree(paraDataArray[i]);
	} // Of for i

有序链接结点

	// Step 2.Link these nodes.
	for (int i = 1; i < tempNumNodes; i++) {
		for (int j = 0; j < i; j++) {
			System.out.println("indices " + paraIndicesArray[j] + " vs. " + paraIndicesArray[i]);
			if (paraIndicesArray[i] == paraIndicesArray[j] * 2 + 1) {
				tempAllNodes[j].leftChild = tempAllNodes[i];
				System.out.println("Linking " + j + " with " + i);
				break;
			} else if (paraIndicesArray[i] == paraIndicesArray[j] * 2 + 2) {
				tempAllNodes[j].rightChild = tempAllNodes[i];
				System.out.println("Linking " + j + " with " + i);
				break;
			} // Of if
		} // Of for j
	} // Of for i

我们先得到一个非根结点i,然后用j枚举所有小于i的数,得到结点j,结点j一定是i的父辈,而其中定然有一个结点j是i的直接父辈,于是进行连接

核心代码部部分

从树的第一个非根分支结点开始,逐层向下进行遍历,遍历过程中枚举他的全部长辈,直到枚举出父级,并进行连接。
即进行全部每个结点的前继枚举(若其有前继)

// Step 3. The root is the first node.
	value = tempAllNodes[0].value;
	leftChild = tempAllNodes[0].leftChild;
	rightChild = tempAllNodes[0].rightChild;

最后,将我们创建的链树的根交付给当前对象的属性,从而完成基于双顺序表地对当前对象的二叉树创建。因为是引用,只要交付根节点即可实现访问.

数据模拟

    char[] tempCharArray = { 'A', 'B', 'C', 'D', 'E', 'F' };
	int[] tempIndicesArray = { 0, 1, 2, 4, 5, 12 };
	BinaryCharTree tempTree2 = new BinaryCharTree(tempCharArray, tempIndicesArray);
 
	System.out.println("\r\nPreorder visit:");
	tempTree2.preOrderVisit();
	System.out.println("\r\nIn-order visit:");
	tempTree2.inOrderVisit();
	System.out.println("\r\nPost-order visit:");
	tempTree2.postOrderVisit();

运行结果:
在这里插入图片描述

Day25—— 栈模拟树的中序遍历

用栈实现递归

递归的本质:搁置当前选择
递归与迭代:
递归设计的思想是分解问题为若干个操作相同的子操作的过程
迭代需要将问题化解若干步类型可能不同,操作复杂级也可能不同的操作

左——根——右

先按中序遍历的原理将路过的结点压入栈中,如下图,递归的顺序为A->B->D,按此顺序入栈。真正访问结点的顺序应为结点出栈的顺序。
在这里插入图片描述

当不断递归直到D结点时,由于其没有左孩子故继续访问失败,则打印图中语句,当前结点d出栈,并移步到判断其有无右孩子,此时D结点无右孩子,故当前结点自然结束。进入下一轮,下一轮与当前已经是完全不同的结点,记录这是一个截然不同的调入点2。如果其有右孩子,那么右孩子入栈,这部分操作转换为下一个右孩子的操作,右孩子自己也是个独立的结点,所以记录这是一个截然不同的调入点3。
在这里插入图片描述
在这里插入图片描述

可总结为四个步骤:

  1. 得到了当前的核心结点(核心结点的含义其实可以等价理解为递归方法中的当前函数)
  2. 核心结点若有左子树:【左结点入栈,进入左子树,并回到第一步】;若无则进入第三步
  3. 出栈取得结点,将其作为核心结点,并访问其值
  4. 若核心结点有右子树,【右结点入栈,进入右子树,并回到第一步】;若无则进入第三步

中序遍历代码实现

	/**
	 * 
	 *********************
	 * In-order visit with stack.
	 *********************
	 */
	public void inOrderVisitWithStack() {
		ObjectStack tempStack = new ObjectStack();
		BinaryCharTree tempNode = this;
		while (!tempStack.isEmpty() || tempNode != null) {
			if (tempNode != null) {
				tempStack.push(tempNode);
				tempNode = tempNode.leftChild;
			} else {
				tempNode = (BinaryCharTree) tempStack.pop();
				System.out.print("" + tempNode.value + " ");
				tempNode = tempNode.rightChild;
			} // Of if
		} // Of while
	}// Of inOrderVisitWithStack

数据模拟

在这里插入图片描述

	char[] tempCharArray = { 'A', 'B', 'C', 'D', 'E', 'F' };
	int[] tempIndicesArray = { 0, 1, 2, 4, 5, 12 };
	BinaryCharTree tempTree2 = new BinaryCharTree(tempCharArray, tempIndicesArray);
 
	System.out.println("\r\nPreorder visit:");
	tempTree2.preOrderVisit();
	System.out.println("\r\nIn-order visit:");
	tempTree2.inOrderVisit();
	System.out.println("\r\nPost-order visit:");
	tempTree2.postOrderVisit();
	
	System.out.println("\r\nIn-order visit with stack:");
	tempTree2.inOrderVisitWithStack();

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值