数据结构于算法

1,递归

八皇后算法
package com.ws;

public class Queue8 {
int max=8;
//定义数组Array,保存皇后放置位置的结果,
int[] array =new int[max];
static int count = 0;
public static void main(String[] args) {
// TODO Auto-generated method stub
Queue8 queue8=new Queue8();
queue8.check(0);
System.out.println(count);
}
//编写一个方法,放置皇后
private void check(int n){
if(n == max){
print(); //n==8
return;
}
for (int i = 0; i < max; i++) {
//先把当前皇后n 放到该行的第1列;
array[n]=i;
//判断当放置第n个皇后到i列,是否冲突,
if(judge(n)){
//接着放n+1个皇后,开始递归
check(n+1);
}//如果冲突,继续执行array[n]=i;既将本皇后放置在本行的后移的位置   既循环i的++了,
}
}
//查看当我们放置皇后后,就检查皇后是否和前面放置的冲突
private boolean judge(int n){
for (int i = 0; i < n; i++) {
if(array[i]==array[n]||Math.abs(n-i)==Math.abs(array[n]-array[i])){ //先判断是否在同一列,和判断是否在同一斜线
return false;
}
}return true;
}
//写一个方法,可以将皇后摆放的位置输出
private void print(){
count++;
for(int i=0;i<array.length;i++){
System.out.println(array[i]+"");
}
System.out.println();
}
}

 

 

2,链表

①求单链表中有效节点的个数
     public int getLenth(HeroNode heroNode) {
  if(heroNode.next==null){
  return 0;
  }
  int lenth=0;
  HeroNode cur=head.next;
  while(cur!=null){
  lenth++;
  cur=cur.next;
  }
return lenth;
}
②查找单链表中的倒数第 k 个结点
 public static HeroNode findLastIndexNode(HeroNode heroNode, int index) {
  if(heroNode.next==null){
  return null;
  }
  int size=getLenth(heroNode);
  if(index<0||index>size){
  return null;
  }
  HeroNode cur=heroNode.next;
  for (int i = 0; i < size-index; i++) {
cur=cur.next;
}
  return cur;    
  }

 

③单链表的反转【腾讯面试题,有点难度】

image-20220301134202699

     public  void revesetList(HeroNode heroNode) {
  if(heroNode.next==null||heroNode.next.next==null){
  return;
  }
        //定义一个辅助的指针(变量),帮助我们遍历原来的链表
  HeroNode cur=heroNode.next;
  HeroNode next=null;
  HeroNode reserveHead = new HeroNode(0, " ", " ");
        //遍历原来的链表,每遍历一个节点,就将其取出,并放在新的链表 reverseHead 的最前
  while(cur!=null){
  next=cur.next;  //指向当前节点[cur]的下一个节点
  cur.next=reserveHead.next;
                reserveHead.next=cur;
  cur=next;
  }
heroNode.next=reserveHead.next;
}
 
 
④从尾到头打印单链表 【百度,要求方式 1:反向遍历 。 方式 2:Stack 栈】
 public static void revesePrint(HeroNode heroNode) {
if(heroNode.next==null){
return;
}
Stack<HeroNode> stack=new Stack<HeroNode>();
HeroNode cur =heroNode.next;
while(cur!=null){
stack.push(cur);
cur=cur.next;
}
while(stack.size()>0){
System.out.println(stack.pop());//stack的特点是先进后出,
}
}
⑤约瑟夫问题(小孩出圈)

image-20220302145520056

package com.ws;

public class JosephuLinkedList {
public static void main(String[] args) {
CircleSingleLinkedList cList=new CircleSingleLinkedList();
cList.addBoy(5);
cList.showBoy();
cList.countBoy(1, 5, 100);

}

}

class CircleSingleLinkedList {

private Boy first=null;

public void addBoy(int nums) {
if(nums<1){
System.out.println("输入的值不正确");
return;}
Boy curBoy=null;
for (int i = 0; i < nums; i++) {
Boy boy = new Boy(i);
// 如果是第一个小孩
if(i==1){
first=boy;
first.setNext(first);
curBoy=first;
}else {
curBoy.setNext(boy);
boy.setNext(first);
curBoy=boy;
}
}
}

//遍历循环当前列表
public void showBoy(){

if(first==null){
System.out.println("没有小孩");
return;
}
Boy curBoy=first;
while(true){
System.out.printf("小孩的编号为d%\n",curBoy.getNo());
if(curBoy.getNext()==first){
break;
}
curBoy=curBoy.getNext();
}
}

//根据用户输入输出小孩
  public void countBoy(int startNo,int CountNum,int nums){
  if(startNo<1||first==null||startNo>nums){
  System.out.println("参数输入有误");
  return;
  }
  Boy helper=first;
  while(true){
  if(helper.getNext()==first){
  break;
  }
  helper=helper.getNext();
  }
 
  //小孩报数前,先让头节点和辅助节点移动startNo-1次
  for (int i = 0; i < startNo-1; i++) {
  first=first.getNext();
  helper=helper.getNext();
 
}
  //当小孩报数时,同时移动m-1次;
  while(true){
  if(helper==first){
  break;
  }
  for (int j = 0; j < CountNum; j++) {
first=first.getNext();
helper=helper.getNext();
}
  System.out.printf("当前输出的小孩是d%\n",first.getNo());
  first=first.getNext();
  helper=helper.getNext();
  }
  System.out.printf("最后在圈中的小孩是的d%\n",first.getNo());
  }

}

//创造节点
class Boy{
privateint no;
private Boy next;

public Boy(int no){
this.no=no;
}

public int getNo() {
return no;
}

public void setNo(int no) {
this.no = no;
}

public Boy getNext() {
return next;
}

public void setNext(Boy next) {
this.next = next;
}
}
⑥一位数的计算器
package com.ws;
public class Calculator {

public static void main(String[] args) {
// 根据前面老师的思路
String expression="3+2*6-2";
//创建两个栈
ArrayStack2 numStack=new ArrayStack2(10);
ArrayStack2 operStack=new ArrayStack2(10);

int index=0;
int num1=0;
int num2=0;
int oper=0;
int res=0;
char ch=' ';//将每次扫描得到的char保存到ch;

while(true){
//依次得到expression每一个字符
ch=expression.substring(index, index+1).charAt(0);
//判断ch是什么;
if(operStack.isOper(ch)){
//如果是运算符,判断符号栈是否为空,
if(!operStack.isEmpty()){//比较运算符优先级
if(operStack.priority(ch)<=operStack.priority(operStack.peek())){
num1=numStack.pop();
num2=numStack.pop();
oper=operStack.pop();
res=numStack.cal(num1, num2, oper);
//将运算结果如数栈
numStack.push(res);
operStack.push(ch);
}else{
operStack.push(ch);
}
}else{
//直接入栈
operStack.push(ch);
}
}else{
numStack.push(ch-48);
}
//让index+1,并判断是否到最后
index++;
if(index>=expression.length()){
break;
}
}
while(true){
//如果符号栈为空计算结束;
if(operStack.isEmpty()){
break;
}else{
num1=numStack.pop();
num2=numStack.pop();
oper=operStack.pop();
res=numStack.cal(num1, num2, oper);
//将运算结果如数栈
numStack.push(res);
}
}
//将数栈最后一个数pop出来,

System.out.printf("表达式%s = %d",expression,numStack.pop());
}

}

//先创建一个栈

class ArrayStack2{
private int maxSize;
private int[] stack;
private int top=-1;
public ArrayStack2(int maxSize){
this.maxSize=maxSize;
stack = new int[maxSize];
}

//栈满
public boolean isFull(){
return top==maxSize-1;
}
//栈空
public boolean isEmpty() {
return top==-1;
}
//入栈

public void push(int value) {
if (isFull()) {
System.out.println("栈满");
return;
}
top++;
stack[top] = value;
}
//增加一个方法,可以看到栈顶的值
public int peek(){
return stack[top];
}
//出栈
public int pop() {
if(isEmpty()){
throw new RuntimeException("栈空");
}
int value=stack[top];
top--;
return value;
}
//遍历栈,遍历时需要从栈顶遍历数据
public void list() {
if(isEmpty()){
System.out.println("栈空");
return;
}
for(int i=top;i>=0;i--){
System.out.printf("stack[d%]=d%\n",i,stack[i]);
}
}

//定义优先级,数字越大级别越高
public int priority(int oper){
if(oper=='*'||oper=='/'){
return 1;
}else if(oper=='+'||oper=='-') {
return 0;
}else {
return -1;    //假定只有+-*/
}
}

//判断运算符,
public boolean isOper(char val){
return val =='+'||val=='-'||val=='*'||val=='/';
}

//计算方法
public int cal(int num1,int num2,int oper){
int res=0;
switch (oper) {
case '+':
res=num1+num2;
break;
case '-':
res=num2=num1;
break;
case '*':
res=num1*num2;
break;
case '/':
res=num2/num1;
break;

default:
break;
}
return res;

}

}

3,排序

①冒泡排序
package com.sort;

import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;

public class BubbleSort {
public static void main(String[] args) {
//int art[] = {3, 9, -1, 10, -2};

/* //第一躺排序
int temp=0;
boolean flag= false; //是否进行过交换,
for(int j=0;j<art.length;j++){
for (int i = 0; i < art.length-1-j; i++) {
if(art[i]>art[i+1]){
flag=true;
temp=art[i];
art[i]=art[i+1];
art[i+1]=temp;
}
  }
System.out.println("排序后的数组");
System.out.println(Arrays.toString(art));
if(!flag){
break;
}else {
flag=false;
}
}*/


int[] art= new int[80000];
for(int i=0;i<80000;i++){
art[i]=(int)(Math.random()*800000);
}
Date date1=new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date1str=simpleDateFormat.format(date1);
System.out.println("排序时间是="+date1str);

bubbleSort(art);
/* System.out.println("排序后");
System.out.println(Arrays.toString(art));*/

Date date2=new Date();
//SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date2str=simpleDateFormat.format(date2);
System.out.println("排序时间是="+date2str);

}
//抽取成工具类
public static void bubbleSort(int[] art){

//第一躺排序
int temp=0;
boolean flag= false; //是否进行过交换,
for(int j=0;j<art.length;j++){
for (int i = 0; i < art.length-1-j; i++) {
if(art[i]>art[i+1]){
flag=true;
temp=art[i];
art[i]=art[i+1];
art[i+1]=temp;
}
  }
if(!flag){
break;
}else {
flag=false;
}
}
}

}

 

②选择排序
package com.sort;


import java.util.Arrays;
//选择排序
public class SelectSort {

public static void main(String[] args) {
// TODO Auto-generated method stub
int [] arr = {20, 17, 36, 12, 7};
selectSort(arr);
System.out.println(Arrays.toString(arr));
}

public static void selectSort(int[] arr){

for (int i = 0; i < arr.length-1; i++) {
int minIndex = i;
int min=arr[i];

for (int j = i + 1; j < arr.length; j++) {
if(min>arr[j]){
//说明我们假定的不是最小的,
min = arr[j];
minIndex = j;
}
}
arr[minIndex]=arr[i];
arr[i]=min;

}
}
}

 

③插入排序
package com.sort;

import java.util.Arrays;
//插入排序
public class InsertSort {

public static void main(String[] args) {
// TODO Auto-generated method stub
Integer [] arr = {101,34,119,1};
insertSort(arr);
System.out.println(Arrays.toString(arr));
}

public static void insertSort(Integer[] arr){
//定义待插入的数
for (int j = 1; j < arr.length; j++) {
int insertValue = arr [j];
int insertInsert=j-1; //即arr[j]前面那个数的坐标
//insertInsert >= 0 保证在给insertValue 找插入位置时,不越界
//insertValue<arr[insertInsert] 待插入的数,还没有找到插入位置
//将arr[insertInsert] 后移
while(insertInsert>=0&&insertValue<arr[insertInsert]){
arr[insertInsert+1]=arr[insertInsert];
insertInsert--;
}
//当退出while循环时,说明插入位置找到 insertIndex + 1
arr[insertInsert+1] = insertValue;


}
}
}

 

④希尔排序
package com.sort;

import java.util.Arrays;


//希尔排序
public class ShellSort {

public static void main(String[] args) {
// TODO Auto-generated method stub
int[] arr={8,9,1,7,2,3,5,4,6,0};
shellSort(arr);
System.out.println(Arrays.toString(arr));
}
//移位法
public static void shellSort(int[] arr){
int temp=0;
//希尔排序的第一轮

for(int k=arr.length/2;k>0;k/=2){
for (int i = k; i < arr.length; i++) {
int j=i;
temp=arr[j];
//遍历各组中的所有元素(共五组。每组两个元素)步长5
if(arr[j]<arr[j-k]){
while(j-k>=0&&temp<arr[j-k]){
arr[j]=arr[j-k];
j=j-k;
}
arr[j]=temp;
}
}
}

}
//交换法
public static void shellSort2(int[] arr){
int temp=0;
//希尔排序的第一轮

for(int k=arr.length/2;k>0;k/=2){
for (int i = k; i < arr.length; i++) {
//遍历各组中的所有元素(共五组。每组两个元素)步长5
for (int j = i-k; j >= 0; j-=k) {
//如果当前元素大于加上步长后的元素,说明交换
if(arr[j]>arr[j+k]){
  temp=arr[j];
  arr[j]=arr[j+k];
  arr[j+k]=temp;
}
}
}
}
}
}

⑤快速排序

⑥归并排序
package com.sort;

import java.util.Arrays;

//归并排序
public class MergetSort {

public static void main(String[] args) {
// TODO Auto-generated method stub
int arr[]={8,4,5,7,1,3,6,2};
int temp[] =new int[arr.length];
mergSort(arr, 0, arr.length-1, temp);
System.out.println("归并排序后"+Arrays.toString(arr));
}

//拆分
public static void mergSort(int[] arr,int left,int right,int[] temp){
if(left<right){
int mid=(left+right)/2; //中间的索引
mergSort(arr, left, mid, temp); //向左递归分解
mergSort(arr, mid+1, right, temp); //向右递归分解
merge(arr,left,mid , right, temp); //合并
}
}
//合并
public static void merge(int[] arr,int left,int mid,int right,int[] temp){
int i=left; //左边有序数列的初始索引
int j=mid+1; //初始化j 右边有序数列的初始索引
int t=0; //指向temp数组的当前索引,
//一
//先把左右两边的数据填充到temp数组
//直到左右两边的有序序列,有一边处理完毕为止,
while(i<=mid&&j<=right){
if(arr[i]<arr[j]){
temp[t]=arr[i];
t+=1;
i+=1;
}else{
temp[t]=arr[j];
t+=1;
j+=1;
}
}
//二
//将剩下一边的数据依次填充到temp,
while(i<=mid){
temp[t]=arr[i];
t+=1;
i+=1;
}
while(j<=right){
temp[t]=arr[j];
t+=1;
j+=1;
}
//三
//将temp数组的元素拷贝到arr
//注意,并不是每次都拷贝所有
t=0;
int tempLeft = left;
while(tempLeft<=right){ //不好理解
arr[tempLeft]=temp[t];
t+=1;
tempLeft+=1;
}


}
}
⑦基数排序
  1. 基数排序是对传统桶排序的扩展,速度很快.

  2. 基数排序是经典的空间换时间的方式,占用内存很大, 当对海量数据排序时,容易造成 OutOfMemoryError 。

  3. 基数排序时稳定的。[注:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些 记录的相对次序保持不变,即在原序列中,r[i]=r[j],且 r[i]在 r[j]之前,而在排序后的序列中,r[i]仍在 r[j]之前, 则称这种排序算法是稳定的;否则称为不稳定的

  4. 有负数的数组,我们不用基数排序来进行排序, 如果要支持负数,参考: https://code.i-harness.com/zh-CN/q/e98fa9

package com.sort;

import java.util.Arrays;

//桶排序(基数排序)
public class RadixSort {

public static void main(String[] args) {
// TODO Auto-generated method stub
int [] arr={53,3,542,748,14,214};
redixSort(arr);
}
public static void redixSort(int[] arr ){

int max=arr[0];
for (int j = 0; j < arr.length; j++) {
if(arr[j]>max){
max=arr[j];
}
}
int maxLength=(max + "").length(); //巧妙取长度
int[][] bucket = new int[10][arr.length];
//为了记录每个桶中实际存放了多少个数据,我们定义一个一维数组来记录各个桶的每次放入的数据个数
//bucketElementCounts[0] 记录的就是bucket[0]桶的放入数据的个数
int[] bucketElementCounts=new int[10];

for (int j = 0, n=1;j < maxLength; j++,n*=10) {
//第一轮
for (int i = 0; i < arr.length; i++) {
int digitOfElement= arr[i]/n%10;
//放入到对应的桶中
bucket[digitOfElement][bucketElementCounts[digitOfElement]]=arr[i];
bucketElementCounts[digitOfElement]++;
}
//按照这个桶的顺序(取出数据)
int index=0;
//遍历每一个桶
for (int k = 0; k < bucketElementCounts.length; k++) {
//如果桶中有数据,才放入原数组,
if(bucketElementCounts[k]!=0){
//循环该桶即第K个桶,即第k个一维数组
for (int l = 0; l < bucketElementCounts[k]; l++) {
arr[index]=bucket[k][l];
index++;
}
} //
bucketElementCounts[k]=0;
}
System.out.println("第"+ (j+1) +"轮对各位的排序处理:"+Arrays.toString(arr));
}
}
}
⑧堆排序
package tree.thread;

import java.util.Arrays;

public class HeapSort {

public static void main(String[] args) {
// TODO Auto-generated method stub
int arr[] = {4,6,8,5,9};
heapSort(arr);

}
//编写一个堆排序的方法
public static void heapSort(int arr[]){
int temp;
System.out.println("堆排序");
for (int i = arr.length/2-1; i >=0; i--) {
adjustHeap(arr, i,arr.length);
}
for (int j = arr.length-1; j > 0 ;j--) { //巧妙
//交换
temp = arr[j];
arr[j] = arr[0];
arr[0]=temp;
adjustHeap(arr, 0, j);
}
System.out.println(Arrays.toString(arr));
}
//将一个数组(二叉树),调整成一个大顶堆
//
/**功能,完成将以i对应的非叶子结点的树调整成大顶堆
* @param arr
* @param i 表示索引。
* @param length 表示多少个元素调整 是在逐渐减少
*/
public static void adjustHeap(int arr[], int i, int length){
int temp =arr[i];
//开始调整
for (int k = i*2+1; k < length; k = k*2+1) {
if( k+1 < length && arr[k] < arr[k+1] ){//说明左子节点值小于右字节点的值
k++;
}
if(arr[k]>temp){ //如果子结点大于父结点
arr[i]=arr[k]; // 把较大的值赋给当前结点
i = k; // i指向 k ,继续循环比较
}else {
break; //!
}
}
//for循环结束后,我们已经将以i 为父结点的树的最大值,放在了最顶部。
arr[i] = temp;
}
}

 

⑨常用排序算法总结对比

image-20220307230818168

1,稳定:如果 a 原本在 b 前面,而 a=b,排序之后 a 仍然在 b 的前面;

2,不稳定:如果 a 原本在 b 的前面,而 a=b,排序之后 a 可能会出现在 b 的后面;

3,内排序:所有排序操作都在内存中完成;

4,外排序:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行;

5,时间复杂度: 一个算法执行所耗费的时间

6 ,空间复杂度:运行完一个程序所需内存的大小。

7,n: 数据规模

8 ,k: “桶”的个数

9,In-place: 不占用额外内存

10,Out-place: 占用额外内存

4,查找

二分查找(递归)

image-20220309135106973

非递归

package atguigu;

public class BinarySearchNoRecur {

public static void main(String[] args) {
// TODO Auto-generated method stub
int[] arr = {1,3,8,10,11,67,100};
int index = binarySearch(arr, 8);
System.out.println("index="+index);
}
//二分查找非递归实现
/**
* @param arr
* @param tarfet
* @return 返回对应下标
*/
public static int binarySearch(int[] arr,int tarfet){
int left =0;
int right = arr.length-1;
while (left <= right) {
int mid = (left + right)/2;
if(arr[mid] == tarfet){
return mid;
}else if (arr[mid] > tarfet) {
right = mid - 1; //需要向左边查找;
}else {
left = mid +1;
}
}return -1;
}
}

 

 

5,哈希表

image-20220309135033241

package com.sort;

import java.util.Scanner;

//哈希表的创建和遍历
public class HashTabDemo {

public static void main(String[] args) {
// TODO Auto-generated method stub
HashTab hashTab = new HashTab(7);
//写一个简单菜单
String key=" ";
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.println("add+添加雇员");
System.out.println("list+遍历");
System.out.println("find+查找雇员");
System.out.println("exit+添加雇员");
key = scanner.next();
switch (key) {
case "add":
System.out.println("输入ID:");
int id =scanner.nextInt();
System.out.println("输入名字");
String name=scanner.next();
//创建 雇员
Emp emp = new Emp(id, name);
hashTab.add(emp);
break;
case "list":
hashTab.list();
break;
case "find":
System.out.println("请输入要查找的id");
id=scanner.nextInt();
hashTab.findEmpById(id);
break;
case "exit":
scanner.close();
System.exit(0);
default:
break;
}
}
}

}
//创建哈希表HashTab 管理多条链表
class HashTab{
private int size; //表示有多少条链表
private EmpLinkedList[] empLinkedListsArray;
//构造器
public HashTab(int size){
this.size=size;
//初始化我们的链表
empLinkedListsArray=new EmpLinkedList[size];
//不要忘了分别初始化我们的每一个链表,不让可能引起空指针异常
for (int i = 0; i < size; i++) {
empLinkedListsArray[i] = new EmpLinkedList();
}
}

//添加雇员
public void add(Emp emp){
//根据员工的id得到该员工应当添加到那条链表
int empLinkedListNo=hashFun(emp.id);
//将emp添加到对应的链表中
empLinkedListsArray[empLinkedListNo].add(emp);
}

//遍历我们的哈希表
public void list(){
System.out.println("------------");
for (int i = 0; i < size; i++) {
empLinkedListsArray[i].list(i);
}
}

public void findEmpById(int id){
int empLinkedListNo=hashFun(id);
//将emp添加到对应的链表中
Emp emp = empLinkedListsArray[empLinkedListNo].findEmpById(id);
if(emp!=null){
System.out.printf("在第%d条链表中找到雇员id = %d\n",emp.id);
}else {
System.out.println("没有找到该雇员");
}
}

//散列函数
public int hashFun(int id) {
return id % size;
}
}

//雇员
class Emp{
public int id;
public String name;
public Emp next;
public Emp(int id, String name){
super();
this.id=id;
this.name=name;
}
}

class EmpLinkedList{
//头指针,执行第一个Emp,因此我们链表的head是直接指向第一个Emp
private Emp head; //默认为空
//添加雇员
//1,默认加到链表最后(id自增长的)
public void add(Emp emp){
if(head==null){
head=emp;
return;
}
//如果不是,使用一个辅助指针,帮助定位到最后
Emp curEmp=head;
while(true){
if(curEmp.next==null){
break;
}
curEmp=curEmp.next;
}
//退出时,直接将emp加入链表
curEmp.next=emp;
}

public void list(int no) {
System.out.println("=========");
if(head==null){
System.out.println("当前链表为空");
return;
}
System.out.println("当前链表为:");
Emp curEmp=head;
while (true) {
System.out.printf("=>id=%d name=%s\t",curEmp.id,curEmp.name);
if(curEmp.next==null){
break;
}
curEmp=curEmp.next;
}
}

//根据id查找雇员

public Emp findEmpById(int id) {
//判断链表是否为空
if(head==null){
System.out.println("链表为空");
return null;
}
Emp curEmp=head;
while (true) {
if(curEmp.id==id){
break;
}
if(curEmp.next==null){
curEmp=null;
break;
}
curEmp=curEmp.next;
}
return curEmp;
}
}

6, 树

①二叉树的创建与遍历
package tree;

public class BinaryTreeDemo {

public static void main(String[] args) {
// TODO Auto-generated method stub
//先创建一个二叉树
BinaryTree binaryTree=new BinaryTree();
HeroNode root = new HeroNode(1, "松江");
HeroNode node2 = new HeroNode(2, "吴用");
HeroNode node3 = new HeroNode(3, "卢俊义");
HeroNode node4 = new HeroNode(4, "林冲");
HeroNode node5 = new HeroNode(5, "关胜");
//先手动创建二叉树,之后在递归创建
root.setLeft(node2);
root.setRight(node3);
node3.setRight(node4);
node3.setLeft(node5);
binaryTree.setRoot(root);
//测试
System.out.println("前序遍历");//
binaryTree.preOreder();

System.out.println("前序遍历方式查找:");
HeroNode resNode=binaryTree.preOrderSearch(5);
if(resNode!=null){
System.out.printf("找到了信息为no=%d name=%s",resNode.getNo(),resNode.getName());
}else{
System.out.printf("没有找到信息为 no= %d 的英雄",5);
}

//测试删除结点
binaryTree.preOreder();
binaryTree.delete(3);
System.out.println("删除节点后");
binaryTree.preOreder();
}

}


class BinaryTree{

private HeroNode root;

public void setRoot(HeroNode root){
this.root = root;
}

//删除节点
public void delete(int no){
if(root!=null){
if(root.getNo() == no){
root = null;
}else {
//进行递归删除
root.delete(no);
}
}else {
System.out.println("空树");
}
}

//前序遍历
public void preOreder(){
if(this.root!=null){
this.root.preOrder();
}else{
System.out.println("二叉树为空,无法遍历");
}
}
//中序遍历
public void infixOreder(){
if(this.root!=null){
this.root.infixOrder();
}else{
System.out.println("二叉树为空,无法遍历");
}
}
//后序遍历
public void Oreder(){
if(this.root!=null){
this.root.Order();
}else{
System.out.println("二叉树为空,无法遍历");
}
}
//前序
public HeroNode preOrderSearch(int no){
if(root!=null){
return root.preOrdersearch(no);
}else{
return null;
}
}//中序
public HeroNode infixOrderSearch(int no){
if(root!=null){
return root.infixOrderSearch(no);
}else{
return null;
}
}//后续
public HeroNode postOrderSearch(int no){
if(root!=null){
return root.postOrderSearch(no);
}else{
return null;
}
}
}

class HeroNode{
private int no;
private String name;
private HeroNode left;
private HeroNode right;
public HeroNode(int no,String name){
this.name=name;
this.no=no;
}
/**
* @return the no
*/
public int getNo() {
return no;
}
/**
* @param no the no to set
*/
public void setNo(int no) {
this.no = no;
}
/**
* @return the name
*/
public String getName() {
return name;
}
/**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
/**
* @return the left
*/
public HeroNode getLeft() {
return left;
}
/**
* @param left the left to set
*/
public void setLeft(HeroNode left) {
this.left = left;
}
/**
* @return the right
*/
public HeroNode getRight() {
return right;
}
/**
* @param right the right to set
*/
public void setRight(HeroNode right) {
this.right = right;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "HeroNode [no=" + no + ", name=" + name + "]";
}
//前序遍历
public void preOrder(){
System.out.println(this);   //先输出父节点
//递归向左子树前序遍历
if(this.left!=null){
this.left.preOrder();
}
//递归向右子数遍历
if(this.right!=null){
this.right.preOrder();
}
}
//中须遍历
public void infixOrder(){
if(this.left!=null){
this.left.infixOrder();
}
System.out.println(this);
if(this.right!=null){
this.right.infixOrder();
}
}
//后续遍历
public void Order(){
if(this.right!=null){
this.right.Order();
}
if(this.left!=null){
this.left.Order();
}
System.out.println(this);

}
//递归删除结点
public void delete(int no){
if(this.left!=null&&this.left.no==no){
this.left=null;
return;
}
if(this.right!=null&&this.right.no==no){
this.right=null;
return ;
}
if(this.left!=null){
this.left.delete(no);
}
if(this.right!=null){
this.right.delete(no);
}
}

//前序遍历查找
public HeroNode preOrdersearch(int no){
//先比较当前节点是不是
if(this.no==no){
return this;
}
HeroNode resNode=null;
if(this.left!=null){
resNode  = this.left.preOrdersearch(no);
}
if(resNode!= null){  //说明找到了
return resNode;
}
if(this.right!=null){
resNode=this.right.preOrdersearch(no);
}
return resNode;
}

//中序遍历查找
public HeroNode infixOrderSearch(int no){
HeroNode reNode=null;
if(this.left!=null){
reNode=this.left.infixOrderSearch(no);
}
if(reNode!=null){
return reNode;
}
if(this.no==no){
return this;
}
if(this.right!=null){
reNode=this.right.infixOrderSearch(no);
}
    returnreNode;
}
//后续查找
public HeroNode postOrderSearch(int no){
HeroNode resNode = null;
if(this.left!=null){
resNode=this.left.postOrderSearch(no);
}
if(resNode!=null){
return resNode;
}
if(this.right!=null){
resNode = this.right.postOrderSearch(no);
}
if(resNode!=null){
return resNode;
}
if(this.no==no){
return this;
}
return resNode;
}
}
②顺序存储二叉树5

特点: 应用实例:堆排序

  1. 顺序二叉树通常只考虑完全二叉树

  2. 第 n 个元素的左子节点为 2 * n + 1

  3. 第 n 个元素的右子节点为 2 * n + 2

  4. 第 n 个元素的父节点为 (n-1) / 2

  5. n : 表示二叉树中的第几个元素(按 0 开始编号如图所示)

package tree;

public class ArrayBinaryTreeDemo {

public static void main(String[] args) {
// TODO Auto-generated method stub
int[] arr={1,2,3,4,5,6,7};
ArrBiaryTree arrBiaryTree= new ArrBiaryTree(arr);
arrBiaryTree.preOrder();
}
//编写一个类先顺序存储二叉树的遍历
}
class ArrBiaryTree{
private int[] arr; //存储数据结点的数组

public ArrBiaryTree(int[] arr){
this.arr=arr;
}
//重载preOrder方法
public void preOrder(){
this.preOrder(0);
}
//编写一个方法 完成顺序存储二叉树的前序遍历
public void preOrder(int index){
//如果数组为空,或者arr.length=0;
if(arr == null||arr.length==0){
System.out.println("数组为空,不能按照二叉树的前序遍历");
}
//向左递归遍历
if(index*2+1<arr.length){
preOrder(2*index+1);
} //向右递归
if(index*2+2<arr.length){
preOrder(2*index+2);
}
//输出当前这个元素
System.out.println(arr[index]);

}
}
③ 线索化二叉树
  1. n 个结点的二叉链表中含有 n+1 【公式 2n-(n-1)=n+1】 个空指针域。利用二叉链表中的空指针域,存放指向 该结点在某种遍历次序下的前驱和后继结点的指针(这种附加的指针称为"线索")

  2. 这种加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树(Threaded BinaryTree)。根据线索性质 的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树三种

  3. 一个结点的前一个结点,称为前驱结点

  4. 一个结点的后一个结点,称为后继结点

package tree.thread;
public class ThreadeBinaryTreeDemo {

public static void main(String[] args) {
// TODO Auto-generated method stub
HeroNode root=new HeroNode(1, "tom");
HeroNode note2=new HeroNode(3, "jack");
HeroNode note3=new HeroNode(6, "smith");
HeroNode note4=new HeroNode(8, "mary");
HeroNode note5=new HeroNode(10, "king");
HeroNode note6=new HeroNode(14, "dim");

root.setLeft(note2);
root.setRight(note3);
note2.setLeft(note4);
note2.setRight(note5);
note3.setLeft(note6);
//测试中序遍历的线索化
BinaryTree binaryTree = new BinaryTree();
binaryTree.setRoot(root);
binaryTree.threadedNodes();

//测试
/* HeroNode leftNode = note5.getLeft();
System.out.println("十号节点的前驱结点是="+leftNode);*/
binaryTree.threadedList();
}

}
//创建ThreadedBiaryTree实现了线索化的二叉树

class BinaryTree{

private HeroNode root;
//在递归进行线索化时,pre 总是保留前一个结点
private HeroNode pre=null;

public void setRoot(HeroNode root){
this.root = root;
}
//重载线索化方法
public void threadedNodes(){
this.threadedNodes(root);
}

//遍历线索化二叉树
public void threadedList(){
HeroNode node = root;
while (node !=null) {
//循环找到leftTyp==1的结点,
//后面随着遍历而变化,因为当leftType==1时,说明该节点是按照线索化
while (node.getLeftType()==0) {
node=node.getLeft();
}//打印处理后的有序结点
System.out.println(node);
while (node.getRightType()==1) {
node = node.getRight();
System.out.println(node);
/*System.out.println("我是pre:"+pre);*/
}
//替换遍历的结点
node = node.getRight();
}
}

//编写对二叉树进行线索化的方法
public void threadedNodes(HeroNode node) {
if(node==null){
return;
}

//一 先线索化左子树
threadedNodes(node.getLeft());
//二 线索化当前结点


//处理当前节点的前驱结点
//以8号结点来看,8.left=null,
if(node.getLeft()==null){
node.setLeft(pre);
//修改当前节点的左指针类型
node.setLeftType(1);
}
//处理后继结点
if(pre != null && pre.getRight() == null){
//让前驱结点的右指针指向当前结点
pre.setRight(node);
pre.setRightType(1);
}
//每处理一个结点,让这个结点是下个结点的前驱结点
pre = node;
//三 线索化右子树
threadedNodes(node.getRight());
}

//删除节点
public void delete(int no){
if(root!=null){
if(root.getNo() == no){
root = null;
}else {
//进行递归删除
root.delete(no);
}
}else {
System.out.println("空树");
}
}

//前序遍历
public void preOreder(){
if(this.root!=null){
this.root.preOrder();
}else{
System.out.println("二叉树为空,无法遍历");
}
}
//中序遍历
public void infixOreder(){
if(this.root!=null){
this.root.infixOrder();
}else{
System.out.println("二叉树为空,无法遍历");
}
}
//后序遍历
public void Oreder(){
if(this.root!=null){
this.root.Order();
}else{
System.out.println("二叉树为空,无法遍历");
}
}
//前序
public HeroNode preOrderSearch(int no){
if(root!=null){
return root.preOrdersearch(no);
}else{
return null;
}
}//中序
public HeroNode infixOrderSearch(int no){
if(root!=null){
return root.infixOrderSearch(no);
}else{
return null;
}
}//后续
public HeroNode postOrderSearch(int no){
if(root!=null){
return root.postOrderSearch(no);
}else{
return null;
}
}
}



//创建HeroNode结点
class HeroNode{
private int no;
private String name;
private HeroNode left;
private HeroNode right;

//如果leftType==0,表示指向的是左指数,如果是1则表示指向前驱结点
//如果rightType==0,表示指向的是右指数,如果是1则表示指向后继结点
private int leftType;
private int rightType;
/**
* @return the leftType
*/
public int getLeftType() {
return leftType;
}
/**
* @param leftType the leftType to set
*/
public void setLeftType(int leftType) {
this.leftType = leftType;
}
/**
* @return the rightType
*/
public int getRightType() {
return rightType;
}
/**
* @param rightType the rightType to set
*/
public void setRightType(int rightType) {
this.rightType = rightType;
}
public HeroNode(int no,String name){
this.name=name;
this.no=no;
}
/**
* @return the no
*/
public int getNo() {
return no;
}
/**
* @param no the no to set
*/
public void setNo(int no) {
this.no = no;
}
/**
* @return the name
*/
public String getName() {
return name;
}
/**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
/**
* @return the left
*/
public HeroNode getLeft() {
return left;
}
/**
* @param left the left to set
*/
public void setLeft(HeroNode left) {
this.left = left;
}
/**
* @return the right
*/
public HeroNode getRight() {
return right;
}
/**
* @param right the right to set
*/
public void setRight(HeroNode right) {
this.right = right;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "HeroNode [no=" + no + ", name=" + name + "]";
}
//前序遍历
public void preOrder(){
System.out.println(this); //先输出父节点
//递归向左子树前序遍历
if(this.left!=null){
this.left.preOrder();
}
//递归向右子数遍历
if(this.right!=null){
this.right.preOrder();
}
}
//中须遍历
public void infixOrder(){
if(this.left!=null){
this.left.infixOrder();
}
System.out.println(this);
if(this.right!=null){
this.right.infixOrder();
}
}
//后续遍历
public void Order(){
if(this.right!=null){
this.right.Order();
}
if(this.left!=null){
this.left.Order();
}
System.out.println(this);

}
//递归删除结点
public void delete(int no){
if(this.left!=null&&this.left.no==no){
if(this.left.left==null&&this.left.right!=null){
this.left=this.left.right;
}
else if(this.left.left==null&&this.left.right==null) {
this.left=null;
}else {
this.left=this.left.left;
}
return;
}
if(this.right!=null&&this.right.no==no){
if(this.right.left==null&&this.right.right!=null){
this.right=this.right.right;
}else if (this.right.left==null&&this.right.right==null) {
this.right=null;
}else {
this.right=this.right.left;
}

return ;
}
if(this.left!=null){
this.left.delete(no);
}
if(this.right!=null){
this.right.delete(no);
}
}


//前序遍历查找
public HeroNode preOrdersearch(int no){
//先比较当前节点是不是
if(this.no==no){
return this;
}
HeroNode resNode=null;
if(this.left!=null){
resNode = this.left.preOrdersearch(no);
}
if(resNode!= null){ //说明找到了
return resNode;
}
if(this.right!=null){
resNode=this.right.preOrdersearch(no);
}
return resNode;
}

//中序遍历查找
public HeroNode infixOrderSearch(int no){
HeroNode reNode=null;
if(this.left!=null){
reNode=this.left.infixOrderSearch(no);
}
if(reNode!=null){
return reNode;
}
if(this.no==no){
return this;
}
if(this.right!=null){
reNode=this.right.infixOrderSearch(no);
}
return reNode;
}
//后续查找
public HeroNode postOrderSearch(int no){
HeroNode resNode = null;
if(this.left!=null){
resNode=this.left.postOrderSearch(no);
}
if(resNode!=null){
return resNode;
}
if(this.right!=null){
resNode = this.right.postOrderSearch(no);
}
if(resNode!=null){
return resNode;
}
if(this.no==no){
return this;
}
return resNode;
}
}
④赫夫曼数
  1. 路径和路径长度:在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路,称为路径。通路中分支的数目称为路径长度。若规定根结点的层数为 1,则从根结点到第 L 层结点的路径长度为 L-1

  2. 结点的权及带权路径长度:若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。结 点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的乘积

  3. 树的带权路径长度:树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为 WPL(weighted path length) ,权值越大的结点离根结点越近的二叉树才是最优二叉树。

  4. WPL 最小的就是赫夫曼

package huffmantree;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class HuffmanTree {

public static void main(String[] args) {
// TODO Auto-generated method stub
int arr[]={13,7,8,3,29,6,1};
Node root= createHuffmanTree(arr);
preOrder(root);
}
//编写一个前序遍历的方法
public static void preOrder(Node root){
if(root!=null){
root.preOrder();
}else{
System.out.println("是空树,不能遍历");
}
}
//创建赫夫曼树的方法
/**
* @param arr 需要创建成赫夫曼树的数组
* @return 返回数组的root;
*/
public static Node createHuffmanTree(int[] arr){
List<Node> nodes=new ArrayList<Node>();
for (int value : arr) {
nodes.add(new Node(value));
}
while (nodes.size()>1) {
Collections.sort(nodes);
System.out.println("nodes = "+nodes);
int i=1;
//取出权值最小的两个二叉树结点
Node left = nodes.get(0);
Node right = nodes.get(1);
//构件一棵新的二叉树
Node parent = new Node(left.value+right.value);
parent.left=left;
parent.right=right;
//从ArrayList中删除处理过的二叉树
nodes.remove(left);
nodes.remove(right);
//将parent加入到ArrayList
nodes.add(parent);
Collections.sort(nodes);
System.out.println("第"+i+"次处理后:"+nodes);
i++;
}
return nodes.get(0);
}

}
//创建节点类
//为了让Node对象持续排序Collections集合排序
//让Node实现Comparable接口
class Node implements Comparable<Node>{
int value; //结点权值
Node left; //左子结点
Node right; //右子结点

public void preOrder(){
System.out.println(this);
if(this.left!=null){
this.left.preOrder();
}
if(this.right!=null){
this.right.preOrder();
}
}
public Node(int value){
this.value=value;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "Node [value=" + value + "]";
}

@Override
public int compareTo(Node o) {
// TODO Auto-generated method stub
return this.value-o.value;
}
}
⑤赫夫曼编码及其实际作用(压缩与解压缩)
package huffmantree;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
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 content = "i like like like java do you like a java";
byte[] contenBytes = content.getBytes();

byte[] huffmanCodeBytes = huffmanzip(contenBytes);
System.out.println(Arrays.toString(huffmanCodeBytes) + "长度 = " + huffmanCodeBytes.length);
//测试一把ByteToBitString 方法
//System.out.println(byteToBitString(false, (byte)-1));

byte[] sourcBytes = decode(huffmanCodes, huffmanCodeBytes);
System.out.println("原来的字符串 = "+new String(sourcBytes));
*/

String zipFile = "d://dst.zip";
String dstFile="d://1.jpg";
unZipFile(zipFile, dstFile);
System.out.println("解压成功");
/*
zipFile(srcFile, dsFile);
System.out.println("压缩文件Ok");*/
/*
* List<Node> nodes = getNodes(contenBytes); System.out.println(nodes);
* System.out.println("赫夫曼树:"); Node huffmanTreeRoot =
* createHuffmanTree(nodes); System.out.println("前序遍历");
* huffmanTreeRoot.preOrder(); //测试是否生成了赫夫曼编码
*
* Map<Byte,String> huffmanCodes = getCode(huffmanTreeRoot);
* System.out.println("生成的赫夫曼编码表是:"+huffmanCodes); //测试生成
* huffmanCodesBytes byte[] huffmanCodesBytes = zip(contenBytes,
* huffmanCodes);
* System.out.println("huffmanCodesBytes:"+Arrays.toString(
* huffmanCodesBytes));
*/
}
//编写一个方法对文件进行压缩
/**
* @param srcFile 你传入的希望压缩的文件的全路径
* @param dstFile 我们压缩后将压缩文件放在那个目录
*/
public static void zipFile(String srcFile,String dstFile){
//创建输出流
OutputStream os = null;
ObjectOutputStream oos=null;
//创建文件的输入流
FileInputStream is =null;
try {
is = new FileInputStream(srcFile);
//创建一个和源文件一样的byte[],
byte[] b  = new byte[is.available()];
//读取文件
is.read(b);
//获取到文件对应的编码表,直接对源文件压缩
byte[] huffmanBytes = huffmanzip(b);
  //创建文件输出流,存放压缩文件
os = new FileOutputStream(dstFile);
   //创建一个和文件输出流关联的ObjectOutputStream
oos= new ObjectOutputStream(os);
//把赫夫曼编码后的字节数组写入压缩文件
oos.writeObject(huffmanBytes);  //我们是把
//这里我们以对象流的方式写入赫夫曼的编码,为了以后我们恢复源文件时使用,
//注意一定要把赫夫曼编码写入压缩文件
oos.writeObject(huffmanCodes);
} catch (Exception e) {
// TODO: handle exception
System.out.println(e.getMessage());
}finally {
try {
is.close();
os.close();
oos.close();
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
//编写一个方法,对压缩文件解压
/**
* @param zipFile 准备解压的文件
* @param dstFile 将文件解压到那个路径
*/
public static void unZipFile(String zipFile,String dstFile){
//定义文件输入流
InputStream is = null;
// 定义一个对象输入流
ObjectInputStream ois =null;
//定义文件的输出流
OutputStream os =null;
try {
//创建文件输入流
is = new FileInputStream(zipFile);
//创建一个和 is关联的对象输入流
ois = new ObjectInputStream(is);
//读取Byte数组,
byte[] huffmanbytes= (byte[]) ois.readObject();
//读取赫夫曼编码表
Map<Byte, String> huffmanCodes = (Map<Byte, String>) ois.readObject();

//解码
byte[] bytes =decode(huffmanCodes, huffmanbytes);
os = new FileOutputStream(dstFile);
//写数据到dstFile文件
os.write(bytes);
} catch (Exception e) {
// TODO: handle exception
}finally {
try {
os.close();
ois.close();
is.close();
} catch (Exception e2) {
// TODO: handle exception
}
}
}
/**
*   编写一方法,完成对压缩数据的解码
 @param huffmanCodes 赫夫曼编码表 map
* @param huffmanBytes 赫夫曼编码得到的字节数组
* @return 就是原来的字符串对应的数组
*/
private static byte[] decode(Map<Byte, String> huffmanCodes, byte[] huffmanBytes) {
//1,先得到 huffmanBytes 对应的二进制字符串
StringBuilder stringBuilder = new StringBuilder();
//将byte转成二进制的字符串
for (int i = 0; i < huffmanBytes.length; i++) {

byte b  =huffmanBytes[i];
boolean flag = (i == huffmanBytes.length-1);
 
stringBuilder.append(byteToBitString(!flag, b));
//System.out.println("赫夫曼树的编码是:"+stringBuilder.toString());
}
//把字符串按照指定的赫夫曼编码进行解码
//把赫夫曼编码表进行调换,
Map<String,Byte> map =new HashMap<String,Byte>();
for (Map.Entry<Byte, String> entry : huffmanCodes.entrySet()) {
map.put(entry.getValue(), entry.getKey());
}
System.out.println("map"+ map);
//创建要给集合,
List<Byte> list = new ArrayList<>();
//i 可以理解成一个索引 扫描 stringBuilder
for (int i = 0; i < stringBuilder.length(); ) {
int count = 1;   //小的计数器
boolean flag = true;
Byte b = null;
while (flag) {
//取出一个字符 '1'
String key =stringBuilder.substring(i,i+count);   //i 不动,让count移动,指定匹配到一个字符
b=map.get(key);
if(b==null){
count++;
}else {
flag=false;
}
}list.add(b);
i += count;
}
byte b[] = new byte[list.size()];
for (int i = 0; i < b.length; i++) {
b[i] = list.get(i);
}
             return b;
}
/**
       //将byte转成一个二进制字符串
* @param flag 如果是true表示需要补高位  
* @param b 传入的byte值
* @return 是该 b 对应的二进制字符串(按补码形式返回)
*/
private static String byteToBitString(boolean flag,byte b){
//使用temp变量保存
int temp = b;
//如果是正数,存在补高位的问题
if(flag){
temp |= 256;
}
  //按位与 1 0000 0000 |   0000 0001   => 1 0000 0001    
String str = Integer.toBinaryString(temp); //返回的是temp二进制的补码
if(flag){
return str.substring(str.length()-8);
}else {
return str;
}
   
}
// 使用一个方法,将前面的方法封装起来,方便调用

/**
* @param bytes
*           原始的字符串对应数组
* @return 是经过赫夫曼编码处理的字节数组(压缩后的数组)
*/
private static byte[] huffmanzip(byte[] bytes) {
List<Node> nodes = getNodes(bytes);
// 根据nodes创建赫夫曼树
Node huffmanTreeRoot = createHuffmanTree(nodes);
// 根据赫夫曼树生成对应的赫夫曼编码
Map<Byte, String> huffmanCodes = getCode(huffmanTreeRoot);
// 根据生成的赫夫曼编码 得到压缩后的赫夫曼字节数组
byte[] huffmanCodesBytes = zip(bytes, huffmanCodes);
return huffmanCodesBytes;
}

// 编写一个方法,将字符串对应的byte[] 数组,通过生成的赫夫曼编码处理后的byte[]
/**
* @param contenBytes
*           这时原始的字符串对应的 byte[]
* @param huffmanCodes
*           生成的赫夫曼编码 map
* @return 返回赫夫曼编码处理后的 byte[]
*/
private static byte[] zip(byte[] contenBytes, Map<Byte, String> huffmanCodes) {
// 1.先利用赫夫曼编码表,将 bytes 转成赫夫曼编码对应的字符串,
StringBuilder stringBuilder = new StringBuilder();
// 遍历bytes数组
for (Byte b : contenBytes) {
stringBuilder.append(huffmanCodes.get(b));
}
int len;
if (stringBuilder.length() % 8 == 0) {
len = stringBuilder.length() / 8;
} else {
len = stringBuilder.length() / 8 + 1;
}
// 创建一个存储压缩后的byte数组
byte[] huffmanCodeBytes = new byte[len];
int index = 0;
for (int i = 0; i < stringBuilder.length(); i += 8) {
String styByte;
if (i + 8 > stringBuilder.length()) { // 不够8位
styByte = stringBuilder.substring(i);
} else {
styByte = stringBuilder.substring(i, i + 8);
}
// 将styByte转成一个byte,放入到huffmanCodeBytes
huffmanCodeBytes[index] = (byte) Integer.parseInt(styByte, 2);
index++;

}
// System.out.println("");
return huffmanCodeBytes;
}

// 前序遍历
// 1,将赫夫曼树转换为赫夫曼编码,存放在Map<Byte,String>形式 :{32= 01, 97= 100, 100= 11000, 117=
// 11001, 101= 1110, 118= 11011, 105= 101, 121= 11010, 106= 0010, 107= 1111,
// 108= 000, 111= 0011}
static Map<Byte, String> huffmanCodes = new HashMap<Byte, String>();
// 2,将生成的赫夫曼编码表示,需要去拼接路径,定义一个StringBuilder,储存某个叶子结点的路径
static StringBuilder stringBuilder = new StringBuilder();

// 为了调用方便,重载getCode
private static Map<Byte, String> getCode(Node root) {
if (root == null) {
return null;
}
getCode(root.left, "0", stringBuilder);
getCode(root.right, "1", stringBuilder);
return huffmanCodes;
}

/**
* 功能:将传入的 node 结点的所有叶子结点的赫夫曼编码得到,并放入到 huffmanCodes 集合
*
* @param node
*           传入结点
* @param code
*           路径: 左子结点是 0, 右子结点 1
* @param stringBuilder
*           用于拼接路径
*/
private static void getCode(Node node, String code, StringBuilder stringBuilder) {
StringBuilder stringBuilder2 = new StringBuilder(stringBuilder);
// 将code加入stringBuilder2
stringBuilder2.append(code);
if (node != null) { // 如果node == null不处理
// 判断当前node 是叶子结点还是非叶子结点
if (node.data == null) {
// 递归处理
// 向左
getCode(node.left, "0", stringBuilder2);
getCode(node.right, "1", stringBuilder2);
} else { // 说明是叶子结点
// 表示找到了某个叶子结点的最后,
huffmanCodes.put(node.data, stringBuilder2.toString());
}
}
}

// bytes 接收字节数组
// 返回值就是List形式,
private static List<Node> getNodes(byte[] bytes) {
// 创建一个ArrayList.
ArrayList<Node> nodes = new ArrayList<Node>();
// 遍历bytes ,统计每一个字符出现的次数
Map<Byte, Integer> counts = new HashMap<>();
for (byte b : bytes) {
Integer count = counts.get(b);
if (count == null) { // Map还没有这个字符数据
counts.put(b, 1);
} else {
counts.put(b, count + 1);
}
}
// 把每一个键值对转化为一个Node对象,并加入到nodes集合,
// 遍历Map
for (Map.Entry<Byte, Integer> entry : counts.entrySet()) {
nodes.add(new Node(entry.getKey(), entry.getValue()));
}
return nodes;
}

// 同过List 生成一个赫夫曼树
public static Node createHuffmanTree(List<Node> nodes) {
while (nodes.size() > 1) {
// 排序 从大到小
Collections.sort(nodes);
// 取出第一颗最小的二叉树
Node leftNode = nodes.get(0);
Node rightNode = nodes.get(1);
// 创建一个新的二叉树,它的根节点没有data 只有权值
Node parent = new Node(null, leftNode.weight + rightNode.weight);
parent.left = leftNode;
parent.right = rightNode;
// 将处理过的二叉树移除
nodes.remove(leftNode);
nodes.remove(rightNode);
nodes.add(parent);
}
// 根节点就是
return nodes.get(0);
}

}
class Node implements Comparable<Node>{
Byte data;   //存放数值
int weight; //权值 表示字符出现的次数
Node left;
Node right;
public Node(Byte data,int weight) {
this.data=data;
this.weight=weight;
}
@Override
public int compareTo(Node o) {
// 从小到大排序
return this.weight-o.weight;
}
/*  
*/
@Override
public String toString() {
return "Node [data=" + data + ", weight=" + weight + "]";
}
//前序遍历
public void preOrder(){
System.out.println(this);
if(this.left!=null){
this.left.preOrder();
}
if(this.right!=null){
this.right.preOrder();
}
}
}
⑥ 二叉排序树的删除

第一种情况: 删除叶子节点 (比如:2, 5, 9, 12)

思路

(1) 需求先去找到要删除的结点 targetNode

(2) 找到 targetNode 的 父结点 parent

(3) 确定 targetNode 是 parent 的左子结点 还是右子结点

(4) 根据前面的情况来对应删除 左子结点 parent.left = null 右子结点 parent.right = null;

第二种情况: 删除只有一颗子树的节点 

比如 1 思路

(1) 需求先去找到要删除的结点 targetNode

(2) 找到 targetNode 的 父结点 parent

(3) 确定 targetNode 的子结点是左子结点还是右子结点

(4) targetNode 是 parent 的左子结点还是右子结点

(5) 如果 targetNode 有左子结点

5. 1 如果 targetNode 是 parent 的左子结点 parent.left = targetNode.left;

5.2 如果 targetNode 是 parent 的右子结点 parent.right = targetNode.left;

(6) 如果 targetNode 有右子结点

6.1 如果 targetNode 是 parent 的左子结点 parent.left = targetNode.right;

6.2 如果 targetNode 是 parent 的右子结点 parent.right = targetNode.right

情况三 : 删除有两颗子树的节点. (比如:7, 3,10 )

思路 (1) 需求先去找到要删除的结点 targetNode

(2) 找到 targetNode 的 父结点 parent

(3) 从 targetNode 的右子树找到最小的结点

(4) 用一个临时变量,将 最小结点的值保存 temp = 11

(5) 删除该最小结点

(6) targetNode.value = temp

package binaryTree;
public class BianySortTreeDemo {

public static void main(String[] args) {
// TODO Auto-generated method stub
int[] arr = {7,3,10,12,5,1,9,2};
BinarySortTree binarySortTree= new BinarySortTree();
for (int i = 0; i < arr.length; i++) {
binarySortTree.add(new Node(arr[i]));
}
System.out.println("中序遍历二叉排序树:");
binarySortTree.infixOrder();
binarySortTree.delNode(2);
System.out.println("删除后:");
binarySortTree.infixOrder();
}
}
//创建二叉树
class BinarySortTree{
private Node root;
//查找要删除的结点
public Node search(int value){
if(root == null){
return null;
}else {
return root.search(value);
}
}
//查找父节点
public Node searchParent(int value){
if(root == null){
return null;
}else {
return root.searchParent(value);
}
}
//编写方法
// 2,删除
/**
* @param node 传入的结点(当作一个二叉树的根结点)
* @return 返回的是以Node 为根节点的二叉排序树的最小结点的值
*/
public int delRightTree(Node node){
Node target = node;
//循环的查找左节点,就会找到最小值
while(target.left != null){
target = target.left;
}
//这时 target就指向了最小节点
//删除最小结点
delNode(target.value);
return target.value;
}
//删除结点
public void delNode(int value){
Node targetNode = null;
if(root == null){
return;
}else { //需要找到要删除的结点 targetNode
targetNode = search(value);
}
//如果我们发现这颗二叉树只有一个结点(头结点)
if(root.left == null && root.right ==null){
root = null;
return;
}
//去查找targetNode的父结点
Node parent = searchParent(value);
if(targetNode.left==null && targetNode.right == null){ //如果要删除的结点是叶子结点,
//判断targetNode是父节点的左子节点还是右子结点,
if(parent.left != null && parent.left.value == value){ //是左结点
parent.left=null;
}else if (parent.right !=null && parent.right.value == value) { //是右结点
parent.right=null;
}
}else if (targetNode.left != null && targetNode.right != null) { //有两个子树结点
int minVal = delRightTree(targetNode.right);
targetNode.value = minVal;
}else { //只有一棵子树
//如果要删除的结点有左子结点
if(targetNode.left != null){
if(parent!=null){
if(parent.left.value == value){ //targetNode是parent的左子结点
parent.left=targetNode.left;
}else { //targetNode 是parent的右子结点
parent.right=targetNode.left;
}
}else {
root = targetNode.left;
}
}
else{ //要删除的树有右子结点
if(parent != null){
if(parent.left.value == value){ //targetNode是parent的左子结点
parent.left = targetNode.right;}
else { //如果targetNode是parent的右子结点
parent.right = targetNode.right;
}
}else {
root = targetNode.right;
}
}
}
}

public void add(Node node){
if(root==null){
root = node;
}else {
root.add(node);
}
}
public void infixOrder(){
if(root!=null){
root.infixOrder();
}else {
System.out.println("二叉树为空,不能遍历");
}
}
}
//创建Node结点
class Node{
int value;
Node left;
Node right;
public Node(int value) {
this.value = value;
}
//查找要删除的结点
/**
* @param value 希望要删除的节点的值
* @return 返回找到的结点 否则返回null
*/
public Node search(int value){
if(value == this.value){
return this;
}else if (value < this.value) { //如果查找的值小于当前结点,向左子树查找
//如果左字节点为空,
if(this.left==null){
return null;
}
return this.left.search(value);
}else {
if(this.right==null){
return null;
}
return this.right.search(value);
}

}

/** 查找要删除结点的父结点
* @param value 要找的结点的值
* @return 要删除的结点的父结点
*/
public Node searchParent(int value){
if((this.left!=null && this.left.value == value)||(this.right!=null && this.right.value == value)){
return this;
}else {
//如果查找的值小于当前结点的值,并且当前结点的左子树是不是为空
if(value < this.value && this.left!= null){
return this.left.searchParent(value);
}else if (value >= this.value && this.right != null) {
return this.right.searchParent(value);
}
return null;
}
}
//添加结点
//递归的形式添加,注意需要满足二叉排序树的要求
public void add(Node node){
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 infixOrder(){
if(this.left!=null){
this.left.infixOrder();
}
System.out.println(this.value);
if(this.right!=null){
this.right.infixOrder();
}
}
}
</font>⑦平衡二叉树>>Avl树的旋转 

左旋转

image-20220312132918218

右旋转

image-20220312133002872

特殊情况(双旋转)

image-20220312134616425

package avl;
public class AvlTreeDemo {
public static void main(String[] args) {
//int arr[] = {4,3,6,5,7,8};
// int arr[] = {10,12,8,9,7,6};
int arr[] = {10,11,7,6,8,9};
//创建一个对象
AvlTree avlTree = new AvlTree();
for (int i = 0; i < arr.length; i++) {
avlTree.add(new Node(arr[i]));
}
avlTree.infixOrder();
System.out.println("在平衡处理之后:");
System.out.println(avlTree.getRoot().height()); //4
System.out.println("左子树高度:" + avlTree.getRoot().leftHeight()); //1
System.out.println("右子树高度:" + avlTree.getRoot().rightHeight()); //3
}

}//创建AvlTree
class AvlTree{
private Node root;

public Node getRoot() {
return root;
}

public void setRoot(Node root) {
this.root = root;
}
public void add(Node node){
if(root==null){
root = node;
}else {
root.add(node);
}
}
public void infixOrder(){
if(root!=null){
root.infixOrder();
}else {
System.out.println("二叉树为空,不能遍历");
}
}
}

class Node{
int value;
Node left;
Node right;
public Node(int value) {
this.value = value;
}

//返回左子树的高度
public int leftHeight(){
if(left == null){
return 0;
}return left.height();
}
//返回右子树的高度
public int rightHeight(){
if(right == null){
return 0;
}else {
return right.height();
}
}
//返回当以当前结点为根节点的高度

public int height(){
return Math.max(left == null ? 0 : left.height() , right == null ? 0 : right.height()) + 1 ;
}
//左旋转方法 》》 //在添加里里面进行旋转
private void leftRotate(){
// 创建新的结点,以当前结点的值
Node newNode = new Node(value);
//把新的结点的左子树设置成当前结点的左子树
newNode.left = left;
//把新的结点的右子树设置成当前结点右子树的左子树
newNode.right = right.left;
//把当前结点的值替换成右子结点的值
value = right.value;
//把当前节点右子树设置成当前结点右子树的右子树,(跳过去)
right = right.right;
//把当前结点的左子节点设置成新的结点
left = newNode;
}
//右旋转方法
private void rightRotate(){
Node newNode = new Node(value);
newNode.right= right;
newNode.left = left.right;
value = left.value;
left= left.left;
right = newNode;
}

//添加结点
//递归的形式添加,注意需要满足二叉排序树的要求
public void add(Node node){
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);
}
}
//当添加完一个结点后, 如果右子树的高度 - 左子树的高度 > 1 ,左旋转
if(rightHeight()-leftHeight() > 1){
// 如果它的右子树的左子树的高度如果大于它的右子树的右子树的高度
if(right != null && right.leftHeight() > right.rightHeight()){
//先对右子节点进行右旋转,
right.rightRotate();
//再对当前结点进行左旋转
leftRotate();
}else {
leftRotate();
}
return ;
}
if (leftHeight() - rightHeight() > 1) { // 如果左子树的高度 - 右子树的高度 > 1 ,右旋转
//如果它的左子树的右子树的高度大于它的左子树的左子树的高度
if(left != null && left.rightHeight() > left.leftHeight()){
//先对左子节点进行左旋转
left.leftRotate();
//再对当前结点进行右旋转
rightRotate();
}else {
rightRotate();
}
}
}
//中序遍历
public void infixOrder(){
if(this.left!=null){
this.left.infixOrder();
}
System.out.println(this.value);
if(this.right!=null){
this.right.infixOrder();
}
}
}

⑧,图(dfs<<>>bfs)

  1. 访问初始结点 v,并标记结点 v 为已访问。

  2. 查找结点 v 的第一个邻接结点 w。

  3. 若 w 存在,则继续执行 4,如果 w 不存在,则回到第 1 步,将从 v 的下一个结点继续。

  4. 若 w 未被访问,对 w 进行深度优先遍历递归(即把 w 当做另一个 v,然后进行步骤 123)。

  1. 查找结点 v 的 w 邻接结点的下一个邻接结点,转到步骤 3。

     

  1. image-20220312204046096

package graph;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;

public class Graph {
private ArrayList<String> vertexList; // 存储顶点集合
private int[][] edgs;  //存储图对应的邻阶矩阵
private int numOfEdgs;
//定义一个数组boolean[] 记录某个结点是否被访问
private boolean[] isVisited ;
public static void main(String[] args) {
// TODO Auto-generated method stub
//测试一把图是否创建ok
int n=5;  //结点个数
String VertextValue[] = {"A","B","C","D","E"};
//创建图对象
Graph graph = new Graph(n);
for (String vertexvalue : VertextValue) {
graph.insertVertex(vertexvalue);
}
//添加边
//A-B A-C B-C B-D B-E
graph.insertEdgs(0,1,1);
graph.insertEdgs(0,2,1);
graph.insertEdgs(1,2,1);
graph.insertEdgs(1,3,1);
graph.insertEdgs(1,4,1);
//显示一把邻结矩阵
//graph.showGraph();
System.out.println("深度遍历");
graph.dfs();

System.out.println("广度优先");
graph.bfs();
}
//构造器

public Graph( int n) {
//初始化矩阵和vertexList
  edgs = new int[n][n];
  vertexList = new ArrayList<String>(n);
  numOfEdgs = 0;
}
//得到第一个邻接接结点的下标W
/**
* @param index
* @return 如果存在就返回对应下标,否则返回-1
*/
public int getFirstNeighber(int index){
for(int j = 0;j<vertexList.size();j++){
if(edgs[index][j] > 0){
return j;
}
}return -1;
}
//根据前一个邻接结点的下标来获取下一个邻接结点
public int getNextNeighbor( int v1,int v2){
for (int j = v2+1; j < vertexList.size(); j++) {
if(edgs[v1][j] > 0){
return j;
}
} return -1;
}

//深度优先遍历算法
//i第一次就是 0
private void dfs(boolean[] isVisited,int i){
//首先我们访问该结点,输出
System.out.print(getValueByIndex(i)+"->");
//将结点设置成已经访问
isVisited[i] = true;
int w=getFirstNeighber(i);  //查找结点i的第一个邻接结点
while (w != -1) {
 if(!isVisited[w]){
 dfs(isVisited, w);
}//如果w已经被访问过,
 w=getNextNeighbor(i, w);
}
}
//对dfs进行重载,遍历我们所有的结点 并进行dfs
public void dfs(){
isVisited = new boolean[5];
for(int i=0;i<getNunOfVertex();i++){
if(!isVisited[i]){
dfs(isVisited,i);
}
}
}
//对一个结点进行广度优先遍历算法的方法
private void bfs(boolean[] isVisited,int i) {
int u ; //表示队列的头结点对应下标
int w ; //表示邻接结点
//队列,记录结点访问的顺序
LinkedList queue = new LinkedList();
//访问结点,输出结点信息
System.out.print(getValueByIndex(i)+"=>");
//标记为以访问
isVisited[i]=true;
//将结点加入队列
queue.addLast(i);
while (!queue.isEmpty()) {
//取出队列的头结点的下标
u = (Integer)queue.removeFirst();
   //得到第一个邻接点的下标
w = getFirstNeighber(u);
while (w != -1) {   //找到
//是否访问过
if(!isVisited[w]){
System.out.print(getValueByIndex(w) + "=>");
//标记已经访问
isVisited[w]=true;
//入队
queue.addLast(w);
}//以u为前驱点,找w后面的下一个邻接点
w = getNextNeighbor(u, w);  //提现出我们的广度优先算法
}
}
}
//遍历所有的结点,都进行广度优先算法
public void bfs(){
isVisited = new boolean[5];
for (int i = 0; i < getNunOfVertex(); i++) {
if(!isVisited[i]){
bfs(isVisited,i);
}
}
}
//图中常用方法
//返回结点个数
public int getNunOfVertex(){
return vertexList.size();
}
//得到边的数目
public int getNumOfEdgs(){
return numOfEdgs;
}
//返回结点i(下标)
public String getValueByIndex(int i){
return vertexList.get(i);
}
//返回V1和V2的值
public int getWeight(int v1,int v2){
return edgs[v1][v2];
}
//显示图对应的矩阵
public void showGraph(){
for (int[] link : edgs) {
System.err.println(Arrays.toString(link));
}
}
//插入结点
public void insertVertex(String vertex){
vertexList.add(vertex);
}
/** 添加边
* @param v1   表示点的下标 "A" "B"  
* @param v2 第二个顶点的下标,
* @param weight
*/
public void insertEdgs(int v1 ,int v2, int weight){
edgs[v1][v2] = weight;
edgs[v2][v1] = weight;
numOfEdgs++;
}
}

7,十大算法

①二分查找--------------------->4
②分治算法

复杂的问题拆解为小问题,是很多高效算法的基础

分治法在每一层递归上都有三个步骤:

  1. 分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题

  2. 解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题

  3. 合并:将各个子问题的解合并为原问题的解。

汉诺塔问题

package atguigu;

public class Hanoitower {

public static void main(String[] args) {
// TODO Auto-generated method stub
hanoiTower(10, 'A','B', 'C');
}
   //汉诺塔移动的方法
//分治算法
public static void hanoiTower(int num ,char a,char b,char c){
//只有一个盘
if(num == 1){
System.out.println("第一个盘从 "+ a +"->" +c);
}else{
//1,先把上面的盘都移动到B
hanoiTower(num-1, a, c, b);  //移动过程会使用到C
//2,把最下边的盘 A->C
System.out.println("第 "+ num + "个盘从 " + a + "->" +c);
//3,把B塔的所有盘B->C
hanoiTower(num-1, b, a, c); //移动过程使用到A
}
}
}
③ 动态规划问题

(1) vi=v0=0; //表示 填入表 第一行和第一列是 0

(2) 当 w[i]> j 时:vi=vi-1 // 当准备加入新增的商品的容量大于 当前背包的容量时,就直接使用上一个 单元格的装入策略

(3) 当 j>=w[i]时: vi=max{vi-1, v[i]+vi-1]}

/*当 准备加入的新增的商品的容量小于等于当前背包的容量, // 装入的方式:
v[i-1][j]: 就是上一个单元格的装入的最大值
v[i] : 表示当前商品的价值
v[i-1][j-w[i]] : 装入 i-1 商品,到剩余空间 j-w[i]的最大值
当 j>=w[i]时: v[i][j]=max{v[i-1][j], v[i]+v[i-1][j-w[i]]} :
*/
package atguigu;import java.util.Arrays;
import java.util.List;

import org.omg.CORBA.ParameterModeHelper;

public class KnapsackProblem {

public static void main(String[] args) {
// TODO Auto-generated method stub
int[] w={1,4,3};
int[] val={1500,3000,2000};  //这里的val[i]就是v[i]
int m= 4;//背包的容量
int n= val.length;//物品的个数

//为了记录放入商品情况
int[][] path = new int[n+1][m+1];
//创建二维数组
//v[i][j]的含义 表示在前i个物品中能够装入容量为j的背包中的最大价值
int[][] v = new int[n+1][m+1];

for (int i = 0; i < v.length; i++) {
v[i][0]=0;  //将第一列置为0
}for (int i = 0; i < v[0].length; i++) {
v[0][i] = 0;  //将第一行设置为0;
}

//根据公式来动态处理
for (int i = 1; i < v.length; i++) {  //不处理第一行 i是从1开始的
for (int j = 1; j < v[0].length; j++) {  //不处理第一列 j同上
if(w[i-1] > j){  //因为我们 i 从1开始所以修改为 w[i-1]
v[i][j] = v[i-1][j];
}
else {//因为i是从1 开始 因此公式调整成
//v[i][j] = Math.max(v[i-1][j] , val[i-1] + v[i-1][j-w[i-1]]); //为了记录商品放到背包情况
if(v[i-1][j] < val[i-1] + v[i-1][j-w[i-1]]){
v[i][j] = val[i-1] + v[i-1][j-w[i-1]];
path[i][j] = 1;
}else {
v[i][j] = v[i-1][j];
path[i][j] = 1;
}
}
}
}
//输出V看看目前请情况
for (int i = 0; i < v.length; i++) {
for (int j = 0; j < v[i].length; j++) {
System.out.print(v[i][j] + " ");
}
System.out.println();
}
/*
//遍历path   这样输出会把所有情况全部得到,其实我们只需要最后的放入
for (int i = 0; i < path.length; i++) {    
for (int j = 0; j < path.length; j++) {
if(path[i][j] == 1){
System.out.printf("第%d个商品放入背包",i);
}
}
}*/
int i= path.length - 1;   //行的最大下标
int j = path[0].length - 1;   //列的最大下标
while (i > 0 && j > 0) {
if(path[i][j] == 1){
System.out.printf("第%d个商品放入背包\n",i);
j -= w[i-1]; //w[i-1]
}
i--;
}
}
}
④KMP算法
  1. KMP 是一个解决模式串在文本串是否出现过,如果出现过,最早出现的位置的经典算法

  2. Knuth-Morris-Pratt 字符串查找算法,简称为 “KMP 算法”,常用于在一个文本串 S 内查找一个模式串 P 的 出现位置,这个算法由 Donald Knuth、Vaughan Pratt、James H. Morris 三人于 1977 年联合发表,故取这 3 人的 姓氏命名此算法.

  3. KMP 方法算法就利用之前判断过信息,通过一个 next 数组,保存模式串中前后最长公共子序列的长度,每次 回溯时,通过 next 数组找到,前面匹配过的位置,省去了大量的计算时间

package atguigu;
import java.util.Arrays;
//KMP算法
public class KMPAlgorithm {

public static void main(String[] args) {
// TODO Auto-generated method stub
String str1 = "BBC ABCDAB ABCDABCDABDE";
String str2 = "ABCDABD";
int[] next = kmpNext(str2);
System.out.println("next = "+ Arrays.toString(next));
int index = KmpSearch(str1, str2, next);
System.out.println(index);
}
/**
*
* @param str1 源字符串
* @param str2 子串
* @param next 部分匹配表, 是子串对应的部分匹配表
* @return 如果是-1 就是没有匹配到,否则返回第一个匹配的位置
*/
public static int KmpSearch(String str1,String str2,int[] next){
//遍历
for (int i = 0,j = 0; i < str1.length(); i++) {
//KMP算法的核心点,可以验证
while (j > 0 && str1.charAt(i) != str2.charAt(j)) {
j = next[j - 1];
}
if(str1.charAt(i) == str2.charAt(j)){
j++;
}
if(j == str2.length()){
return i - j + 1;
}
}
return -1;
}
//获得一个字符串的部分匹配值
public static int[] kmpNext(String dest){
//创建一个next数组保存部分匹配值
int[] next = new int[dest.length()];
next[0] = 0;
for (int i = 1,j = 0; i < dest.length(); i++) {
//当 dest.charAt(i) != dest.charAt(j) ,我们需要从 next[j-1]获取新的 j
//直到我们发现 有 dest.charAt(i) == dest.charAt(j)成立才退出
//这时 kmp 算法的核心点
while (j > 0 && dest.charAt(i) != dest.charAt(j)) {
j = next[j - 1];
}
if(dest.charAt(i) == dest.charAt(j)){
j++;
}
next[i] = j;
}
return next;
}
}

 

posted @ 2022-03-01 14:07  扬帆起航$  阅读(42)  评论(0)    收藏  举报