数据结构与算法weeks04

       树结构的实际应用、图
       在这里插入图片描述

开始~~~



一、树结构的实际应用

1.堆排序

1.1基本介绍

       1)堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序。
       2)堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆。注意 : 没有要求结点的左孩子的值和右孩子的值的大小关系。
       3)每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆
在这里插入图片描述       上述堆中的节点可以按层进行编号,即映射到数组中:
在这里插入图片描述
       如果当前节点值为arr[i],则其左右节点值为:arr[2i + 1]、arr[2i + 2]
       4)每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆
在这里插入图片描述

       5)一般升序采用大顶堆,降序采用小顶堆

1.2基本思想

       升序排列需要使用堆排序。

       1)将待排序序列构造成一个大顶堆。此时,整个序列的最大值就是堆顶的根节点。
       2)将根节点与末尾元素进行交换,此时末尾就为最大值。
       3)然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。

1.3代码

       1)将一个数组转成大顶堆

public static void adjustHeap(int arr[],int i,int length){
	int temp = arr[i];//先取出当前元素的值,保存在临时变量
	//开始调整
	//1.k = i * 2 + 1,k是i结点的左子结点
	for(int k = i * 2 + 1;k < length;k = k * 2 + 2){
		if(k + 1 < length && arr[k] < arr[k + 1]){//说明左子结点的值小于右子结点的值
			k++;//k指向右子节点
		}
		if(arr[k] > temp){//如果子结点大于父结点
			arr[i] = arr[k];//把较大的值赋给当前结点
			i = k;//i指向k,继续循环比较
		}else{
			break;
		}
	}
	//当for循环结束后,我们已经以i为父结点的树的最大值,放在了最顶(局部)
	arr[i] = temp;//将temp值放到调整后的位置
}

       2)堆排序

public static void heapSort(int arr[]){
	int temp = 0;
	//将无序序列构建一个堆,根据升降序需求选择大顶堆或小顶堆
	for(int i = arr.length / 2 - 1;i >= 0;i--){
		adjustHeap(arr, i, arr.length);
	}
	//2).将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;
	//3).重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列序。
	for(int j = arr.length - 1;j > 0;j--){
		//交换
		temp = arr[j];
		arr[j] = arr[0];
		arr[0] = temp;
		adjustHeap(arr, 0, j);
	}
}

2.赫夫曼树及其应用

2.1赫夫曼树

2.1.1基本介绍

       1)给定n个权值作为n个叶子结点,构造一棵二叉树,若该树的带权路径长度(wpl)达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。
       2)赫夫曼树是带权路径长度最短的树,权值较大的结点离根较近。

2.1.2重要概念和举例说明

       1)路径和路径长度
       2)结点的权及带权路径长度
       3)树的带权路径长度
       4)WPL最小的就是赫夫曼树
在这里插入图片描述       第二个为赫夫曼树。

2.1.3构成赫夫曼树的步骤

       1)将节点按照权值从小到大进行排序
       2)取出根节点权值最小的两颗二叉树
       3)组成一颗新的二叉树, 该新的二叉树的根节点的权值是前面两颗二叉树根节点权值的和
       4)再将这颗新的二叉树,以根节点的权值大小 再次排序, 不断重复 1-2-3-4 的步骤,直到数列中,所有的数据都被处理,就得到一颗赫夫曼树。

2.1.4代码
public static Node createHuffmanTree(int[] arr){
	//1.遍历arr数组
	//2.将arr的每个元素构成一个Node
	//3.将Node放入到ArrayList中
	ArrayList<Node> nodes = new ArrayList<>();
	for(int value : arr){
		nodes.add(new Node(value));
	}
	while(nodes.size() > 1){
		//排序
		Collections.sort(nodes);
		//取出根节点权值最小的两颗二叉树
		//(1) 取出权值最小的节点(二叉树)
		Node leftNode = nodes.get(0);
		//(2) 取出权值第二小的节点(二叉树)
		Node rightNode = nodes.get(1);
		//(3) 构建一颗新的二叉树
		Node parent = new Node(leftNode.value + rightNode.value);
		parent.left = leftNode;
		parent.right = rightNode;
		//(4) 从ArrayList中删除前两个元素
		nodes.remove(leftNode);
		nodes.remove(rightNode);	
		//(5) 将parent加入到nodes
		nodes.add(parent);
	}
	return nodes.get(0);
}

2.2赫夫曼编码及其应用

       赫夫曼编码是一种可变字长编码方式。原理是按照字符出现的次数构建一颗赫夫曼树, 次数作为权值;然后根据赫夫曼树给各个字符规定编码,向左的路径为0,向右的路径为1。

2.2.1数据的解压缩

       通常使用赫夫曼编码来实现数据的压缩,具体代码如下:
       压缩:
       1)先创建一个节点类定义一个节点,用于存储字符值以及对应的权重。

class Node implements Comparable<Node> {
	Byte data;// 存放数据(字符)本身,比如'a' = 97 ' ' = 32
	int weight;// 权值,表示字符出现的次数
	Node left;
	Node right;
	public Node(int weight) {
		super();
		this.weight = weight;
	}
	public Node(Byte data, int weight) {
		super();
		this.data = data;
		this.weight = weight;
	}
	@Override
	public String toString() {
		return "Node [data=" + data + ", weight=" + weight + "]";
	}
	@Override
	public int compareTo(Node o) {
		// TODO Auto-generated method stub
		return this.weight - o.weight;
	}
	// 前序遍历
	public void preOrder() {
		System.out.println(this);
		if (this.left != null) {
			this.left.preOrder();
		}
		if (this.right != null) {
			this.right.preOrder();
		}
	}
}

       2)将要压缩的字符串数组转成集合。

private static List<Node> getNodes(byte[] bytes) {
	// 1.创建一个list
	ArrayList<Node> nodes = new ArrayList<>();
	// 遍历bytes,统计每一个byte出现的次数,使用map
	HashMap<Byte, Integer> counts = new HashMap<>();
	for (byte b : bytes) {
		Integer count = counts.get(b);
		if (count == null) {
			counts.put(b, 1);
		} else {
			counts.put(b, count + 1);
		}
	}
	// 把每一个键值对转成一个Node对象,并加入到nodes集合
	for (Entry<Byte, Integer> entry : counts.entrySet()) {
		nodes.add(new Node(entry.getKey(), entry.getValue()));
	}
	return nodes;
}

       3)通过集合创建对应的赫夫曼树。

public static Node createHuffmanTree(List<Node> nodes) {
	while (nodes.size() > 1) {
		Collections.sort(nodes);
		Node leftNode = nodes.get(0);
		Node rightNode = nodes.get(1);
		Node parent = new Node(leftNode.weight + rightNode.weight);
		parent.left = leftNode;
		parent.right = rightNode;
		nodes.remove(leftNode);
		nodes.remove(rightNode);
		nodes.add(parent);
	}
	return nodes.get(0);
}

       4)通过递归获得赫夫曼树对应的赫夫曼编码。

private static void getCodes(Node node, String code, StringBuilder stringBuilder) {
	StringBuilder stringBuilder2 = new StringBuilder(stringBuilder);
	// 将code加入到stringBuilder2中
	stringBuilder2.append(code);
	if (node != null) {// 如果node == null不处理
		// 判断当前node是叶子结点还是非叶子结点
		if (node.data == null) {// 非叶子结点
			// 递归处理
			// 向左递归
			getCodes(node.left, "0", stringBuilder2);
			getCodes(node.right, "1", stringBuilder2);
		} else {// 说明是一个叶子结点
				// 就表示找到某个叶子结点的最后
			huffmanCodes.put(node.data, stringBuilder2.toString());
		}
	}
}

       5)获得压缩后的byte数组

public static byte[] zip(byte[] bytes, Map<Byte, String> huffmanCodes) {
	// 1.利用huffmanCodes将bytes转成赫夫曼编码对应的字符串
	StringBuilder stringBuilder = new StringBuilder();
	// 遍历byte数组
	for (byte b : bytes) {
		stringBuilder.append(huffmanCodes.get(b));
	}
	System.out.println(stringBuilder);
	// 将字符串转成byte[]
	// 统计返回byte[] huffmanCodeBytes长度
	// 一句话 int len = (stringBuilder.length() + 7) / 8
	int len;
	if (stringBuilder.length() % 8 == 0) {
		len = stringBuilder.length() / 8;
	} else {
		len = stringBuilder.length() / 8 + 1;
	}	
	int startindex = (len - 1) * 8;
	int zeroIndex = 0;
	while(startindex < (stringBuilder.length() - 1) && stringBuilder.charAt(startindex) == '0'){
		zeroCount.add(0);
		startindex++;
		zeroIndex++;
	}
	// 创建压缩后的byte数组
	byte[] huffmanCodeBytes = new byte[len];
	int index = 0;// 记录是第几个byte
	for (int i = 0; i < stringBuilder.length(); i += 8) {
		String strByte;
		if (i + 8 > stringBuilder.length()) {
			strByte = stringBuilder.substring(i);
		} else {
			strByte = stringBuilder.substring(i, i + 8);
		}
		// 将strByte转成一个byte,放入到huffmanCodeBytes中
					
		huffmanCodeBytes[index] = (byte) Integer.parseInt(strByte, 2);
		index++;
	}
	return huffmanCodeBytes;

       解压:

       1)将一个byte转成字符串

private static String byteToBitString(boolean flag, byte b) {
	// 使用变量保存b
	int temp = b;// 将b转成int
	// 如果是正数需要补高位
	if (flag) {
		temp |= 256;
	}
	String str = Integer.toBinaryString(temp);
	if (flag) {
		return str.substring(str.length() - 8);
	} else {
		return str;
	}
}

       2)将压缩的byte数组转成二进制字符串,然后从对应的map中得到指定的value值。

public static byte[] decode(Map<Byte, String> huffmanCodes, byte[] huffmanBytes) {
	//1.先得到huffmanBytes对应的二进制的字符串,形式10101000...
	StringBuilder stringBuilder = new StringBuilder();
	//将byte数组转成二进制的字符串
	for(int i = 0;i < huffmanBytes.length;i++){
		byte b = huffmanBytes[i];
		boolean flag = true;
		if(i == huffmanBytes.length - 1 && Integer.parseInt("" + huffmanBytes[i]) >= 0){
			if(zeroCount.size() != 0){
				for(Integer l : zeroCount){
					stringBuilder.append("" + l);						
				}
			}
			flag = false;
		}
		//判断是不是最后一个字节
//			boolean flag = (i == huffmanBytes.length - 1);
		stringBuilder.append(byteToBitString(flag, b));
	}
	System.out.println(stringBuilder.toString());
	//把字符串照指定的霍夫曼编码进行解码
	HashMap<String,Byte> map = new HashMap<>();
	for(Map.Entry<Byte, String> entry : huffmanCodes.entrySet()){
		map.put(entry.getValue(), entry.getKey());
	}
	//创建一个集合,存放byte
	ArrayList<Byte> list = new ArrayList<>();
	//i扫描stringBuilder
	for(int i =0;i < stringBuilder.length();){
		int count = 1;//小的计数器
		boolean flag = true;
		Byte b = null;
		while(flag){
			String key = stringBuilder.substring(i, i + count);
			b = map.get(key);
			if(b == null){
				count++;
			}else{
				flag = false;
			}
		}
		list.add(b);
		i += count;//i直接移动到count
	}
	//把list中的数据放入到byte[]并返回
	byte[] b = new byte[list.size()];
	for(int i = 0;i < b.length;i++){
		b[i] = list.get(i);
	}
	return b;
}
2.2.2对本地文件的解压缩

       代码:

1.解压文件

public static void unzipFile(String srcFile,String dstFile) {
	FileInputStream fis = null;
	FileOutputStream fos = null;
	ObjectInputStream ois = null;
	try {
		fis = new FileInputStream(srcFile);
		fos = new FileOutputStream(dstFile);
		ois = new ObjectInputStream(fis);
		byte[] huffmanBytes = (byte[]) ois.readObject();
		System.out.println(Arrays.toString(huffmanBytes));
		Map<Byte, String> huffmanCodes = (Map<Byte, String>) ois.readObject();
		System.out.println(huffmanCodes);
		zeroCount = (List<Integer>) ois.readObject();
		byte[] decode = decode(huffmanCodes, huffmanBytes);
//			System.out.println(new String(decode));
		fos.write(decode);
	} catch (Exception e) {
		e.printStackTrace();
	}finally{
		if(fis != null){
			try {
				fis.close();
			} catch (IOException e) {
				e.printStackTrace();
			}	
		}
		if(fos != null){
			try {
				fos.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		if(ois != null){
			try {
				ois.close();
			} catch (IOException e) {
				e.printStackTrace();
			}				
		}
	}
}

//2.压缩文件
public static void zipFile(String srcFile,String dstFile) {
	FileInputStream fis = null;
	FileOutputStream fos = null;
	ObjectOutputStream oos = null;
	try {
		fis = new FileInputStream(srcFile);
		fos = new FileOutputStream(dstFile);
		byte[] oriData = new byte[fis.available()];
		fis.read(oriData);
		byte[] encode = huffmanZip(oriData);
		oos = new ObjectOutputStream(fos);
		System.out.println(Arrays.toString(encode));
		System.out.println(huffmanCodes);
		oos.writeObject(encode);
		oos.writeObject(huffmanCodes);
		oos.writeObject(zeroCount);
	} catch (Exception e) {
		e.printStackTrace();
	}finally{
		if(fis != null){
			try {
				fis.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		if(fos != null){
			try {
				fos.close();
			} catch (IOException e) {
				e.printStackTrace();
			}				
		}
		if(oos != null){
			try {
				oos.close();
			} catch (IOException e) {
				e.printStackTrace();
			}				
		}
	}	
}

2.3二叉排序树

2.3.1一个需求

       一个数列 (7, 3, 10, 12, 5, 1, 9),要求能够高效的完成对数据的查询和添加

2.3.2解决方案

       1)使用数组
       数组未排序:
       优点:直接在数组尾添加,速度快。
       缺点:查找速度慢
       数组排序:
       优点:可以使用二分查找,查找速度快
       缺点:为了保证数组有序,在添加新数据时,找到插入位置后,后面的数据需整体移动,速度慢。
       2)使用链式存储-链表:
       不管链表是否有序,查找速度都慢,添加数据速度比数组快,不需要数据整体移动。

2.3.3二叉排序树

       1)二叉排序树介绍
       二叉排序树:BST:(Binary Sort(Search) Tree), 对于二叉排序树的任何一个非叶子节点,要求左子节点的值比当前节点的值小,右子节点的 值比当前节点的值大。
       2)二叉排序树创建和遍历

//递归的形式添加节点,注意需要满足二叉排序树的要求
public void add(Node node){
	if(node == null){
		return;
	}
	//判断传入的节点的值,和当前子树的根结点的值的关系
	if(node.value < this.value){
		//如果当前结点的左子结点为null
		if(this.left == null){
			this.left = node;
		}else {
			//递归的向左子树添加
			this.left.add(node);
		}
	}else{//添加的结点的值大于当前结点的值
		if(this.right == null){
			this.right = node;
		}else{
			this.right.add(node);
		}
	}
}
//中序遍历
public void infixOrder(){
	if(this.left != null){
		this.left.infixOrder();
	}
	System.out.println(this);
	if(this.right != null){
		this.right.infixOrder();
	}
}

       3)二叉排序树的删除:有三种情况需要考虑。即删除叶子节点、删除只有一颗子树的节点、删除有两颗子树的节点

public void delNode(int value){
	if(root == null){
		return;
	}else{
		//1.需要先去找到要删除的结点targetNode
		Node targetNode = search(value);
		//如果没找到要删除的结点
		if(targetNode == null){
			return;
		}
		//如果当前二叉排序树只一个结点
		if(root.left == null && root.right == null){
			root = null;
			return;
		}	
		//找到targetNode的父结点
		Node parent = searchParent(value);
		//如果要删除的结点是叶子结点
		if(targetNode.left == null && targetNode.right == null){
			if(parent.left != null && parent.left.value == value){//是左子结点
				parent.left = null;
			}else if(parent.right != null && parent.right.value == value){
				parent.right = null;
			}
		}else if(targetNode.left != null && targetNode.right != null){//如果要删除的结点两个子树。
			int minVal = delRightMin(targetNode.right);
			targetNode.value = minVal;
		}else{//删除只一颗子树的结点
			//如果要删除的结点只左子结点
			if(targetNode.left != null){
				//如果target是parent的左子结点
				if(parent.left.value == value){
					parent.left = targetNode.left;
				}else{
					//如果target是parent的右子结点
					parent.right = targetNode.left;
				}
			}else{//如果要删除的结点只右子结点
				//如果target是parent的左子结点
				if(parent.left.value == value){
					parent.left = targetNode.right;
				}else{
					//如果target是parent的右子结点
					parent.right = targetNode.right;
				}
			}
		}
	}
	}

2.4平衡二叉树

       一个案例:数列{1,2,3,4,5,6}的二叉排序树为:
在这里插入图片描述
       上述案例存在一些问题:左子树全部为空,从形式上看,更像一个单链表;插入速度没有影响;查询速度明显降低(因为需要依次比较), 不能发挥BST的优势,因为每次还需要比较左子树,其查询速度比单链表还慢。
       解决方案-平衡二叉树(AVL)

2.4.1基本介绍

       1)平衡二叉树也叫平衡二叉搜索树(Self-balancing binary search tree)又被称为AVL树, 可以保证查询效率较高。
       2)具有以下特点:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

2.4.2左旋转
public void leftRoatate() {
	// 创建新的节点,以当前根节点的值
	Node newNode = new Node(value);
	// 把新的节点的左子树设置成当前节点的左子树
	newNode.left = left;
	// 把新的结点的右子树设置成当前节点的右子树的左子树
	newNode.right = right.left;
	// 把当前结点的值替换成右子结点的值
	value = right.value;
	// 把当前结点的右子树设置成当前节点右子树的右子树
	right = right.right;
	// 把当前节点的左子树(左子结点)设置成新的结点
	left = newNode;
}
2.4.3右旋转
public void rightRoatate() {
	Node newNode = new Node(value);
	newNode.right = right;
	newNode.left = left.right;
	value = left.value;
	left = left.left;
	right = newNode;
}
2.4.4全旋转

       有一些数列在进行大的旋转之前,需要对某个子树进行旋转操作。
在这里插入图片描述       如上图,需要先对节点7的这个树进行左旋转,然后在以10为根节点进行右旋转。

// 当添加完一个结点后,如果:右子树的高度 - 左子树的高度 > 1,左旋转
if (rightHeight() - leftHight() > 1) {
	if (right != null && right.leftHight() > right.rightHeight()) {
		right.rightRoatate();
	}
	leftRoatate();// 左旋转
	return;
}
if (leftHight() - rightHeight() > 1) {
	if (left != null && left.rightHeight() > left.leftHight()) {
		// 先对当前结点的左结点->左旋转
		left.leftRoatate();
	}
	// 右旋转
	rightRoatate();
}

二、图

1.基本介绍

1.1为什么要有图?

       1)线性表局限于一个直接前驱和一个直接后继的关系
       2)树也只能有一个直接前驱也就是父节点
       3)当我们需要表示多对多的关系时, 就用到了图。

1.2图的举例说明

在这里插入图片描述

1.3图的常用概念

       顶点(vertex)、边(edge)、路径
       无向图:
在这里插入图片描述在这里插入图片描述       有向图:
在这里插入图片描述       带权图:
在这里插入图片描述

1.4图的表示方式

       图的表示方式有两种:
       (1) 邻接矩阵
       邻接矩阵是表示图形中顶点之间相邻关系的矩阵,对于n个顶点的图而言,矩阵是的row和col表示的是1…n个点。
在这里插入图片描述       (2) 邻接表
       邻接矩阵需要为每个顶点都分配n个边的空间,其实有很多边都是不存在,会造成空间的一定损失。
       邻接表的实现只关心存在的边,不关心不存在的边。因此没有空间浪费,邻接表由数组+链表组成。
在这里插入图片描述

2.图的深度优先遍历

2.1图的遍历介绍

       所谓图的遍历,即是对结点的访问。一个图有那么多个结点,如何遍历这些结点,需要特定策略,一般有两种访问策略: (1)深度优先遍历 (2)广度优先遍历。

2.2深度优先遍历基本思想

       深度优先遍历,从初始访问结点出发,初始访问结点可能有多个邻接结点,深度优先遍历的策略就是首先访问第一个邻接结点,然后再以这个被访问的邻接结点作为初始结点,访问它的第一个邻接结点,可以这样理解:每次都在访问完当前结点后首先访问当前结点的第一个邻接结点。
       访问策略是优先往纵向挖掘深入,而不是对一个结点的所有邻接结点进行横向访问。
       显然,深度优先搜索是一个递归的过程。

2.3深度优先遍历算法步骤

       1)访问初始结点v,并标记结点v为已访问。
       2)查找结点v的第一个邻接结点w。
       3)若w存在,则继续执行4,如果w不存在,则回到第1步,将从v的下一个结点继续。
       4)若w未被访问,对w进行深度优先遍历递归(即把w当做另一个v,然后进行步骤123)。
       5)查找结点v的w邻接结点的下一个邻接结点,转到步骤3。

2.4实际案例

在这里插入图片描述       代码:

// 深度优先算法DFS
private void dfs(boolean[] isVisited, int i) {
	// 首先访问该结点,输出
	System.out.print(getValueByIndex(i) + "->");
	// 将该节点设置成已经访问
	isVisited[i] = true;
	// 查找结点i的第一个邻接结点w
	int w = getFirstNeighbor(i);
	while (w != -1) {
		if (!isVisited[w]) {
			dfs(isVisited, w);
		}
		// 如果w结点已经被访问过
		w = getNextNeighbor(i, w);
	}
}

3.图的广度优先遍历

3.1广度优先遍历基本思想

       图的广度优先搜索(Broad First Search) 。类似于一个分层搜索的过程,广度优先遍历需要使用一个队列以保持访问过的结点的顺序,以便按这个顺序来访问这些结点的邻接结点。

3.2广度优先遍历算法步骤

private void bfs(boolean[] isVisited, int i) {
	int u;// 表示队列头结点对应下标
	int w;// 邻接结点
	// 队列,记录结点访问的顺序
	LinkedList queue = new LinkedList();
	// 访问结点,输出结点信息
	System.out.print(getValueByIndex(i) + "->");
	// 标记为已访问
	isVisited[i] = true;
	// 将结点加入队列
	queue.addLast(i);
	while (!queue.isEmpty()) {
		// 取出队列的头结点下标
		u = (Integer) queue.removeFirst();
		// 得到第一个邻接结点的下标w
		w = getFirstNeighbor(u);
		while (w != -1) {
			// 是否访问过
			if (!isVisited[w]) {
				System.out.print(getValueByIndex(w) + "->");
				// 标记已经访问
				isVisited[w] = true;
				// 入队
				queue.addLast(w);
			}
			w = getNextNeighbor(u, w);// 体现出广度优先
		}
	}
}

4.图的遍历总代码

public class Graph {
	public static void main(String[] args) {
		int n = 8;// 结点个数
		// String[] vertexs = { "A", "B", "C", "D", "E" };
		String[] vertexs = { "1", "2", "3", "4", "5", "6", "7", "8" };
		// 创建图
		GraphDemo graph = new GraphDemo(n);
		// 循环的添加顶点
		for (String vertex : vertexs) {
			graph.insertVertex(vertex);
		}
		// 添加边
		graph.insertEdge(0, 1, 1);
		graph.insertEdge(0, 2, 1);
		graph.insertEdge(1, 3, 1);
		graph.insertEdge(1, 4, 1);
		graph.insertEdge(3, 7, 1);
		graph.insertEdge(4, 7, 1);
		graph.insertEdge(2, 5, 1);
		graph.insertEdge(2, 6, 1);
		graph.insertEdge(5, 6, 1);


		graph.showGraph();

		// 深度优先
		 System.out.println("深度优先:");
		 graph.dfs();
		// 广度优先
//		System.out.println("广度优先");
//		graph.bfs();

	}
}

class GraphDemo {
	private ArrayList<String> vertexList;// 存储顶点集合
	private int[][] edges;// 存储图对应的邻接矩阵
	private int numOfEdges;// 表示边的数目
	private boolean[] isVisited;

	// 构造器
	public GraphDemo(int n) {
		// 初始化矩阵和vertex
		edges = new int[n][n];
		vertexList = new ArrayList<String>(n);
		numOfEdges = 0;
		isVisited = new boolean[n];
	}

	// 广度优先算法
	private void bfs(boolean[] isVisited, int i) {
		int u;// 表示队列头结点对应下标
		int w;// 邻接结点
		// 队列,记录结点访问的顺序
		LinkedList queue = new LinkedList();
		// 访问结点,输出结点信息
		System.out.print(getValueByIndex(i) + "->");
		// 标记为已访问
		isVisited[i] = true;
		// 将结点加入队列
		queue.addLast(i);

		while (!queue.isEmpty()) {
			// 取出队列的头结点下标
			u = (Integer) queue.removeFirst();
			// 得到第一个邻接结点的下标w
			w = getFirstNeighbor(u);
			while (w != -1) {
				// 是否访问过
				if (!isVisited[w]) {
					System.out.print(getValueByIndex(w) + "->");
					// 标记已经访问
					isVisited[w] = true;
					// 入队
					queue.addLast(w);
				}
				w = getNextNeighbor(u, w);// 体现出广度优先
			}
		}

	}

	/**
	 * 
	 * @Description 得到第一个邻接结点的下标w
	 * @author jiatianyu
	 * @date 2021年3月13日下午4:16:39
	 * @param index
	 * @return 如果存在就返回对应的下标,否则返回-1
	 */
	public int getFirstNeighbor(int index) {
		for (int j = 0; j < vertexList.size(); j++) {
			if (edges[index][j] > 0) {
				return j;
			}
		}
		return -1;
	}

	/**
	 * 
	 * @Description 根据前一个邻接结点的下标来获取下一个邻接结点
	 * @author jiatianyu
	 * @date 2021年3月13日下午4:23:06
	 * @param v1
	 * @param v2
	 * @return
	 */
	public int getNextNeighbor(int v1, int v2) {
		for (int j = v2 + 1; j < vertexList.size(); j++) {
			if (edges[v1][j] > 0) {
				return j;
			}
		}
		return -1;
	}

	// 深度优先算法DFS
	private void dfs(boolean[] isVisited, int i) {
		// 首先访问该结点,输出
		System.out.print(getValueByIndex(i) + "->");
		// 将该节点设置成已经访问
		isVisited[i] = true;
		// 查找结点i的第一个邻接结点w
		int w = getFirstNeighbor(i);
		while (w != -1) {
			if (!isVisited[w]) {
				dfs(isVisited, w);
			}
			// 如果w结点已经被访问过
			w = getNextNeighbor(i, w);
		}
	}

	// 遍历所的结点,都进行广度优先搜索
	public void bfs() {
		for (int i = 0; i < getNumOfVertex(); i++) {
			if (!isVisited[i]) {
				bfs(isVisited, i);
			}
		}
	}

	// 对dfs进行一个重载,遍历我们所的结点,并进行dfs
	public void dfs() {
		// 遍历所的结点
		for (int i = 0; i < getNumOfVertex(); i++) {
			if (!isVisited[i]) {
				dfs(isVisited, i);
			}
		}
	}

	// 常用方法
	public void showGraph() {
		for (int[] link : edges) {
			System.out.println(Arrays.toString(link));
		}
	}

	// 返回结点的个数
	public int getNumOfVertex() {
		return vertexList.size();
	}

	// 得到边的个数
	public int getNumOfEdges() {
		return numOfEdges;
	}

	// 返回结点i对应的数据
	public String getValueByIndex(int i) {
		return vertexList.get(i);
	}

	// 返回v1和v2的权值
	public int getWeight(int v1, int v2) {
		return edges[v1][v2];
	}

	// 插入结点
	public void insertVertex(String vertex) {
		vertexList.add(vertex);
	}

	// 添加边
	/**
	 * 
	 * @Description
	 * @author jiatianyu
	 * @date 2021年3月12日下午10:20:29
	 * @param v1表示点的下标即第几个顶点"A"-"B"
	 *            "A"->0 "B"->1
	 * @param v2
	 * @param weight
	 */
	public void insertEdge(int v1, int v2, int weight) {
		edges[v1][v2] = weight;
		edges[v2][v1] = weight;
		numOfEdges++;
	}
}

总结

To be continued~~,10大算法。

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值