树结构的实际应用、图
开始~~~
目录
一、树结构的实际应用
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大算法。