数据结构与算法 -- 树结构与图结构

树的概念

形式化定义:算法的集合树(Tree)是由一个或多个结点组成的有限集合T,其中有一个特定的称为根的结点;其他结点可分为(m≥0)个互不相交的有限集T1,T2,T3,…,Tm,每个集合自己又是一棵树,且称为根的子树。java

逻辑结构:算法

树的表示数组

  • 图形表示法
  • 表表示法:(A(B(E,F),C(G),D(H,I,J)))

树的术语数据结构

  • 结点的度:结点子树个数为结点的度
  • 树的度:树中结点度的最大值
  • 森林:0棵或多棵不相交的树的集合

树的存储结构能够采用具备多个指针域的多重链表,结点中指针域的个数应由树的度来决定。ide

 

二叉树

定义:二叉树是n(n≥0)个结点的有限集,它或者是空集(n=0),或者由一个根结点及两棵不相交的左子树和右子树组成。this

特色spa

  • 在二叉树中,不存在度大于2的结点;
  • 二叉树是有序树(树为无序树),其子树的顺序不能颠倒。

基本形态:二叉树有五种不一样的形态。指针

满二叉树:深度为k具备个结点的二叉树,称为满二叉树。code

彻底二叉树:若是一棵具备n个结点的深度为k的二叉树,它的每个结点都与深度为k的满二叉树中编号为1~n的结点一一对应,则称这棵二叉树为彻底二叉树。彻底二叉树又称为顺序二叉树。blog

性质

  • 若二叉树的层数从1开始,则二叉树的第k层结点数,最多为个(k≥1)。

  • 深度(高度)为k的二叉树最多结点数为(k≥1)。
  • 若对任意一棵二叉树,若是叶子结点(度为0)个数为,度为2的结点个数为,则有
  • 具备n个结点的彻底二叉树高度为

遍历二叉树L,R,T分别表明二叉树的左子树、右子树、根结点,若规定二叉树中必须先左后右(左右顺序不能颠倒),则只有TLRLTRLRT三种遍历规则。

  • TLR称为前根遍历(或前序遍历、先序遍历、先根遍历)
  • LTR称为中根遍历(或中序遍历)
  • LRT称为后根遍历(或后序遍历)

二叉树的存储结构:

  • 顺序存贮结构:将一棵二叉树按彻底二叉树顺序存放到一个一维数组中。
  • 二叉链表存贮结构:将对于非彻底二叉树,宜采用链式存储结构。

二叉树例程:

/**
 * 二叉树
 * @author wangfei
 *
 */
public class TestBinaryTree {

	public static void main(String[] args) {
		//建立一棵树
		BinaryTree binaryTree = new BinaryTree();
		//建立一个根节点
		Node root = new Node(1);
		//把根节点赋值给树
		binaryTree.setRoot(root);
		//建立一个左节点
		Node rootL = new Node(2);
		//把新建立的节点设置为根节点的子节点
		root.setLeftNode(rootL);
		//建立一个右节点
		Node rootR = new Node(3);
		//把新建立的节点设置为根节点的子节点
		root.setRightNode(rootR);
		//为第二层建立两个子节点
		root.setLeftNode(new Node(4));
		root.setRightNode(new Node(5));
		//前序遍历树
		binaryTree.frontShow();
		System.out.println("===========================");
		//中序遍历树
		binaryTree.midShow();
		System.out.println("===========================");
		//后序遍历树
		binaryTree.afterShow();
		System.out.println("===========================");
		//前序查找
		Node result = binaryTree.frontSearch(5);
		System.out.println(result);
		System.out.println("===========================");
		//删除一个子树
		binaryTree.delete(4);
		binaryTree.frontShow();
	}

}
public class BinaryTree {

	Node root;
	
	//设置根节点
	public void setRoot(Node root) {
		this.root = root;
	}
	
	//获取根节点
	public Node getRoot(Node root) {
		return root;
	}
	
	//前序遍历
	public void frontShow() {
		if(root!=null) {
			root.frontShow();
		}
	}
	
	//中序遍历
	public void midShow() {
		if(root!=null) {
			root.midShow();
		}
	}
	
	//后序遍历
	public void afterShow() {
		if(root!=null) {
			root.afterShow();
		}
	}
	
	//前序查找
	public Node frontSearch(int i) {
		return root.frontSearch(i);
	}
	
	//删除一个子树
	public void delete(int i) {
		if(root.value==i) {
			root = null;
		}else {
			root.delete(i);
		}
	}
}
public class Node {

	//节点的权
	int value;
	//左儿子
	Node leftNode;
	//右儿子
	Node rightNode;
	
	public Node(int value) {
		this.value = value;
	}
	
	//设置左儿子
	public void setLeftNode(Node leftNode) {
		this.leftNode = leftNode;
	}
	//设置右儿子
	public void setRightNode(Node rightNode) {
		this.rightNode = rightNode;
	}
	
	//前序遍历
	public void frontShow() {
		//先遍历当前节点内容
		System.out.println(value);
		//左节点
		if(leftNode!=null) {
			leftNode.frontShow();
		}
		//右节点
		if(rightNode!=null) {
			rightNode.frontShow();
		}
	}

	//中序遍历
	public void midShow() {
		//左子节点
		if(leftNode!=null) {
			leftNode.midShow();
		}
		//当前节点
		System.out.println(value);
		//右子节点
		if(rightNode!=null) {
			rightNode.midShow();
		}
	}

	//后序遍历
	public void afterShow() {
		//左子节点
		if(leftNode!=null) {
			leftNode.afterShow();
		}
		//右子节点
		if(rightNode!=null) {
			rightNode.afterShow();
		}
		//当前节点
		System.out.println(value);
	}

	//前序查找
	public Node frontSearch(int i) {
		Node target = null;
		//对比当前节点的值
		if(this.value==i) {
			return this;
		//当前节点的值不是要查找的节点
		}else {
			//查找左儿子
			if(leftNode!=null) {
				//查不到的话,target仍是null
				target = leftNode.frontSearch(i);
			}
			//若是不为空,说明在左儿子中已经找到
			if(target!=null) {
				return target;
			}
			//查找右儿子
			if(rightNode!=null) {
				target = rightNode.frontSearch(i);
			}
		}
		return target;
	}

	//删除节点
	public void delete(int i) {
		Node parent = this;
		//判断左儿子
		if(parent.leftNode!=null && parent.leftNode.value==i) {
			parent.leftNode = null;
			return;
		}
		//判断右儿子
		if(parent.rightNode!=null && parent.rightNode.value==i) {
			parent.rightNode = null;
			return;
		}
		//递归检查并删除左儿子
		parent = leftNode;
		if(parent!=null) {
			parent.delete(i);
		}
		//递归检查并删除右儿子
		parent = rightNode;
		if(parent!=null) {
			parent.delete(i);
		}
		
	}
}

 

图的概念

定义图是由顶点集V和顶点间的关系集合E(边的集合)组成的一种数据结构,能够用二元组定义为:G=(V,E)。

有向图和无向图:为新的对话框类添加方在图中,若用箭头标明了边是有方向性的,则称这样的图为有向图,不然称为无向图。

上图可描述为:

G1=(V1,E),V1={a,b,c,d},E1={(a,b),(a,c),(a,d),(b,d),(c,d)}

G2=(V2,E2) V2={1,2,3}E2={<1,2>,<1,3>,<2,3>,<3,1>}

度、入度、出度:具备n个顶点,在图中,一个顶点依附的边或弧的数目,称为该顶点的度。在有向图中,一个顶点依附的弧头数目,称为该顶点的入度。一个顶点依附的弧尾数目,称为该顶点的出度,某个顶点的入度和出度之和称为该顶点的度。

另外,若图中有n个顶点,e条边或弧,第i个顶点的度为di,则有

:若在图的边或弧中给出相关的数,称为权。权能够表明一个顶点到另外一个顶点的距离,耗费等,带权图通常称为网。

连通图和非连通图在无向图中,若任意两个顶点都是连通的,则称此无向图为连通图,不然称为非连通图。

强连通图和非强连通图在有向图中,若图中任意两个顶点都是连通的,则称此有向图为强连通图,不然称为非强连通图

 

图的邻接矩阵

定义:

在邻接矩阵表示中,除了存放顶点自己信息外,还用一个矩阵表示各个顶点之间的关系。若(ij)∈E(G)或〈ij〉∈E(G),则矩阵中第i行第j列元素值为1,不然为0

从无向图的邻接矩阵能够得出以下结论:

  • 矩阵是对称的;
  • i行或第i1的个数为顶点i的度;
  • 矩阵中1的个数的一半为图中边的数目;
  • 很容易判断顶点i和顶点j之间是否有边相连(看矩阵中ij列值是否为1)。

从有向图的邻接矩阵能够得出以下结论:

  • 矩阵不必定是对称的;
  • i行中1的个数为顶点i的出度;
  • i列中1的个数为顶点i的入度;
  • 矩阵中1的个数为图中弧的数目;
  • 很容易判断顶点i和顶点j是否有弧相连。

 

图的邻接表

定义将每一个结点的边用一个单链表连接起来,若干个结点能够获得若干个单链表,每一个单链表都有一个头结点,全部头结点组成一个一维数组,称这样的链表为邻接表。

从无向图的邻接表能够获得以下结论

  • 第i个链表中结点数目为顶点i的度;
  • 全部链表中结点数目的一半为图中的边数;
  • 占用的存储单元数目为n+2e(e:边数)。

从有向图的邻接表能够获得以下结论

  • 第i个链表中结点数目为顶点i的出度;
  • 全部链表中结点数目为图中弧数;
  • 占用的存储单元数目为n+e。

深度优先搜索思想:深度优先搜索遍历相似于树的先序遍历。假定给定图G的初态是全部顶点均未被访问过,在G中任选一个顶点i做为遍历的初始点,则深度优先搜索遍历可定义以下:

  • 首先访问顶点i,并将其访问标记置为访问过,即visited[i] =1;
  • 而后搜索与顶点i有边相连的下一个顶点j,若j未被访问过,则访问它,并将j的访问标记置为访问过,visited[j]=1,而后从j开始重复此过程,若j已访问,再看与i有边相连的其它顶点;
  • 若与i有边相连的顶点都被访问过,则退回到前一个访问顶点并重复刚才过程,直到图中全部顶点都被访问完止。

广度优先搜索的思想:广度优先搜索遍历相似于树的按层次遍历。设图G的初态是全部顶点均未访问,在G中任选一顶点i做为初始点,则广度优先搜索的基本思想是:

  • 首先访问顶点i,并将其访问标志置为已被访问,即visited[i]=1;
  • 接着依次访问与顶点i有边相连的全部顶点W1,W2,…,Wt;
  • 而后再按顺序访问与W1,W2,…,Wt有边相连又不曾访问过的顶点;
  • 依此类推,直到图中全部顶点都被访问完为止。

图结构例程:

/**
 * 图
 * @author wangfei
 *
 */
public class Graph {

	private Vertex[] vertexs;
	private int currentSize;
	public int[][] adjMat;
	private MyStack stack = new MyStack();
	//当前遍历的下标
	private int currentIndex;
	
	public Graph(int size) {
		vertexs = new Vertex[size];
		adjMat = new int[size][size];
	}
	
	/**
	 * 向图中加入一个顶点
	 * @param v
	 */
	public void addVertex(Vertex v) {
		vertexs[currentSize++] = v;
	}
	
	/**
	 * 向相邻顶点添加链接线
	 * @param v1
	 * @param v2
	 */
	public void addEdge(String v1, String v2) {
		//找出两个顶点的下标
		int index1 = 0;
		for(int i=0; i<vertexs.length; i++) {
			if(vertexs[i].getValue().equals(v1)) {
				index1 = i;
				break;
			}
		}
		int index2 = 0;
		for(int i=0; i<vertexs.length; i++) {
			if(vertexs[i].getValue().equals(v2)) {
				index2 = i;
				break;
			}
		}
		adjMat[index1][index2] = 1;
		adjMat[index2][index1] = 1;
	}
	
	/**
	 * 深度优先搜索算法遍历图
	 */
	public void dfs() {
		//把第0个顶点标记为已访问状态
		vertexs[0].visited = true;
		//把第0个元素压入栈中
		stack.push(0);
		//打印顶点的值
		System.out.println(vertexs[0].getValue());
		//遍历
		out:while(!stack.isEmpty()) {
			for(int i=currentIndex; i<vertexs.length; i++) {
				//若是和下一个遍历的元素是通的
				if(adjMat[currentIndex][i]==1 && vertexs[i].visited==false) {
					//把下一个元素压入栈中
					stack.push(i);
					vertexs[i].visited = true;
					System.out.println(vertexs[i].getValue());
					continue out;
				}
			}
			//弹出栈顶元素
			stack.pop();
			//修改当前位置为栈顶元素
			if(!stack.isEmpty()) {
				currentIndex = stack.peek();
			}
		}
	}
}
/**
 * 顶点类
 * @author wangfei 
 *
 */
public class Vertex {

	private String value;
	public boolean visited;
	
	
	public String getValue() {
		return value;
	}
	
	public void setValue(String value) {
		this.value = value;
	}

	public Vertex(String value) {
		super();
		this.value = value;
	}
	
	@Override
	public String toString() {
		return value;
	}
	
	
}
public class MyStack {
	
	//栈的底层咱们使用数组来存储数据
		int[] elements;

		public MyStack() {
			elements = new int[0];
		}
		
		//压入元素
		public void push(int element) {
			// 建立一个新的数组
			int[] newArr = new int[elements.length + 1];
			// 把原数组中的元素复制到新数组中
			for (int i = 0; i < elements.length; i++) {
				newArr[i] = elements[i];
			}
			// 把添加的元素放入新数组中
			newArr[elements.length] = element;
			// 使用新数组替换旧数组
			elements = newArr;
		}
		
		//取出栈顶元素
		public int pop() {
			//栈中没有元素
			if(elements.length==0) {
				throw new RuntimeException("stack is empty");
			}
			//取出数组的最后一个元素
			int element = elements[elements.length-1];
			//建立一个新的数组
			int[] newArr = new int[elements.length-1];
			//原数组中除了最后一个元素的其它元素都放入新的数组中
			for(int i=0;i<elements.length-1;i++) {
				newArr[i]=elements[i];
			}
			//替换数组
			elements=newArr;
			//返回栈顶元素
			return element;
		}
		
		//查看栈顶元素
		public int peek() {
			//栈中没有元素
			if(elements.length==0) {
				throw new RuntimeException("stack is empty");
			}
			return elements[elements.length-1];
		}
		
		//判断栈是否为空
		public boolean isEmpty() {
			return elements.length==0;
		}
		
}