抽象数据类型(ADT)描述了数据的逻辑结构和抽象运算,通常用数据对象、数据关系、基本操作集这样的三元组来表示,从而构成一个完整的数据结构定义
算法是解决特定问题求解决步骤的描述,再计算机中表现为指令的有限序列,并且每条指令表示一个或多个操作。
输入、输出、又穷性、确定性、可行性
输入:算法具有零个或多个输入
输出:算法至少有一个或多个输出
有穷性:指算法再执行有限的步骤之后,自动结束而不会出现无心循环,并且每一个步骤再可接受的时间内完成
确定性:算法的每一个步骤都具有确定的意义,不会出现二义性
可行性:算法的每一步都必须是可行的,也就是说,每一步都能通过执行有限次数完成
好的算法应考虑:正确性、可读性、健壮性、效率与低存储量需求
正确性、可读性、健壮性、时间复杂度、空间复杂度
存储结构有顺序存储、链式存储、索引存储和散列存储
有序表既可以链式存储又可顺序存储,属于逻辑结构
表中元素具有抽象性,仅讨论元素间的逻辑关系,不考虑元素究竟表示什么内容
表头可能是原子、列表,表尾必为列表
取表头:列表第一个原子
取表尾:列表删去第一个元素后的剩余列表
顺序存储的存储结构是随机存储的存储结构
优点:
随机访问,可通过首地址和元素序号在时间O(1)内找到指定的元素
存储密度高,每个结点只存储数据元素
缺点:
相邻的元素物理上也相邻,插入和删除操作需要移动大量元素,时间复杂度为O(n)
中间元素删除:创建长度-1的新数组,将原数组头尾复制到新数组中(for循环分为删除之前之后的元素),新数组替换原数组
public class TestArray2 {
public static void main(String[] args) {
//目标数组
int[] arr = new int[] {9,8,7,6,5,4};
//要删除的元素下标
int dst = 3;
//创建新数组,长度是原数组-1
int[] newArr = new int[arr.length-1];
//复制原数组中除了要删除元素的其他元素
for(int i = 0;i;i++){
//要删除元素之前的元素
if(i {
newArr[i] = arr[i];
//要删除元素之后的元素
}else {
newArr[i] = arr[i+1];
}
}
arr = newArr;
System.out.print(Arrays.toString(arr));
}
}
中间元素插入:创建长度+1的新数组,原数组插入位置前后元素复制到新数组,插入元素放入新数组对应坐标处,新数组替换原数组
静态查找表:操作只涉及查询特定数据元素是否在查找表中、检索满足条件的某个特定的数据元素的各种属性,适合顺序、折半和散列查找
动态查找表:二叉排序树的查找、散列查找
对线性的链表只能进行顺序查找,ASL=(n+1)/2
折半查找:ASL=log以二为底(n+1)-1
线性查找: for循环一个一个找
分块查找(引索顺序查找):ASL=(s^2+2s+n)/2s
二分查找:
public class TestBinarySearch {
public static void main(String[] args) {
//目标数组
int[] arr = new int[] {1,2,3,4,5,6,7,8,9};
//目标元素
int target = 2;
//记录开始位置
int begin = 0;
//记录结束的位置
int end = arr.length-1;
//记录中间的位置
int mid = (begin+end)/2;
//记录目标位置
int index = -1;
//循环查找
while(true) {
//不存在元素时
if(begin > end) {
return;
}
//判断中间元素是不是要查找的元素
if(arr[mid] == target) {
index = mid;
break;
//中间元素不是要查的元素
}else {
//判断中间这个元素是不是比目标元素大
if(arr[mid]>target) {
//把结束位置调整到中间位置前一个位置
end = mid - 1;
//判断中间这个元素是不是比目标元素小
}else {
//把开始位置调整到中间位置后一个位置
begin = mid + 1;
}
mid = (begin+end)/2;
}
}
System.out.print(index);
}
}
栈是限制存储点的线性结构,n个不同元素进栈,出栈元素不同排列个数为1/(n+1)C(上n下2n)=1/(n+1) 2n!/n!n!
采用链式存储的栈叫做链栈,链栈的优点是便于多个栈共享存储空间和提高效率,且不存在栈满上溢。所有操作都在单链表表头进行,链栈没有头结点,Lhead指向栈顶元素。
package demo1;
public class MyStack {
//栈的底层使用数组存储数据
int[] elements;
public MyStack() {
elements = new int[0];
}
//压入元素
public void push(int element){
int[] newArr = new int[elements.length + 1];
for(int i = 0;i < elements.length;i++) {
newArr[i] = elements[i];
}
//把添加的元素放入新数组中
newArr[elements.length] = element;
elements = newArr;
}
//取出栈顶元素
public int pop(){
//栈中无元素
if(elements.length == 0) {
throw new RuntimeException("stack is empty");
}
//取出数组里最后一个元素
int element = elements[elements.length - 1];
//创建一个新的数组
int newArr[] = new int[elements.length - 1];
//原数组中除了最后一个元素的数组都放入新的数组中
for(int i = 0;i < elements.length-1;i++) {
newArr[i] = elements[i];
}
//替换数组
elements = newArr;
//返回栈顶元素
return element;
}
//查看栈顶元素
public int peek(){
//栈中无元素
if(elements.length == 0) {
throw new RuntimeException("stack is empty");
}
return elements[elements.length-1];
}
//判断栈是否为空
public boolean isEmpty() {
return elements.length == 0;
}
public static void main(String[] args) {
//先创建一个栈
MyStack ms = new MyStack();
//压入数据
ms.push(9);
ms.push(8);
ms.push(7);
// //取出栈顶元素
// System.out.print(ms.pop());
System.out.print(ms.pop());
System.out.print(ms.pop());
//查看栈顶元素
System.out.println(ms.peek());
ms.pop();
//System.out.println(ms.peek());
System.out.println(ms.isEmpty());
//空栈
// int pop = ms.pop();
// System.out.print(pop);
//
}
}
栈和队列的主要区别是插入、删除操作的限定不一样
最不适合作链式队列的链表是只带队首指针的非循环双链表
执行广度优先搜索图时,需要使用队列作为辅助空间
顺序队列:
队头指针front指向队头元素,队尾指针rear指向队尾元素的下一个位置
初始状态:Q.front= =Q.rear= =0
进队操作:队不满时,先送值到队尾元素加一
出队操作:队不空时,先取队头元素值,再讲队头指针加一
循环 队列:
初始时:Q.front= =Q.rear=0
队首指针进一:Q.front = (Q.front+1)%MaxSize
队尾指针进一:Q.rear = (Q.rear+1)%MaxSize
队列长度:(Q.rear+MaxSize -Q.front)%MaxSize
队空:Q.front= =Q.rear
牺牲一个单元 队满(Q.rear+1)%MaxSize= =Q.front
单链表表示的链式队列特别适合于数据元素变化比较大的情形,不存在队列满、产生溢出的问题
双端队列:一端进行插入删除,另一端进行插入/删除
中缀表达式变为后缀表达式:
isp(前)
操作符 isp icp
井 0 0
( 1 6
*/ 5 4
± 3 2
) 6 1
矩阵的压缩处理:
对称矩阵:若对于一个n阶方阵A【1…n】【1…n】中任意一个元素aij都有aij=aji。将对称矩阵存放在一维数组B【n(n+1)/2】,只存放下三角部分(含主对角)的元素
三角矩阵:
上三角矩阵:下三角所有元素均为常数,存储完下三角区和主对角线所有元素后,存储对角线上方的常量
下三角矩阵:上三角所有元素均为常数
稀疏矩阵:
适用于压缩存储稀疏矩阵的俩种存储结构是三元组表和十字链表
将非零元素及其相应的行列构成一个三元组(行标,坐标,值)
M=【40001】
对应三元组:
i j v
0 0 4
0 4 1
package demo1;
public class MyQueue {
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() {
//把数组中第0个元素取出来
int element = elements[0];
//创建一个新数组
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 static void main(String[] args) {
// TODO Auto-generated method stub
MyQueue mq = new MyQueue();
//入队
mq.add(9);
mq.add(8);
mq.add(7);
//出队
System.out.println(mq.poll());
mq.add(5);
System.out.println(mq.poll());
System.out.println(mq.isEmpty());
}
}
非随机存储的存储结构
优点:
解决顺序表需要大量连续存储单元的缺点
插入删除不需要移动大量元素,只需修改指针
缺点:
附加指针域,浪费存储空间
不能直接找到表中某个特定的节点,需要从表头遍历,依次查找
引入头结点的优点:
头插法建立单链表:将新节点插入当前节点的表头,读入数据的顺序与生成的链表的元素顺序相反,时间复杂度为O(n)
尾插法建立单链表:将新节点插入当前节点的表尾,读入数据的顺序与生成的链表的元素顺序相同,时间复杂度为O(n)
在给定的节点后插入新节点,时间复杂度为O(1)
/**
* 单链表
*
*/
//一个节点
public class Node {
// 节点内容
int data;
// 下一个节点
Node next;
public Node(int data) {
this.data = data;
}
// 为节点追加节点
// public void append(Node node) {
// //this.next = node;
// //当前节点
// Node currentNode = this;
// //循环向后找
// while(true) {
// //取出下一个节点
// Node nextNode = currentNode.next;
// //如果下一个节点为null,当前节点为最后的节点
// if(nextNode == null) {
// break;
// }
// //赋给当前节点
// currentNode = nextNode;
// }
// //把需要追加的节点追加为找到的当前节点的下一个节点
// currentNode.next = node;
// }
public Node append(Node node) {
// this.next = node;
// 当前节点
Node currentNode = this;
// 循环向后找
while (true) {
// 取出下一个节点
Node nextNode = currentNode.next;
// 如果下一个节点为null,当前节点为最后的节点
if (nextNode == null) {
break;
}
// 赋给当前节点
currentNode = nextNode;
}
// 把需要追加的节点追加为找到的当前节点的下一个节点
currentNode.next = node;
return this;
}
// 插入一个节点作为当前节点的下一个节点
public void after(Node node) {
// 取出下一个节点作为下下个节点
Node nextNext = next;
// 把新节点作为当前节点的下一个节点
this.next = node;
// 把下下一个节点设置为新节点的下一个节点
node.next = nextNext;
}
// 显示节点信息
public void show() {
Node currentNode = this;
while (true) {
System.out.print(currentNode.data + " ");
// 取出下一个节点
currentNode = currentNode.next;
// 如果是最后一个节点
if (currentNode == null) {
break;
}
}
System.out.println();
}
// 删除下一个节点
public void removeNext() {
// 取出下一个节点
Node newNext = next.next;
// 把下下一个节点设置为当前节点的下一个节点
this.next = newNext;
}
// 获取下一个节点
public Node next() {
return this.next;
}
// 获取节点中的数据
public int getData() {
return this.data;
}
// 当前节点是否是最后一个节点
public boolean isLast() {
return next == null;
}
public static void main(String[] args) {
// 创建节点
Node n1 = new Node(1);
Node n2 = new Node(2);
Node n3 = new Node(3);
// 追加节点
// n1.append(n2);
// n2.append(n3);
// n1.append(n3);
n1.append(n2).append(n3).append(new Node(4));
// 取出下一个节点
// System.out.println(n1.next().getData());
// System.out.println(n2.next().getData());
// System.out.println(n1.next().next().next().getData());
// 判断节点是否为最后一个节点
// System.out.println(n1.isLast());
// System.out.println(n1.next().next().next().next().isLast());
// 显示所有节点内容
n1.show();
// 删除一个节点
// n1.next().removeNext();
// n1.show();
// 插入一个新节点
Node node = new Node(5);
n1.next().after(node);
n1.show();
}
}
循环单链表仅设尾指针,可以从表中任意节点开始遍历整个链表
循环双链表头结点的prior指针指向表尾结点
/**
* 循环链表
*
*/
public class LoopNode {
// 节点内容
int data;
// 下一个节点(好强)
LoopNode next = this;
public LoopNode(int data) {
this.data = data;
}
// 插入一个节点作为当前节点的下一个节点
public void after(LoopNode node) {
// 取出下一个节点作为下下个节点
LoopNode nextNext = next;
// 把新节点作为当前节点的下一个节点
this.next = node;
// 把下下一个节点设置为新节点的下一个节点
node.next = nextNext;
}
// 删除下一个节点
public void removeNext() {
// 取出下一个节点
LoopNode newNext = next.next;
// 把下下一个节点设置为当前节点的下一个节点
this.next = newNext;
}
// 获取下一个节点
public LoopNode next() {
return this.next;
}
// 获取节点中的数据
public int getData() {
return this.data;
}
// 当前节点是否是最后一个节点
public boolean isLast() {
return next == null;
}
public static void main(String[] args) {
// 创建节点
LoopNode n1 = new LoopNode(1);
LoopNode n2 = new LoopNode(2);
LoopNode n3 = new LoopNode(3);
LoopNode n4 = new LoopNode(4);
//增加节点
n1.after(n2);
n2.after(n3);
n3.after(n4);
System.out.println(n1.next().getData());
System.out.println(n2.next().getData());
System.out.println(n3.next().getData());
System.out.println(n4.next().getData());
}
}
优点:
插入、删除操作时间复杂度为O(1)
public class DoubleNode {
// 上一个节点
DoubleNode pre = this;
// 节点数据
int data;
// 下一个节点
DoubleNode next = this;
public DoubleNode(int data) {
this.data = data;
}
// 增加节点
public void after(DoubleNode node) {
// 原来的下一个节点
DoubleNode nextNext = next;
// 把新节点作为当前节点的下一个节点
this.next = node;
// 把当前节点设置为新节点的前一个节点
node.pre = this;
// 让原来的下一个节点作新节点的下一个节点
node.next = nextNext;
// 让原来的下一个节点的上一个节点为新节点
nextNext.pre = node;
}
// 下一个节点
public DoubleNode next() {
return this.next;
}
// 上一个节点
public DoubleNode pre() {
return this.pre;
}
// 获取节点中的数据
public int getData() {
return this.data;
}
// 当前节点是否是最后一个节点
public boolean isLast() {
return next == null;
}
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);
// 追加节点
n1.after(n2);
n2.after(n3);
// 查看上一个、自己、下一个节点的内容
System.out.println(n2.pre().getData());
System.out.println(n2.getData());
System.out.println(n2.next().getData());
System.out.println(n3.next().getData());
System.out.println(n1.pre().getData());
}
}
public class TestRecursive {
public static void main(String[] args) {
// TODO Auto-generated method stub
print(3);
}
//递归操作
public static void print(int i) {
if(i>0) {
System.out.println(i);
print(i-1);
}
}
public class TestFebonacci {
public static void main(String[] args) {
// TODO Auto-generated method stub
//斐波那契数列: 1 1 2 3 5 8 13
int i = febomacci(3);
System.out.println(i);
}
//打印第n项斐波那契数列
public static int febomacci(int i) {
if(i == 1 || i == 2) {
return 1;
}else {
return febomacci(i-1)+febomacci(i-2);
}
}
public class TestHanoi {
public static void main(String[] args) {
// TODO Auto-generated method stub
hanoi(3,'A','B','C');
}
/**
* @param n :共有N个盘子
* @param from:开始的柱子
* @param in:中间的柱子
* @param to:目标的柱子
* 无论有多少个盘子,都认为只有俩个,上面的所有盘子和最下面一个盘子。
* 通过柱子交换位置做相同动作
*/
public static void hanoi(int n,char from,char in,char to) {
//只有一个盘子
if(n == 1) {
System.out.println("第1个盘子"+from+"移到"+to);
//无论有多少个盘子,都认为只有俩个,上面的所有盘子和最下面一个盘子。
}else {
//移动上面所有的盘子到中间位置
hanoi(n-1, from, to, in);
//移动下面的盘子
System.out.println("第"+n+"个盘子"+from+"移到"+to);
//把上面的所有的盘子从中间位置移到目标位置
hanoi(n-1, in, from, to);
}
}
串的数据对象为字符集
串的存储结构:
定长顺序存储表示:为每个串变量分配一个固定长度的存储区,串的实际长度小于等于最大长度,超过于定义长度的串值会被截断
堆分配存储表示:存储空间动态分配
块链存储表示:链表方式存储串值,每个结点存放一个或者多个字符,每个结点称为块,最后一个节点占不满时用#补上
串的模式匹配:
简单模式匹配:最坏时间复杂度为O(mn),n和m分别是主串和模式串(子串)的长度
KMP算法:利用PM表进行字符串匹配,匹配失败时就去找前一个元素值的部分匹配值,移动位数=已匹配的字符数-对应的部分匹配值,匹配过程中主串没有回退,时间复杂度为
O(m+n)
内部排序:元素全部存放在内存中
外部排序:
衡量一个算法的优劣?
一. 事后统计的方法
二. 事前分析估算的方法
T(n):一个算法中的语句执行次数称为语句频度
低次项、系数、常数项可忽略
计算时间复杂度方法:
(1) 用常数1代替运行时间中所有加法常数
(2)修改后的运行次数函数中,只保留最高阶项
(3)去除最高阶项的系数
平均时间复杂度:所有可能的输入实例以等概率出现的情况下,该算法的运行时间
最坏时间复杂度:最坏情况下的时间复杂度,是算法在任何输入实例上运行时间的界限【通常情况下都讨论这个】
2. 空 间复杂度
public class BubbleSort {
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] arr = new int[] {5,7,2,9,4,1,0,5,7};
System.out.println(Arrays.toString(arr));
bubbleSort(arr);
System.out.println(Arrays.toString(arr));
}
//冒泡排序
/*
* 5,7,2,9,4,1,0,5,7 比较length-1轮
* 5,7,2,9,4,1,0,5,7 5比
* 5,2,7,9,4,1,0,5,7 2比
* 5,7,2,4,1,0,5,7,9 7比
* ......
*/
public static void bubbleSort(int[] arr) {
//控制共比较多少轮
for(int i = 0;i;i++) {
//控制比较的次数
for(int j = 0;j;j++) {
if(arr[j]>arr[j+1]) {
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
平均性能最优
public class QuickSort {
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] arr = new int[] {3,4,6,7,2,7,2,8,0};
quickSort(arr,0,arr.length-1);
System.out.println(Arrays.toString(arr));
}
public static void quickSort(int[] arr,int start,int end) {
if(start {
//把数组第0个数字作为标准数
int stard = arr[start];
//记录需要排序的下标
int low = start;
int high = end;
//循环找比标准数大的数和标准数小的数
while(low {
//右边的数字比标准数小
while(low {
high--;
}
//使用右边的数字替换左边的数
arr[low] = arr[high];
//如果左边的数字比标准数小
while(low {
low++;
}
arr[high] = arr[low];
}
//把标准数赋给低所在的位置的元素
arr[low] = stard;
//处理所有小的数字
quickSort(arr, start, low);
//处理所有大的数字
quickSort(arr, low+1, end);
}
}
时间复杂度O(n^2)
public class InsertSort {
public static void main(String[] args) {
int[] arr = new int[] {5,3,2,8,5,9,1,0};
insertSort(arr);
System.out.println(Arrays.toString(arr));
}
public static void insertSort(int[] arr) {
//遍历所有的数字
for(int i = 1;i;i++) {
//如果当前数字比前一个数字小
if(arr[i] {
//把当前遍历数字存储起来
int temp = arr[i];
int j;
//遍历当前数字前面所有的数字
for(j = i-1;j>=0&&temp; j--) {
//把前一个数字赋给后一个数字
arr[j+1] = arr[j];
}
//循环结束后把临时变量(外层for循环当前的元素)赋给不满足条件的后一个值
arr[j+1] = temp;
}
}
}
public class ShellSort {
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] arr = new int[] {3,5,2,7,9,1,2,0,4,7,4,3,8};
shellSort(arr);
System.out.println(Arrays.toString(arr));
}
public static void shellSort(int[] arr) {
int k =1;
//遍历所有步长
for(int d = arr.length/2;d>0;d/=2) {
//遍历所有的元素
for(int i = d;i;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;
}
}
}
System.out.println("第"+k+"次排序结果"+Arrays.toString(arr));
k++;
}
}
空间效率为O(1),时间复杂度为O(n^2),是一种不稳定的排序方式
public class SelectSort {
public static void main(String[] args) {
int[] arr = new int[] {3,4,5,7,1,2,0,3,6,8};
selectSort(arr);
System.out.println(Arrays.toString(arr));
}
public static void selectSort(int[] arr) {
// TODO Auto-generated method stub
//遍历所有的数
for(int i = 0;i < arr.length;i++) {
int minIndex = i;
//把当前遍历的数和后面所有的数依次进行比较,并记录下最小的数的下标
for(int j = i+1;j;j++) {
//如果后面比较的数比记录的最小的数小
if(arr[minIndex]>arr[j]) {
//记录下最小的那个数的下标
minIndex = j;
}
}
//如果最小的数和当前遍历的数的下标不一致,说明下标为minIndex的数比当前遍历的数小
if(i != minIndex) {
int temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
}
}
L(i)>=L(2i)且L(i)>=L(2i+1)或L(i)<=L(2i)且L(i)<=L(2i+1),可以将一堆数组视为一棵完全二叉树,满足前条件的称为大根堆,最大元素存放在根节点,任意非根节点的值小于等于其双亲节点的值
用二叉树排列数组大小
大顶堆(升序排列):任何一个父节点都大于它的子节点
小顶堆(降序排列):任何一个父节点都小于它的子节点
堆排序
package demo2;
/*
* 堆排序
*/
import java.util.Arrays;
public class HeapSort {
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[leftNode] > arr[max]) {
max = leftNode;
}
if (rightNode < size && arr[rightNode] > arr[max]) {
max = rightNode;
}
// 交换位置
if (max != index) {
int temp = arr[index];
arr[index] = arr[max];
arr[max] = temp;
// 交换位置后,可能会破坏之前排好的堆,所以,之前排好的堆需要重新调整
maxHeap(arr, size, max);
}
}
public static void heapSort(int[] arr) {
// 开始位置是最后一个非叶子节点,及最后一个节点的父节点
int start = (arr.length - 1) / 2;
// 结束位置是数组的长度-1,调整为大顶堆
for (int i = start; i >= 0; i--) {
maxHeap(arr, arr.length, i);
}
//先把数组中第0个和堆中最后一个数交换位置,再把前面的处理为大顶堆
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 main(String[] args) {
// TODO Auto-generated method stub
int[] arr = new int[] { 9, 6, 8, 7, 0, 1, 10, 4, 2 };
heapSort(arr);
System.out.println(Arrays.toString(arr));
}
}
public class MergeSort {
public static void main(String[] args) {
int[] arr = new int[] {1,3,5,2,4,6,8,10};
//merge(arr, 0, 2, arr.length-1);
//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 = (high+low)/2;
if(low {
//处理左边
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; k++) {
arr[k+low] = temp[k];
}
}
public class RadixSort {
public static void main(String[] args) {
int[] arr = new int[] {23,6,189,45,9,287,56,1,798,34,65,652,5};
radixSort(arr);
System.out.println(Arrays.toString(arr));
}
public static void radixSort(int[] arr) {
//存数组中最大的数字
int max = Integer.MIN_VALUE;
for(int i = 0;i;i++) {
if(arr[i]>max) {
max = arr[i];
}
}
//求最大数字是几位数
int max_Length = (max+"").length();
//用于临时存储数据的数组
int[][] temp = new int[10][arr.length];
//用于记录在temp中相应的数组中存放的数字的数量
int[] counts = new int[10];
//System.out.println(max_Length);
//根据最大长度的数决定比较的次数
for(int i = 0,n = 1;i;i++,n *= 10) {
//把每一个数字分别计算余数
for(int j = 0;j;j++) {
//计算余数
int ys = arr[j]/n%10;
//把当前遍历的数据放入指定的数组中
temp[ys][counts[ys]] = arr[j];
//记录数量
counts[ys]++;
}
//记录取的元素需要放的位置
int index = 0;
//把数字取出来
for(int k =0;k;k++) {
//记录数量的数组中当前余数记录的数量不为0
if(counts[k] != 0) {
//循环取出元素
for(int l = 0;l;l++) {
//取出元素
arr[index] = temp[k][l];
//记录下一个位置
index++;
}
//把数量置空
counts[k] = 0;
}
}
System.out.println(Arrays.toString(arr));
//if(i == 0) {
// for(int[] nums:temp) {
// System.out.println(Arrays.toString(nums));
// }
//}
}
}
/*
* 优化 利用队列 基数排序
*/
public class RadixQueueSort {
public static void main(String[] args) {
int[] arr = new int[] { 23, 6, 189, 45, 9, 287, 56, 1, 798, 34, 65, 652, 5 };
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 (arr[i] > max) {
max = arr[i];
}
}
// 求最大数字是几位数
int max_Length = (max + "").length();
// 用于临时存储数据的队列
MyQueue[] temp = new MyQueue[10];
// 为队列数组赋值
for (int i = 0; i < temp.length; i++) {
temp[i] = new MyQueue();
}
// System.out.println(max_Length);
// 根据最大长度的数决定比较的次数
for (int i = 0, n = 1; i < max_Length; 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++;
}
}
}
}
}
树是一种递归的数据结构,是一种逻辑结构也是一种分层结构
节点的度:一个节点子节点的个数
节点的权:这个节点赋予的数值
叶子结点:没有子节点的节点
满二叉树:所有叶子节点都在最后一层,而且节点总数为2^n-1,n是树的高度
完全二叉树:所有叶子节点都在最后一层或倒数第二层,且最后一层的叶子结点在左边连续,倒数第二层的叶子节点在右边连续
完全二叉树n个节点,叶节点个数为n/2,n0=n2+1,若一个节点没有左孩子,则它必是叶节点
左斜树:都是左节点的树
右斜树:都是右节点的树
分支节点(非终端节点):度大于0的节点
高度为h的m叉树至多是(m^h-1)/(m-1)个结点
具有n个结点的m叉树的最小高度为【log底为m (n(m-1)+1)】
顺序存储的空间利用率较低,二叉树一般采用链式存储结构,含有n个结点的二叉链表中,含有n+1个空链域
树的存储结构:
双亲表示法:根节点下标为0,伪指针域为-1
孩子表示法:每个节点的孩子节点都用单链表连接成一个线性结构
孩子兄弟表示法:指向第一个孩子结点的指针,节点值,指向下一个兄弟结点的指针
二叉树是一种逻辑结构,线索二叉树是物理结构
线索二叉树:没有左孩子指向前驱
后续线索二叉树不能解决后序后继的问题
中序线索树中,若某节点有左节点,前驱节点为左子树最右的节点
树转换成二叉树:左孩子右兄弟
森林中树的棵树为根节点A的棵树
森林转换成二叉树:第一棵的根为整个树的根,留左子树,第二棵放在右子树,第三棵放在第二棵树下
情形一:节点u和v是兄弟节点,二者之间还有一个兄弟节点k,转换后节点v就变成节点k的右孩子,k是u的右孩子;情形二:v是u的第二个孩子节点,转换时,v变成u的第一个孩子的右孩子
森林F对应的二叉树为B,它有m个结点,B的根为P,P的右子树节点个数为n,F第一棵树的节点个数为m-n;F有n个非终端节点,B中右指针为空的节点有n+1
有序树转二叉树,后根排列为中序排列
在含有n个节点的二叉链表中,含有n+1个空链域
/*
* 树节点
*/
public class TreeNode {
//节点的权
int value;
//左儿子
TreeNode leftNode;
//右儿子
TreeNode rightNode;
public TreeNode(int value) {
this.value = value;
}
//设置左儿子
public void setlNode(TreeNode lNode) {
this.leftNode = lNode;
}
//设置右儿子
public void setrNode(TreeNode rNode) {
this.rightNode = rNode;
}
}
/*
* 二叉树
*/
public class BinaryTree {
//根节点
TreeNode root;
//设置根节点
public void setRoot(TreeNode root) {
this.root = root;
}
//获取根节点
public TreeNode getRoot() {
return root;
}
public static void main(String[] args) {
//创建一棵树
BinaryTree binTree = new BinaryTree();
//创建一个根节点
TreeNode root = new TreeNode(1);
//把根节点赋给树
binTree.setRoot(root);
//创建一个左节点
TreeNode rootL = new TreeNode(2);
//创建一个右节点
TreeNode rootR = new TreeNode(3);
//把新创建的结点设置为根节点的子节点
root.setlNode(rootL);
root.setrNode(rootR);
}
}
中序与前序遍历可以一致
前序遍历:
先取根节点,遍历左子树,后遍历右子树
中序遍历:
先取左节点,再取根结点,最后取右节点
前序为入栈,中序为出栈
后序遍历:
先取左节点,再取右节点,最后想要遍历的结点
时间复杂度为O(n),若只知道二叉树的先序序列和后序序列无法确定一棵二叉树
层次遍历:
借助队列,将第一层的根节点入队出队,访问出队节点,若有左/右子树,则将其入队出队访问出队节点
出栈序列个数为1/(n+1)C(n,2n)
package demo3;
/*
* 树节点
*/
public class TreeNode {
//节点的权
int value;
//左儿子
TreeNode leftNode;
//右儿子
TreeNode rightNode;
public TreeNode(int value) {
this.value = value;
}
//设置左儿子
public void setLeftNode(TreeNode lNode) {
this.leftNode = lNode;
}
//设置右儿子
public void setRightNode(TreeNode rNode) {
this.rightNode = rNode;
}
//前序遍历
public void frontShow() {
//遍历当前节点的内容
System.out.println(value);
//左节点
if(leftNode != null) {
leftNode.frontShow();
}
//右节点
if(rightNode != null) {
rightNode.frontShow();
}
}
//中序遍历
public void midShow() {
// TODO Auto-generated method stub
//左节点
if(leftNode != null) {
leftNode.midShow();
}
//当前节点
System.out.println(value);
//右节点
if(rightNode != null) {
rightNode.midShow();
}
}
//后序遍历
public void afterShow() {
// TODO Auto-generated method stub
//左节点
if(leftNode != null) {
leftNode.afterShow();
}
//右节点
if(rightNode != null) {
rightNode.afterShow();
}
//当前节点
System.out.println(value);
}
}
package demo3;
/*
* 二叉树
*/
public class BinaryTree {
//根节点
TreeNode root;
//设置根节点
public void setRoot(TreeNode root) {
this.root = root;
}
//获取根节点
public TreeNode getRoot() {
return root;
}
public void frontShow() {
root.frontShow();
}
public void midShow() {
// TODO Auto-generated method stub
root.midShow();
}
public void afterShow() {
// TODO Auto-generated method stub
root.afterShow();
}
public static void main(String[] args) {
//创建一棵树
BinaryTree binTree = new BinaryTree();
//创建一个根节点
TreeNode root = new TreeNode(1);
//把根节点赋给树
binTree.setRoot(root);
//创建一个左节点
TreeNode rootL = new TreeNode(2);
//创建一个右节点
TreeNode rootR = new TreeNode(3);
//把新创建的结点设置为根节点的子节点
root.setLeftNode(rootL);
root.setRightNode(rootR);
//为第二层的左节点创建俩个子节点
rootL.setLeftNode(new TreeNode(4));
rootL.setRightNode(new TreeNode(5));
//为第二层的右节点创建俩个左节点
rootR.setLeftNode(new TreeNode(6));
rootR.setRightNode(new TreeNode(7));
//前序遍历
binTree.frontShow();
System.out.println("*************");
//中序遍历
binTree.midShow();
System.out.println("*************");
//后序遍历
binTree.afterShow();
}
}
package demo3;
/*
* 树节点
*/
public class TreeNode {
// 节点的权
int value;
// 左儿子
TreeNode leftNode;
// 右儿子
TreeNode rightNode;
public TreeNode(int value) {
this.value = value;
}
// 设置左儿子
public void setLeftNode(TreeNode lNode) {
this.leftNode = lNode;
}
// 设置右儿子
public void setRightNode(TreeNode rNode) {
this.rightNode = rNode;
}
// 前序遍历
public void frontShow() {
// 遍历当前节点的内容
System.out.println(value);
// 左节点
if (leftNode != null) {
leftNode.frontShow();
}
// 右节点
if (rightNode != null) {
rightNode.frontShow();
}
}
// 中序遍历
public void midShow() {
// TODO Auto-generated method stub
// 左节点
if (leftNode != null) {
leftNode.midShow();
}
// 当前节点
System.out.println(value);
// 右节点
if (rightNode != null) {
rightNode.midShow();
}
}
// 后序遍历
public void afterShow() {
// TODO Auto-generated method stub
// 左节点
if (leftNode != null) {
leftNode.afterShow();
}
// 右节点
if (rightNode != null) {
rightNode.afterShow();
}
// 当前节点
System.out.println(value);
}
// 前序查找
public TreeNode frontSearch(int i) {
// TODO Auto-generated method stub
TreeNode target = null;
// 对比当前节点的值
if (this.value == i) {
return this;
// 当前结点的值不是要查找的节点
} else {
// 查找左儿子
if (leftNode != null) {
// 有可能查到,若查不到,target = null
target = leftNode.frontSearch(i);
}
// 若不为空,在左儿子中已经找到
if (target != null) {
return target;
}
// 查找右儿子
if (rightNode != null) {
target = rightNode.frontSearch(i);
}
}
return target;
}
// 中序查找
public TreeNode midSearch(int i) {
// TODO Auto-generated method stub
TreeNode target = null;
// 查找左儿子
if (leftNode != null) {
// 有可能查到,若查不到,target = null
target = leftNode.frontSearch(i);
}
// 若不为空,在左儿子中已经找到
if (target != null) {
return target;
}
if (this.value == i) {
return this;
}
// 查找右儿子
if (rightNode != null) {
target = rightNode.frontSearch(i);
}
// 若不为空,在右儿子中已经找到
if (target != null) {
return target;
}
if (this.value == i) {
return this;
}
return target;
}
// 后序查找
public TreeNode afterSearch(int i) {
// TODO Auto-generated method stub
TreeNode target = null;
// 查找左儿子
if (leftNode != null) {
// 有可能查到,若查不到,target = null
target = leftNode.frontSearch(i);
}
// 若不为空,在左儿子中已经找到
if (target != null) {
return target;
}
// 查找右儿子
if (rightNode != null) {
target = rightNode.frontSearch(i);
}
// 若不为空,在右儿子中已经找到
if (target != null) {
return target;
}
if (this.value == i) {
return this;
}
return target;
}
}
package demo3;
/*
* 二叉树
*/
public class BinaryTree {
//根节点
TreeNode root;
//设置根节点
public void setRoot(TreeNode root) {
this.root = root;
}
//获取根节点
public TreeNode getRoot() {
return root;
}
public void frontShow() {
root.frontShow();
}
public void midShow() {
// TODO Auto-generated method stub
root.midShow();
}
public void afterShow() {
// TODO Auto-generated method stub
root.afterShow();
}
public TreeNode frontSearch(int i) {
// TODO Auto-generated method stub
return root.frontSearch(i);
}
public TreeNode afterSearch(int i) {
// TODO Auto-generated method stub
return root.afterSearch(i);
}
public TreeNode midSearch(int i) {
// TODO Auto-generated method stub
return root.midSearch(i);
}
public static void main(String[] args) {
//创建一棵树
BinaryTree binTree = new BinaryTree();
//创建一个根节点
TreeNode root = new TreeNode(1);
//把根节点赋给树
binTree.setRoot(root);
//创建一个左节点
TreeNode rootL = new TreeNode(2);
//创建一个右节点
TreeNode rootR = new TreeNode(3);
//把新创建的结点设置为根节点的子节点
root.setLeftNode(rootL);
root.setRightNode(rootR);
//为第二层的左节点创建俩个子节点
rootL.setLeftNode(new TreeNode(4));
rootL.setRightNode(new TreeNode(5));
//为第二层的右节点创建俩个左节点
rootR.setLeftNode(new TreeNode(6));
rootR.setRightNode(new TreeNode(7));
//前序遍历
binTree.frontShow();
System.out.println("*************");
//中序遍历
binTree.midShow();
System.out.println("*************");
//后序遍历
binTree.afterShow();
System.out.println("*************");
//前序查找
TreeNode result = binTree.frontSearch(5);
System.out.println(result);
TreeNode result1 = binTree.midSearch(5);
System.out.println(result1);
TreeNode result2 = binTree.afterSearch(5);
System.out.println(result2);
}
}
package demo3;
/*
* 树节点
*/
public class TreeNode {
// 节点的权
int value;
// 左儿子
TreeNode leftNode;
// 右儿子
TreeNode rightNode;
public TreeNode(int value) {
this.value = value;
}
// 设置左儿子
public void setLeftNode(TreeNode lNode) {
this.leftNode = lNode;
}
// 设置右儿子
public void setRightNode(TreeNode rNode) {
this.rightNode = rNode;
}
// 前序遍历
public void frontShow() {
// 遍历当前节点的内容
System.out.println(value);
// 左节点
if (leftNode != null) {
leftNode.frontShow();
}
// 右节点
if (rightNode != null) {
rightNode.frontShow();
}
}
// 中序遍历
public void midShow() {
// TODO Auto-generated method stub
// 左节点
if (leftNode != null) {
leftNode.midShow();
}
// 当前节点
System.out.println(value);
// 右节点
if (rightNode != null) {
rightNode.midShow();
}
}
// 后序遍历
public void afterShow() {
// TODO Auto-generated method stub
// 左节点
if (leftNode != null) {
leftNode.afterShow();
}
// 右节点
if (rightNode != null) {
rightNode.afterShow();
}
// 当前节点
System.out.println(value);
}
// 前序查找
public TreeNode frontSearch(int i) {
// TODO Auto-generated method stub
TreeNode target = null;
// 对比当前节点的值
if (this.value == i) {
return this;
// 当前结点的值不是要查找的节点
} else {
// 查找左儿子
if (leftNode != null) {
// 有可能查到,若查不到,target = null
target = leftNode.frontSearch(i);
}
// 若不为空,在左儿子中已经找到
if (target != null) {
return target;
}
// 查找右儿子
if (rightNode != null) {
target = rightNode.frontSearch(i);
}
}
return target;
}
// 中序查找
public TreeNode midSearch(int i) {
// TODO Auto-generated method stub
TreeNode target = null;
// 查找左儿子
if (leftNode != null) {
// 有可能查到,若查不到,target = null
target = leftNode.frontSearch(i);
}
// 若不为空,在左儿子中已经找到
if (target != null) {
return target;
}
if (this.value == i) {
return this;
}
// 查找右儿子
if (rightNode != null) {
target = rightNode.frontSearch(i);
}
// 若不为空,在右儿子中已经找到
if (target != null) {
return target;
}
if (this.value == i) {
return this;
}
return target;
}
// 后序查找
public TreeNode afterSearch(int i) {
// TODO Auto-generated method stub
TreeNode target = null;
// 查找左儿子
if (leftNode != null) {
// 有可能查到,若查不到,target = null
target = leftNode.frontSearch(i);
}
// 若不为空,在左儿子中已经找到
if (target != null) {
return target;
}
// 查找右儿子
if (rightNode != null) {
target = rightNode.frontSearch(i);
}
// 若不为空,在右儿子中已经找到
if (target != null) {
return target;
}
if (this.value == i) {
return this;
}
return target;
}
//删除一个子树
public void delete(int i) {
// TODO Auto-generated method stub
TreeNode parent = this;
//判断左儿子
if(parent.leftNode != null && parent.leftNode.value == i) {
parent.leftNode = null;
return;
}
//判断右儿子
if(parent.rightNode != null && parent.rightNode.value == i) {
parent.rightNode = null;
return;
}
//递归检查并删除左儿子
parent = leftNode;
if(parent != null) {
parent.delete(i);
}
//递归检查并删除右儿子
parent = rightNode;
if(parent != null) {
parent.delete(i);
}
}
}
package demo3;
/*
* 二叉树
*/
public class BinaryTree {
//根节点
TreeNode root;
//设置根节点
public void setRoot(TreeNode root) {
this.root = root;
}
//获取根节点
public TreeNode getRoot() {
return root;
}
public void frontShow() {
if(root != null) {
root.frontShow();
}
}
public void midShow() {
// TODO Auto-generated method stub
if(root != null) {
root.midShow();
}
}
public void afterShow() {
// TODO Auto-generated method stub
if(root != null) {
root.afterShow();
}
}
public TreeNode frontSearch(int i) {
// TODO Auto-generated method stub
return root.frontSearch(i);
}
public TreeNode afterSearch(int i) {
// TODO Auto-generated method stub
return root.afterSearch(i);
}
public TreeNode midSearch(int i) {
// TODO Auto-generated method stub
return root.midSearch(i);
}
public void delete(int i) {
// TODO Auto-generated method stub
if(root.value == i) {
root = null;
}else {
root.delete(i);
}
}
public static void main(String[] args) {
//创建一棵树
BinaryTree binTree = new BinaryTree();
//创建一个根节点
TreeNode root = new TreeNode(1);
//把根节点赋给树
binTree.setRoot(root);
//创建一个左节点
TreeNode rootL = new TreeNode(2);
//创建一个右节点
TreeNode rootR = new TreeNode(3);
//把新创建的结点设置为根节点的子节点
root.setLeftNode(rootL);
root.setRightNode(rootR);
//为第二层的左节点创建俩个子节点
rootL.setLeftNode(new TreeNode(4));
rootL.setRightNode(new TreeNode(5));
//为第二层的右节点创建俩个左节点
rootR.setLeftNode(new TreeNode(6));
rootR.setRightNode(new TreeNode(7));
//前序遍历
binTree.frontShow();
System.out.println("*************");
//中序遍历
binTree.midShow();
System.out.println("*************");
//后序遍历
binTree.afterShow();
System.out.println("*************");
//前序查找
TreeNode result = binTree.frontSearch(5);
System.out.println(result);
TreeNode result1 = binTree.midSearch(5);
System.out.println(result1);
TreeNode result2 = binTree.afterSearch(5);
System.out.println(result2);
System.out.println("*************");
//删除一个子树
binTree.delete(1);
binTree.frontShow();
}
}
任何一个数组都可以看成是一棵完全二叉树
顺序存储的二叉树只考虑完全二叉树
第n个元素的左子节点是: 2n+1
第n个元素的右子节点是: 2n+2
第n个元素的父节点是: (n-1)/2
package demo3;
/*
* 顺序存储的二叉树
*/
public class ArrayBinaryTree {
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]);
// 左节点
if (2 * index + 1 < data.length) {
frontShow(2 * index + 1);
}
// 右节点
if (2 * index + 2 < data.length) {
frontShow(2 * index + 2);
}
}
public static void main(String[] args) {
int[] data = new int[] {1,2,3,4,5,6,7};
ArrayBinaryTree tree = new ArrayBinaryTree(data);
tree.frontShow();
}
}
线索化二叉树,一个节点的前一个节点,叫前继节点。一个节点的后一个节点,叫后继节点
lchild:指向左子树或前驱节点,ltag:标识lchild指向左子树或前驱节点,data,rtag:标识rchild指向右子树或后驱节点,rchild:指向右子树或后驱节点
package demo3;
/*
*线索二叉树
*/
public class ThreadedBinaryTree {
// 根节点
ThreadedNode root;
// 临时存储前驱节点
ThreadedNode pre = null;
// 遍历线索二叉树
public void threadIterate() {
// 用于临时存储当前遍历节点
ThreadedNode node = root;
while (node != null) {
// 循环找到最开始的节点
while (node.leftType == 0) {
node = node.leftNode;
}
// 打印当前节点的值
System.out.println(node.value);
// 如果当前节点的右指针指向的是后继节点,可能后继节点还有后继节点
while (node.rightType == 1) {
node = node.rightNode;
System.out.println(node.value);
}
// 替换遍历的节点
node = node.rightNode;
}
}
// 设置根节点
public void setRoot(ThreadedNode root) {
this.root = root;
}
// 中序线索化二叉树
public void threadNodes() {
threadNodes(root);
}
public void threadNodes(ThreadedNode node) {
// 当前节点如果为null,直接返回
if (node == null) {
return;
}
// 处理左子树
threadNodes(node.leftNode);
// 处理前驱节点
if (node.leftNode == null) {
// 让当前节点的左指针指向前驱节点
node.leftNode = null;
// 改变当前节点左指针类型
node.leftType = 1;
}
// 处理前驱的右指针,如果前驱节点的右指针是null(没有指下右子树)
if (pre != null && pre.rightNode == null) {
// 让前驱节点的右指针指向当前节点
pre.rightNode = node;
// 改变前驱节点的右指针
pre.rightType = 1;
}
// 每处理一个节点,当前节点是下一个节点的前驱结点
pre = node;
// 处理右子树
threadNodes(node.rightNode);
//
}
// 获取根节点
public ThreadedNode getRoot() {
return root;
}
public void frontShow() {
if (root != null) {
root.frontShow();
}
}
public void midShow() {
// TODO Auto-generated method stub
if (root != null) {
root.midShow();
}
}
public void afterShow() {
// TODO Auto-generated method stub
if (root != null) {
root.afterShow();
}
}
public ThreadedNode frontSearch(int i) {
// TODO Auto-generated method stub
return root.frontSearch(i);
}
public ThreadedNode afterSearch(int i) {
// TODO Auto-generated method stub
return root.afterSearch(i);
}
public ThreadedNode midSearch(int i) {
// TODO Auto-generated method stub
return root.midSearch(i);
}
public void delete(int i) {
// TODO Auto-generated method stub
if (root.value == i) {
root = null;
} else {
root.delete(i);
}
}
public static void main(String[] args) {
// 创建一棵树
ThreadedBinaryTree binTree = new ThreadedBinaryTree();
// 创建一个根节点
ThreadedNode root = new ThreadedNode(1);
// 把根节点赋给树
binTree.setRoot(root);
// 创建一个左节点
ThreadedNode rootL = new ThreadedNode(2);
// 创建一个右节点
ThreadedNode rootR = new ThreadedNode(3);
// 把新创建的结点设置为根节点的子节点
root.setLeftNode(rootL);
root.setRightNode(rootR);
// 为第二层的左节点创建俩个子节点
rootL.setLeftNode(new ThreadedNode(4));
ThreadedNode fiveNode = new ThreadedNode(5);
rootL.setRightNode(fiveNode);
// 为第二层的右节点创建俩个左节点
rootR.setLeftNode(new ThreadedNode(6));
rootR.setRightNode(new ThreadedNode(7));
// 中序遍历
binTree.midShow();
System.out.println("*************");
// 中前线索化二叉树
binTree.threadNodes();
// 查找五节点
// ThreadedNode afterFive = fiveNode.rightNode;
// System.out.println(afterFive.value);
binTree.threadIterate();
}
}
也就是最优二叉树,n个带权叶子结点构成的所有二叉树中,带权路径长度最小的二叉树
树的带权路径长度WPL:树中所有叶子结点的带权路径长度之和
只有度为0和2的节点,n0=n2+1
赫夫曼编码中,一个编码不能是另一个编码的前缀
1.排序
2.取出根节点权值最小的倆棵二叉树
3.组成一棵新的二叉树,前面取出的两棵二叉树是新二叉树的子树
4.根结点的权值是前面取出的俩棵二叉树根节点权值之和
package demo3;
/*
* 赫夫曼树节点
*/
public class Node implements Comparable {
// 节点的权
int value;
// 左儿子
Node leftNode;
// 右儿子
Node rightNode;
public Node(int value) {
this.value = value;
}
@Override
public int compareTo(Node o) {
// TODO Auto-generated method stub
return -(this.value-o.value);
}
@Override
public String toString() {
return "Node [value=" + value + "]";
}
}
package demo3;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/*
* 赫夫曼树
*/
public class HuffmanTree {
//创建赫夫曼树
public static Node createHuffmanTree(int[] arr) {
//先使用数组中所有的元素创建若干个二叉树(只有一个节点)
List nodes = new ArrayList<>();
for(int value:arr) {
nodes.add(new Node(value));
}
//循环处理
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);
//把取出来的二叉树移除
nodes.remove(left);
nodes.remove(right);
//放入原来的二叉树集合中
nodes.add(parent);
}
//System.out.println(nodes);
return nodes.get(0);
}
public static void main(String[] args) {
int[] arr = {3,7,8,29,5,11,23,14};
Node node = createHuffmanTree(arr);
System.out.println(node);
}
}
定长编码:计算机接收到一句话时首先转成ASCII码,随后转成二进制
非定长编码:计算一句话每个字符出现多少次,出现越少的转换成编码越长,出现越多转换成编码越短。字符的编码不能是其他字符编码的前缀,符合此要求的编码叫做前缀编码
编码步骤:
package demo3;
/*
* 赫夫曼编码节点
*/
public class Node02 implements Comparable {
Byte data; //Byte封装类型,使其可以为空
// 节点的权
int weight;
// 左儿子
Node02 left;
// 右儿子
Node02 right;
public Node02(Byte data,int weight) {
this.data = data;
this.weight = weight;
}
@Override
public int compareTo(Node02 o) {
// TODO Auto-generated method stub
return o.weight - this.weight;
}
@Override
public String toString() {
return "Node02 [data=" + data + ", weight=" + weight + "]";
}
}
package demo3;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/*
* 赫夫曼编码
*/
public class HuffmanCode {
public static void main(String[] args) {
String msg = "can you can a can as a can canner can a can.";
byte[] bytes = msg.getBytes();
//进行赫夫曼编码压缩
byte[] b = huffmanZip(bytes);
//System.out.println(bytes.length);
//System.out.println(b.length);
//使用赫夫曼编码解码
byte[] newBytes = decode(huffcodes,b);
//System.out.println(Arrays.toString(bytes));
//System.out.println(Arrays.toString(newBytes));
System.out.println(new String(newBytes));
}
//使用指定的赫夫曼编码表进行解码
private static byte[] decode(Map huffcodes, byte[] bytes) {
StringBuilder sb = new StringBuilder();
// 把byte数组转为一个二进制字符串
for(int i = 0;i;i++) {
//String s = Integer.toBinaryString(b);
//System.out.println(s);
byte b = bytes[i];
//是否是最后一个
boolean flag = (i == bytes.length-1);
sb.append(byteToBitStr(!flag,b));
}
//System.out.println(sb.toString());
//把字符串按照指定的赫夫曼编码进行解码
//把赫夫曼编码的键值对进行调换
Map, Byte> map = new HashMap<>();
for(Map.Entry entry:huffcodes.entrySet()) {
map.put(entry.getValue(), entry.getKey());
}
//创建一个集合用于存byte数组
List list = new ArrayList<>();
//处理字符串
for(int i = 0;ilength();) {
int count = 1;
boolean flag = true;
Byte b = null;
//截取出一个key
while(flag) {
String key = sb.substring(i, i+count);
b = map.get(key);
if(b == null) {
count++;
}else {
flag = false;
}
}
//System.out.println(key);
//System.out.println(b);
list.add(b);
i+=count;
}
//把集合转为数组
byte[] b = new byte[list.size()];
for(int i = 0;i;i++) {
b[i] = list.get(i);
}
//System.out.println(list);
return b;
}
private static String byteToBitStr(boolean flag,byte b) {
int temp = b;
if(flag) {
temp |= 256; //保证非八位二进制能用0补齐成八位二进制
}
String str = Integer.toBinaryString(temp);
if(flag) {
return str.substring(str.length()-8);
}else {
return str;
}
}
//进行赫夫曼编码压缩的方法
private static byte[] huffmanZip(byte[] bytes) {
// 先统计每一个byte出现的次数,并放入集合中
List nodes = getNodes(bytes);
//创建一个赫夫曼树
Node02 tree = createHuffmanTree(nodes);
//System.out.println(tree);
//创建一个赫夫曼编码表
Map,String> huffCodes = getCodes(tree);
//System.out.println(huffCodes);
//编码
byte[] b = zip(bytes,huffcodes);
return b;
}
//进行赫夫曼编码
private static byte[] zip(byte[] bytes, Map huffcodes) {
// TODO Auto-generated method stub
StringBuilder sb = new StringBuilder();
//把需要压缩的byte数组处理成二进制字符串
for(byte b:bytes) {
sb.append(huffcodes.get(b));
}
//System.out.println(sb.toString());
//定义长度
int len;
if(sb.length()/8 == 0) {
len = sb.length()/8;
}else {
len = sb.length()/8+1;
}
//用于存储压缩后的byte
byte[] by = new byte[len];
//记录新byte的位置
int index = 0;
for(int i = 0;ilength();i+=8) {
String strByte;
if(i+8>sb.length()) {
strByte = sb.substring(i);
}else {
strByte = sb.substring(i,i+8);
}
byte byt = (byte) Integer.parseInt(strByte,2);
//System.out.println(strByte+":"+byt);
by[index] = byt;
index++;
}
return by;
}
//用于临时存储路径
static StringBuilder sb = new StringBuilder();
//用于存储赫夫曼编码
static Map,String> huffcodes = new HashMap<>();
//根据赫夫曼树获取赫夫曼编码
private static Map getCodes(Node02 tree) {
// TODO Auto-generated method stub
if(tree == null) {
return null;
}
getCodes(tree.left,"0",sb);
getCodes(tree.right,"1",sb);
return huffcodes;
}
private static void getCodes(Node02 node, String code, StringBuilder sb) {
// TODO Auto-generated method stub
StringBuilder sb2 = new StringBuilder(sb);
sb2.append(code);
if(node.data == null) {
getCodes(node.left,"0",sb2);
getCodes(node.right,"1",sb2);
}else {
huffcodes.put(node.data, sb2.toString());
}
}
//创建一个赫夫曼树
private static Node02 createHuffmanTree(List nodes) {
// TODO Auto-generated method stub
while(nodes.size()>1) {
//排序
Collections.sort(nodes);
//取出俩个权值最低的二叉树
Node02 left = nodes.get(nodes.size()-1);
Node02 right = nodes.get(nodes.size()-2);
//创建一个新的二叉树
Node02 parent = new Node02(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集合
private static List getNodes(byte[] bytes) {
// TODO Auto-generated method stub
List nodes = new ArrayList<>();
//存储每一个byte出现的次数
Map, Integer> counts = new HashMap<>();
//统计每一个byte出现的次数
for(byte b : bytes) {
Integer count = counts.get(b);
if(count == null) {
counts.put(b, 1);
}else {
counts.put(b, count+1);
}
}
//System.out.println(counts);
//把每一个键值对转成node对象
for(Map.Entry entry:counts.entrySet()) {
nodes.add(new Node02(entry.getKey(),entry.getValue()));
}
return nodes;
}
}
赫夫曼编码压缩解压文件:
package demo3;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/*
* 赫夫曼编码压缩文件
*/
public class HuffmanZipFile {
public static void main(String[] args) {
String src = "E:/1.bmp";
String dst = "E:/2.zip";
// try {
// zipFile(src, dst);
// } catch (IOException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
try {
unZip(dst, "E:/3.bmp");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// 文件的解压
public static void unZip(String src, String dst) throws Exception {
// 创建一个输入流
InputStream is = new FileInputStream("E:/2.zip");
ObjectInputStream ois = new ObjectInputStream(is);
// 读取byte数组
byte[] b = (byte[]) ois.readObject();
// 读取赫夫曼编码表
Map, String> codes = (Map, String>) ois.readObject();
ois.close();
is.close();
// 解码
byte[] bytes = decode(codes, b);
// 创建一个输出流
OutputStream os = new FileOutputStream(dst);
// 写出数据
os.write(bytes);
os.close();
}
// 压缩文件
public static void zipFile(String src, String dst) throws IOException {
// 创建一个输入流
InputStream is = new FileInputStream(src);
// 创建一个和输入流指向的文件大小一样的数组
byte[] b = new byte[is.available()];
// 读取文件内容
is.read(b);
is.close();
// 使用赫夫曼编码进行编码
byte[] byteZip = huffmanZip(b);
// System.out.println(b.length);
// System.out.println(byteZip.length);
// 输出流
OutputStream os = new FileOutputStream(dst);
ObjectOutputStream oos = new ObjectOutputStream(os);
// 把压缩后的byte数组写入文件
oos.writeObject(byteZip);
// 把赫夫曼编码表写入文件
oos.writeObject(huffcodes);
oos.close();
os.close();
}
// 使用指定的赫夫曼编码表进行解码
private static byte[] decode(Map huffcodes, byte[] bytes) {
StringBuilder sb = new StringBuilder();
// 把byte数组转为一个二进制字符串
for (int i = 0; i < bytes.length; i++) {
// String s = Integer.toBinaryString(b);
// System.out.println(s);
byte b = bytes[i];
// 是否是最后一个
boolean flag = (i == bytes.length - 1);
sb.append(byteToBitStr(!flag, b));
}
// System.out.println(sb.toString());
// 把字符串按照指定的赫夫曼编码进行解码
// 把赫夫曼编码的键值对进行调换
Map, Byte> map = new HashMap<>();
for (Map.Entry entry : huffcodes.entrySet()) {
map.put(entry.getValue(), entry.getKey());
}
// 创建一个集合用于存byte数组
List list = new ArrayList<>();
// 处理字符串
for (int i = 0; i < sb.length();) {
int count = 1;
boolean flag = true;
Byte b = null;
// 截取出一个key
while (flag) {
String key = sb.substring(i, i + count);
b = map.get(key);
if (b == null) {
count++;
} else {
flag = false;
}
}
// System.out.println(key);
// System.out.println(b);
list.add(b);
i += count;
}
// 把集合转为数组
byte[] b = new byte[list.size()];
for (int i = 0; i < b.length; i++) {
b[i] = list.get(i);
}
// System.out.println(list);
return b;
}
private static String byteToBitStr(boolean flag, byte b) {
int temp = b;
if (flag) {
temp |= 256; // 保证非八位二进制能用0补齐成八位二进制
}
String str = Integer.toBinaryString(temp);
if (flag) {
return str.substring(str.length() - 8);
} else {
return str;
}
}
// 进行赫夫曼编码压缩的方法
private static byte[] huffmanZip(byte[] bytes) {
// 先统计每一个byte出现的次数,并放入集合中
List nodes = getNodes(bytes);
// 创建一个赫夫曼树
Node02 tree = createHuffmanTree(nodes);
// System.out.println(tree);
// 创建一个赫夫曼编码表
Map, String> huffCodes = getCodes(tree);
// System.out.println(huffCodes);
// 编码
byte[] b = zip(bytes, huffcodes);
return b;
}
// 进行赫夫曼编码
private static byte[] zip(byte[] bytes, Map huffcodes) {
// TODO Auto-generated method stub
StringBuilder sb = new StringBuilder();
// 把需要压缩的byte数组处理成二进制字符串
for (byte b : bytes) {
sb.append(huffcodes.get(b));
}
// System.out.println(sb.toString());
// 定义长度
int len;
if (sb.length() / 8 == 0) {
len = sb.length() / 8;
} else {
len = sb.length() / 8 + 1;
}
// 用于存储压缩后的byte
byte[] by = new byte[len];
// 记录新byte的位置
int index = 0;
for (int i = 0; i < sb.length(); i += 8) {
String strByte;
if (i + 8 > sb.length()) {
strByte = sb.substring(i);
} else {
strByte = sb.substring(i, i + 8);
}
byte byt = (byte) Integer.parseInt(strByte, 2);
// System.out.println(strByte+":"+byt);
by[index] = byt;
index++;
}
return by;
}
// 用于临时存储路径
static StringBuilder sb = new StringBuilder();
// 用于存储赫夫曼编码
static Map, String> huffcodes = new HashMap<>();
// 根据赫夫曼树获取赫夫曼编码
private static Map getCodes(Node02 tree) {
// TODO Auto-generated method stub
if (tree == null) {
return null;
}
getCodes(tree.left, "0", sb);
getCodes(tree.right, "1", sb);
return huffcodes;
}
private static void getCodes(Node02 node, String code, StringBuilder sb) {
// TODO Auto-generated method stub
StringBuilder sb2 = new StringBuilder(sb);
sb2.append(code);
if (node.data == null) {
getCodes(node.left, "0", sb2);
getCodes(node.right, "1", sb2);
} else {
huffcodes.put(node.data, sb2.toString());
}
}
// 创建一个赫夫曼树
private static Node02 createHuffmanTree(List nodes) {
// TODO Auto-generated method stub
while (nodes.size() > 1) {
// 排序
Collections.sort(nodes);
// 取出俩个权值最低的二叉树
Node02 left = nodes.get(nodes.size() - 1);
Node02 right = nodes.get(nodes.size() - 2);
// 创建一个新的二叉树
Node02 parent = new Node02(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集合
private static List getNodes(byte[] bytes) {
// TODO Auto-generated method stub
List nodes = new ArrayList<>();
// 存储每一个byte出现的次数
Map, Integer> counts = new HashMap<>();
// 统计每一个byte出现的次数
for (byte b : bytes) {
Integer count = counts.get(b);
if (count == null) {
counts.put(b, 1);
} else {
counts.put(b, count + 1);
}
}
// System.out.println(counts);
// 把每一个键值对转成node对象
for (Map.Entry entry : counts.entrySet()) {
nodes.add(new Node02(entry.getKey(), entry.getValue()));
}
return nodes;
}
}
线性结构:
顺序存储、不排序:查找困难
顺序存储,排序:删除插入困难
链式存储:无论是否排序,查找困难
二叉查找树:对于二叉树中任何一个非叶子结点,要求左子节点比当前节点值小,右子节点比当前节点值大
二叉树是一种动态查找表。查找效率较高。
树的结构不是一次生成的,而是在查找过程中,当树中不存在关键字等于给定值的结点时再进行插入。新插入的结点一定是一个新添加的叶子结点,并且是查找不成功时查找路径上访问的最后一个结点的左孩子或右孩子结点。所以二叉查找树不存在相同的节点。
根节点的前趋节点是左子树的最大的那个节点,后继节点是右子树最小的那个节点,删除根节点时需要将根节点的后继节点删除,把后继节点的值替换根节点。如果根节点的后继节点存在子节点(一定是右子节点),则在删除后继节点后,将后继节点的子节点替换后继节点。有俩个子节点的节点删除同理。
删除操作三种情况:
1)若被删除节点Z是叶节点直接删除
2)若节点z只有一棵左子树或右子树,则让z的子树成为z父节点的子树,替代z的位置
3)若节点z有左右俩棵子树,则令z的直接后继(或直接前驱)替代z,然后从二叉排序树中删除这个直接后继(或直接前驱),转换成第一二种情况
左右子树高度之差绝对值不超过1,平均查找长度为(O(log(底为2)n)),单支树平均查找长度O(n)
最理想的深度为log以2为底(n+1)
若有序表是动态查找表,选择二叉排序树为逻辑结构
package demo3;
/*
* 二叉排序树
*/
public class Node03 {
int value;
Node03 left;
Node03 right;
public Node03(int value) {
this.value = value;
}
// 向子树中添加节点
public void add(Node03 node) {
// TODO Auto-generated method stub
if (node == null) {
return;
}
// 判断传入的节点值比当前子树的根节点的值是大还是小
// 添加节点比当前节点值小
if (node.value < this.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 middleShow(Node03 node) {
// TODO Auto-generated method stub
if (node == null) {
return;
}
middleShow(node.left);
System.out.println(node.value);
middleShow(node.right);
}
// 查找节点
public Node03 search(int value) {
// TODO Auto-generated method stub
if (this.value == value) {
return this;
} else if (value < this.value) {
if (left == null) {
return null;
}
return left.search(value);
} else {
if (right == null) {
return null;
}
return right.search(value);
}
}
// 搜索父节点
public Node03 searchParent(int value) {
// TODO Auto-generated method stub
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);
}
}
return null;
}
}
package demo3;
/*
* 二叉排序树
*/
public class BinarySortTree {
Node03 root;
// 向二叉排序树添加节点
public void add(Node03 node) {
// 如果是一棵空树
if (root == null) {
root = node;
} else {
root.add(node);
}
}
public void middleShow() {
if (root != null) {
root.middleShow(root);
}
}
public Node03 search(int value) {
if (root == null) {
return null;
} else {
return root.search(value);
}
}
// 删除节点
public void delete(int value) {
if (root == null) {
return;
} else {
// 找到这个节点
Node03 target = search(value);
// 如果没有这个节点
if (target == null) {
return;
}
// 找到他的父节点
Node03 parent = searchParent(value);
// 要删除的节点是叶子结点
if (target.left == null && target.right == null) {
// 要删除的节点是父节点的左子节点
if (parent.left.value == value) {
parent.left = null;
// 要删除的节点是父节点的右子节点
} else {
parent.right = null;
}
// 要删除的节点有俩个子节点
} else if (target.left != null && target.right != null) {
// 删除右子树中值最小的节点,获取该节点的值
int min = deleteMin(target.right);
// 替换目标节点中的值
target.value = min;
// 要删除的节点有一个左子节点或者右子节点
} else {
// 有左子节点
if (target.left != null) {
// 要删除的节点是父节点的左子节点
if (parent.left.value == value) {
parent.left = target.left;
// 要删除的节点是父节点的右子节点
} else {
parent.right = target.left;
}
// 有右子节点
} else {
// 要删除的节点是父节点的左子节点
if (parent.left.value == value) {
parent.left = target.right;
// 要删除的节点是父节点的右子节点
} else {
parent.right = target.right;
}
}
}
}
}
// 删除一棵树中最小的节点
private int deleteMin(Node03 node) {
Node03 target = node;
// 循环向左找
while (target.left != null) {
target = target.left;
}
// 删除最小的节点
delete(target.value);
return target.value;
}
// 搜索父节点
public Node03 searchParent(int value) {
if (root == null) {
return null;
} else {
return root.searchParent(value);
}
}
public static void main(String[] args) {
int[] arr = new int[] { 7, 3, 10, 12, 5, 1, 9 };
// 创建一个二叉排序树
BinarySortTree bst = new BinarySortTree();
// 循环添加
for (int i : arr) {
bst.add(new Node03(i));
}
// 查看树中的值
// bst.middleShow();
// 查找
// Node03 node = bst.search(10);
// System.out.println(node.value);
// Node03 node02 = bst.search(101);
// System.out.println(node02);
// Node03 p1 = bst.searchParent(1);
// System.out.println(p1.value);
// 删除叶子结点
// bst.delete(1);
// bst.middleShow();
// System.out.println("-----");
// 删除只有一个子节点的节点
// bst.delete(3);
// bst.middleShow();
//删除有俩个子节点的节点
bst.delete(7);
bst.middleShow();
}
}
任何一颗子树而言,左子树与右子树的高度差不超过1,保证查找效率
节点数公式:Cn=C(n-1)+C(n-2)+1
单旋转:
左左无右:
变成↓
左左有右
变成↓
左左左右(鬼畜取名)
第一步↓
双旋转:
当左边的左子树高度<右边的左子树高度
分成俩个部分:
package demo3;
/*
* 平衡二叉树
*/
public class Node04 {
int value;
Node04 left;
Node04 right;
public Node04(int value) {
this.value = value;
}
// 返回当前节点高度
public int height() {
return Math.max(left == null ? 0 : left.height(), right == null ? 0 : right.height()) + 1;
}
// 获取左子树的高度
public int leftHeight() {
if (left == null) {
return 0;
}
return left.height();
}
// 获取右子树的高度
public int rightHeight() {
if (right == null) {
return 0;
}
return right.height();
}
// 向子树中添加节点
public void add(Node04 node) {
// TODO Auto-generated method stub
if (node == null) {
return;
}
// 判断传入的节点值比当前子树的根节点的值是大还是小
// 添加节点比当前节点值小
if (node.value < this.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) {
//双旋转
if(left!=null && left.leftHeight() {
//先左旋转
left.leftRotate();
//再右旋转
rightRotate();
}else {
// 进行右旋转
rightRotate();
}
}
// 左旋转
if (leftHeight() - rightHeight() <= -2) {
//双旋转
if(right!=null && right.rightHeight() {
right.rightRotate();
leftRotate();
}else {
leftRotate();
}
}
}
//左旋转
private void leftRotate() {
Node04 newLeft = new Node04(value);
newLeft.left = left;
newLeft.right = right.left;
value = right.value;
right = right.right;
left = newLeft;
}
// 右旋转
private void rightRotate() {
// 创建一个新的节点,值等于当前节点的值
Node04 newRight = new Node04(value);
// 把新节点的右子树设置为当前节点的右子树
newRight.right = right;
// 把新节点的左子树设置为当前节点的左子树的右子树
newRight.left = left.right;
// 把当前节点的值换为左子节点的值
value = left.value;
// 把当前节点的左子树设置为左子树的左子树
left = left.left;
// 把当前节点的右子树设置为新节点
right = newRight;
}
// 中序遍历二叉排序树,结果是从小到大的
public void middleShow(Node04 node) {
// TODO Auto-generated method stub
if (node == null) {
return;
}
middleShow(node.left);
System.out.println(node.value);
middleShow(node.right);
}
// 查找节点
public Node04 search(int value) {
// TODO Auto-generated method stub
if (this.value == value) {
return this;
} else if (value < this.value) {
if (left == null) {
return null;
}
return left.search(value);
} else {
if (right == null) {
return null;
}
return right.search(value);
}
}
// 搜索父节点
public Node04 searchParent(int value) {
// TODO Auto-generated method stub
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);
}
}
return null;
}
}
package demo3;
/*
* 平衡二叉树
*/
public class BBinaryTree {
Node04 root;
// 向二叉排序树添加节点
public void add(Node04 node) {
// 如果是一棵空树
if (root == null) {
root = node;
} else {
root.add(node);
}
}
public void middleShow() {
if (root != null) {
root.middleShow(root);
}
}
public Node04 search(int value) {
if (root == null) {
return null;
} else {
return root.search(value);
}
}
// 删除节点
public void delete(int value) {
if (root == null) {
return;
} else {
// 找到这个节点
Node04 target = search(value);
// 如果没有这个节点
if (target == null) {
return;
}
// 找到他的父节点
Node04 parent = searchParent(value);
// 要删除的节点是叶子结点
if (target.left == null && target.right == null) {
// 要删除的节点是父节点的左子节点
if (parent.left.value == value) {
parent.left = null;
// 要删除的节点是父节点的右子节点
} else {
parent.right = null;
}
// 要删除的节点有俩个子节点
} else if (target.left != null && target.right != null) {
// 删除右子树中值最小的节点,获取该节点的值
int min = deleteMin(target.right);
// 替换目标节点中的值
target.value = min;
// 要删除的节点有一个左子节点或者右子节点
} else {
// 有左子节点
if (target.left != null) {
// 要删除的节点是父节点的左子节点
if (parent.left.value == value) {
parent.left = target.left;
// 要删除的节点是父节点的右子节点
} else {
parent.right = target.left;
}
// 有右子节点
} else {
// 要删除的节点是父节点的左子节点
if (parent.left.value == value) {
parent.left = target.right;
// 要删除的节点是父节点的右子节点
} else {
parent.right = target.right;
}
}
}
}
}
// 删除一棵树中最小的节点
private int deleteMin(Node04 node) {
Node04 target = node;
// 循环向左找
while (target.left != null) {
target = target.left;
}
// 删除最小的节点
delete(target.value);
return target.value;
}
// 搜索父节点
public Node04 searchParent(int value) {
if (root == null) {
return null;
} else {
return root.searchParent(value);
}
}
public static void main(String[] args) {
//int[] arr = new int[] { 8,9,6,7,5,4 };
//int[] arr = new int[] { 2,1,4,3,5,6 };
int[] arr = new int[] { 8,9,5,4,6,7 };
// 创建一个二叉排序树
BBinaryTree bst = new BBinaryTree();
// 循环添加
for (int i : arr) {
bst.add(new Node04(i));
}
//查看高度
System.out.println(bst.root.height());
System.out.println(bst.root.value);
//System.out.println(bst.root.leftHeight());
//System.out.println(bst.root.rightHeight());
}
}
内存存储:
优点:使用电信号来保存信息,不存在机器操作,所以访问速度很快
缺点:造价高,断电后数据丢失,一般作为CPU的高速缓存
B树的大部分操作所需的磁盘存取个数与B树的高度成正比
B树中所有的叶节点都在同一层
2-3树: 可以有俩个子节点的节点叫二节点,二节点要么有俩个节点要么没有节点,可以有三个子节点的节点叫三节点,三节点要么有三个节点要么没有节点
B树的阶: 2-3树阶是3,2-3-4树阶是4
B树的查找:
1.在B树找节点
2.在节点内找关键字,由于B树存储在磁盘内,因此前一个查找操作时在磁盘上进行的,后一个查找操作是在内存中进行的,即找到目标节点中,先将节点信息读入内存,然后在节点内采用顺序查找法或折半查找法
B树的插入:
复杂
1.定位,利用B树查找算法,插入位置一定在最底层的某个非叶节点
2.插入,溢出进行分裂
B树的删除:
1.直接删除
2.兄弟够借
3.兄弟不够借,需合并
节点的孩子个数=关键字个数+1
B+树:
非叶节点只存储引索信息,不存储数据
叶子结点最右边的指针指向下一个相邻的叶节点,所有叶节点组成了一个有序链表
B树与B+树的主要区别:
建立了关键字和存储地址间的直接映射关系,一个萝卜一个坑,先挖有序坑(散列函数),再填萝卜(整条数据)
设计俩个原则:计算简单 分布均匀
设计方法:
散列冲突的解决方案:
开放地址法:
在地址后面再找一个地址存储重复值
package demo4;
/*
* 散列表学生信息
*/
public class StuInfo {
private int age;
private int count;
public int getAge() {
return age;
}
public StuInfo(int age, int count) {
super();
this.age = age;
this.count = count;
}
public void setAge(int age) {
this.age = age;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public int hashCode() {
return age;
}
public StuInfo(int age) {
super();
this.age = age;
}
@Override
public String toString() {
return "StuInfo [age=" + age + ", count=" + count + "]";
}
}
package demo4;
/*
* 散列表直接定址法
*/
public class HashTable {
private StuInfo[] data = new StuInfo[100];
//想散列表中添加数据
public void put(StuInfo stuInfo) {
//调用散列函数获取存储位置
int index = stuInfo.hashCode();
//添加元素
data[index] = stuInfo;
}
public StuInfo get(StuInfo stuInfo) {
return data[stuInfo.hashCode()];
}
public static void main(String[] args) {
// TODO Auto-generated method stub
StuInfo s1 = new StuInfo(16, 3);
StuInfo s2 = new StuInfo(17, 11);
StuInfo s3 = new StuInfo(18, 23);
StuInfo s4 = new StuInfo(19, 24);
StuInfo s5 = new StuInfo(20, 9);
HashTable ht = new HashTable();
ht.put(s1);
ht.put(s2);
ht.put(s3);
ht.put(s4);
ht.put(s5);
//想要获取的目标数据
StuInfo target = new StuInfo(18);
StuInfo info = ht.get(target);
System.out.println(info);
}
}
顶点 边
邻接表:每个顶点与其他顶点是否联通,上左为顶点,01标识是否联通
深度优先搜索算法 类似栈
广度优先搜索算法 类似队列
有向图:E={<1,2>,<2,1>}
无向图:E={(1,3),(1,2)}
若一个无向图
完全无向图:n(n-1)/2
完全有向图:n(n-1)
强联通:有顶点v,w,v到w和w到v都有路径
度:无向图依附于顶点v的边的条数
入度:有向图以顶点v为终点的有向边的数目
出度:有向图以顶点v为起点的有向边的数目
带全图/网:边上有数值
简单路径:路径系列中,顶点不重复
存储结构:
临接矩阵:
唯一
A[i][j]=1(是边,出度)/0(不是边)
A[i][j]=w(ij)(是边,放权值)/0或无穷(不是边)
无向图额邻接矩阵为对称矩阵,可压缩存储,空间复杂度为O(n^2)
无向图,邻接矩阵第i行/列非0元素个数是顶点i的度
有向图,邻接矩阵第i行非0元素个数是顶点i的出度,第i列非0元素个数是顶点i的入度
稠密图适合用邻接矩阵的存储表示
设图G的邻接矩阵为A,A^n[i][j]等于i到j长度为n的路径的数目
邻接表:
稀疏图用,不唯一
无向图存储空间为O(|v|+2|E|),有向图存储空间为O(|v|+|E|),邻接表查找俩顶点是否有边,效率低
十字链表:
容易找到出度入度,不唯一
顶点节点:data,firstin:以该顶点为弧头的第一个顶点,firstiout:以该顶点为弧尾的第一个顶点
弧节点:tailvex:指向弧尾,headvex:指向弧头,hlink:指向弧头相同的下一条弧,tlink:指向弧尾相同的下一条弧,info
邻接多重表:
无向图的另一种链式存储结构,容易求得边的各种信息,求俩个顶点是否有边麻烦
顶点节点:data,firstedge:第一条依附于该定点的边
边节点:mark:标记该边是否搜索过,ivex:该边依附的俩个顶点在图中的位置,ilink:指向下一条依附于ivex的边,jvex,jlink,info
广度优先搜索BFS
是二叉树的层次遍历算法的扩展
最坏情况下空间复杂度为O(|V|),邻接表时间复杂度O(|V|+|E|),邻接矩阵时间复杂度O(|V|^2)
深度优先算法DFS
基于邻接矩阵的遍历所得到的DFS序列和BFS序列是惟一的,基于邻接表的遍历所得到的DFS序列和BFS序列不唯一。
时间复杂度为O(|V|)
非强连通分量一次调用BFS(G,i)或DFS(G,i),无法访问到该连通分量的所有顶点
最小生成树:不是唯一的,权值之和唯一,边数为顶点数减一
prim算法:
时间复杂度为O(|V|^2)适用于求解边稠密的图的最小生成树
Kruskal算法:
时间复杂度为O(|E|log|E|),适用于边稀疏而顶点较多的图
Dijkstra算法:
基于贪心策略,时间复杂度为O(|V|^2),不允许边上带负权值
Floyd算法:
时间复杂度为O(|V|^3),允许带有负权值,不允许有负权值的边的回路,也适用于带权无向图
有向无环图(DAG图)
AOV网:有向图称为顶点表示活动的网络,若一个顶点有多个直接后继,拓扑排序结果不唯一。若顶点已经排在一个线性有序的序列中,每个顶点有唯一的直接后继前驱关系,则唯一
拓扑排序算法:
1.选择一个没有前驱的顶点并输出
2.从网中删除该顶点和所有以他为起点的有向边
3.重复到AOV为空或者网中不存在无前驱的顶点为止,后一种情况说明有向图中必然存在环
邻接表存储拓扑结构的时间复杂度为O(|V|+|E|),邻接矩阵存储为O(|V|^2)
AOE网:以边上的权值表示该活动的开销,用边表示活动的网络
关键活动:最大路径长度的路径
最早发生时间Ve(k):事件vk的最早发生时间决定了所有从vk开始的活动能够开工的最早时间
最迟发生时间Vl(k):再不推迟整个工程完成的前提下,保证它的后继事件vj在其最迟发生时间vl(j)能够发生时,该事件最迟必须发生的时间
活动ai最早开始时间e(i):活动弧的起点所表示的事件的最早发生时间
活动ai最迟开始时间e(i):活动弧的起点所表示的事件的最迟发生时间与该活动所需时间之差
求关键路径算法:
1.求ve()
2.求vl()
3.e()=该弧的起点的顶点的ve()
4.l(i)=该弧的终点的顶点的vl()减去该弧持续的时间
5.根据l(i)-e(i)=0的关键活动,得到关键路径
可以加快关键活动缩短工期,不能任意缩短,关键路径不唯一
package demo5;
/*
* 顶点
*/
public class Vertex {
private String value;
public boolean visited;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
@Override
public String toString() {
return "Vertex [value=" + value + "]";
}
public Vertex(String value) {
super();
this.value = value;
}
}
package demo5;
import java.util.Arrays;
/*
* 图
*/
public class Graph {
private Vertex[] vertex;
private int currentSize;
public int[][] adjMat;
//之前写过了栈的代码,直接拿来用
private MyStack stack = new MyStack();
//当前遍历的下标
private int currentIndex;
public Graph(int size) {
vertex = new Vertex[size];
adjMat = new int[size][size];
}
public void addFage(String v1,String v2) {
//找出俩个顶点的下标
int index1 = 0,index2 = 0;
for(int i = 0;i;i++) {
if(vertex[i].getValue().equals(v1)) {
index1 = i;
break;
}
}
for(int i = 0;i;i++) {
if(vertex[i].getValue().equals(v2)) {
index2 = i;
break;
}
}
adjMat[index1][index2] = 1;
adjMat[index2][index1] = 1;
}
//向图中加入一个顶点
public void addVertex(Vertex v) {
vertex[currentSize++] = v;
}
//深度优先搜索算法
public void dfs() {
//把第0个顶点标记为已访问状态
vertex[0].visited = true;
//把第0个顶点的下标
stack.push(0);
//打印顶点的值
System.out.println(vertex[0].getValue());
//遍历
//out:
while(!stack.isEmpty()) {
for(int i = currentIndex+1;i;i++) {
//如果和下一个遍历的元素是联通的
if(adjMat[currentIndex][i] == 1 && vertex[i].visited == false) {
//把下一个元素压入展中
stack.push(i);
vertex[i].visited = true;
System.out.println(vertex[i].getValue());
//currentIndex = i;
//continue out;
}
}
//遍历完发现没有相通的,则弹出栈顶元素
stack.pop();
//修改当前位置为栈顶元素的位置
if(!stack.isEmpty()) {
currentIndex = stack.peek();
}
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
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 g = new Graph(5);
g.addVertex(v1);
g.addVertex(v2);
g.addVertex(v3);
g.addVertex(v4);
g.addVertex(v5);
//增加边
g.addFage("A", "C");
g.addFage("B", "C");
g.addFage("B", "A");
g.addFage("B", "D");![在这里插入图片描述](https://img-blog.csdnimg.cn/3e5ad18cebcd41e9939a11e3ab926d85.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5a2z56u5,size_20,color_FFFFFF,t_70,g_se,x_16)
g.addFage("B", "E");
for(int[] a:g.adjMat) {
System.out.println(Arrays.toString(a));
}
//深度优先遍历
g.dfs();
}
}