数据结构目录
数据结构
根据韩顺平老师的数据结构与算法视频学习的
包括:线性结构和非线性结构
- 线性结构:最常用的数据结构。特点是数据与元素之间存在一对一关系,例如数组,队列,链表,栈等。按照存储结构又分为顺序存储结构和链式存储结构。
- 顺序存储结构:元素的存储地址值是连续的。例如数组。
- 链式存储结构:元素的存储地址值不一定是连续的,元素节点存放又数据以及相邻节点的信息。例如链表。
- 非线性结构:树结构,图,多维数组等。
字符串
待完成笔记
数组
稀疏数组(Sparse Array)
*概念:*当二位数组中,存在大量的无效数据,可以使用稀疏数组,对原数组进行压缩。
结构:
- 稀疏数组的第一行分别表示原数组行数,列数,有效数据
- 其余行表示用于表示有效数据的行数,列数,值。
链表(Linked List)
***概念:***链表是以节点的形式存储数据的,两个相邻的节点的地址值并不一定连续。
特点:
- 由于其节点存放的地址值并不连续,所以增删只需要修改指定节点的next域即可,错做熟读块,效率高。
- 查找和修改都需要从头节点开始一个一个找,所以效率低。
单(向)链表
*概念:*最简单的链表结构。节点包含data域和next域(指向下一个节点)。
双(向)链表
*概念:*节点有3个域组成,其中两个连接,一个指向前一个指向后。头节点的pre域为null,最后一个节点的next域为null。
环形链表
*概念:*节点与单链表的节点构造相同,不同的是长辈一个节点的next域始终指向第一个节点,构成一个环状。
环形链表可以解决约瑟夫问题,编号为1~n的人围坐在一起围成一圈,约定编号k的人从1开始报数,数到m的人出列,下个人在从1* 开始数,直到第m个人。。。一直到所有人出列。由此产生一个出队序列
堆、栈、队列
堆
*概念:*堆就是用数组实现的二叉树。例如一个元素在数组中的位置为i,则其左子节点为2*i+1,右子节点为2*i+2。
堆又分为大根堆和小根堆。
- 大根堆,又叫最大堆。父节点的每个值都比子节点的值要大。
- 小根堆,又叫最小堆。父节点的每个值都比子节点的值要小。
栈
*概念:*一种先入后出的线性结构。
前缀表达式(波兰表达式)
运算符在操作数之前。
例如:(3+4)*5-6 前缀表达式:-*+3456
中缀表达式
就是日常生活中使用的。
后缀表达式(逆波兰表达式)
运算符在数组的后面
例如:(3+4)*5-6 后缀表达式:34+5*6-
队列
*概念:*是一个线性结构。可以使用数组或者链表来实现。特点就是先进先出,即先进入队列的只能先取出来,后进入队列的只有等前面的数据都取出来时才能取出。
环形(循环)队列
*概念:*为了解决“假溢出”现象,充分利用空间。使用一些方法使其乘坐“首位相连”状态。
二叉树
概念:
满二叉树
概念:所有叶子节点都在最后一层,即一共n层,节点数为2^n-1个。
完全二叉树
*概念:*所有的叶子节点都在最后一层或者倒数第二层。
二叉树遍历
前序遍历(DLR)
指的是先遍历根节点,在遍历左节点,其次时右节点。
/*前序遍历*/
public void DLR() {
System.out.println(this); //首先输出当前节点。
if (left != null) {
this.left.DLR(); //其次输出当左子节点。
}
if (right != null) {
this.right.DLR(); //最后输出当右子节点。
}
}
中序遍历(LDR)
先遍历左,再根节点,最后右子节点。
/*中序遍历*/
public void DLR() {
if (left != null) {
this.left.DLR(); //首先输出当左子节点。
}
System.out.println(this); //其次输出当前节点。
if (right != null) {
this.right.DLR(); //最后输出当右子节点。
}
}
后序遍历(LRD)
先遍历左子节点,再遍历右子节点。最后遍历根节点。
/*后序遍历*/
public void DLR() {
if (left != null) {
this.left.DLR(); //首先输出当左子节点。
}
if (right != null) {
this.right.DLR(); //其次输出当右子节点。
}
System.out.println(this); //最后输出当前节点。
}
线索二叉树
根据二叉树遍历的结果,位于节点前面的被成为前驱,位于节点后面的被称为后继。
由于前驱和后继都是在遍历中产生的,若不遍历,便无法得知某个节点的前驱和后继。
由于这个问题,如果再给节点添加两个引用,用于指向节点的前驱和后驱,会造成大量的空间浪费。由于二叉树有很多节点的左右指针指向为空,为了提高利用率,于是在原来节点的基础上,指向空的引用再指向前驱或后继。同时为了分清左右指针指向的是子节点还是前驱或后继,需要再原来节点的基础上再添加两个变量用于表示左右指针指向的到底是谁。
***线索化:***给原来节点添加线索的过程叫线索化。
线索二叉树分为3种:前序线索化二叉树,后序线索化二叉树,中序线索化二叉树。不同线索化二叉树只能使用对应的遍历方式。
public class 线索二叉树 {
public static void main(String[] args) {
Node node01 = new Node(1, "111");
Node node02 = new Node(2, "222");
Node node03 = new Node(3, "333");
Node node04 = new Node(4, "444");
Node node05 = new Node(5, "555");
node01.setLeft(node02);
node01.setRight(node03);
node03.setLeft(node05);
node03.setRight(node04);
ThreadedBinaryTree binaryTree = new ThreadedBinaryTree(node01);
/*System.out.println("线索化前遍历------------------------");
binaryTree.mid();
binaryTree.threaded(); //线索化
System.out.println("线索化------------------------");
System.out.println("node2的前驱节点:" + node02.left + ",node2的后继节点:" + node02.right); //测试成功
System.out.println("线索化后遍历------------------------");
binaryTree.threadedMid();*/
System.out.println("线索化前遍历------------------------");
binaryTree.before();
System.out.println("线索化------------------------");
binaryTree.threaded01(); //线索化
System.out.println("线索化后遍历------------------------");
binaryTree.threadedBefore();
}
static class ThreadedBinaryTree {
private Node root = null;
private Node pre = null; //为线索化添加的辅助指针
public ThreadedBinaryTree(Node root) {
this.root = root;
}
//重载
public void threaded() {
this.threaded(root);
}
public void threaded01() {
this.threaded01(root);
}
public void threaded02() {
this.threaded02(root);
}
/*将二叉树中序线索化*//*为中序*/
public void threaded(Node node) {
if (node == null) {
System.out.println("二叉树为空");
}
//将左子树线索化
if (node.left != null) {
threaded(node.left);
}
//将当前节点线索化(如果有前驱,则自己的左指针指向前驱,并且让前驱的右指针指向自己),同一个节点的左右指针无法同时操作
if (node.left == null) {
node.setLeft(pre);
node.setlTag(1);
}
if (pre != null && pre.right == null) {
//让前驱的右指针指向自己(如果)
pre.setRight(node);
pre.setrTag(1);
}
pre = node; //前节点指针后移
//将右子树线索化
if (node.right != null) {
threaded(node.right);
}
}
/**
* 遍历中序线索二叉树(中序遍历),不需要使用递归,效率提高
*/
public void threadedMid() {
Node node = root;
while (node != null) {
//查找到第一个节点,第一个节点的特征时lTag为1,输出
while (node.lTag != 1) {
node = node.getLeft();
}
System.out.println(node);
//根据rTag==1,往后输出节点
while (node.rTag == 1) {
node = node.getRight();
System.out.println(node);
}
node = node.getRight(); //节点指针后移
}
}
/*前序线索化*/
public void threaded01(Node node) {
if (node == null) {
System.out.println("二叉树为空");
}
//将当前节点线索化
if (node.left == null) {
node.setLeft(pre);
node.setlTag(1);
}
if (pre != null && pre.right == null) {
pre.setRight(node);
pre.setrTag(1);
}
pre = node;
//将左子树线索化
if (node.left != null && node.lTag != 1) {
threaded01(node.getLeft());
}
//将右子树线索化
if (node.right != null && node.lTag != 1) {
threaded01(node.getRight());
}
}
/*前序线索化二叉树遍历*/
public void threadedBefore() {
Node node = root;
while (node != null) {
while (node.lTag != 1) {
System.out.println(node);
node = node.getLeft();
}
System.out.println(node);
node = node.getRight();
}
}
/*后序线索化*/
public void threaded02(Node node) {
//将左子树线索化
if (node.left != null && node.lTag != 1) {
threaded02(node.getLeft());
}
//将右子树线索化
if (node.right != null && node.lTag != 1) {
threaded02(node.getRight());
}
//将当前节点线索化
if (node.left == null) {
node.setLeft(pre);
node.setlTag(1);
}
if (pre != null && pre.right == null) {
pre.setRight(node);
pre.setrTag(1);
}
pre = node;
}
/*删除节点*/
public void delete(int no) {
if (root == null) {
System.out.println("此树为空");
} else {
if (root.getNo() == no) {
root = null;
System.out.println("已删除");
} else {
boolean a = root.delete(no);
if (a == false) {
System.out.println("未查询到该节点");
}
}
}
}
/*前序遍历*/
public void before() {
if (root != null) {
root.before();
}
}
/*中序遍历*/
public void mid() {
if (root != null) {
root.mid();
}
}
/*后序遍历*/
public void behind() {
if (root != null) {
root.behind();
}
}
/*前序查找*/
public void beforeSearch(int no) {
System.out.println(root.beforeSearch(no));
}
/*中序查找*/
public void midSearch(int no) {
System.out.println(root.midSearch(no));
}
/*后序查找*/
public void behindSearch(int no) {
System.out.println(root.behindSearch(no));
}
}
/*线索二叉树的节点类有些不同,由于同一个left可能表示左孩子,也可能表示前驱,所以加个lTag表示left的状态,当lTag为0时表示左孩子,当lTag为1时表示前驱*/
static class Node {
private int no;
private String name;
private Node left;
private Node right;
private int lTag; //0表示左孩子,1表示前驱
private int rTag; //0表示右孩子,1表示后继
/*删除子节点:若该节点不是叶子节点,则直接删除该子树*/
public boolean delete(int no) {
if (this.left != null && this.left.getNo() == no) {
this.setLeft(null);
System.out.println("已删除");
return true;
}
if (this.right != null && this.right.getNo() == no) {
this.setRight(null);
System.out.println("已删除");
return true;
}
if (this.left != null && this.left.delete(no)) {
return true;
}
if (this.right != null && this.right.delete(no)) {
return true;
}
return false;
}
/*前序遍历*/
public void before() {
System.out.println(this);
if (left != null) {
this.left.before();
}
if (right != null) {
this.right.before();
}
}
/*中序遍历*/
public void mid() {
if (left != null) {
this.left.mid();
}
System.out.println(this);
if (right != null) {
this.right.mid();
}
}
/*后序遍历*/
public void behind() {
if (left != null) {
this.left.behind();
}
if (right != null) {
this.right.behind();
}
System.out.println(this);
}
/*前序查找*/
public Node beforeSearch(int no) {
System.out.println("前");
if (this.no == no) {
return this;
}
Node resNode = null;
//查询左子树
if (this.left != null) {
resNode = this.left.beforeSearch(no);
}
if (resNode != null) {
return resNode;
}
//左子树未查询,查询右子树
if (this.right != null) {
resNode = this.right.beforeSearch(no);
}
return resNode;
}
/*中序查找*/
public Node midSearch(int no) {
Node resNode = null;
if (this.left != null) {
resNode = this.left.midSearch(no);
}
if (resNode != null) {
return resNode;
}
System.out.println("中");
if (this.no == no) {
return this;
}
if (this.right != null) {
resNode = this.right.midSearch(no);
}
return resNode;
}
/*后序查找*/
public Node behindSearch(int no) {
Node resNode = null;
if (this.left != null) {
resNode = this.left.behindSearch(no);
}
if (resNode != null) {
return resNode;
}
if (this.right != null) {
resNode = this.right.behindSearch(no);
}
System.out.println("后");
if (this.no == no) {
return this;
}
return resNode;
}
public int getNo() {
return no;
}
public int getlTag() {
return lTag;
}
public void setlTag(int lTag) {
this.lTag = lTag;
}
public int getrTag() {
return rTag;
}
public void setrTag(int rTag) {
this.rTag = rTag;
}
public void setNo(int no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Node getLeft() {
return left;
}
public void setLeft(Node left) {
this.left = left;
}
public Node getRight() {
return right;
}
public void setRight(Node right) {
this.right = right;
}
public Node(int no, String name) {
this.no = no;
this.name = name;
left = null;
right = null;
}
@Override
public String toString() {
return "Node{" +
"no=" + no +
", name='" + name + '\'' +
'}';
}
}
}
赫夫曼树(HuffmanTree)
*概念:*给定n个权值作为一棵树的n个节点,构造出一棵树,并且这棵树的带权路径长度(wpl)最小,这样的树叫赫夫曼树。
节点的带权路径长度:节点的权值与路径长度的乘积。
树的带权路径长度(wpl):所有的叶子节点的带权路径长度之和。
赫夫曼树生成步骤:
- 对所有权值装入数组,并进行排序,
- 取出权值最小的两个作为子节点,并从权值数组中删除,他们的父节点的权值为两个最小的子节点权值之和。父节点的权值加入数组中。
- 依次循环12步骤。
public class HuffmanTree {
public static void main(String[] args) {
int[] arr = {8, 4, 58, 0, 14, 5, 6};
Node head = huffmanTree(arr);
show(head);
}
/**
* 前序遍历方法
*
* @param node
*/
public static void show(Node node) {
if (node != null) {
node.show();
} else {
System.out.println("此树为空");
}
}
public static Node huffmanTree(int[] arr) {
//在list集合上便于操作
List<Node> list = new ArrayList<>();
for (int i : arr) {
list.add(new Node(i));
}
System.out.println(list);
while (list.size() > 1) {
//1.对list进行排序
Collections.sort(list);
//2.取出最小的两个并从list中删除,生成其父节点并加入list中
Node leftNode = list.remove(0);
Node rightNode = list.remove(0);
Node fatherNode = new Node(leftNode.getValue() + rightNode.getValue());
fatherNode.setLeft(leftNode);
fatherNode.setRight(rightNode);
list.add(fatherNode);
System.out.println(list);
}
//此时赫夫曼树已经成型,返回头节点
return list.get(0);
}
/*由于需要比较排序,所以需要实现Comparable接口,实现compareTo接口*/
public static class Node implements Comparable<Node> {
private int value; //权
private Node left;
private Node right;
//前序遍历
public void show() {
if (this.getLeft()==null&&this.getRight()==null){
System.out.println(this.getValue());
}
if (this.getLeft() != null) {
this.getLeft().show();
}
if (this.getRight() != null) {
this.getRight().show();
}
}
public Node(int value) {
this.value = value;
this.left = null;
this.right = null;
}
@Override
public int compareTo(Node o) {
//升序
return this.getValue() - o.getValue();
}
}
}
赫夫曼编码
广泛的运用于数据文件的压缩。
**例:**i like like like java do you like a java一句话,对应的ASCII与二进制分表如图。
再以不同字符(包括空格)出现的次数作为权值,生成哈夫曼树。
在根据哈夫曼树,根节点向左为0,向右为1对字符进行编码。
可以得到哈夫曼编码表:
在根据哈夫曼编码表对那句话进行编码,则对应的编码为:(无损压缩)
相比于之前少了很多。
/*赫夫曼编码,将数据变为赫夫曼树,在根据赫夫曼树对其进行编码*/
public class HuffermanCode {
static Node node = null; //将字符串置换为哈夫曼树
static Map<Byte, String> haffermanTable = new HashMap<>(); //获取根据哈夫曼树的哈夫曼编码表
public static void main(String[] args) {
//压缩文本
/*String str = "i like like like java do you like a java ooo 啊"; //源文件
byte[] zip = huffermanZip(str);
System.out.println("压缩前:" + str + " ,长度为:" + str.length());
System.out.println("压缩后:" + Arrays.toString(zip) + " ,长度为:" + zip.length);
String decode = decode(zip, haffermanTable);
System.out.println("解压结果:" + decode);*/
//压缩文件
String str1 = "d://1.jpg";
String str2 = "d://1.zip";
zipFile(str1, str2);
String str3="e://1.jpg";
unZip(str2,str3);
}
/**
* 对文件进行解压
*
* @param str2 要解压的文件路径
* @param str1 解压出来的文件路径
*/
public static void unZip(String str2, String str1) {
FileInputStream fis = null;
byte[] bytes = null;
try {
fis = new FileInputStream(str2);
bytes = new byte[fis.available()];
for (int i = 0; i < bytes.length; i++) {
bytes[i]= (byte) fis.read();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
FileOutputStream fos = null;
try {
fos = new FileOutputStream(str1);
String decode = decode(bytes, haffermanTable);
fos.write(decode.getBytes());
System.out.println("已解压");
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 对文件进行了哈夫曼压缩
*
* @param str1 源文件路径
* @param str2 压缩文件目的路径
*/
public static void zipFile(String str1, String str2) {
//1.读取源文件为字节数组
FileInputStream fis = null;
byte[] bytes = null; //用于存储源文件的字节数组
try {
fis = new FileInputStream(str1);
bytes = new byte[fis.available()]; //获取文件长度,并创建相同长度的字节数组
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (byte) fis.read();
}
System.out.println("压缩前:" + bytes.length);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//2.对源文件的字节数组进行压缩,并输出到目的位置
FileOutputStream fos = null;
byte[] bytes2 = huffermanZip(bytes);
System.out.println("压缩后:" + bytes2.length);
try {
fos = new FileOutputStream(str2);
fos.write(bytes2);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 将压缩文件解压
*
* @param bytes 压缩文件
* @param huffermanMap 哈夫曼编码表
* @return 原文件
*/
public static String decode(byte[] bytes, Map<Byte, String> huffermanMap) {
//1.首先将byte数组转换位二进制文件
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < bytes.length - 1; i++) {
if (i == bytes.length - 2) {
stringBuilder.append(byteToStr(bytes[i], bytes[i + 1])); //byte最后一个元素表示的是倒数第二个元素的二进制位数
} else {
stringBuilder.append(byteToStr(bytes[i], 8));
}
}
//2.再将二进制转换为源文件的byte数组
ArrayList<Byte> list = new ArrayList<>(); //用于存放Byte的集合
Map<String, Byte> map = new HashMap<>(); //将哈夫曼编码表反过来,二进制作为key,byte作为value
Set<Map.Entry<Byte, String>> entries = huffermanMap.entrySet();
for (Map.Entry<Byte, String> entry : entries) {
map.put(entry.getValue(), entry.getKey());
}
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 {
list.add(b);
flag = false;
}
}
i += count;
}
//3.将原文件byte数组再转换为string并返回
byte[] bytes1 = new byte[list.size()];
for (int i = 0; i < list.size(); i++) {
bytes1[i] = list.get(i);
}
return new String(bytes1);
}
/**
* 将byte类型转换为二进制字符串
*
* @param b
* @param num 截取位数。由于int二进制有32位,原数据二进制为每8位一个byte,所以一般截取最后8位即可;如果是倒数第二位byte,则截取位数根据byte数组最后一个确定
* @return
*/
public static String byteToStr(byte b, int num) {
int temp = b; //将byte转换位int类型
//如果是正数,则需要补码
if (temp > 0) {
temp |= 256;
}
String str = Integer.toBinaryString(temp); //使用Integer的toBinaryString将int类型的转换位二进制字符串,长度位32
System.out.println(str);
return str.substring(str.length() - num); //截取str后num位并返回
}
/*获得压缩后的赫夫曼编码字节数组*/
public static byte[] huffermanZip(byte[] bytes) {
return huffermanZip(new String(bytes));
}
/*获得赫夫曼编码字节数组*/
public static byte[] huffermanZip(String str) {
node = haffermanTree(strToList(str));
haffermanTable = getHaffermanTable(node);
return zip(str, haffermanTable);
}
/**
* 对字符串进行压缩
*
* @param str 未经处理的字符串
* @param map 哈夫曼编码表
* @return 经过压缩的字节数组, 数组的最后一个元素表示倒数第二个byte所对应的二进制位数
*/
public static byte[] zip(String str, Map<Byte, String> map) {
byte[] bytes = str.getBytes();
StringBuilder strBuilder = new StringBuilder();
for (byte b : bytes) {
strBuilder.append(map.get(b)); //最终变成1010100010111111110010001011111111001000101111111100100101001101110001110000011011101000111100101000101111111100110001001010011011100
}
//此时拿到的strBuilder长度为133,需要转换为字节进行传送
//int length = strBuilder.length() % 8 == 0 ? strBuilder.length() / 8 : strBuilder.length() / 8 + 1;
int length = (strBuilder.length() + 7) / 8 + 1; //之所以+1,是因为最后一位要记录最后一个byte的位数
byte[] haffermanCodeBytes = new byte[length];
int index = 0; //用于记录是床几个byte
for (int i = 0; i < strBuilder.length(); i += 8) {
String strByte;
if (i + 8 > strBuilder.length()) {
strByte = strBuilder.substring(i); //不足8位,则前面依次补0
} else {
strByte = strBuilder.substring(i, i + 8); //截头不截尾
}
haffermanCodeBytes[index] = (byte) Integer.parseInt(strByte, 2);
index++;
}
haffermanCodeBytes[haffermanCodeBytes.length - 1] = (byte) (strBuilder.length() % 8);
return haffermanCodeBytes;
}
/**
* 根据哈夫曼树生成哈夫曼编码表
*
* @param node haffermanTree的根节点
* @return 返回哈夫曼编码表,以Map的形式,key是字节,value是该字节的编码
*/
public static Map<Byte, String> getHaffermanTable(Node node) {
Map<Byte, String> map = new HashMap<>();
putInTable(map, node, "", new StringBuilder());
return map;
}
public static void putInTable(Map<Byte, String> map, Node node, String code, StringBuilder str) {
StringBuilder str2 = new StringBuilder(str);
str2.append(code);
if (node != null) {
if (node.data != null) {
map.put(node.data, str2.toString());
} else {
putInTable(map, node.left, "0", str2); //向左路径为0
putInTable(map, node.right, "1", str2); //向右路径为1
}
}
}
/*生成哈夫曼树*/
public static Node haffermanTree(List<Node> list) {
while (list.size() > 1) {
//1.对list进行排序
Collections.sort(list);
//2.取出最小的两个并从list中删除,生成其父节点并加入list中
Node leftNode = list.remove(0);
Node rightNode = list.remove(0);
Node fatherNode = new Node(null, leftNode.weight + rightNode.weight);
fatherNode.setLeft(leftNode);
fatherNode.setRight(rightNode);
list.add(fatherNode);
}
return list.get(0);
}
/**
* 将一个字符串转换为Node的list集合
*
* @param str
* @return
*/
public static List<Node> strToList(String str) {
byte[] bytes = str.getBytes();
HashMap<Byte, Integer> map = new HashMap<>();
for (byte b : bytes) {
map.put(b, map.get(b) == null ? 1 : map.get(b) + 1);
}
List<Node> lists = new ArrayList<>();
for (Map.Entry<Byte, Integer> entry : map.entrySet()) {
lists.add(new Node(entry.getKey(), entry.getValue()));
}
return lists;
}
/*前序遍历*/
public static void show(Node node) {
if (node != null) {
node.show();
} else {
System.out.println("该赫夫曼树为空");
}
}
}
class Node implements Comparable<Node> {
Byte data; //数据
int weight; //权值,该数据出现的次数
Node left;
Node right;
public Node(Byte data, int weight) {
this.data = data;
this.weight = weight;
}
/*前序遍历*/
public void show() {
if (this.left == null && this.right == null) {
System.out.println(this);
}
if (this.left != null) {
this.left.show();
}
if (this.right != null) {
this.right.show();
}
}
/*升序排序*/
@Override
public int compareTo(Node o) {
return this.weight - o.weight;
}
@Override
public String toString() {
return "Node{" +
"data=" + data +
", weight=" + weight +
'}';
}
public void setData(Byte data) {
this.data = data;
}
public void setWeight(int weight) {
this.weight = weight;
}
public void setLeft(Node left) {
this.left = left;
}
public void setRight(Node right) {
this.right = right;
}
}
多叉树
***原因:***由于二叉树的节点太少,当有大量数据时,可能会频繁的创建节点,并且海量的节点也导致树的高度太大,影响操作速度。因此,在原来二叉树的基础上改进,每个节点可以有更多的数据项和子节点,为多叉树。
*多叉树:*2-3树、2-3-4树,B树等
B树
B树(B-tree):小圆圈表示数据。由图可知一个节点由多个数据,多个子节点。
B树优点:优化了大块数据的读写,普遍运用在数据库和文件系统中
- 有效的降低了树的高度。
- 利用磁盘预读原理,将一个节点的大小设置为一次IO流读取的大小,则每个节点只需要一次IO操作就可以完全载入。(假如树的度为1024,则600亿元素最多只需要4次IO流操作就可以读取到想要的元素,因此广泛运用在数据库系统中。)
节点的度:表示一个节点有多少个结点,度就是多少。例如二叉树的节点的度最大为2
树的度:表示一个树的节点的度最大的那个。
2-3树
*概念:*2-3树是最简单的B树结构
特点:
- 2-3树的所有叶子节点都在同一层(B树都满足)。
- 2-3树由2节点和3节点构成。
2节点:有2个子节点,要么没节点。 3节点:有3个子节点,要么没有节点。
B+树
与B树相比,所有的数据都存放在叶子节点。非叶子节点不在存放数据,只有索引。更适合应用在文件系统。
B*树
根据B+树变化的。在B+树上的基础上,非根非叶子节点添加了指向兄弟节点的指针。
哈希
笔记待整理
图
链表结构局限于一个前驱和一个后继关系,树结构也只能有一个父节点。当需要实现多对多关系的结构,就是图
常用概念:
-
顶点(vertex):就是节点,例如A、B、C|
-
边(edge):
-
路径:例如D->C:
- D->B->C
- D->A->B->C
-
无向图:顶点之间的连接没有方向。如图就是。
-
有向图:顶点之间连接有方向。
-
带权图:边带有权值
*图的表现方式:*二维数组(邻接矩阵);链表(邻接表)
邻接矩阵
**概念:**由n个顶点组成的n*n的二位数组。[x,y]=1表示连通,[x,y]=0表示不相连。
邻接表
**概念:**由哈希表(数组+链表)表示。
*优点:*邻接矩阵会为每个顶点都分配n个边,但很多边不存在,为0,这就造成了空间浪费。邻接表则值只表示存在的边,没有空间浪费。
代码实现图结构
/*图结构:使用邻接矩阵实现*/
public class Graph {
ArrayList<String> vertex; //用于存放顶点
int[][] arr; //表示邻接矩阵
int edge; //表示边的数量
boolean[] isVisited; //辅助数组,进行图的遍历的时候用于表示对应顶点是否被访问过。
public Graph(int n) {
arr=new int[n][n];
vertex=new ArrayList<>(n);
edge=0;
isVisited=new boolean[n];
}
/**
* 显示矩阵
*/
public void show(){
for (int[] ints : arr) {
for (int i : ints) {
System.out.print(i+" ");
}
System.out.println();
}
}
/**
* 添加顶点
* @param str
*/
public void addVertex(String str){
vertex.add(str);
}
/**
* 添加边
* @param a 顶点a
* @param b 顶点b
* @param weight 权重
*/
public void addEdge(String a,String b,int weight){
arr[vertex.indexOf(a)][vertex.indexOf(b)]=weight;
arr[vertex.indexOf(b)][vertex.indexOf(a)]=weight;
}
}
深度/广度优先遍历
*深度优先遍历(DFS):*利用了递归,从一个初始节点开始,选择一条路径走到底,当这条路径走到尽头,就回溯一步接着走,在走到尽头再回溯。
/**
* 深度优先算法
* @param str 传入开始遍历的值
*/
public void depthFirthSearch(String str){
int index = vertex.indexOf(str);
if (isVisited[index]==false) {
System.out.print(str + " ");
isVisited[index] = true;
for (int i = 0; i < vertex.size(); i++) {
if (arr[index][i] == 1 && isVisited[i]==false) {
depthFirthSearch(vertex.get(i));
}
}
}
}
*广度优先遍历(BFS):*从初始节点开始,先将与该节点相连的节点权访问一遍,再以下一个节点为初始节点,将与其相连的节点再都访问一边。
/**
* 广度优先算法(DFS)
* @param str
*/
public void broadFirstSearch(String str) {
int index = vertex.indexOf(str);
while (index < vertex.size()) {
if (isVisited[index] == false) {
System.out.print(vertex.get(index) + " ");
isVisited[index]=true;
for (int i = 0; i < vertex.size(); i++) {
if (arr[index][i] != 0 && isVisited[i] == false) {
System.out.print(vertex.get(i) + " ");
isVisited[i]=true;
}
}
}
index++;
}
}