java中的二叉树
创建一个基本的二叉树,其中节点的插入不依赖于任何特定的排序规则(例如二叉搜索树的规则),你可以设计一个简单的插入逻辑,比如总是向最左侧未填满的位置插入节点。以下是一个简单的Java实现,使用队列来辅助按层次顺序插入节点,不进行大小比较。
1.创建二叉树
步骤 1: 定义 TreeNode 类
首先,定义TreeNode
类,这个类将包含数据和指向左右子节点的引用。
左右节点都为TreeNode类
public class TreeNode {
int data;
TreeNode left;
TreeNode right;
public TreeNode(int data) {
this.data = data;
this.left = null;
this.right = null;
}
}
步骤 2: 创建 BinaryTree 类
接下来,创建一个BinaryTree
类,其中包含插入和层次遍历(用于验证树的结构)的方法。
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;
public class BinaryTree {
TreeNode root;
public BinaryTree() {
root = null;
}
public void insert(int data) {
if (root == null) {
root = new TreeNode(data);
return;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
TreeNode temp = queue.poll();
//将队列头取出并移除,判断队列头节点的左子节点是否为空,不为空加入队列,然后判断右节点
if (temp.left == null) {
temp.left = new TreeNode(data); //新建左节点,连接上一节点
break;
} else {
queue.offer(temp.left);
}
if (temp.right == null) {
temp.right = new TreeNode(data);
break;
} else {
queue.offer(temp.right);
}
}
}
public void printLevelOrder() {
if (root == null) return;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
TreeNode temp = queue.poll();
System.out.print(temp.data + " ");
if (temp.left != null) {
queue.offer(temp.left);
}
if (temp.right != null) {
queue.offer(temp.right);
}
}
}
}
步骤 3: 主函数来驱动程序
最后,创建一个主函数来驱动程序,从用户那里接收输入并插入到树中。
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
BinaryTree tree = new BinaryTree();
System.out.println("Enter the number of nodes:");
int count = scanner.nextInt();
System.out.println("Enter " + count + " integers:");
for (int i = 0; i < count; i++) {
int data = scanner.nextInt();
tree.insert(data);
}
System.out.println("Level order traversal of the binary tree:");
tree.printLevelOrder();
scanner.close();
}
}
这个程序首先定义了一个基本的二叉树节点类TreeNode
,然后定义了一个BinaryTree
类来管理树的插入和层次遍历。在主函数中,程序从用户接收一个整数序列并将它们逐个插入到树中。插入操作不考虑节点数据的大小,而是简单地在第一个找到的可用位置插入新节点。通过层次遍历方法,可以检查树的结构是否正确。
2.遍历二叉树:
示例二叉树:
前序遍历:先根,再左,再右
从根节点开始,在找不到左字节点后再找右子节点:
前序结束结果为:A B D F E C G I J H K
中序遍历:先左,再根,再右
把根节点放中间,左右子树放俩边
结果为:F D B E A I G J C H K
后序遍历:先左,再右,再根
从最后一行,左子树最左节点开始,先遍历左子节点然后右子节点然后根节点,根节点放最后
结果为:F D E B I J G K H C A
代码示例:
1. 前序遍历(Pre-order)
前序遍历首先访问根节点,然后递归地执行前序遍历左子树,接着递归地执行前序遍历右子树。
public void preorder() {
preorderRec(root);
}
private void preorderRec(TreeNode node) {
if (node != null) {
System.out.print(node.data + " ");
preorderRec(node.left);
preorderRec(node.right);
}
}
2. 中序遍历(In-order)
中序遍历首先递归地访问左子树,然后访问根节点,最后递归地访问右子树。对于二叉搜索树,这种遍历会按照升序访问所有节点。
public void inorder() {
inorderRec(root);
}
private void inorderRec(TreeNode node) {
if (node != null) {
inorderRec(node.left);
System.out.print(node.data + " ");
inorderRec(node.right);
}
}
3. 后序遍历(Post-order)
后序遍历首先递归地访问左子树,然后递归地访问右子树,最后访问根节点。
public void postorder() {
postorderRec(root);
}
private void postorderRec(TreeNode node) {
if (node != null) {
postorderRec(node.left);
postorderRec(node.right);
System.out.print(node.data + " ");
}
}
4. 层次遍历(Level-order)
层次遍历从根节点开始,一层层向下遍历,每层从左至右访问所有节点。这通常需要使用队列来实现。
public void printLevelOrder() {
if (root == null) return;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
TreeNode temp = queue.poll();
System.out.print(temp.data + " ");
if (temp.left != null) {
queue.offer(temp.left);
}
if (temp.right != null) {
queue.offer(temp.right);
}
}
}
我将使用一个简单的图表示方法,并展示如何在该图上实现这两种搜索算法。
3.图的表示
首先,定义图的数据结构。我们将使用邻接表来表示图,这是图表示的一种高效方式:
import java.util.*;
class Graph {
private int numVertices; // 图中的顶点数
private LinkedList<Integer>[] adjLists; // 邻接表
// 构造函数
public Graph(int vertices) {
numVertices = vertices;
adjLists = new LinkedList[vertices];
for (int i = 0; i < vertices; i++) {
adjLists[i] = new LinkedList<>();
}
}
// 添加边
void addEdge(int src, int dest) {
adjLists[src].add(dest);
}
// 打印图
void printGraph() {
for (int i = 0; i < numVertices; i++) {
System.out.println("Adjacency list of vertex " + i);
System.out.print("head");
for(Integer node : adjLists[i]){
System.out.print(" -> " + node);
}
System.out.println("\n");
}
}
}
4.深度优先搜索(DFS)
深度优先搜索(DFS)是一种图遍历算法,它的目标是尽可能深地探索图的分支。DFS使用递归或栈数据结构来实现,其基本过程如下:
- 访问起始节点:从图中的某个起始节点开始。
- 标记节点:将起始节点标记为已访问。
- 递归探索:从当前节点出发,递归地访问每个未被访问的邻接节点,直到所有可达的节点都被访问。
- 回溯:一旦到达一个没有未访问邻接节点的节点,算法将回溯到上一个节点,继续探索未被访问的邻接节点。
优点:
- 简单且容易实现。
- 可以用来发现图中所有连接的分量。
- 在找到所有从起点到特定点的路径方面非常有效。
缺点:
- 不保证找到最短路径。
- 递归实现可能导致大量的递归调用,对于非常大的图可能导致栈溢出。
DFS 可以递归实现或使用栈。这里提供递归的实现方法:
void DFS(int vertex, boolean[] visited) {
visited[vertex] = true;
System.out.print(vertex + " ");
for (int adjVertex : adjLists[vertex]) {
if (!visited[adjVertex]) {
DFS(adjVertex, visited);
}
}
}
void DFS(int startVertex) {
boolean[] visited = new boolean[numVertices];
DFS(startVertex, visited);
}
5.广度优先搜索(BFS)
广度优先搜索(BFS)从图的根节点开始,一层层地向外扩展,即它先访问离根节点最近的节点,然后是次近的节点,以此类推。BFS通常使用队列来实现,其步骤如下:
- 初始化队列:将起始节点加入队列,并标记为已访问。
- 队列操作:只要队列不为空,就从队列中移除一个节点,并访问其所有未被访问的邻接节点。
- 标记并入队:将访问的邻接节点标记为已访问,并加入队列。
- 重复执行:重复上述过程,直到队列为空。
优点:
- 可以保证在无权图中找到从起始点到任何可达点的最短路径。
- 层次性的搜索使得BFS特别适用于层次化或最短路径问题。
缺点:
- 在所有层的节点都必须保持在内存中的情况下,可能会消耗较多内存。
- 对于路径的搜索不如DFS那样自然和直接。
BFS 使用队列实现,并逐层遍历图的节点:
void BFS(int startVertex) {
boolean[] visited = new boolean[numVertices];
Queue<Integer> queue = new LinkedList<>();
visited[startVertex] = true;
queue.add(startVertex);
while (!queue.isEmpty()) {
int vertex = queue.poll();
System.out.print(vertex + " ");
for (int adjVertex : adjLists[vertex]) {
if (!visited[adjVertex]) {
visited[adjVertex] = true;
queue.add(adjVertex);
}
}
}
}
应用对比
深度优先搜索(DFS)和广度优先搜索(BFS)是两种基本的图遍历技术,它们在解决许多计算机科学问题中起到核心作用,如网络爬虫、路径查找、社交网络分析等。下面是对这两种搜索策略的详细解释和对比。
- 路径查找:BFS用于在未加权图中查找最短路径;DFS用于探索所有可能的路径。
- 连通性:DFS可以用来分析图的连通性,比如检测图中是否存在环。
- 层次遍历:BFS经常用于需要按层次顺序遍历节点的场合,如在社交网络的好友推荐。