数据结构与算法基础(Java)
1.数据结构
数据结构是指相互之间存在着一种或者多种关系的数据元素的集合和该集合中数据元素之间的关系组成,数据结构就是数据和数据之间的关系。
数据结构分为 数据的存储结构和数据的逻辑结构。
1.1存储结构
- 顺序结构
- 链式结构
1.2逻辑存储
- 集合结构
- 线性结构
- 树形结构
- 图形结构
2.算法
算法是指解题方案的准确而完整的描述,是一系列解决问题的清晰的指令,算法代表着用系统的方法描述问题的策略机制。简单的说,算法就是解决问题的思路。
2.1特性
- 输入
- 输出
- 有穷性
- 确定性
- 可行性
2.2基本要求
- 正确性
- 可读性
- 健壮性
- 时间复杂度
- 空间复杂度
没有最好的算法只有最适合的算法
3.线性结构
3.1数组
典型的顺序存储结构
3.1.1数组的基本使用
package cn.stdy.strcut;
public class TestArray {
public static void main(String[] args) {
// 创建一个数组
int [] arr1 = new int[3];
// 获取数组的长度
int length1 = arr1.length;
System.out.println(length1);
// 访问数组中的元素 数组名[下标] 下标从0开始 最大可以取到length - 1
int element0 = arr1[0];
System.out.println("element0: " + element0);
// 为数组中的元素赋值
arr1[0] = 99;
System.out.println("element0: " + element0);
arr1[1] = 98;
arr1[2] = 97;
// 遍历数组
for (int i = 0; i < arr1.length; i ++){
System.out.println("element" + i + ": " + arr1[i]);
}
// 创建数组的同时为数组中的元素赋值
int[] arr2 = new int[]{1,2,3,4,5};
// 获取数组的长度
System.out.println("length: " + arr2.length);
}
}
3.1.2数组元素的添加
数组的长度是不可变的
package cn.stdy.strcut;
import java.util.Arrays;
public class TestOpArr {
public static void main(String[] args) {
// 解决数组长度不可变的问题
int arr[] = {9,8,7};
// 快速查看数组中元素
System.out.println(Arrays.toString(arr));
// 要加入数组的目标元素
int dst = 6;
// 创建一个新的数组 长度是原数组长度 + 1
int arr1[] = new int[arr.length + 1];
// 把原数组中的数据复制到新数组中
for (int i = 0; i < arr.length;i++)
arr1[i] = arr[i];
// 把目标元素放入到新数组最后
arr1[arr1.length - 1] = dst;
// 新数组替换原数组
arr = arr1;
System.out.println(Arrays.toString(arr));
}
}
3.1.3数组元素的删除
package cn.stdy.strcut;
import java.util.Arrays;
public class TestOpArray1 {
public static void main(String[] args) {
// 删除数组中指定下标的元素
// 原数组
int[] arr = {1,2,3,4,5,6,7};
// 创建一个新的数组长度是原数组长度-1
int [] newArr = new int[arr.length - 1];
System.out.println(Arrays.toString(arr));
// 要删除的下标
int dst = 3;
// 遍历新数组 将原数组中的值复制到新数组
for (int i = 0;i < newArr.length; i ++){
// 如果当前下标小于要删除元素的下标
if (i < dst){
// 将原数组的对应下标的值赋值给新数组的对应下标
newArr[i] = arr[i];
// 如果当前下标大于等于要删除元素的下标
}else {
// 将原数组的对应下标 + 1 的值复制到新数组的当前下标
newArr[i] = arr[i + 1];
}
}
// 新数组替换原数组
arr = newArr;
System.out.println(Arrays.toString(arr));
}
}
3.1.4面向对象的数组
将数组封装成一个对象,提供对内部元素的增删改查操作的方法
package cn.stdy.strcut;
import java.util.Arrays;
/**
* 面向对象的数组
*/
public class MyArray {
// 底层为一个数组
private int[] elements = new int[0];
// 向数组末尾添加元素
public void add(int element){
// 创建一个新的数组长度为原数组 +1
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 void delete(int index){
// 判断索引是否合法
if (index < 0 || index >= elements.length){
throw new RuntimeException("非法索引: " + index);
}
// 创建一个新数组长度为原数组长度 - 1
int [] newArr = new int[elements.length - 1];
// 将元素中的元素复制到新数组中
for (int i = 0;i < newArr.length; i ++){
// 如果当前索引小于要删除索引
if (i < index){
// 将原数组当前索引的元素赋值给新数组当前索引元素
newArr[i] = elements[i];
}else {
// 将原数组的索引+1的值赋值给新数组当前索引
newArr[i] = elements[i + 1];
}
}
// 新数组替换原数组
elements = newArr;
}
// 在指定索引处插入指定的元素
public void insert(int index,int element){
// 判断索引是否合法
if (index < 0 || index >= elements.length){
throw new RuntimeException("非法索引: " + index);
}
// 创建一个数组长度为原数组长度 - 1
int [] newArr = new int[elements.length + 1];
// 将原数组中的元素复制到新数组中
for (int i = 0;i < elements.length; i ++){
// 如果当前索引值小于要插入新元素索引值
if (i < index){
// 将原数组中对应索引的元素复制到新数组
newArr[i] = elements[i];
}else {
// 将原数组的当前索引的值复制到新数组的当前索引+1的位置
newArr[i + 1] = elements[i];
}
}
// 将要插入的元素的值赋值给数组指定索引
newArr[index] = element;
// 将新数组赋值给原数组
elements = newArr;
}
// 修改指定索引的值
public void set(int index,int element){
elements[index] = element;
}
// 获取指定索引处的值
public int get(int index){
return elements[index];
}
// 线性查找
public int search(int element){
for (int i = 0; i < elements.length;i++){
if (elements[i] == element)
return i;
}
return -1;
}
// 二分法查找
public int binarySearch(int element){
// 记录开始位置
int begin = 0;
// 记录结束位置
int end = elements.length - 1;
// 记录中间位置
int mid = (begin + end) / 2;
// 循环查找
while (true){
// 如果开始位置和结束位置重合 就代表没有这个元素
if (begin > end) return -1;
// 如果中间索引的元素等于查找的目标元素
if (elements[mid] == element) return mid;
// 如果中间索引的元素小于查找的目标元素
if (elements[mid] < element) begin = mid + 1;
// 如果中间索引的元素大于查找的目标元素
else end = mid - 1;
// 重新给中间索引赋值
mid = (begin + end) / 2;
}
}
// 获取数组的长度
public int size(){
return elements.length;
}
// 打印数组元素的方法
public void show(){
System.out.println(Arrays.toString(elements));
}
}
3.1.5线性查找
package cn.stdy.strcut;
/**
* 数组的查找方法
*/
public class TestSearch {
public static void main(String[] args){
// 目标数组
int[] arr = {1,2,3,4,5,6,7,8,9};
// 目标元素
int target = 10;
// 下标
int index = -1;
// 遍历数组进行查找
for (int i = 0;i < arr.length;i++){
if (arr[i] == target){
index = i;
break;
}
}
// 打印下标
System.out.println("index :" + index);
}
}
3.1.6数组的二分法查找
package cn.stdy.strcut;
/**
* 数组的二分法查找
* 前提 数组是有序的
*/
public class TestBinarySearch {
public static void main(String[] args) {
// 目标数组
int [] arr = {1,2,3,4,5,6,7,8,9};
// 目标元素
int target = 1;
// 开始索引
int begin = 0;
// 结束索引
int end = arr.length - 1;
// 中间索引
int mid = (begin + end) / 2;
// 返回的结果索引
int index = -1;
// 循环查找
while(true){
// 如果结束索引小于开始索引就代表不存在这个元素
if (begin > end) break;
// 如果中间索引的元素等于目标元素
if (arr[mid] == target){
index = mid;
break;
}
// 如果中间索引元素大于目标元素
if (arr[mid] > target){
// 代表目标元素在中间索引元素的左边
end = mid - 1;
}else {
// 代表目标元素在中间索引元素的右边
begin = mid + 1;
}
// 重新给中间索引赋值
mid = (begin + end) / 2;
}
System.out.println("index : " + index);
}
}
3.2栈
package cn.stdy.strcut.list;
/**
* 栈的实现
*/
public class MyStack {
// 栈底层使用数组存放数据
private 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 < newArr.length; 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;
}
// 栈的元素个数
public int size(){
return elements.length;
}
public static void main(String[] args) {
// 创建栈
MyStack myStack = new MyStack();
// 压入元素进栈
myStack.push(9);
myStack.push(8);
myStack.push(7);
myStack.push(6);
// 产看栈顶元素
System.out.println("peek element: " + myStack.peek());
// 出栈
myStack.pop();
myStack.pop();
myStack.pop();
System.out.println("peek element: " + myStack.peek());
// 栈的长度
System.out.println(myStack.size());
// 判断栈是否为空
System.out.println(myStack.isEmpty());
}
}
3.3队列
package cn.stdy.strcut.list;
/**
* 队列
*/
public class MyQueue {
// 底层使用数组
private int [] elements;
public MyQueue(){
elements = new int[0];
}
// 元素入队方法
public void add(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 poll(){
if (elements.length < 0) new RuntimeException("queue is empty!");
// 出队元素
int element = elements[0];
// 创建一个新数组长度为原数组-1
int[] newArr = new int[elements.length - 1];
// 复制元素到新数组
for (int i = 0;i < newArr.length;i ++)
newArr[i] = elements[i + 1];
// 替换数组
elements = newArr;
return element;
}
// 判断队列是否为空
public boolean isEmpty(){
return elements.length == 0;
}
// 队列长度
public int size(){
return elements.length;
}
// 对头元素
public int getHead(){
return elements[0];
}
public static void main(String[] args) {
// 创建队列
MyQueue myQueue = new MyQueue();
// 加入元素
myQueue.add(1);
myQueue.add(2);
myQueue.add(3);
myQueue.add(4);
// 获取队头元素
System.out.println(myQueue.getHead());
// 队列是否为空
System.out.println(myQueue.isEmpty());
// 出队
myQueue.poll();
myQueue.poll();
myQueue.poll();
// 队列长度
System.out.println(myQueue.size());
// 队头元素
System.out.println(myQueue.getHead());
}
}
3.4单链表
package cn.stdy.strcut.linked;
/**
* 单链表
*/
public class MyNode {
// 存放的数据
private int data;
// 指针 指向下一个节点
private MyNode next;
// 构造方法
public MyNode(int data){
this.data = data;
}
// 添加节点
public MyNode append(MyNode node){
// 获取当前节点
MyNode currentNode = this;
// 循环找出最后一个节点
while (true){
// 获取当前节点的下一个节点
MyNode nextNode = currentNode.next;
// 如果当前节点的下一个节点为null
if (nextNode == null) break;
// 将当前节点向下移动
currentNode = nextNode;
}
// 追加新节点到最后一个节点
currentNode.next = node;
return currentNode;
}
// 获取下一个节点
public MyNode next(){
return this.next;
}
// 获取节点数据
public int getData(){
return this.data;
}
// 判断当前节点是否为最后一个节点
public boolean isLast(){
return this.next == null;
}
}
3.4.1删除单链表节点
// 显示所有节点信息
public void show(){
// 获取当前节点
MyNode currentNode = this;
// 循环打印节点信息
while (true){
// 打印当前节点信息
System.out.print(currentNode.data);
//如果节点为最后一个节点 跳出循环
if (currentNode.next == null) break;
// 打印分割符
System.out.print(" --> ");
// 将当前节点移动到下一个节点
currentNode = currentNode.next;
}
System.out.println();
}
// 删除下一个节点
public void removeNext(){
// 获取当前节点的下下个节点
MyNode next = this.next.next;
// 将当前节点的下一个节点指向下下个节点
this.next = next;
}
3.4.2单链表节点插入
// 插入一个节点
public void insert(MyNode node){
// 获取当前节点的下一个节点
MyNode next = this.next;
// 将新节点追加到当前节点
this.next = node;
// 将原有的下一个节点追加到新节点后面
node.next = next;
}
3.5循环链表
package cn.stdy.strcut.linked;
/**
* 循环链表
*/
public class MyLoopNode {
// 存放的数据
private int data;
// 指针 指向下一个节点
private MyLoopNode next = this;
// 构造方法
public MyLoopNode(int data){
this.data = data;
}
// 获取下一个节点
public MyLoopNode next(){
return this.next;
}
// 获取节点数据
public int getData(){
return this.data;
}
// 判断当前节点是否为最后一个节点
public boolean isLast(){
return this.next == null;
}
// 插入一个节点
public void insert(MyLoopNode node){
// 获取当前节点的下一个节点
MyLoopNode next = this.next;
// 将新节点追加到当前节点
this.next = node;
// 将原有的下一个节点追加到新节点后面
node.next = next;
}
}
package cn.stdy.strcut.linked;
public class TestLoopNode {
public static void main(String[] args) {
// 测试循环链表
MyLoopNode n1 = new MyLoopNode(1);
MyLoopNode n2 = new MyLoopNode(2);
MyLoopNode n3 = new MyLoopNode(3);
MyLoopNode n4 = new MyLoopNode(4);
// 追加节点
n1.insert(n1);
n2.insert(n3);
n3.insert(n4);
n4.insert(n1);
// 打印
System.out.println(n1.next().getData());
System.out.println(n2.next().getData());
System.out.println(n3.next().getData());
System.out.println(n4.next().getData());
}
}
3.6双向循环链表
package cn.stdy.strcut.linked;
/**
* 双向循环链表
*/
public class DoubleNode {
// 上一个节点
private DoubleNode pre = this;
// 下一个节点
private DoubleNode next = this;
// 节点数据
private int data;
// 构造方法
public DoubleNode(int data){
this.data = data;
}
// 获取下一个节点
public DoubleNode next(){
return this.next;
}
// 获取上一个节点
public DoubleNode pre(){
return this.pre;
}
// 获取节点数据
public int getData(){
return this.data;
}
// 插入节点
public void insert(DoubleNode node){
// 获取当前节点的下一个节点
DoubleNode next = this.next;
// 将当前节点的下一个节点指向插入的节点
this.next = node;
// 将插入的节点的上一个节点指向当前节点
node.pre = this;
// 将原来的下个节点的上一个节点指向插入的节点
next.pre = node;
// 将插入的节点的下一个节点指向原来的下一个节点
node.next = next;
}
}
package cn.stdy.strcut.linked;
public class TestDoubleNode {
public static void main(String[] args) {
// 测试双向循环链表
DoubleNode n1 = new DoubleNode(1);
DoubleNode n2 = new DoubleNode(2);
DoubleNode n3 = new DoubleNode(3);
DoubleNode n4 = new DoubleNode(4);
// 打印节点信息
// System.out.println(n1.getData());
// System.out.println(n1.next().getData());
// System.out.println(n1.pre().getData());
// 节点追加
n1.insert(n2);
n2.insert(n3);
n3.insert(n4);
// 打印节点信息
System.out.println(n1.getData()); // 1
System.out.println(n1.next().getData()); // 2
System.out.println(n1.pre().getData()); // 4
System.out.println(n2.getData()); // 2
System.out.println(n2.next().getData()); // 3
System.out.println(n2.pre().getData()); // 1
System.out.println(n3.getData()); // 3
System.out.println(n3.next().getData()); // 4
System.out.println(n3.pre().getData()); // 2
System.out.println(n4.getData()); // 4
System.out.println(n4.next().getData()); // 1
System.out.println(n4.pre().getData()); // 3
}
}
4.递归
在一个方法中,方法中有对自己的调用,方法自己本身调用自己的方式称为递归,如果无限递归就会导致栈溢出,因此递归一定要有结束条件。
package cn.stdy.strcut.recursive;
public class TestRecursive {
public static void main(String[] args) {
print(100);
}
public static void print(int i){
if (i < 0 ){
return;
}
System.out.println(i);
print(i - 1);
}
}
4.1斐波纳切数列
package cn.stdy.strcut.recursive;
public class TestFebonacci {
public static void main(String[] args) {
// 斐波纳切数列 : 1 1 2 3 5 8 13
// 前两项为1 从第三项开始 值为前两项的和
System.out.println("10 : " + febonacci(6));
}
public static int febonacci(int i){
if (i < 2){
return 1;
}else {
return febonacci(i - 1) + febonacci(i - 2);
}
}
}
4.2汉诺塔
package cn.stdy.strcut.recursive;
/**
* 汉诺塔
*/
public class TestHanoi {
public static void main(String[] args) {
hanoi(5,'A','B','C');
}
public static void hanoi(int n,char form,char in,char to){
// 如果只有一个盘子i
if (n == 1){
System.out.println("第1个盘从" + form + "移动到" + to);
}else {
// 移动上面的盘子
hanoi(n -1,form,to,in);
// 移动下面的盘子
System.out.println("第" + n + "个盘子从" + form + "移动到" + to);
// 把上面的所有盘子从中间移动到目标位置
hanoi(n-1,in,form,to);
}
}
}
5.排序算法
评判一个算法优劣的两个指标,时间复杂度、空间复杂度。一般情况下只考虑时间复杂度。
5.1时间复杂度
一个算法执行的代码的行数是可以确定的,一个算法的语句执行的次数称为语句的频度T(n)。
5.2交换排序
5.2.1数组冒泡排序
package cn.stdy.strcut.sort;
import java.util.Arrays;
/**
* 冒泡排序
*/
public class BubbleSort {
public static void main(String[] args) {
int [] arr = {7,3,8,2,7,5,9,1,9,3};
System.out.println(Arrays.toString(arr));
bubbleSort(arr);
System.out.println(Arrays.toString(arr));
}
// 冒泡排序 共需要比较length-1轮
public static void bubbleSort(int [] arr){
// 外层循环控制比较轮数
for (int i = 0;i < arr.length - 1;i ++){
// 内层循环控制比较次数 已经比较过的值不用比较
for (int j = 0; j < arr.length - 1 - i;j ++){
if (arr[j] > arr[j + 1]){
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
System.out.println(Arrays.toString(arr));
}
}
}
5.2.2快速排序
package cn.stdy.strcut.sort;
import java.util.Arrays;
/**
* 快速排序
*/
public class QuickSort {
public static void main(String[] args) {
int[] arr = new int[]{7, 5, 1, 2, 7, 3, 5, 8, 1, 2, 3, 0, 9, 5, 8, 0};
quickSort(arr, 0, arr.length - 1);
System.out.println(Arrays.toString(arr));
}
/**
* 快速排序
*
* @param arr 需要排序的数组
* @param start 开始位置
* @param end 结束位置
*/
public static void quickSort(int[] arr, int start, int end) {
// 开始索引要小于结束索引
if (start < end) {
// 将开始位置的数记为标准数
int stand = arr[start];
// 开始位置为低位索引
int low = start;
// 结束位置为高位索引
int high = end;
// 循环进行排序
while (low < high) {
// 如果开始索引小于结束索引 并且右边的数大于标准数
while (low < high && stand <= arr[high]) {
// 高位索引左移
high--;
}
// 右边的数小于标准数 替换掉右边的数
arr[low] = arr[high];
// 如果开始索引小于结束索引 并且左边的数于标准数
while (low < high && stand >= arr[low]) {
// 低 位索引右移
low++;
}
// 左边的数大于标准数 替换掉左边的数
arr[high] = arr[low];
}
// 将标准数赋值
arr[low] = stand;
// 递归 比较左边较小的数
quickSort(arr, start, low);
// 递归 比较右边较大的数
quickSort(arr, low + 1, end);
}
}
}
5.3插入排序
5.3.1直接插入排序
package cn.stdy.strcut.sort;
import java.util.Arrays;
/**
* 插入排序
*/
public class InsertSort {
public static void main(String[] args) {
int [] arr = {7,4,8,1,2,5,8,9,1,2,3,5};
insertSort(arr);
System.out.println(Arrays.toString(arr));
}
public static void insertSort(int []arr){
// 从第2个元素开始循环遍历数组
for (int i = 1;i < arr.length;i ++) {
// 如果当前元素比前一个元素小
if (arr[i] < arr[i - 1]){
// 将当前元素存入临时变量
int temp = arr[i];
// 记录插入的元素的位置
int j;
// 遍历当前元素之前的所有元素 比较前面的元素是否比当前元素大
for ( j = i - 1;j >= 0 && arr[j] > temp;j--){
// 前面的元素大于当前元素 将当前元素替换前面的元素
arr[j + 1] = arr[j];
}
// 将临时变量赋值给当前循环中最小元素的位置
arr[j + 1] = temp;
}
}
}
}
5.3.2希尔排序
package cn.stdy.strcut.sort;
import java.util.Arrays;
/**
* 希尔排序
*/
public class ShellSort {
public static void main(String[] args) {
int [] arr = {7,5,7,1,3,2,8,5,1,2,3,5,1,0,2};
shellSort(arr);
System.out.println(Arrays.toString(arr));
}
public static void shellSort(int arr[]){
// 遍历所有步长
for (int d = arr.length / 2;d > 0; d/=2){
// 遍历数组元素
for (int i = d; i < arr.length; i++){
// 遍历本组中的所有元素
for (int j = i - d;j >= 0;j -= d){
// 如果前面的元素大于对应步长后的元素
if (arr[j] > arr[j + d]){
// 交换位置
int temp = arr[j];
arr[j] = arr[j + d];
arr[j + d] = temp;
}
}
}
}
}
}
5.4选择排序
5.4.1简单选择排序
package cn.stdy.strcut.sort;
import java.util.Arrays;
/**
* 简单排序
*/
public class SelectSort {
public static void main(String[] args) {
int[] arr = {7,3,2,1,9,5,9,1,5,9,0};
selectSort(arr);
System.out.println(Arrays.toString(arr));
}
public static void selectSort(int[] arr){
// 遍历数组
for (int i = 0; i < arr.length; i++){
// 当前遍历数的下标
int minIndex = i;
// 遍历后面的所有数
for (int j = i + 1;j < arr.length; j++){
// 如果后面的数比当前下标的数要小
if (arr[i] > arr[j]){
// 记录最小下标
minIndex = j;
}
}
// 如果后面没有找到比当前数更小的下标就不用替换
if (minIndex != i){
int temp = arr[i];
// 将后面最小的数放入到当前下标数
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
}
}
}
5.4.2归并排序
package cn.stdy.strcut.sort;
import java.util.Arrays;
public class MergeSort {
public static void main(String[] args) {
int[] arr = {7,5,2,3,7,5,8,9,1,2,3,8,5};
System.out.println(Arrays.toString(arr));
mergeSort(arr,0,arr.length - 1);
System.out.println(Arrays.toString(arr));
}
// 归并排序
public static void mergeSort(int arr[], int low, int high) {
int middle = (low + high) / 2;
if (low < high) {
// 处理左边
mergeSort(arr, low, middle);
// 处理右边
mergeSort(arr, middle + 1, high);
// 归并
merge(arr, low, middle, high);
}
}
public static void merge(int arr[], int low, int middle, int high) {
// 用于存储归并排序后的临时数组
int[] temp = new int[high - low + 1];
// 记录第一个数组中需要遍历的下标
int i = low;
// 记录第二个数组中需要遍历的下标
int j = middle + 1;
// 用于记录存放在临时数组的下标
int index = 0;
// 循环遍历数组的两个部分 取出小的数字放入临时数组中
while (i <= middle && j <= high) {
if (arr[i] < arr[j]) {
temp[index] = arr[i];
// 让下标向后移一位
i++;
} else {
temp[index] = arr[j];
// 让下标向后移一位
j++;
}
index++;
}
// 处理多余数据
while (j <= high) {
temp[index] = arr[j];
j++;
index++;
}
while (i <= middle) {
temp[index] = arr[i];
i++;
index++;
}
// 将临时数组的数据放入原数组中
for (int k = 0; k < temp.length; k++) {
arr[low + k] = temp[k];
}
}
}
6.基数排序
6.1基数排序的数组实现
package cn.stdy.strcut.sort;
import java.util.Arrays;
/**
* 基数排序
*/
public class radixSort {
public static void main(String[] args) {
int[] arr = {75, 32, 95, 1, 95, 90, 13, 285, 0, 3, 19};
System.out.println(Arrays.toString(arr));
radixSort(arr);
System.out.println(Arrays.toString(arr));
}
public static void radixSort(int arr[]) {
// 找到数组中最大的数
int max = Integer.MIN_VALUE;
for (int i = 0; i < arr.length; i++) {
if (max < arr[i]) {
max = arr[i];
}
}
// 根据最大的数的位数确定排序的次数
int count = (max + "").length();
// 定义存放临时数据的数组
int[][] temp = new int[10][arr.length];
// 根据最大数的位数循环比较 j 代表比较的轮数 n代表取余的位数
for (int i = 0, n = 1; i < count; i++, n *= 10) {
// 定义对应位置存放数字的个数的数组
int[] counts = new int[10];
// 把每一个数字进行取余操作并放入临时数组的对应位置
for (int j = 0; j < arr.length; j++) {
// 取余操作
int ys = arr[j] / n % 10;
// 将得到的余数放入临时数组的对应位置
temp[ys][counts[ys]] = arr[j];
// 记录当前余数放入的位置
counts[ys]++;
}
// 记录原数组的位置的索引
int index = 0;
// 每一轮放置余数后 将对应位置的数放入原数组
for (int j = 0;j < counts.length;j ++){
// 如果记录的次数为0 那就不用遍历这个基数的数组
if (counts[j] != 0){
// 通过基数的次数 将排序后的数字放入原数组
for (int c = 0; c < counts[j];c++){
arr[index] = temp[j][c];
index++;
}
}
}
}
}
}
6.2基数排序的队列实现
package cn.stdy.strcut.sort;
import cn.stdy.strcut.list.MyQueue;
import java.util.Arrays;
/**
* 基数排序的队列实现
*/
public class RadixQueueSort {
public static void main(String[] args) {
int[] arr = {516, 58, 12, 35, 952, 105, 71, 29, 0, 5, 85, 1, 2};
System.out.println(Arrays.toString(arr));
radixQueueSort(arr);
System.out.println(Arrays.toString(arr));
}
// 基数排序
public static void radixQueueSort(int[] arr) {
// 需要找到数组中最大的数
int max = Integer.MIN_VALUE;
// 遍历数组找到最大的数
for (int i = 0; i < arr.length; i++) {
if (max < arr[i]) {
max = arr[i];
}
}
// 通过最大数的位数可以确定排序的轮数
int count = (max + "").length();
// 定义存放数据的临时队列数组
MyQueue[] temp = new MyQueue[10];
// 将数组中的队列实例化 避免空指针异常
for (int i = 0; i < temp.length;i ++){
temp[i] = new MyQueue();
}
// 循环将对应数放入对应基数的队列 i代表排序的轮数 n代表排序时取余的位数
for (int i = 0, n = 1; i < count; i++, n *= 10) {
// 循环原数组对元素进行取余放入对应的临时数组对应基数的队列中
for (int j = 0; j < arr.length; j++) {
// 数组元素取余
int ys = arr[j] / n % 10;
// 根据元素取余的结果将元素放入对应的基数队列中
temp[ys].add(arr[j]);
}
// 记录元素组元素的索引
int index = 0;
for (int k = 0; k < temp.length; k++) {
// 将临时数组中队列的元素重新放回原数组
while (!temp[k].isEmpty()) {
arr[index] = temp[k].poll();
index++;
}
}
}
}
}
7.树结构
7.1概述
线性结构,特别是数组的插入和删除元素的代价比较大。链表相对来说可以缓解一定的压力,但是在查找(随机访问的时候)的时候代价比较大。
树结构,包含一个根节点,一个根节点可以有多个子结点,子结点也可以有多个子结点,一个父节点可以成为两个或多个节点的双亲节点。
节点的度:节点的子节点的个数
节点的权:节点存储的数据
叶子节点:没有子结点的节点
子树:一个树可以分出多个子树
层:根节点为第一层,其子节点为第二层,依次类推
树的高度:数的最大层数
森林:多个树组成森林
7.2二叉树
二叉树:任何一个节点的子节点的数量不超过2的树
二叉树的子节点分为左节点和右节点
满二叉树:所有叶子节点都在最后一层,而且节点的总数为2^n-1,n为树的高度
完全二叉树:所有的叶子节点都在最后一层或到数第二层,且最后的叶子节点在左边连续,倒数第二节的叶子节点在右边连续
二叉树的形态:空树、左斜树、右斜树
7.2.1二叉树的简单实现
BinaryTree.java
package cn.stdy.strcut.tree;
/**
* 二叉树的实现类
*/
public class BinaryTree {
// 根节点
private TreeNode root;
// 指定二叉树根节点
public TreeNode getRoot() {
return root;
}
// 获取二叉树根节点
public void setRoot(TreeNode root) {
this.root = root;
}
}
TreeNode.java
package cn.stdy.strcut.tree;
/**
* 树的节点类
*/
public class TreeNode {
// 节点的权
private int value;
// 节点的左边子节点
private TreeNode leftNode;
// 节点的右边子节点
private TreeNode rightNode;
public TreeNode(int value){
this.value = value;
}
public void setLeftNode(TreeNode leftNode) {
this.leftNode = leftNode;
}
public void setRightNode(TreeNode rightNode) {
this.rightNode = rightNode;
}
public TreeNode getRightNode() {
return rightNode;
}
public TreeNode getLeftNode() {
return leftNode;
}
public int getValue() {
return value;
}
}
测试类
package cn.stdy.strcut.tree;
public class TestBinary {
public static void main(String[] args) {
// 创建一个二叉树
BinaryTree binaryTree = new BinaryTree();
// 创建一个根节点
TreeNode root = new TreeNode(1);
// 设置二叉树的根节点
binaryTree.setRoot(root);
// 创建两个子节点
TreeNode leftNode = new TreeNode(2);
TreeNode rightNode = new TreeNode(3);
// 设置根节点的左右节点
root.setLeftNode(leftNode);
root.setRightNode(rightNode);
}
}
7.2.2二叉树的遍历
前序遍历:根左右
TreeNode.java中添加
// 前序遍历 根左右
public void frontShow() {
// 先打印根节点
System.out.println(value);
// 打印左节点
if (leftNode != null) leftNode.frontShow();
// 打印右节点
if (rightNode != null) rightNode.frontShow();
}
BinaryTree.java中添加
// 二叉树的前序遍历
public void frontShow(){
root.frontShow();
}
中序遍历:左根右
TreeNode.java中添加
// 中序遍历 左根右
public void midShow(){
// 先打印左节点
if (leftNode != null) leftNode.midShow();
// 打印根节点
System.out.println(value);
// 最后打印右节点
if (rightNode != null) rightNode.midShow();
}
BinaryTree.java中添加
// 二叉树的前序遍历
public void frontShow(){
root.frontShow();
}
后序遍历:左右根
TreeNode.java中添加
// 后序遍历 左右根
public void afterShow(){
// 先打印左节点
if (leftNode != null) leftNode.afterShow();
// 打印右节点
if (rightNode != null) rightNode.afterShow();
// 最后打印根节点
System.out.println(value);
}
BinaryNode.java中添加
// 二叉树的后序遍历
public void afterShow(){
root.afterShow();
}
测试方法
package cn.stdy.strcut.tree;
public class TestBinary {
public static void main(String[] args) {
// 创建一个二叉树
BinaryTree binaryTree = new BinaryTree();
// 创建一个根节点
TreeNode root = new TreeNode(1);
// 设置二叉树的根节点
binaryTree.setRoot(root);
// 创建两个子节点
TreeNode leftNode = new TreeNode(2);
TreeNode rightNode = new TreeNode(3);
// 设置根节点的左右节点
root.setLeftNode(leftNode);
root.setRightNode(rightNode);
// 创建两子节点给左节点
TreeNode llNode = new TreeNode(4);
TreeNode lrNode = new TreeNode(5);
// 设置根节点的左节点的左右节点
leftNode.setLeftNode(llNode);
leftNode.setRightNode(lrNode);
// 创建两个子节点给右节点
TreeNode rlNode = new TreeNode(6);
TreeNode rrNode = new TreeNode(7);
// 设置根节点的右节点的左右节点
rightNode.setLeftNode(rlNode);
rightNode.setRightNode(rrNode);
// 调用二叉树前序方法
binaryTree.frontShow();
System.out.println("--------");
// 调用二叉树的中序方法
binaryTree.midShow();
System.out.println("--------");
// 调用二叉树的后序方法
binaryTree.afterShow();
}
}
图例
7.2.3二叉树查找
先序查找
1.先将查找的值和根节点的值比较,如果相等就返回根节点
2.如果根节点没有找到,就判断左节点是否为空,如果不为空,就用左节点递归调用先序查找方法
3.如果左节点没有找到,就判断右节点是否为空,如果不为空,就用右节点递归调用先序查找方法
4.如果都没有找到就会返回一个空的结果变量
TreeNode.java中添加
// 先序查找
public TreeNode frontSearch(int i) {
// 要查找的目标节点
TreeNode target = null;
// 首先在根节点比较
if (i == value) {
// 如果节点的值相等就直接返回
return this;
}
// 如果根节点找不到 就到左节点查找
if (leftNode != null) {
target = leftNode.frontSearch(i);
}
if (target == null) {
// 如果左节点找不到 就到右节点查找
if (rightNode != null) {
target = rightNode.frontSearch(i);
}
}
// 实在找不到就返回null
return target;
}
BinaryTree.java中添加
// 二叉树的先序查找
public TreeNode frontSearch(int i){
return root.frontSearch(i);
}
中序查找
1.先判断二叉树的左子节点是否为空,如果不为空就使用左节点递归调用中序查找的方法并将左节点赋值给结果变量
2.如果结果变量为空,代表左节点没有找到,那就到根节点进行查找,如果根节点的值等于要查找的值,直接返回当前节点
3.如果在根节点没有找到,那就判断右节点是否为空,如果右节点不为空,那就使用右节点递归调用中序查找方法并将右节点的值赋值给结果变量
4.如果都没有找到就会返回一个空的结果变量
TreeNode.java中添加
// 中序查找
public TreeNode midSearch(int i) {
// 要查找的目标节点
TreeNode target = null;
// 首先在左节点比较
if (leftNode != null) {
target = leftNode.midSearch(i);
}
if(target != null) {
return target;
}
// 如果左节点没有找到就在根节点查找
if (this.value == i) {
return this;
}
// 如果根节点没有找到 就在右节点进行比较
if (rightNode != null){
target = rightNode.midSearch(i);
}
// 实在找不到就返回null
return target;
}
BinaryTree.java中添加
// 二叉树的中序查找
public TreeNode midSearch(int i){
return root.midSearch(i);
}
后序查找
1.首先判断左节点是否为空,如果不为空就使用左节点递归调用中序查找的方法并将左节点赋值给结果变量
2.如果结果变量为空,代表左节点没有找到,那就判断右节点是否为空,如果右节点不为空,那就使用右节点递归调用后序查找方法,并将查找结果赋值给结果变量
3.如果结果变量为空,那就到根节点进行查找,如果根节点的值和查找值匹配,就返回根节点
4.如果都没有找到就会返回一个空的结果变量
TreeNode.java中添加
// 后序查找
public TreeNode afterSearch(int i){
// 定义查找目标节点
TreeNode target = null;
// 先根据左节点查找
if (leftNode != null) {
target = leftNode.afterSearch(i);
}
if (target != null) {
return target;
}
// 根据右节点查找
if (rightNode != null) {
target = rightNode.afterSearch(i);
}
// 如果左右节点都找不到就根据根节点查找
if (target != null){
return target;
}
if (value == i ) {
return this;
}
return target;
}
BinaryTree.java中添加
// 二叉树的后序查找
public TreeNode afterSearch(int i){
return root.afterSearch(i);
}
测试类
package cn.stdy.strcut.tree;
public class TestBinary {
public static void main(String[] args) {
// 创建一个二叉树
BinaryTree binaryTree = new BinaryTree();
// 创建一个根节点
TreeNode root = new TreeNode(1);
// 设置二叉树的根节点
binaryTree.setRoot(root);
// 创建两个子节点
TreeNode leftNode = new TreeNode(2);
TreeNode rightNode = new TreeNode(3);
// 设置根节点的左右节点
root.setLeftNode(leftNode);
root.setRightNode(rightNode);
// 创建两子节点给左节点
TreeNode llNode = new TreeNode(4);
TreeNode lrNode = new TreeNode(5);
// 设置根节点的左节点的左右节点
leftNode.setLeftNode(llNode);
leftNode.setRightNode(lrNode);
// 创建两个子节点给右节点
TreeNode rlNode = new TreeNode(6);
TreeNode rrNode = new TreeNode(7);
// 设置根节点的右节点的左右节点
rightNode.setLeftNode(rlNode);
rightNode.setRightNode(rrNode);
// 调用二叉树的先序查找方法
TreeNode treeNode = binaryTree.frontSearch(7);
System.out.println(treeNode.getValue());
// 调用二叉树的中序查找
TreeNode treeNode1 = binaryTree.midSearch(11);
System.out.println(treeNode1.getValue());
TreeNode treeNode2 = binaryTree.afterSearch(7);
System.out.println(treeNode2.getValue());
}
}
7.2.4二叉树节点删除
TreeNode.java
// 删除一个子树
public void delete(int i){
TreeNode parent = this;
// 判断左节点是否为空
if (leftNode != null && leftNode.getValue() == i){
leftNode = null;
}
// 判断右节点是否为空
if (rightNode != null && rightNode.getValue() == i){
rightNode = null;
}
// 递归删除左节点
parent = leftNode;
if (parent != null){
parent.delete(i);
}
// 递归删除右节点
parent = rightNode;
if (parent != null){
parent.delete(i);
}
}
BinaryTree.java
// 删除子树
public void delete(int i) {
root.delete(i);
}
测试方法
package cn.stdy.strcut.tree;
public class TestBinary {
public static void main(String[] args) {
// 创建一个二叉树
BinaryTree binaryTree = new BinaryTree();
// 创建一个根节点
TreeNode root = new TreeNode(1);
// 设置二叉树的根节点
binaryTree.setRoot(root);
// 创建两个子节点
TreeNode leftNode = new TreeNode(2);
TreeNode rightNode = new TreeNode(3);
// 设置根节点的左右节点
root.setLeftNode(leftNode);
root.setRightNode(rightNode);
// 创建两子节点给左节点
TreeNode llNode = new TreeNode(4);
TreeNode lrNode = new TreeNode(5);
// 设置根节点的左节点的左右节点
leftNode.setLeftNode(llNode);
leftNode.setRightNode(lrNode);
// 创建两个子节点给右节点
TreeNode rlNode = new TreeNode(6);
TreeNode rrNode = new TreeNode(7);
// 设置根节点的右节点的左右节点
rightNode.setLeftNode(rlNode);
rightNode.setRightNode(rrNode);
// 删除子树
binaryTree.delete(7);
binaryTree.frontShow();
}
}
7.3顺序存储二叉树
顺序存储的二叉树通常情况下只考虑完全二叉树
第n个元素的左节点的位置: 2*n + 1
第n个元素的右节点的位置:2*n + 2
第n个元素的父节点的位置:(n - 1) / 2
ArrayBinaryTree.java
package cn.stdy.strcut.tree;
/**
* 顺序存储的二叉树
*/
public class ArrayBinaryTree {
// 底层使用数组存储
private int[] data;
public ArrayBinaryTree(int [] data){
this.data = data;
}
// 先序遍历
public void frontShow(){
frontShow(0);
}
public void frontShow(int index){
// 判断数组是否为空
if (data == null && data.length == 0){
return;
}
// 遍历当前节点
System.out.println(data[index]);
// 2 * index + 1 遍历左节点
if (2 * index + 1 < data.length){
frontShow( 2 * index + 1);
}
// 2 * index + 2 遍历右节点
if (2 * index + 2 < data.length){
frontShow(2 * index + 2);
}
}
// 中序遍历
public void midShow(){
midShow(0);
}
public void midShow(int index){
// 判断数组是否为空
if (data == null && data.length == 0){
return;
}
// 2 * index + 1 遍历左节点
if (2 * index + 1 < data.length){
midShow(2 * index + 1);
}
// 遍历根节点
System.out.println(data[index]);
// 2 * index + 2 遍历右节点
if (2 * index + 2 < data.length){
midShow(2 * index + 2);
}
}
// 后序遍历
public void afterShow(){
afterShow(0);
}
public void afterShow(int index){
// 判断数组是否为空
if (data == null && data.length == 0){
return;
}
// 2 * index + 1 遍历左节点
if (2 * index + 1 < data.length){
midShow(2 * index + 1);
}
// 2 * index + 2 遍历右节点
if (2 * index + 2 < data.length){
midShow(2 * index + 2);
}
// 遍历根节点
System.out.println(data[index]);
}
}
测试类
package cn.stdy.strcut.tree;
/**
* 测试顺序二叉树
*/
public class TestArrayBinaryTree {
public static void main(String[] args) {
int [] data = {1,2,3,4,5,6,7};
ArrayBinaryTree tree = new ArrayBinaryTree(data);
// 先序遍历
tree.frontShow();
// 中序遍历
tree.midShow();
// 后序遍历
tree.afterShow();
}
}
7.3.1堆排序(顺序存储二叉树实现)
package cn.stdy.strcut.sort;
import java.util.Arrays;
/**
* 堆排序
*/
public class HeapSort {
public static void main(String[] args) {
int arr [] = {3,1,2,9,5,8,1,0,2};
heapSort(arr);
System.out.println(Arrays.toString(arr));
}
// 利用大顶堆进行堆排序
public static void heapSort(int arr[]){
// 堆排序是从最后一个非叶子节点开始的
int start = (arr.length - 1) / 2;
// 将数组排序成为大顶推
for (int i = start;i >= 0 ;i--){
maxHeap(arr,arr.length,i);
}
// 将大顶堆的第一个和最后一个调整位置 再把前面的变成大顶堆
for (int i = arr.length - 1;i > 0; i--){
int temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
// 进行排序
maxHeap(arr,i,0);
}
}
// 调整为大顶推的方法
public static void maxHeap(int arr[],int size,int index){
// 左叶子节点
int leftNode = 2 * index + 1;
// 右叶子节点
int rightNode = 2 * index + 2;
// 最大节点索引
int max = index;
// 如果左边的叶子节点大于根节点
if (leftNode < size && arr[max] < arr[leftNode]){
// 记录左节点为最大节点
max = leftNode;
}
// 如果右边的叶子节点大于根节点
if (rightNode < size && arr[max] < arr[rightNode]){
// 记录右边为最大节点
max = rightNode;
}
// 如果子节点大于根节点 替换节点值
if (max != index){
int temp = arr[index];
arr[index] = arr[max];
arr[max] = temp;
// 交换位置可能破坏之前排好的堆 保证原来的值的正确性 递归比较后续子的节点值
maxHeap(arr,size,max);
}
}
}
7.4线索二叉树
二叉树在储存数据的时候,每一个节点会存储当前节点的权重信息和子节点的信息,但是对于叶子节点,由于没有子节点,只存储了节点的权重信息而没有储存叶子节点信息这样就导致了一定的资源的浪费。
线索二叉树就是让普通二叉树中叶子节点中没有的子节点存储父节点的信息,对于一个线索二叉树,一个节点的前一个节点叫前驱节点,后一个节点叫后驱节点。
ThreadTreeNode.java
package cn.stdy.strcut.tree;
/**
* 线索二叉树的节点
*/
public class ThreadTreeNode {
// 节点的权
int value;
// 左节点
ThreadTreeNode leftNode;
// 右节点
ThreadTreeNode rightNode;
// 左右节点的类型 0 代表有下一个节点 1 代表没有下一个节点 需要指向上一个节点
int leftType;
int rightType;
public ThreadTreeNode(int value){
this.value = value;
}
public int getValue() {
return value;
}
public ThreadTreeNode getLeftNode() {
return leftNode;
}
public void setLeftNode(ThreadTreeNode leftNode) {
this.leftNode = leftNode;
}
public ThreadTreeNode getRightNode() {
return rightNode;
}
public void setRightNode(ThreadTreeNode 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 ThreadTreeNode frontSearch(int i) {
// 结果节点
ThreadTreeNode target =null;
// 先根据根节点比较
if (value == i){
return this;
}
// 根据左节点查找
if (leftNode != null){
target = leftNode.frontSearch(i);
}
// 如果左节点没有找到 就到右节点查找
if (target == null) {
if (rightNode != null){
target = rightNode.frontSearch(i);
}
}
// 返回结果
return target;
}
// 中序查找
public ThreadTreeNode midSearch(int i){
// 结果变量
ThreadTreeNode target = null;
// 先到左节点查找
if (leftNode != null){
target = leftNode.midSearch(i);
}
// 如果左节点没有找到 就在根节点查找
if (target != null){
return target;
}
if (value == i){
return this;
}
// 如果根节点没有找到 就到右节点查找
if (rightNode != null){
target = rightNode.midSearch(i);
}
// 返回结果
return target;
}
// 后序查找
public ThreadTreeNode afterSearch(int i){
// 定义结果节点
ThreadTreeNode target = null;
// 先在左节点查找
if (leftNode != null){
target = leftNode.afterSearch(i);
}
// 如果左节点没有找到 就在右节点查找
if (target != null){
return target;
}
// 在右节点查找
if (rightNode != null){
target = rightNode.afterSearch(i);
}
if (target != null) {
return target;
}
// 在根结点查找
if (value == i) return this;
// 返回结果
return target;
}
}
ThreadBinaryTree.java
package cn.stdy.strcut.tree;
/**
* 线索二叉树
*/
public class ThreadBinaryTree {
// 根节点
ThreadTreeNode root;
// 上一个节点
ThreadTreeNode pre = null;
// 设置根节点
public void setRoot(ThreadTreeNode root){
this.root = root;
}
// 获取根节点
public ThreadTreeNode getRoot(){
return root;
}
// 中序线索化二叉树
public void threadNodes(){
threadNodes(root);
}
public void threadNodes(ThreadTreeNode node){
// 如果当前节点为空 直接返回
if (node == null) {
return;
}
// 处理左子树
threadNodes(node.leftNode);
// 如果左节点为空 那就将左节点指向上一个节点
if (node.leftNode == null){
node.leftNode = pre;
node.leftType = 1;
}
// 如果前驱节点的右节点为空 那就让前驱右节点指向自己
if (pre != null && pre.rightNode == null){
pre.rightNode = node;
pre.rightNode.rightType = 1;
}
// 获取上一个节点 当前节点是下一个节点的前驱节点
pre = node;
// 处理右子树
threadNodes(node.rightNode);
}
// 线索树的先序
public void frontShow(){
root.frontShow();
}
// 线索树的中序
public void midShow(){
root.midShow();
}
// 线索树的后序
public void afterShow(){
root.afterShow();
}
// 先序查找
public ThreadTreeNode frontSearch(int i){
return root.frontSearch(i);
}
// 中序查找
public ThreadTreeNode midSearch(int i){
return root.midSearch(i);
}
// 后序查找
public ThreadTreeNode afterSearch(int i){
return root.afterSearch(i);
}
}
测试方法
package cn.stdy.strcut.tree;
public class TestThreadBinaryTree {
public static void main(String[] args) {
// 创建一个二叉树
ThreadBinaryTree binaryTree = new ThreadBinaryTree();
// 创建一个根节点
ThreadTreeNode root = new ThreadTreeNode(1);
// 设置二叉树的根节点
binaryTree.setRoot(root);
// 创建两个子节点
ThreadTreeNode leftNode = new ThreadTreeNode(2);
ThreadTreeNode rightNode = new ThreadTreeNode(3);
// 设置根节点的左右节点
root.setLeftNode(leftNode);
root.setRightNode(rightNode);
// 创建两子节点给左节点
ThreadTreeNode llNode = new ThreadTreeNode(4);
ThreadTreeNode lrNode = new ThreadTreeNode(5);
// 设置根节点的左节点的左右节点
leftNode.setLeftNode(llNode);
leftNode.setRightNode(lrNode);
// 创建两个子节点给右节点
ThreadTreeNode rlNode = new ThreadTreeNode(6);
ThreadTreeNode rrNode = new ThreadTreeNode(7);
// 设置根节点的右节点的左右节点
rightNode.setLeftNode(rlNode);
rightNode.setRightNode(rrNode);
binaryTree.frontShow();
System.out.println("----------------");
binaryTree.midShow();
System.out.println("----------------");
binaryTree.afterShow();
// 线索化二叉树
binaryTree.threadNodes();
// 查找
ThreadTreeNode after = lrNode.rightNode;
System.out.println(after.getValue());
}
}
7.4.1线索二叉树的遍历
在ThreadBinaryTree.java中添加
// 线索树的遍历
public void threadIterate(){
// 用于存储当前节点
ThreadTreeNode node = root;
// 循环遍历线索二叉树
while (node != null){
// 找到开始的左节点
while (node.leftType == 0){
node = node.leftNode;
}
// 打印左节点的值
System.out.println(node.value);
// 找到右节点并打印值
if (node.rightType == 1){
node = node.rightNode;
System.out.println(node.value);
}
// 替换遍历的节点
node = node.rightNode;
}
}
7.5赫夫曼树
n个带权叶子节点构成的所有二叉树中,带权路径最小的二叉树
叶节点的带权路径:从根节点到叶节点经过的路径 经过一个节点路径+1 向左记为0 向右记为1
树的带权路径的长度WPL(weighted path length):树中所有带权路径之和
对于上面的三棵树,它们的带权路径长度分别为:
a:2x9+2x5+2x4+2x2=18+10+8+4=40
b:1x9+2x5+3x4+3x2=9+10+12+6=37
c:1x4+2x2+3x9+3x5=4+4+27+15=50
可以看出,权值越大节点离根节点越近的树才是最优二叉树
7.5.1赫夫曼树代码实现
Node.java
package cn.stdy.strcut.tree;
public class Node implements Comparable<Node>{
// 节点的权重
int value;
// 左节点
Node left;
// 右节点
Node right;
public Node(int value) {
this.value = value;
}
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 int getValue() {
return value;
}
@Override
public int compareTo(Node o) {
return o.value - this.value;
}
@Override
public String toString() {
return "Node{" +
"value=" + value +
", left=" + left +
", right=" + right +
'}';
}
}
HuffmanTree.java
package cn.stdy.strcut.tree;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* 赫夫曼树
*/
public class HuffmanTree {
public static void main(String[] args) {
int arr [] = {2,3,6,5,8,3,1,2,7,5,8};
Node huffmanTree = createHuffmanTree(arr);
System.out.println(huffmanTree);
}
// 创建赫夫曼树
public static Node createHuffmanTree(int [] arr){
List<Node> nodes = new ArrayList<Node>();
// 先使用数组创建若干个节点
for (int i = 0; i < arr.length; i ++){
nodes.add(new Node(arr[i]));
}
// 循环创建赫夫曼树
while (nodes.size() > 1){
// 排序
Collections.sort(nodes);
// 取出最小权值的两个二叉树
Node left = nodes.get(nodes.size() - 1);
Node right = nodes.get(nodes.size() - 2);
// 创建一颗二叉树 权重是两个最小二叉树的权重之和
Node parent = new Node(left.value + right.value);
// 将最小的两颗树加入到新树中
parent.setLeft(left);
parent.setRight(right);
// 把取出来的数删除掉
nodes.remove(left);
nodes.remove(right);
// 将新创建的树加入到列表中
nodes.add(parent);
}
// 返回第一个元素
return nodes.get(0);
}
}
7.5.2赫夫曼编码应用
package cn.stdy.strcut.huffmanCode;
import java.io.*;
import java.util.*;
/**
* 赫夫曼树的测试类
*/
public class HuffManTest {
public static void main(String[] args) {
/* // 需要压缩的字符串
String msg = "i can you cna you can you can you caner you de.";
System.out.println(msg);
// 将字符串转换成byte数组
byte[] bytes = msg.getBytes();
// 进行赫夫曼编码压缩
byte[] zip = huffmanZip(bytes);
// 使用指定的赫夫曼编码表进行解码
byte[] newByte = decode(codeTable, zip);
System.out.println(new String(newByte));*/
String src = "/home/yu/IdeaProjects/strcut_demo/src/test1.bmp";
String dts = "/home/yu/IdeaProjects/strcut_demo/src/1.zip";
//zipFile(src,dts);
unzipFile(dts,src);
}
/**
* 使用赫夫曼解压文件
* @param src 压缩文件路径
* @param dts 解压后的路径
*/
public static void unzipFile(String src,String dts){
try {
// 创建文件读取流
FileInputStream in = new FileInputStream(src);
// 创建对象文件读取流
ObjectInputStream ois = new ObjectInputStream(in);
// 读取到字节数组
byte[] bs = (byte[]) ois.readObject();
// 读取到编码表
HashMap<Byte,String> codes = (HashMap<Byte, String>) ois.readObject();
ois.close();
in.close();
// 进行解码操作 获取解码后的文件
byte[] bytes = decode(codes, bs);
// 创建文件输出流
FileOutputStream out = new FileOutputStream(dts);
out.write(bytes);
out.flush();
out.close();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 使用赫夫曼对文件进行压缩
* @param src 源路径
* @param dts 目标路径
*/
public static void zipFile(String src,String dts){
try{
// 创建文件输入流
InputStream in = new FileInputStream(src);
// 创建赫夫曼编码的数组
byte [] bs = new byte[in.available()];
// 读取文件内容编码
in.read(bs);
in.close();
// 进行赫夫曼压缩编码
byte[] zipByte = huffmanZip(bs);
// 创建对象输出流将编码后的字节数组写入压缩文件
FileOutputStream o = new FileOutputStream(dts);
ObjectOutputStream out = new ObjectOutputStream(o);
// 输出文件压缩后的字节数组
out.writeObject(zipByte);
// 输出码表
out.writeObject(codeTable);
out.close();
o.close();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 使用指定的赫夫曼编码表进行解码
*
* @param huffCode
* @param bytes
* @return
*/
private static byte[] decode(Map<Byte, String> huffCode, byte[] bytes) {
StringBuilder sb = new StringBuilder();
// 把一个byte数组转换成一个二进制字符串
for (int i = 0; i < bytes.length; i++) {
byte b = bytes[i];
// 判断是否为最后一个
boolean flag = (i == bytes.length - 1);
sb.append(byteToStr(!flag, b));
}
// 把字符串按照指定的赫夫曼编码表进行解码
// 首先需要将码表的key和value颠倒
Map<String, Byte> tables = new HashMap<>();
for (Map.Entry<Byte,String> entry : huffCode.entrySet()) {
tables.put(entry.getValue(),entry.getKey());
}
// 创建一个集合用于存储byte
List<Byte> byteList = new ArrayList<>();
// 处理解码的字符串
for (int i = 0;i < sb.length();){
int count = 1;
boolean flag = true;
Byte b = null;
while (flag){
// 匹配码表中的key
String key = sb.substring(i,i + count);
b = tables.get(key);
if (b == null){
count++;
}else {
flag = false;
}
}
// 将取到的byte存入集合
byteList.add(b);
// 更新i的取值
i += count;
}
// 创建byte数组
byte[] bs = new byte[byteList.size()];
for (int i = 0;i < byteList.size();i++){
bs[i] = byteList.get(i);
}
return bs;
}
// 将一个byte转换成二进制字符串
public static String byteToStr(boolean flag, byte b) {
int temp = b;
if (flag) {
temp |= 256;
}
String str = Integer.toBinaryString(temp);
if (flag) {
return str.substring(str.length() - 8);
} else {
return str;
}
}
/**
* 进行赫夫曼编码压缩的方法
*
* @param bytes
* @return
*/
private static byte[] huffmanZip(byte[] bytes) {
// 统计每一个byte出现的次数,并放入集合中
List<Node> nodes = getNodes(bytes);
// 根据集合创建一颗赫夫曼树
Node tree = createHuffmanTree(nodes);
// 创建一个赫夫曼编码表
Map<Byte, String> huffCodes = getCodes(tree);
// 编码
byte[] b = zip(bytes, huffCodes);
return b;
}
/**
* 进行赫夫曼编码
*
* @param bytes
* @param huffCodes
* @return
*/
private static byte[] zip(byte[] bytes, Map<Byte, String> huffCodes) {
StringBuilder sb = new StringBuilder();
// 遍历需要编码的数组 进行编码 转换成二进制的字符串
for (byte b : bytes) {
sb.append(huffCodes.get(b));
}
// 定义数组的长度
int len;
if (sb.length() % 8 == 0) {
len = sb.length() / 8;
} else {
len = sb.length() / 8 + 1;
}
// 创建存储压缩编码数组
byte[] by = new byte[len];
// 数组索引
int index = 0;
// 遍历字符串 压缩存入数组
for (int i = 0; i < sb.length(); i += 8) {
String byteStr;
if (i + 8 > sb.length()) {
byteStr = sb.substring(i);
} else {
byteStr = sb.substring(i, i + 8);
}
// 将字符串转换成byte
byte byt = (byte) Integer.parseInt(byteStr, 2);
// 将byte放入数组
by[index] = byt;
index++;
}
// 返回压缩编码后的字节数组
return by;
}
// 临时存放编码的字符串
static StringBuilder sb = new StringBuilder();
// 存放编码表的map 集合
static Map<Byte, String> codeTable = new HashMap<>();
/**
* 根据赫夫曼树进行赫夫曼编码
*
* @return
*/
private static Map<Byte, String> getCodes(Node huff) {
if (huff == null) {
return null;
}
getCodes(huff.left, "0", sb);
getCodes(huff.right, "1", sb);
return codeTable;
}
private static void getCodes(Node tree, String code, StringBuilder sb) {
// 创建一个字符串存放编码
StringBuilder sb2 = new StringBuilder(sb);
// 将编码追加到字符串中
sb2.append(code);
// 如果当前节点没有数据 就递归查找数据
if (tree.data == null) {
getCodes(tree.left, "0", sb2);
getCodes(tree.right, "1", sb2);
} else {
// 如果当前节点有数据 就将数据存放到编码表中
codeTable.put(tree.data, sb2.toString());
}
}
/**
* 根据编码的集合创建赫夫曼树
*
* @param nodes
* @return
*/
private static Node createHuffmanTree(List<Node> nodes) {
// 循环创建
while (nodes.size() > 1) {
// 排序
Collections.sort(nodes);
// 获取最后的两个小的子树
Node left = nodes.get(nodes.size() - 1);
Node right = nodes.get(nodes.size() - 2);
// 创建子树的父节点 权重为两颗子树权重之和
Node parent = new Node(null, left.weight + right.weight);
// 将两个权值较小的树给创建的父节点
parent.left = left;
parent.right = right;
// 将两颗子树在集合中移除
nodes.remove(left);
nodes.remove(right);
// 将创建的树放入集合
nodes.add(parent);
}
// 返回集合的第一个元素就是赫夫曼树
return nodes.get(0);
}
/**
* 把byte数组转为node集合
*
* @param bytes
* @return
*/
private static List<Node> getNodes(byte[] bytes) {
// 创建node集合
List<Node> nodes = new ArrayList<>();
// 统计字符出现次数的map
Map<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);
}
}
// 遍历map集合将数据放入node集合
for (Map.Entry<Byte, Integer> entry : counts.entrySet()) {
nodes.add(new Node(entry.getKey(), entry.getValue()));
}
return nodes;
}
}
7.6排序二叉树
二叉排序树(BTS):也叫做二叉查找树,二叉搜索树,对于二叉树中的任何一个非叶子节点,要求左子节点比当前节点值小,右子节点比当前节点值大
BTS是为了解决顺序存储结构中查找困难的问题,对于顺序结构不排序查找困难,排序后插入困难,而对于链式结构无论是否排序查找都是比较困难的。
7.6.1代码实现
Node.java
package cn.stdy.strcut.tree.binarysort;
public class Node {
int value;
Node left;
Node right;
public Node(int value){
this.value = value;
}
// 添加节点的方法
public void add(Node node){
// 判断当前节点的值是否大于添加节点的值
if (this.value > node.value){
// 添加值小于当前节点 那就添加到左边
// 判断是否有左节点
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 midShow(Node node){
if(node == null) return;
// 先打印左节点
midShow(node.left);
// 打印自己
System.out.println(node.value);
// 打印右节点
midShow(node.right);
}
// 查找节点
public Node search(int i){
if (this.value == i){
return this;
}
if (this.value > i){
if (this.left == null) return null;
else return left.search(i);
}else {
if (this.right == null) return null;
else return right.search(i);
}
}
// 找到一个节点的父节点
public Node searchParent(int value){
// 如果这个节点有子节点 并且子节点的值和自己的值相等 那这个节点就是根节点
if ((this.left != null && this.left.value == value) || (this.right != null && this.right.value == value)){
return this;
}else {
// 如果需要找到父节点的节点的值小于当前节点那就在左子树查找
if (this.value > value && this.left != null){
return this.left.searchParent(value);
}else if (this.value < value && this.right != null){
// 否则 在右子树查找
return this.right.searchParent(value);
}
// 找不到就返回null
return null;
}
}
}
BinarySortTree.java
package cn.stdy.strcut.tree.binarysort;
/**
* 二叉排序树
*/
public class BinarySortTree {
// 根节点
Node root;
// 添加节点的方法
public void add(Node node){
if (root == null){
this.root = node;
}else {
root.add(node);
}
}
// 中序遍历
public void midShow(){
root.midShow(root);
}
// 查找节点
public Node search(int i){
return root.search(i);
}
// 删除节点
public void deleteNode(int i){
// 先根据值找到要删除的目标节点
Node target = this.search(i);
// 如果没有这个这个节点 就直接返回
if (target == null) return;
// 找到这个节点的父节点
Node parent = searchParent(target.value);
// 删除的是叶子节点
if (target.left == null && target.right == null){
// 判断删除节点是父节点的哪一边的节点
if (parent.value > target.value){
// 如果小于父节点就是左子节点
parent.left = null;
}else {
// 否则就是右子节点
parent.right = null;
}
// 删除的节点有两个子节点
}else if (target.left != null && target.right != null){
//找到当前节点右子树中最小的值
int min = findRightMin(target);
// 将右子树的最小值提替换掉删除目标节点的值
target.value = min;
// 删除的节点只有一个叶子节点
}else {
// 判断要删除的节点有哪边的子节点
if (target.left != null){
// 只有左节点 判断要删除的节点是父节点的哪一边的节点
if (parent.value > i){
parent.left = target.left;
// 要删除的节点是父节点的右节点
}else {
parent.right = target.left;
}
// 要删除的节点只有右节点
}else {
// 判断要删除的节点是父节点的哪一边的节点
if (parent.value > i){
parent.left = target.right;
// 要删除的节点是父节点的右节点
}else {
parent.right = target.right;
}
}
}
}
// 查找父节点
public Node searchParent(int value){
return root.searchParent(value);
}
// 找到一个非叶子节点构成的子树中最小的值
public int findRightMin(Node node){
Node target = node;
// 如果左节点不为空
if (target.left != null){
findRightMin(target.left);
}
// 删除这个节点
deleteNode(target.value);
return target.value;
}
}
测试类
package cn.stdy.strcut.tree.binarysort;
/**
* 二叉排序树测试类
*/
public class Test {
public static void main(String[] args) {
int aar [] = {90,89,5,7,8,3,28};
// 创建二叉排序树
BinarySortTree tree = new BinarySortTree();
// 遍历数组生成二叉排序树
for (int i : aar){
tree.add(new Node(i));
}
tree.midShow();
/*Node result = tree.search(7);
System.out.println(result.value);
System.out.println("=========");
// 删除3
tree.deleteNode(3);
// 遍历
tree.midShow();
System.out.println("=======");
// 删除8
tree.deleteNode(8);
tree.midShow();*/
tree.deleteNode(8);
tree.midShow();
}
}
7.7平衡二叉树(AVL树)
对于一棵二叉排序树来说,由于值的不确定性,会导致树的高度是不确定的,如果树的高度比较高,查找的性能是比较低的。
平衡二叉树是建立在二叉排序树的基础之上了,解决了二叉排序树的树的高度不确定的问题,从而在一定程度上解决了二叉排序树的在树的高度过高的时候查找性能低下的问题。
简单的说,在二叉排序树的基础上左子树和右子树的高度差的绝对值不超过1,而且左子树和右子树也是平衡二叉树。
7.7.1AVL树的左右单旋转
Node.java
package cn.stdy.strcut.tree.avl;
public class Node {
int value;
Node left;
Node right;
public Node(int value){
this.value = value;
}
// 树的高度
public int height(){
return Math.max(left==null?0:left.height(),right==null?0:right.height()) + 1;
}
// 获取树的高度
public int height(Node node){
return Math.max(node.left==null?0:node.left.height(),node.right==null?0:node.right.height()) + 1;
}
// 获取左子树的高度
public int leftHeight(){
return left==null?0:left.height();
}
// 获取右子树高度
public int rightHeight(){
return right==null?0:right.height();
}
// 添加节点的方法
public void add(Node node){
// 判断当前节点的值是否大于添加节点的值
if (this.value > node.value){
// 添加值小于当前节点 那就添加到左边
// 判断是否有左节点
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);
}
}
// 判断树是否平衡
// 如果是左子树较高
if (leftHeight() - rightHeight() >= 2){
// 进行右旋转
rightRotate();
}
//如果是右子树较高
if (rightHeight() - leftHeight() >= 2){
// 进行左旋转
leftRotate();
}
}
// 右旋转
public void rightRotate(){
// 创建一个新的节点 值等于当前节点
Node newNode = new Node(value);
// 将当前节点的右节点赋值给新节点的右节点
newNode.right = right;
// 将当前节点的左子节点的右节点赋值给新节点的左节点
newNode.left = left.right;
// 将当前节点的值改为左子节点的值
value = left.value;
// 将当前节点的左子节点指向当前节点的左左节点
left = left.left;
// 将当前节点的右字节点指向新节点
right = newNode;
}
// 左旋转
public void leftRotate(){
// 创建一个新的节点 值等于当前节点
Node newNode = new Node(value);
// 将当前节点的左子节点赋值给新节点的左子节点
newNode.left = left;
// 将当前节点的右子节点的左子节点赋值给新节点的右子节点
newNode.right = right.left;
// 将当前节点的值改为右子节点的值
value = right.value;
// 将当前节点的右子节点指向当前节点的右右节点
right = right.right;
// 将当前节点的左字节点指向新节点
left = newNode;
}
// 中序遍历
public void midShow(Node node){
if(node == null) return;
// 先打印左节点
midShow(node.left);
// 打印自己
System.out.println(node.value);
// 打印右节点
midShow(node.right);
}
// 查找节点
public Node search(int i){
if (this.value == i){
return this;
}
if (this.value > i){
if (this.left == null) return null;
else return left.search(i);
}else {
if (this.right == null) return null;
else return right.search(i);
}
}
// 找到一个节点的父节点
public Node searchParent(int value){
// 如果这个节点有子节点 并且子节点的值和自己的值相等 那这个节点就是根节点
if ((this.left != null && this.left.value == value) || (this.right != null && this.right.value == value)){
return this;
}else {
// 如果需要找到父节点的节点的值小于当前节点那就在左子树查找
if (this.value > value && this.left != null){
return this.left.searchParent(value);
}else if (this.value < value && this.right != null){
// 否则 在右子树查找
return this.right.searchParent(value);
}
// 找不到就返回null
return null;
}
}
}
AVLTree.java
package cn.stdy.strcut.tree.avl;
/**
* avl树
*/
public class AVLTree {
// 根节点
Node root;
// 添加节点的方法
public void add(Node node){
if (root == null){
this.root = node;
}else {
root.add(node);
}
}
// 树的高度
public int height(){
return root.height();
}
// 中序遍历
public void midShow(){
root.midShow(root);
}
// 查找节点
public Node search(int i){
return root.search(i);
}
// 删除节点
public void deleteNode(int i){
// 先根据值找到要删除的目标节点
Node target = this.search(i);
// 如果没有这个这个节点 就直接返回
if (target == null) return;
// 找到这个节点的父节点
Node parent = searchParent(target.value);
// 删除的是叶子节点
if (target.left == null && target.right == null){
// 判断删除节点是父节点的哪一边的节点
if (parent.value > target.value){
// 如果小于父节点就是左子节点
parent.left = null;
}else {
// 否则就是右子节点
parent.right = null;
}
// 删除的节点有两个子节点
}else if (target.left != null && target.right != null){
//找到当前节点右子树中最小的值
int min = findRightMin(target);
// 将右子树的最小值提替换掉删除目标节点的值
target.value = min;
// 删除的节点只有一个叶子节点
}else {
// 判断要删除的节点有哪边的子节点
if (target.left != null){
// 只有左节点 判断要删除的节点是父节点的哪一边的节点
if (parent.value > i){
parent.left = target.left;
// 要删除的节点是父节点的右节点
}else {
parent.right = target.left;
}
// 要删除的节点只有右节点
}else {
// 判断要删除的节点是父节点的哪一边的节点
if (parent.value > i){
parent.left = target.right;
// 要删除的节点是父节点的右节点
}else {
parent.right = target.right;
}
}
}
}
// 查找父节点
public Node searchParent(int value){
return root.searchParent(value);
}
// 找到一个非叶子节点构成的子树中最小的值
public int findRightMin(Node node){
Node target = node;
// 如果左节点不为空
if (target.left != null){
findRightMin(target.left);
}
// 删除这个节点
deleteNode(target.value);
return target.value;
}
}
测试类
package cn.stdy.strcut.tree.avl;
/**
* avl树测试类
*/
public class Test {
public static void main(String[] args) {
int arr [] = {8,9,6,7,5,4};
// 创建二叉排序树
AVLTree tree = new AVLTree();
// 遍历数组生成二叉排序树
for (int i : arr){
tree.add(new Node(i));
}
// 打印树的高度
System.out.println(tree.height());
System.out.println(tree.root.value);
tree.midShow();
// 测试右旋转
int arr1 [] = {5,4,7,6,8,9};
AVLTree avlTree = new AVLTree();
for (int i : arr1){
avlTree.add(new Node(i));
}
System.out.println(avlTree.height());
System.out.println(avlTree.root.value);
avlTree.midShow();
}
}
7.7.2AVL树的双旋转
在AVLTree.java中添加代码
// 判断树是否平衡
// 如果是左子树较高
if (leftHeight() - rightHeight() >= 2){
// 如果左子树的左子树的高度小于右子树 需要双旋转
if (left != null && left.left.height() < left.right.height()){
// 先对左子树进行左旋转
left.leftRotate();
}
// 进行右旋转
rightRotate();
}
//如果是右子树较高
if (rightHeight() - leftHeight() >= 2){
// 如果右子树的右子树的高度小于左子树 需要双旋转
if (right != null && right.left.height() > right.right.height()){
// 先对右子树进行右旋转
right.rightRotate();
}
// 进行左旋转
leftRotate();
}
测试类
package cn.stdy.strcut.tree.avl;
/**
* avl树测试类
*/
public class Test {
public static void main(String[] args) {
int arr[] = {8,9,5,4,6,7};
int arr1[] = {5,4,8,9,7,6};
AVLTree av1 = new AVLTree();
for (int i : arr) {
av1.add(new Node(i));
}
System.out.println(av1.root.value);
System.out.println(av1.height());
av1.midShow();
System.out.println("================");
AVLTree av2 = new AVLTree();
for (int i: arr1) {
av2.add(new Node(i));
}
System.out.println(av2.height());
System.out.println(av2.root.value);
av2.midShow();
}
}
7.8多路查找树
7.8.1计算机的存储方式
1.内存
优点:使用电信号存储数据,不存在机器操作,访问速度快
缺点:价格高,断电后数据会丢失。一般作为CPU的缓存
2.磁盘
磁盘有一个主轴(spindle),上面放着很多盘片(platter),盘片(surface)上的区域称为磁道(track),磁道(track)上的一圈可以分为多个扇区(Sectors),扇区存放着具体的数据,每个扇区之间会有间隔(gap)。通过传动臂(arm)的来回摆动来找到对应的扇区,然后通过扇区上的磁头(read/write head)来完成对磁盘数据的读取和写入。
优点:价格低,容量大,断电后数据不会丢失
缺点:由于存储介质的特性,加上在访问数据时进行的机械运动需要消耗一定的时间,磁盘的访问速度慢。
3.磁盘数据的预读
由于磁盘读写数度的问题,一般在读取数据的时候不是严格的读取指定的数据,而是每次都会预读,就是即使只需要一个字节,磁盘也会从这个字节位置开始,顺序向后读取一定长度的数据放入计算机的内存。
1.计算机科学局部原理:当一个数据被用到的时候,其附近的数据也通常马上会被使用。
PS:预读的长度一般为页的整数倍
2.页(page):页时计算机管理存储器的逻辑块,硬件及操作系统往往将主存和磁盘存储区分割为连续的大小相等的块,每个存储块称为一页(在许多操作系统中,一页通常为4K),主存和磁盘之间以页为单位交换数据。
PS:文件系统及数据库系统的设计者利用了磁盘预读的原理,将一个节点的大小设为等于一个页,这样每个节点只需要一次I/O就可以完全载入。
8.哈希表
也叫散列表,是根据关键码值(key value)直接对数据进行访问的数据结构。通过把关键码的值映射到哈希表中的一个位置来访问和记录,加快数据查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
8.1散列函数的实现方式
直接寻址法:取关键字或关键字的某个线性函数值为散列地址
数字分析法:分析数据中不相同的值作为散列地址
平方取中法:当无法确定关键字中的哪几位分布比较均匀时,先求出关键字的平方值,然后取平方值的中间几 位作为散列地址
折叠法:将关键字分割成位数相同的几部分,最后一部分的位数可以不同,然后取这几部分的叠加和(除去进 位)作为散列地址
随机数法:选择一个随机函数,取关键字的随机值作为散列地址
取余法:取关键字被某个不大于散列表长度的数取余的余数作为散列表的地址
8.2哈希表的简单实现
StuInfo.java
package cn.stdy.strcut.hash;
import java.util.Random;
public class StuInfo {
private int age;
private int count;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public StuInfo(int age) {
this.age = age;
}
public StuInfo(int age, int count) {
this.age = age;
this.count = count;
}
// 计算散列函数的方法
public int hashCode(){
return age;
}
@Override
public String toString() {
return "StuInfo{" +
"age=" + age +
", count=" + count +
'}';
}
}
HashTable.java
package cn.stdy.strcut.hash;
import java.util.Arrays;
/**
* 散列表的简单实现
*/
public class HashTable {
// 存放数据的数组
private Object [] table = new Object[100];
// 存放数据的方法
public void put(Object object){
// 先计算hash值
int index = object.hashCode();
// 存入散列表
table[index] = object;
}
// 取出数据的方法
public Object get(Object object){
return table[object.hashCode()];
}
@Override
public String toString() {
return "HashTable{" +
"table=" + Arrays.toString(table) +
'}';
}
}
测试类
package cn.stdy.strcut.hash;
/**
* 测试类
*/
public class Test {
public static void main(String[] args) {
StuInfo s1 = new StuInfo(18,4);
StuInfo s2 = new StuInfo(19,20);
StuInfo s3 = new StuInfo(20,15);
StuInfo s4 = new StuInfo(21,10);
StuInfo s5 = new StuInfo(22,8);
// 创建散列表
HashTable hashTable = new HashTable();
hashTable.put(s1);
hashTable.put(s2);
hashTable.put(s3);
hashTable.put(s4);
hashTable.put(s5);
// 获取数据
Object o = hashTable.get(new StuInfo(18));
System.out.println(o);
// 打印散列表
System.out.println(hashTable);
}
}
8.3散列冲突的解决方法
8.3.1开放地址法
1.线性探测法:如果一个数据的hash函数发生了冲突,当前hash地址值对应的数组的地址的值已经有了数据,那么就将相同hash码值的数据向后移,并判断后面的位置是否有值,如果没有值,那就存储,如果有值就继续向后找,直到存储为止。
2.二次探测法:在线性探测法的基础之上,将向后查找没有值的位置的步长设置为查找次数的平方
3.再hash法:对发生hash冲突的hash值进行再次hash,找到存储的地址。
8.3.2链地址法
在存储具体的关键码的值的时候,对于相同hash码的值在哈希表的对应位置使用链表存储相同hash码的值。
9.图结构
在树形结构中,任意一个节点只能有一个父节点和最多两个子节点。而在图结构中,节点和节点之间的关系是不确定的,任意的两个节点之间都会产生关系。
- 邻接:对于图中的节点称为顶点,而从A顶点能找到B顶点,B顶点也可以找到A顶点,A和B两个顶点就是邻接顶点
- 路径:从一个顶点到另一个顶点所经过的顶点就是路径,如ABC AB AC BC
- 有向图:顶点和顶点之间的连接是有方向的,只能通过A顶点找到B顶点而不能通过B顶点找到A顶点
- 无向图:顶点和顶点之间的连接是没有方向的,可以通过A顶点找到B顶点,也可以通过B顶点找到A顶点
- 带权图:图中的顶点是存储实际数据的
9.1图的简单代码实现
图在程序中是使用邻接表表示,也可以使用邻接矩阵表示。
如果A顶点和B顶点之间可以连通就记为1,否则就记为0
Vertex.java
package cn.stdy.strcut.graph;
/**
* 图的顶点
*/
public class Vertex {
private String value;
public Vertex(String value){
this.value = value;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
Graph.java
package cn.stdy.strcut.graph;
/**
* 邻接矩阵实现图
*/
public class Graph {
// 存放顶点的数组
private Vertex[] vertices;
// 当前图的顶点个数
private int currentSize = 0;
// 邻接矩阵
private int[][] data;
/**
* 图的构造方法
*/
public Graph(int size){
// 根据图的大小初始化邻接矩阵
data = new int[size][size];
// 初始化存放顶点的数组
vertices = new Vertex[size];
}
/**
* 向图中加入一个顶点
*/
public void addVertex(Vertex vertex){
// 设置顶点自己的关系
data[currentSize][currentSize] = 1;
// 将加入的顶点放入数组
vertices[currentSize++] = vertex;
}
/**
* 添加顶点之间的边
*/
public void setEdge(Vertex v1,Vertex v2){
// 定义顶点在数组中的索引
int index1 = findIndex(v1);
int index2 = findIndex(v2);
// 在邻接矩阵中设置顶点之间的关系
data[index1][index2] = 1;
data[index2][index1] = 1;
}
/**
* 查找顶点在数组中的索引
* @param v1 顶点
* @return
*/
public int findIndex(Vertex v1){
for (int i = 0;i < vertices.length; i ++){
if (v1.getValue().equals(vertices[i].getValue())){
return i;
}
}
return -1;
}
/**
* 打印邻接矩阵
*/
public void dataShow(){
for (int[] arr : data) {
for (int i : arr){
System.out.print(i);
}
System.out.println("\n");
}
}
}
测试类
package cn.stdy.strcut.graph;
public class Test {
public static void main(String[] args) {
// 创建顶点
Vertex v1 = new Vertex("A");
Vertex v2 = new Vertex("B");
Vertex v3 = new Vertex("C");
Vertex v4 = new Vertex("D");
Vertex v5 = new Vertex("E");
// 创建图
Graph graph = new Graph(5);
// 添加顶点
graph.addVertex(v1);
graph.addVertex(v2);
graph.addVertex(v3);
graph.addVertex(v4);
graph.addVertex(v5);
// 设置顶点的边
graph.setEdge(v1,v2);
graph.setEdge(v1,v3);
graph.setEdge(v1,v4);
graph.setEdge(v1,v5);
graph.setEdge(v2,v3);
// 打印邻接矩阵
graph.dataShow();
}
}
9.2图的遍历
9.2.1图的广度遍历
在Graph.java中添加如下代码
// 用于广度优先遍历时存放顶点的栈
private MyStack stack = new MyStack();
// 当前遍历顶点的下标
private int currentIndex;
/**
* 图的广度优先遍历
*/
public void dst(){
// 把第0个顶点设置为访问状态
vertices[0].visited = true;
// 将当前遍历顶点的下标压入栈
stack.push(currentIndex);
// 打印顶点的值
System.out.println(vertices[0].getValue());
// 循环遍历其他节点
out:while (!stack.isEmpty()){
// 除了第一个顶点以外的其他顶点
for (int i = currentIndex + 1;i < vertices.length; i ++){
// 如果下一个遍历的顶点和当前遍历的顶点是通的并且没有遍历过
if (data[currentIndex][i] == 1 && vertices[i].visited == false){
// 将下一个顶点的下标压入栈中
stack.push(i);
vertices[i].visited = true;
// 打印顶点的值
System.out.println(vertices[i].getValue());
// 继续循环
continue out;
}
}
// 遍历完毕弹出栈顶元素
stack.pop();
// 修改当前元素位置
if (!stack.isEmpty()){
currentIndex = stack.peek();
}
}
}
注意:在Vertex类中添加 public boolean visited;属性