目录
数据结构
1. 二维数组 和 稀疏数组 相互转换
1、定义数组 二维数组int [][] twoArray = new int[10][10]; 2、定义稀疏数组 int [][] sparseArray = new int[count][3];
/**
* 二维数组 和 稀疏数组 相互转换
* 1、定义数组 二维数组int [][] twoArray = new int[10][10];
* 2、定义稀疏数组 int [][] sparseArray = new int[count][3];
* @author mgq
* @create 2021-03-05 18:11
*/
public class TwoArrayToSparseArrayDemo {
/**
* 1、创建一个二维数组
* 2、创建一个稀疏数组
* 3、把二维数组 中不等于0的数据 转成稀疏数组
* 4、把稀疏数组转成二维数组
*/
public static void main(String[] args) {
// TwoArrayToSparseArray();
int [] aa = new int[6];
aa[0]=1;
aa[1]=2;
aa[2]=3;
aa[3]=4;
// for (final int i : aa) {
// System.out.print(i);
// }
// System.out.println();
int [] bb =aa;
System.out.println(aa==bb);
// bb[0]=22;
// bb[4]=66;
// for (final int i : aa) {
// System.out.print(i);
// }
// System.out.println();
// for (final int j : bb) {
// System.out.print(j);
// }
}
/**
* 二维数组转出稀疏数组
*/
public static void TwoArrayToSparseArray(){
// TODO: 2021/3/5 定义一个10行10列二维数组
/**
* 二维数组 ,其中7,8,9是其中的值
* 0 0 0 0 0 0 0 0 0 0
* 0 0 0 0 0 0 0 0 0 0
* 0 0 0 7 0 0 0 0 0 0
* 0 0 0 0 0 8 0 0 0 0
* 0 0 0 0 0 0 0 0 0 0
* 0 0 0 0 0 0 0 9 0 0
* 0 0 0 0 0 0 0 0 0 0
* 0 0 0 0 0 0 0 0 0 0
* 0 0 0 0 0 0 0 0 0 0
* 0 0 0 0 0 0 0 0 0 0
*/
int [][] twoArray = new int[10][10];
twoArray[2][3]=7;
twoArray[3][5]=8;
twoArray[5][7]=9;
// 稀疏数组的行数
int count =0;
System.out.println("二维数组=》》》");
// TODO: 2021/3/5 循环二维数组
for (int i = 0; i < twoArray.length; i++) {
for (int j = 0; j < twoArray[i].length; j++) {
System.out.print(twoArray[i][j]+" ");
// TODO: 统计出二维数组中的不等于的有多少个
if (twoArray[i][j]!=0){
count++;
}
}
System.out.println();
}
System.out.println("稀疏数组=》》》");
// TODO: 定义一个稀疏数组,3列,行数count,根据二维数组中统计的count
/**
* 稀疏数组
* 0 0 0
* 0 0 0
* 0 0 0
*/
int [][] sparseArray = new int[count][3];
for (int i = 0; i < sparseArray.length; i++) {
for (int j = 0; j < sparseArray[i].length; j++) {
System.out.print(sparseArray[i][j]+" ");
}
System.out.println();
}
// 记录稀疏数组存放的行数
int count2=0;
// TODO: 把二维数组中不等于0的数据放入到稀疏数组中去
/**
* 稀疏数组中的值
* 2 3 7
* 3 5 8
* 5 7 9
*/
for (int i = 0; i < twoArray.length; i++) {
for (int j = 0; j < twoArray[i].length; j++) {
// TODO: 规则,二维数组中的值行列 ,对应的稀疏数组中列的1,2列两个值;二维数组中的值,对应的是稀疏数组中第三列的值
if (twoArray[i][j]!=0){
sparseArray[count2][0]=i; // i=行
sparseArray[count2][1]=j; // j=列
sparseArray[count2][2]=twoArray[i][j]; // 值=twoArray[i][j]
// 行数+1
count2++;
}
}
}
System.out.println("二维数组中的数转到稀疏数组中=》》》");
for (int i = 0; i < sparseArray.length; i++) {
for (int j = 0; j < sparseArray[i].length; j++) {
System.out.print(sparseArray[i][j]+" ");
}
System.out.println();
}
/**
* 又稀疏数组赋值的二维数组
* 0 0 0 0 0 0 0 0 0 0
* 0 0 0 0 0 0 0 0 0 0
* 0 0 0 7 0 0 0 0 0 0
* 0 0 0 0 0 8 0 0 0 0
* 0 0 0 0 0 0 0 0 0 0
* 0 0 0 0 0 0 0 9 0 0
* 0 0 0 0 0 0 0 0 0 0
* 0 0 0 0 0 0 0 0 0 0
* 0 0 0 0 0 0 0 0 0 0
* 0 0 0 0 0 0 0 0 0 0
*/
System.out.println(" 稀疏数组转出另外一个空的二维数组=》》》");
// TODO: 稀疏数组转出另外一个空的二维数组
int [][] otherTwoArray = new int[10][10];
for (int i = 0; i < sparseArray.length; i++) {
/**
* 稀疏数组
* 行 列 值
* 2 3 7
*/
otherTwoArray[sparseArray[i][0]][sparseArray[i][1]]=sparseArray[i][2];
}
for (int i = 0; i < otherTwoArray.length; i++) {
for (int j = 0; j < otherTwoArray[i].length; j++) {
System.out.print(otherTwoArray[i][j]+" ");
}
System.out.println();
}
}
}
2.数组实现队列
* 数组实现队列思路,先进先出 ,但是由于数组的角标用过不能再次复用,所以需要实现环形队列,看下一个例子 *1、创建队列类 * * 2、属性:// 定义数组 // 队列长度// 队列中第一个值的索引值 // 队列最后一个值得索引值 * * 3、方法:addQueue() , getFirstQueue()*
/**
* 数组实现队列思路,先进先出 ,但是由于数组的角标用过不能再次复用,所以需要实现环形队列,看下一个例子
*1、创建队列类
* * 2、属性:// 定义数组 // 队列长度// 队列中第一个值的索引值 // 队列最后一个值得索引值
* * 3、方法:addQueue() , getFirstQueue()*
* @author mgq
* @create 2021-03-05 20:07
*/
public class ArrayToQueueDemo {
public static void main(String[] args) {
boolean flag = true;
// 开启控制台输入
Scanner scanner = new Scanner(System.in);
ArrayQueue arrayQueue = new ArrayQueue();
while (flag){
System.out.println("请输入a,请设置队列的深度");
System.out.println("请输入b,查看队列所有值");
System.out.println("请输入c,获取队列中的第一个值");
System.out.println("请输入d,添加一个值到队列");
System.out.println("请输入e,退出程序");
// scanner.next() 获取控制台输入的值
String next = scanner.next();
switch(next){
case "a" :
System.out.println("请输入队列的深度");
int length = scanner.nextInt();
arrayQueue.setArrayQueueLength(length);
System.out.println("设置队列深度成功");
break; //可选
case "b" :
int[] array = arrayQueue.getArray();
System.out.println("队列的值如下:");
for (int i = 0; i < array.length; i++) {
if (array[i]!=0){
System.out.println(array[i]);
}
}
break; //可选
case "c" :
int firstQueue = 0;
try {
firstQueue = arrayQueue.getFirstQueue();
} catch (Exception e) {
e.getMessage();
System.out.println("队列中没有值");
break;
}
System.out.println("结果如下:"+firstQueue);
break; //可选
case "d" :
System.out.println("输入要添加的值");
int value = scanner.nextInt();
arrayQueue.addQueue(value);
System.out.println("添加值成功");
break; //可选
case "e" :
flag=false;
break;
// return;
default : //可选
System.out.println("输入格式不符合!!!");
break;
//语句
}
}
System.out.println("程序已退出");
// 关闭控制台
scanner.close();
}
/**
* 1、创建队列类
* 2、属性:
*/
static class ArrayQueue{
// 定义数组
private int [] array;
// 队列长度
private int maxLength=0;
// 队列中第一个值的索引值
private int firstIndex=0;
// 队列最后一个值得索引值
private int endIndex=0;
// 设置队列的最大深度
public void setArrayQueueLength(int length){
array=new int[length];
maxLength=length;
}
// 往队列插入一个值
public void addQueue(int value){
// 判断队列元素是否已经满了
if (isOrNoFull()){
throw new RuntimeException("队列元素已满,不能在此添加");
}
array[endIndex]=value;
// 为下一个索引准备加入值
endIndex++;
}
// 从队列中取出一个值,取出队列中第一个值
public int getFirstQueue(){
// 判断队列中是否有值
if (!isOrNoHaveValue()){
throw new RuntimeException("队列中没有值!!!");
}
// 取出队列最前面的元素
int value = array[firstIndex];
array[firstIndex]=0;
firstIndex++;
return value;
}
// 判断队列元素是否已经满了,用endIndex和maxLength作对比,满了返回true:就不在允许添加元素, false:没有满,可以添加值
public boolean isOrNoFull(){
return endIndex >= maxLength;
}
// 判断队列中是否有值,返回true:说明有值,返回false 没有值
public boolean isOrNoHaveValue(){
return firstIndex!=endIndex;
}
// get//set
public int[] getArray() {
return array;
}
public void setArray(int[] array) {
this.array = array;
}
public int getMaxLength() {
return maxLength;
}
public void setMaxLength(int maxLength) {
this.maxLength = maxLength;
}
public int getFirstIndex() {
return firstIndex;
}
public void setFirstIndex(int firstIndex) {
this.firstIndex = firstIndex;
}
public int getEndIndex() {
return endIndex;
}
public void setEndIndex(final int endIndex) {
this.endIndex = endIndex;
}
}
}
3:数组实现环形队列
数组实现环形队列,解决数组角标不能复用的问题,利用 取余 %来解决这个问题
/**
* 数组实现环形队列,解决数组角标不能复用的问题,利用 取余 %来解决这个问题
*
* java里/和%的区别
* 1》 / 取得是两个数相除后的整数部分。
* 2》 % 取得是两个数相除后的余数部分。
* 例如 int a= 7%3 ,除完以后结果就是2余数是1,那么余数1就是取余结果
* * 例如 int a =2%3; 余数也是2,这种比被除数小的,那么除数直接就是余数
* *1、创建环形队列类
* * * 2、属性:// 定义数组 // 队列长度// 队列中第一个值的索引值 // 队列最后一个值得索引值
* * * 3、方法:addQueue() , getFirstQueue()*
* @author mgq
* @create 2021-03-05 22:04
*/
public class CircleArrayToQueueDemo {
public static void main(String[] args) {
boolean flag = true;
// 开启控制台输入
Scanner scanner = new Scanner(System.in);
ArrayQueue arrayQueue = new ArrayQueue();
while (flag){
System.out.println("请输入a,请设置队列的深度");
System.out.println("请输入b,查看队列所有值");
System.out.println("请输入c,获取队列中的第一个值");
System.out.println("请输入d,添加一个值到队列");
System.out.println("请输入e,退出程序");
// scanner.next() 获取控制台输入的值
String next = scanner.next();
switch(next){
case "a" :
System.out.println("请输入队列的深度");
int length = scanner.nextInt();
arrayQueue.setArrayQueueLength(length);
System.out.println("设置队列深度成功");
break; //可选
case "b" :
int[] array = arrayQueue.getArray();
System.out.println("队列的值如下:");
for (int i = 0; i < array.length; i++) {
if (array[i]!=0){
System.out.println(array[i]);
}
}
break; //可选
case "c" :
int firstQueue = 0;
try {
firstQueue = arrayQueue.getFirstQueue();
} catch (Exception e) {
e.getMessage();
System.out.println("队列中没有值");
break;
}
System.out.println("结果如下:"+firstQueue);
break; //可选
case "d" :
System.out.println("输入要添加的值");
int value = scanner.nextInt();
arrayQueue.addQueue(value);
System.out.println("添加值成功");
break; //可选
case "e" :
flag=false;
break;
// return;
default : //可选
System.out.println("输入格式不符合!!!");
break;
//语句
}
}
System.out.println("程序已退出");
// 关闭控制台
scanner.close();
}
/**
* 1、创建队列类
* 2、属性:
*/
static class ArrayQueue{
// 定义数组
private int [] array;
// 队列长度
private int maxLength=0;
// 队列中第一个值的索引值
private int firstIndex=0;
// 队列最后一个值得索引值
private int endIndex=0;
// 设置队列的最大深度
public void setArrayQueueLength(int length){
array=new int[length];
maxLength=length;
}
// 往队列插入一个值
public void addQueue(int value){
int bianliang=0;
// 判断队列元素是否已经满了
if (isOrNoFull()){
throw new RuntimeException("队列元素已满,不能在此添加");
}
// 实现环形队列修改处——————如果endIndex比maxleng大,那么也要取模%
bianliang=endIndex%maxLength;
array[bianliang]=value;
// 为下一个索引准备加入值
endIndex++;
}
// 从队列中取出一个值,取出队列中第一个值
public int getFirstQueue(){
int bianliang=0;
// 判断队列中是否有值
if (!isOrNoHaveValue()){
throw new RuntimeException("队列中没有值!!!");
}
// 实现环形队列修改处——————如果firstIndex的值已经超过了最大长度maxlength ,说明之前的数都被取了一遍了,其实数组里面都是空了
// ,那么我们就让重新去从数组第一去取,利用取模%来实现.
bianliang=firstIndex%maxLength;
// 取出队列最前面的元素
int value = array[bianliang];
array[bianliang]=0;
firstIndex++;
return value;
}
// 判断队列元素是否已经满了,用endIndex和maxLength作对比,满了返回true:就不在允许添加元素, false:没有满,可以添加值
public boolean isOrNoFull(){
// 环形队列需要修改的地方:尾坐标减去首坐标永远不能大于max-1,也就是不能超过队列深度
return endIndex-firstIndex>maxLength-1;
// return endIndex >= maxLength;
}
// 判断队列中是否有值,返回true:说明有值,返回false 没有值
public boolean isOrNoHaveValue(){
// 环形队列需要修改的地方:就是首坐标永远不能超过尾坐标
return firstIndex-endIndex<0;
}
// get//set
public int[] getArray() {
return array;
}
public void setArray(int[] array) {
this.array = array;
}
public int getMaxLength() {
return maxLength;
}
public void setMaxLength(int maxLength) {
this.maxLength = maxLength;
}
public int getFirstIndex() {
return firstIndex;
}
public void setFirstIndex(int firstIndex) {
this.firstIndex = firstIndex;
}
public int getEndIndex() {
return endIndex;
}
public void setEndIndex(final int endIndex) {
this.endIndex = endIndex;
}
}
}
4:单向链表实现
* 1:实现单向链表的增删改查操作, *:2:并解决各大面试题 * // 实现功能: * 1、 添加链表 向链表中添加数据 * 2、实现添加数据自动根据node1中的no号排序方法 * 3、查询链表所有节点 * 3、修改链表节点 * 5、删除数据节点 * 6、倒叙链表,也就是反转链表,两种方式实现
/**
* 1:实现单向链表的增删改查操作,
*:2:并解决各大面试题
* // 实现功能:
* 1、 添加链表 向链表中添加数据
* 2、实现添加数据自动根据node1中的no号排序方法
* 3、查询链表所有节点
* 3、修改链表节点
* 5、删除数据节点
* 6、倒叙链表,也就是反转链表,两种方式实现
*
* * *1、创建双向链表类
* * * * 2、属性:
* * // public DoubleLikNode next; //指向下一节点对象的指针
* * * * 3、方法:申明一个空得head带头节点, add() , addOrderByNo() ,list(),update(),delete(),reverse(),reverseByStack()
*
* @author mgq
* @create 2021-03-06 10:38
*/
public class SingleLinketListDemo {
public static void main(String[] args) {
LikNode node1 = new LikNode(1, "张三");
LikNode node2 = new LikNode(2, "李四");
LikNode node3 = new LikNode(3, "王五");
LikNode node4 = new LikNode(4, "牛二");
LikNode node6 = new LikNode(6, "牛三");
LikNode node7 = new LikNode(7, "牛四");
// 定义一个空链表头
// 1、 添加链表 向链表中添加数据
SingleLiketList linketList = new SingleLiketList();
linketList.add(node1);
linketList.add(node2);
linketList.add(node3);
linketList.add(node4);
linketList.add(node6);
linketList.add(node7);
// 2、实现添加数据自动根据node1中的no号排序方法
LikNode node5 = new LikNode(5, "排序");
LikNode node8 = new LikNode(8, "排序");
linketList.addOrderByNo(node5);
linketList.addOrderByNo(node8);
// 3、查询链表
linketList.list();
LikNode upt8 = new LikNode(8, "测试修改1");
LikNode upt9 = new LikNode(9, "测试修改不存在no的数据");
// 4、修改链表中元素
linketList.update(upt8);
linketList.update(upt9);
// 修改完以后在查询一遍数据
linketList.list();
// 5、删除数据
LikNode del10 = new LikNode(10, "测试删除不存的节点");
linketList.delete(node1);
linketList.delete(del10);
// 删除后在查询一边
linketList.list();
// 6、倒叙链表,也就是反转链表
// System.out.println("我是反转");
// linketList.reverse();
// linketList.list();
// 第二种反转,利用栈,先进后出
// Stack stack = new Stack();
// 入栈
// stack.push("");
// 出栈
// stack.pop();
linketList.reverseByStack();
}
}
class SingleLiketList{
private LikNode head = new LikNode(); //定义一个空链表头
/**
* 添加元素节点
* 1、首先先定义一个head 头节点,
* 2、定义变量接收头结点,因为head的不允许变动
* 注意:这块因为temp.next是一个新的对象,那么只能用变量来接收,如果在用head接收的话,那么head的引用地址就变了,这也就是为什么需要定义一个temp变量了
* temp=temp.next; // temp == (LikNode next = temp.next);
* * @param likNode
*/
public void add(LikNode likNode){
// 这一步非常重要,因为head中的值需要temp取赋值
LikNode temp = head;
// System.out.println(temp==head);
while (true){
if (temp.next==null) {
// 说明此指针下没有对象,也就是此对象为链表尾节点,那么我们就把下一个新添加的对象付给这个尾节点
temp.next=likNode;
return;
}
// 这块因为temp.next是节点指针中的,那么只能用变量来接收,如果在用head接收的话,那么head的引用地址就变了,这也就是为什么需要定义一个temp变量了
// 当temp是节点指针中指向的对象的时候,那么改变temp就可以改变head中对应的子节点对象的值
temp=temp.next; // temp == (LikNode next = temp.next); temp == 子节点对象
// System.out.println(temp==head);
}
}
/**
* 实现添加元素后 根据 no排序,只需要记住一定要根据上一个节点去定位插入位置,这是单向链表
* 因为正常情况下我们只要根据链表指针找到最后一个尾节点,在尾节点添加即可,
* 那么排序的这个就需要我们在循环过程中对比 no 后,找到需要插入位置的上一个节点,因为是链表,我们只有知道了上一个节点的位置,才之后面节点的位置
* * @param likNode
*/
public void addOrderByNo(LikNode likNode){
LikNode temp = head;
// 先定义一个cur变量,表示当前node徐需要插入的位置的下一个节点
LikNode curNext=null;
while (true){
// 1、如果是最后一个节点,那么就直接插入
if (temp.next==null){
temp.next=likNode;
break;
}
// 2、如果此节点已存在,给出提示
if (temp.next.no==likNode.no){
System.out.println("此节点已存在,不允许再次插入!!!");
break;
}
// 3、根据上一个节点的 temp.next找到下一个节点位置
if (temp.next.no>likNode.no){
// 先让自己指定下一个节点位置
likNode.next=temp.next;
// 然后让上一个节点指向自己
temp.next=likNode;
break;
}
// 如果没有找到,那么就继续循环去找
temp=temp.next;
}
// 因为是单链表,那么我们就要根据指针,给这两个对象建立关系
// likNode.next=curNext;
}
/**
* 获取链表中所有的值
* 1、首先从head中获取,直接循环head
*/
public void list(){
if (head==null) {
System.out.println("链表数据为空");
return;
}
LikNode temp = head;
//当链表中数据不为空,我们就遍历head
while (true){
if (temp.next==null) {
break;
}
System.out.println(temp.next);
temp=temp.next;
}
}
/**
* 修改链表中的元素,根据编号no,
* 步骤:先找到前一个节点,temp.next.no==no 去定位编号一样的,在把指针相互指向即可
*/
public void update(LikNode likNode){
LikNode temp = head;
boolean flag=true; // 标记找不到匹配的no
while (true){
if(temp.next==null){
break;
}
if(temp.next.no==likNode.no){
// 进行修改操作 , temp.next 就是当前对象
LikNode next = temp.next;
next.setNext(likNode.getNext());
next.setName(likNode.getName());
// 如果找到,我们就给flag标记赋值false,如果找不到匹配的no,那么我们就给出提示
flag=false;
break;
}
// 遍历去找下一个节点
temp=temp.next;
}
if (!flag){
System.out.println("说明我们修改了数据");
}else{
System.out.println("根据no编号没有找到需要修改的数据!!!");
}
}
/**
* 删除链表中的元素,
* 步骤,删除元素,需要我们根据no匹配到相同的编号的结点,那么我们只需要,把当前节点的上一个节点的next指针指向当前的节点的下一个节点
*/
public void delete(LikNode node){
LikNode temp = head;
boolean flag =true;
while (true){
if (temp.next==null){break;}
// 找到当前节点
if (temp.next.no==node.no) {
temp.next=temp.next.next;
// 找到了此节点打个标记
flag=false;
break;
}
temp=temp.next;
}
if(!flag){
System.out.println("删除了此节点!!");
}else{
System.out.println("没有找到此编号的节点,无法删除操作");
}
}
/**
* 反转链表
* 步骤:创建另一个链表,然后把当前链表的元素从第一个取出,放到另一个链表中,然后取出第二个放到第一个之前,以此类推。
*/
public void reverse(){
/*LikNode temp = head.next;
// 定义一个新的链表,用于临时接收
LikNode newHead = new LikNode();
// 临时接收当前节点的下一个节点
LikNode tempNext = null;
while (true){
if (temp==null) {
break;
}
// 把当前节点的下一节点赋值给tempNext
tempNext=temp.next;
// 把新链表上的节点赋值给 temp.next,就是实现反转了,这样首先会改变temp.next的引用,这样依赖,我们后面在操作temp.next 就不会影响 tempNext了
temp.next= newHead.next;
// 把当前节点赋给newHead.next,并且 当前节点的next是 上一个节点,因为我们上面步骤 temp.next= newHead.next;已经接收的上次循环的节点
newHead.next=temp;
// 再把当前节点后面的节点赋给temp,让他继续去循环
temp=tempNext;
}
// 把新链表直接给原来的head链表
head.next=newHead.next;*/
// 上面是注释,下面这个是第二遍
LikNode temp = head.next;
LikNode newHead = new LikNode();
LikNode next = null;
while (true){
if(temp==null){break;}
next=temp.next;
temp.next= newHead.next;
newHead.next=temp;
temp=next;
}
head.next=newHead.next;
}
/**
* 通过栈进行数据的反转
*/
public void reverseByStack(){
LikNode temp = this.head;
LikNode cur =null;
Stack<LikNode> stack = new Stack();
while (true){
if (temp.next==null) {
stack.push(temp);
break;
}
//获取当前对象进行压栈/入栈操作
stack.push(temp);
temp=temp.next;
}
System.out.println("开始出栈!!!");
// stack.isEmpty(); 出栈
while (stack.size()>0){
System.out.println(stack.pop());
}
System.out.println();
}
}
/**
* 定义单向链表节点对象
*
* 1、单项链表 ,首先是单项的,其次每一个对象都保存下一个对象的指针,从链表中对位对象每次都需要从头遍历链表,
* 2、单项链表:由 链表头,节点,链表尾(为null的)组成,其中节点中包含指向下一个对象的指针
*/
class LikNode{
public int no; //行号
public String name; //名称
public LikNode next; //下一节点对象
public LikNode() {
}
public LikNode(int no, String name) {
this.no = no;
this.name = name;
}
public int getNo() {
return no;
}
public void setNo(final int no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
public LikNode getNext() {
return next;
}
public void setNext(final LikNode next) {
this.next = next;
}
@Override
public String toString() {
return "SingleLinketList{" +
"no=" + no +
", name='" + name + '\'' +
// ", next=" + next +
'}';
}
}
5:双向链表
前面演示了单项链表,这次演示双向链表的增删改查(单项链表不足,由于每次删除都需要先定位到前面的节点,那么双向链表就没有这个问题) * 双向链表优点: 删除 和 反转 不在需要像单向链表那样那样麻烦了,因为节点的pre会指向前面的节点 * *1、创建双向链表类 * * * 2、属性: * // public DoubleLikNode next; //指向下一节点对象的指针 * // 相比较与单项链表,多了一个指向前一节点对象的指针 * public DoubleLikNode pre; * * * 3、方法:申明一个空得head带头节点, add() , addOrderByNo() ,list(),update(),delete(),reverse(),reverseByStack()
/**
* 前面演示了单项链表,这次演示双向链表的增删改查(单项链表不足,由于每次删除都需要先定位到前面的节点,那么双向链表就没有这个问题)
* 双向链表优点: 删除 和 反转 不在需要像单向链表那样那样麻烦了,因为节点的pre会指向前面的节点
* *1、创建双向链表类
* * * 2、属性:
* // public DoubleLikNode next; //指向下一节点对象的指针
* // 相比较与单项链表,多了一个指向前一节点对象的指针
* public DoubleLikNode pre;
* * * 3、方法:申明一个空得head带头节点, add() , addOrderByNo() ,list(),update(),delete(),reverse(),reverseByStack()
* @author mgq
* @create 2021-03-08 0:27
*/
public class DoubleLinketListDemo {
public static void main(String[] args) {
DoubleLikNode node1 = new DoubleLikNode(1, "张三");
DoubleLikNode node2 = new DoubleLikNode(2, "李四");
DoubleLikNode node3 = new DoubleLikNode(3, "王五");
DoubleLikNode node4 = new DoubleLikNode(4, "牛二");
DoubleLikNode node6 = new DoubleLikNode(6, "牛三");
DoubleLikNode node7 = new DoubleLikNode(7, "牛四");
// 定义一个空链表头
System.out.println("添加了数据");
// 1、 添加链表 向链表中添加数据
DoubleLiketList linketList = new DoubleLiketList();
linketList.add(node1);
linketList.add(node2);
linketList.add(node3);
linketList.add(node4);
linketList.add(node6);
linketList.add(node7);
System.out.println("根据条件添加了数据");
// 2、实现添加数据自动根据node1中的no号排序方法
DoubleLikNode node5 = new DoubleLikNode(5, "排序");
DoubleLikNode node8 = new DoubleLikNode(8, "排序");
linketList.addOrderByNo(node5);
linketList.addOrderByNo(node8);
System.out.println("查询了数据");
// 3、查询链表
linketList.list();
DoubleLikNode upt8 = new DoubleLikNode(8, "测试修改1");
// DoubleLikNode upt9 = new DoubleLikNode(9, "测试修改不存在no的数据");
// 4、修改链表中元素
linketList.update(upt8);
// linketList.update(upt9);
// 修改完以后在查询一遍数据
linketList.list();
// 5、删除数据
// DoubleLikNode del10 = new DoubleLikNode(10, "测试删除不存的节点");
linketList.delete(node2);
// linketList.delete(del10);
// 删除后在查询一边
linketList.list();
// 6、倒叙链表,也就是反转链表
System.out.println("我是反转");
linketList.reverse();
// linketList.list();
// 第二种反转,利用栈,先进后出
// Stack stack = new Stack();
// 入栈
// stack.push("");
// 出栈
// stack.pop();
// linketList.reverseByStack();
}
}
class DoubleLiketList {
private DoubleLikNode head = new DoubleLikNode(); //定义一个空链表头
/**
* 添加元素节点
* 1、首先先定义一个head 头节点,
* 2、定义变量接收头结点,因为head的不允许变动
* 注意:这块因为temp.next是一个新的对象,那么只能用变量来接收,如果在用head接收的话,那么head的引用地址就变了,这也就是为什么需要定义一个temp变量了
* temp=temp.next; // temp == (DoubleLikNode next = temp.next);
* * @param DoubleLikNode
*/
public void add(DoubleLikNode likNode) {
// 这一步非常重要,因为head中的值需要temp取赋值
DoubleLikNode temp = head;
// System.out.println(temp==head);
while (true) {
if (temp.next == null) {
// 说明此指针下没有对象,也就是此对象为链表尾节点,那么我们就把下一个新添加的对象付给这个尾节点
temp.next = likNode;
// 双向链表中 当前节点指向上一节点的指针
temp.next.pre= temp;
return;
}
// 这块因为temp.next是节点指针中的,那么只能用变量来接收,如果在用head接收的话,那么head的引用地址就变了,这也就是为什么需要定义一个temp变量了
// 当temp是节点指针中指向的对象的时候,那么改变temp就可以改变head中对应的子节点对象的值
temp = temp.next; // temp == (LikNode next = temp.next); temp == 子节点对象
// System.out.println(temp==head);
}
}
/**
* 实现添加元素后 根据 no排序,只需要记住一定要根据上一个节点去定位插入位置,这是单向链表
* 因为正常情况下我们只要根据链表指针找到最后一个尾节点,在尾节点添加即可,
* 那么排序的这个就需要我们在循环过程中对比 no 后,找到需要插入位置的上一个节点,因为是链表,我们只有知道了上一个节点的位置,才之后面节点的位置
* * @param likNode
*/
public void addOrderByNo(DoubleLikNode likNode) {
DoubleLikNode temp = head;
// 先定义一个cur变量,表示当前node徐需要插入的位置的下一个节点
DoubleLikNode curNext = null;
while (true) {
// 1、如果是最后一个节点,那么就直接插入
if (temp.next == null) {
temp.next = likNode;
// 双向节点,指向上一个节点
likNode.pre=temp;
break;
}
// 2、如果此节点已存在,给出提示
if (temp.next.no == likNode.no) {
System.out.println("此节点已存在,不允许再次插入!!!");
break;
}
// 3、根据上一个节点的 temp.next找到下一个节点位置
if (temp.next.no > likNode.no) {
// 双向链表,指向前一个节点
temp.next.pre=likNode;
// 先让自己指定下一个节点位置
likNode.next = temp.next;
// 双向链表,指向前一个节点
likNode.pre=temp;
// 然后让上一个节点指向自己
temp.next = likNode;
break;
}
// 如果没有找到,那么就继续循环去找
temp = temp.next;
}
// 因为是单链表,那么我们就要根据指针,给这两个对象建立关系
// likNode.next=curNext;
}
/**
* 获取链表中所有的值
* 1、首先从head中获取,直接循环head
*/
public void list() {
if (head == null) {
System.out.println("链表数据为空");
return;
}
DoubleLikNode temp = head;
//当链表中数据不为空,我们就遍历head
while (true) {
if (temp.next == null) {
break;
}
System.out.println(temp.next);
temp = temp.next;
}
}
/**
* 修改链表中的元素,根据编号no,
* 双向链表就直接定位temp.no==编号就可以了,因为是前后都有指针指向,所以就不像单链表那样需要依靠上一节点了
*/
public void update(DoubleLikNode likNode) {
DoubleLikNode temp = head;
boolean flag = true; // 标记找不到匹配的no
while (true) {
if (temp.next == null) {
if (temp.no==likNode.no){
temp.setNext(likNode.getNext());
temp.setName(likNode.getName());
flag = false;
break;
}
break;
}
if (temp.no==likNode.no){
temp.setNext(likNode.getNext());
temp.setName(likNode.getName());
flag = false;
break;
}
// 遍历去找下一个节点
temp = temp.next;
}
if (!flag) {
System.out.println("说明我们修改了数据");
} else {
System.out.println("根据no编号没有找到需要修改的数据!!!");
}
}
/**
* 删除链表中的元素,
* 双向链表:删除时候不需要依赖上一个节点了在,直接根据编号匹配,然后取消当前关系,重现建立关系
* ===单向链表:步骤,删除元素,需要我们根据no匹配到相同的编号的结点,那么我们只需要,把当前节点的上一个节点的next指针指向当前的节点的下一个节点
*/
public void delete(DoubleLikNode node){
DoubleLikNode temp = head;
boolean flag =true;
while (true){
if (temp.next==null){
if (temp.no==node.no) {
//取消当前节点和前后指针关系,让前节点指向后节点,后节点指向前节点
temp.next.pre=temp.pre;
temp.pre.next=temp.next;
// 找到了此节点打个标记
flag=false;
break;
}
break;
}
// 找到当前节点
// if (temp.next.no==node.no) {
// temp.next=temp.next.next;
// // 找到了此节点打个标记
// flag=false;
// break;
// }
if (temp.no==node.no) {
//取消当前节点和前后指针关系,让前节点指向后节点,后节点指向前节点
temp.next.pre=temp.pre;
temp.pre.next=temp.next;
// 找到了此节点打个标记
flag=false;
break;
}
temp=temp.next;
}
if(!flag){
System.out.println("删除了此节点!!");
}else{
System.out.println("没有找到此编号的节点,无法删除操作");
}
}
/**
* 反转链表
* 双向链表:由于每个节点存在前一个节点的引用,那么我们就可以用pre前节点来遍历
* ===单向链表;步骤:创建另一个链表,然后把当前链表的元素从第一个取出,放到另一个链表中,然后取出第二个放到第一个之前,以此类推。
*/
public void reverse(){
DoubleLikNode temp = head;
DoubleLikNode wei = null;
while(true){
// 我们先拿到最后一个节点元素
if(temp.next==null){
wei=temp; //把最后一个节点放入wei的变量中
break;
}
temp= temp.next;
}
// 把尾指向的pre指针指向head
// 根据pre遍历数据
System.out.println(wei);
while(true){
if (wei.pre==null){
break;
}
System.out.println(wei.pre);
wei=wei.pre;
}
// 上面是注释,下面这个是第二遍
// DoubleLikNode temp = head.next;
// DoubleLikNode newHead = new DoubleLikNode();
// DoubleLikNode next = null;
// while (true){
// if(temp==null){break;}
// next=temp.next;
// temp.next= newHead.next;
// newHead.next=temp;
// temp=next;
// }
// head.next=newHead.next;
}
/**
* 通过栈进行数据的反转
*/
public void reverseByStack(){
DoubleLikNode temp = this.head;
DoubleLikNode cur =null;
Stack<DoubleLikNode> stack = new Stack();
while (true){
if (temp.next==null) {
stack.push(temp);
break;
}
//获取当前对象进行压栈/入栈操作
stack.push(temp);
temp=temp.next;
}
System.out.println("开始出栈!!!");
// stack.isEmpty(); 出栈
while (stack.size()>0){
System.out.println(stack.pop());
}
System.out.println();
}
}
class DoubleLikNode {
public int no; //行号
public String name; //名称
public DoubleLikNode next; //指向下一节点对象的指针
// 相比较与单项链表,多了一个指向前一节点对象的指针
public DoubleLikNode pre;
public DoubleLikNode() {
}
public DoubleLikNode(final int no, final String name) {
this.no = no;
this.name = name;
}
@Override
public String toString() {
return "DoubleLikNode{" +
"no=" + no +
", name='" + name + '\'' +
// ", next=" + next +
", pre=" + pre +
'}';
}
public int getNo() {
return no;
}
public void setNo(final int no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
public DoubleLikNode getNext() {
return next;
}
public void setNext(final DoubleLikNode next) {
this.next = next;
}
public DoubleLikNode getPre() {
return pre;
}
public void setPre(final DoubleLikNode pre) {
this.pre = pre;
}
}
6:数组实现栈结构
数组实现栈结构,上一个写的是链表反转也可以实现栈结构,先进后出 *1、创建栈类 * * 2、属性:// 定义数组 // 栈长度// 栈顶元素位置 * * 3、方法:push() , pop()
/**
* 数组实现栈结构,上一个写的是链表反转也可以实现栈结构,先进后出
*1、创建栈类
* * 2、属性:// 定义数组 // 栈长度// 栈顶元素位置
* * 3、方法:push() , pop()
* @author mgq
* @create 2021-03-08 1:38
*/
public class ArrayStackDemo {
public static void main(String[] args) {
System.out.println("测试栈!!!");
ArrayStack arrayStack = new ArrayStack(5); //设置栈最大深度
while (true) {
System.out.println("a:往栈中插入数据");
System.out.println("b:从栈中取数据");
System.out.println("c:查看栈中数据");
System.out.println("d:退出测试");
Scanner scanner = new Scanner(System.in);
String i = scanner.next();
switch (i){
case "a" :
System.out.println("请输入已添加的值");
int a = scanner.nextInt();
arrayStack.push(a);
System.out.println("添加成功");
break; //可选
case "b" :
int pop = arrayStack.pop();
System.out.println("取出的值是:"+pop);
break;
case "c" :
arrayStack.list();
break;
case "d" :
return;
default:
break;
}
}
}
/**
* 1、创建栈类
* 2、属性:
* 3、方法:
*/
static class ArrayStack{
// 定义数组
private int [] array;
// 栈长度
private int maxLength=0;
// 栈顶元素位置
private int top=-1;
// 设置栈的最大深度
public ArrayStack(int length){
array=new int[length];
maxLength=length;
}
// 往栈中添加元素,压栈 、 入栈
public void push(int value){
top++;
array[top]=value;
}
// 从栈顶取出元素,也就是出栈
public int pop(){
// array[top];
int value = array[top];
array[top]=0;
top--;
return value;
}
public void list(){
for (int i = 0; i < array.length; i++) {
if (array[i]!=0) {
System.out.println(array[i]);
}
}
}
public int[] getArray() {
return array;
}
public void setArray(final int[] array) {
this.array = array;
}
public int getMaxLength() {
return maxLength;
}
public void setMaxLength(final int maxLength) {
this.maxLength = maxLength;
}
public int getTop() {
return top;
}
public void setTop(final int top) {
this.top = top;
}
}
}
7:哈希表 HashTable实现(数组+链表)
* HashTable 哈希表实现:用数组+链表实现哈希表 * 读取速度快,占用内存小 * 数组= SingleLinketListDemo[size] ,其中 数组中根据对象的id/size取模,决定对象存在哪个链表中 * 链表= SingleLinketListDemo * SingleLinketListDemo的链表是无head,那么就把第一个传进的对象作为head头,如果有,就用原来head
/**
* HashTable 哈希表实现:用数组+链表实现哈希表
* 读取速度快,占用内存小
* 数组= SingleLinketListDemo[size] ,其中 数组中根据对象的id/size取模,决定对象存在哪个链表中
* 链表= SingleLinketListDemo
* SingleLinketListDemo的链表是无head,那么就把第一个传进的对象作为head头,如果有,就用原来head
* @author mgq
* @create 2021-03-11 14:15
*/
public class HashTableDemo {
public static void main(String[] args) {
HashTable hashTable = new HashTable(10);
Student stu = new Student();
stu.setId(1);
stu.setName("张三");
Student stu1 = new Student();
stu1.setId(11);
stu1.setName("李四");
Student stu2 = new Student();
stu2.setId(2);
stu2.setName("王五");
hashTable.add(stu);
hashTable.add(stu1);
hashTable.add(stu2);
hashTable.list(stu.id);
hashTable.listAll(stu.id);
hashTable.list(stu2.id);
hashTable.listAll(stu2.id);
/**
* 链表中节点为:Student{id=1, name='张三'}
* 链表中节点为:Student{id=1, name='张三'}
* 链表中节点为:Student{id=11, name='李四'}
* 链表中节点为:Student{id=2, name='王五'}
* 链表中节点为:Student{id=2, name='王五'}
*/
}
/**
* 实现哈希表思路:
* 结构: 由数组+链表组成
* 数组:数组中存在的是链表的数组,里面是链表对象
* 链表:存储对象,注意:其中要存储的对象,是根据数组中取模后的id值,来规定是否存储在哪个数组对应的链表上
* 链表中,如果是有head节点,那么数组存的就是所有的head头,
* * 如果没有head头节点的,那么存储的就是第一个存进来的对象。
* 注意: 数组中每个值都对应一个链表
*/
//---------------------------分割线---------------
/**
* 首先定义学生一个对象:单向链表的(next属性),也可以定义是双向链表的(next属性,pre属性)
*/
static class Student{
private int id;
private String name;
public Student next; // 指向下一个节点的引用指针,这个是单向链表
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
public Student getNext() {
return next;
}
public void setNext(final Student next) {
this.next = next;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(final int id) {
this.id = id;
}
}
// ------------------------------------分割线----------------------
/**
* 定义无head单向链表
*/
static class SingleLinketListDemo{
// 标注:因为当head是空,那么这就是无head链表,如果head=new Student ,这就是有head链表,这次我用无head链表实现
// 直接让上来的第一个节点对象,赋值给head,这样就没有head了
private Student head=null;
/**
* 链表的添加方法
* @param stu
*/
public void add(Student stu){
if (head==null) {
// 如果head等于空,直接把stu赋值给head
head=stu;
return;
}
// 如果head不等于null,说明该链表已经节点,那么循环判断子节点,直接获取到最后节点即可
Student temp =head;
while(true){
if (temp.next==null) {
// 说明到了最后的节点,直接把stu赋值给temp.next
// 标注 ,如果需要根据id,进行插入的数据有序,就看我专门去写的链表demo中有了
temp.next=stu;
return;
}
// 节点后移
temp=temp.next;
}
}
/**
* 循环遍历节点,放到head上
*/
public void list(int id){
Student temp=head;
if(head==null){
System.out.println("链表为空");
return;
}
// System.out.println("链表中节点为:"+temp.toString());
while (true) {
if(temp.getId()!=id){
break;
}
if (temp.next==null) {
//遍历到了最后一个节点
System.out.println("链表中节点为:"+temp.toString());
break;
}
System.out.println("链表中节点为:"+temp.toString());
temp=temp.next;
}
}
/**
* 循环遍历节点,放到head上
*/
public void listAll(){
Student temp=head;
if(head==null){
System.out.println("链表为空");
return;
}
// System.out.println("链表中节点为:"+temp.toString());
while (true) {
if (temp.next==null) {
//遍历到了最后一个节点
System.out.println("链表中节点为:"+temp.toString());
break;
}
System.out.println("链表中节点为:"+temp.toString());
temp=temp.next;
}
}
}
//---------------------------------分割线---------------------
/**
* 创建 哈希表对象
* --属性 ;只有数组
*/
static class HashTable{
private SingleLinketListDemo [] linketList;
private int length;
/**
* 有参构造,初始化SingleLinketListDemo[]数组
* @param size
*/
public HashTable(int size){
linketList=new SingleLinketListDemo[size];
length =size;
// int [] a =[5],a中的值会默认是0,但是如果是对象类型的 数组,会默认为null,
// 所以需要遍历,把linketList 中的值先初始化为一个new SingleLinketListDemo()单向链表对象;
for (int i = 0; i < linketList.length; i++) {
linketList[i]=new SingleLinketListDemo();
}
}
/**
* 哈希表的添加
*/
public void add(Student stu){
// 调用id取模方法,判断此对象应该放入数组哪个下标下的链表
int i = hashFun(stu.getId());
// 根据取模后的id,放入对应的下标的数组中,然后调取add方法
linketList[i].add(stu);
}
/**
* 根据id查询数据
*/
public void list(int id){
int i = hashFun(id);
linketList[i].list(id);
}
/**
* 根据id查询数据
*/
public void listAll(int id){
int i = hashFun(id);
linketList[i].listAll();
}
/**
* 1、对id取模,相同取模后的值对应的对象,就放到同一个链表内
* 并且,当多个id数据添加后,只要取模值一样说明在数组中的下标一样,就放入同一链表
*/
public int hashFun(int id){
// 对id取模
return id%length;
}
public SingleLinketListDemo[] getLinketList() {
return linketList;
}
public void setLinketList(final SingleLinketListDemo[] linketList) {
this.linketList = linketList;
}
public int getSize() {
return length;
}
public void setSize(final int size) {
this.length = size;
}
}
}
8:二叉树数据结构
* 二叉树数据结构实现 * * 属性:left,right左右节点 * * 方法:前序 、中序、后序遍历二叉树,delNode 删除节点方法
/**
* 二叉树数据结构实现
* * 属性:left,right左右节点
* * 方法:前序 、中序、后序遍历二叉树,delNode 删除节点方法
* @author mgq
* @create 2021-03-11 21:08
*/
public class BinaryTreeDemo {
// public static void main(String[] args) {
// // 定义二叉树
// BinaryNode binaryNode1 = new BinaryNode(1,"二叉树第一个节点");
// BinaryNode binaryNode2 = new BinaryNode(2,"二叉树左侧第二个节点");
// binaryNode1.setLeft(binaryNode2);
// BinaryNode binaryNode3 = new BinaryNode(3,"二叉树右侧三个节点");
// binaryNode1.setRight(binaryNode3);
// BinaryNode binaryNode4 = new BinaryNode(4,"二叉树左侧第四个节点");
// binaryNode2.setLeft(binaryNode4);
// BinaryNode binaryNode5 = new BinaryNode(5,"二叉树右侧第五个节点");
// binaryNode2.setRight(binaryNode5);
// System.out.println("前序遍历二叉树======================");
//
// // 前序查询二叉树,看打印循序
// binaryNode1.preSearch();
// System.out.println("中序遍历二叉树======================");
// // 中序遍历二叉树
// binaryNode1.midSearch();
// System.out.println("后序遍历二叉树======================");
// // 后序遍历二叉树
// binaryNode1.afterSearch();
//
// // 删除节点方法
// binaryNode1.delNode(2);
// // 遍历
// binaryNode1.preSearch();
//
// }
/**
* 测试有序数组 用 二叉树来遍历,只适用于完全二叉树,子节点在同一层级
* @param args
*/
public static void main(String[] args) {
int [] arr = {1,2,3,4,5,6,7,8,9};
BinaryTree binaryTree = new BinaryTree(arr);
binaryTree.preSearchSort(0);
}
}
/**
* 创建二叉树节点对象
* 属性:left,right左右节点
* 方法:前序 、中序、后序遍历二叉树
*/
class BinaryNode{
private int no; //序号
private String name; //名称
private BinaryNode left; //左节点
private BinaryNode right; //右节点
/**
* 前序查找
* 步骤:
* 1、先获取当前节点
* 2、递归获取左侧子节点
* 3、递归获取右侧子节点
*/
public void preSearch(){
// 打印当前节点
System.out.println(this.toString());
// 递归左侧子节点
// preSearch(this.left);
if (this.left!=null) {
this.left.preSearch();
}
// 递归右侧子节点
// preSearch(this.right);
if (this.right!=null){
this.right.preSearch();
}
}
/**
* 中序查找
* 1,递归获取左侧子节点
* 2、当前节点
* 3、递归右侧子节点
*/
public void midSearch(){
// 递归左侧子节点
// preSearch(this.left);
if (this.left!=null) {
this.left.preSearch();
}
// 打印当前节点
System.out.println(this.toString());
// 递归右侧子节点
// preSearch(this.right);
if (this.right!=null){
this.right.preSearch();
}
}
/**
* 后序遍历
* 1,递归获取左侧子节点
* 2、递归右侧子节点
* 3、当前节点
*/
public void afterSearch(){
// 递归左侧子节点
// preSearch(this.left);
if (this.left!=null) {
this.left.preSearch();
}
// 递归右侧子节点
// preSearch(this.right);
if (this.right!=null){
this.right.preSearch();
}
// 打印当前节点
System.out.println(this.toString());
}
/**
* 根据编号删除二叉树节点
* @param no
*/
public void delNode(int no){
// 根据编号查找到二叉树父节点的子节点,因为而插入是像链表一样,只能通过父节点找到找到当前节点
// 递归左节点查询是否编号是否符合,如果符合就给节点置为null即可
if (this.left.no==no) {
this.left=null;
return;
}
// 递归右节点查询是否编号是否符合,如果符合就给节点置为null即可
if (this.right.no==no) {
this.right=null;
return;
}
// 如果向左节点没有匹配就开始向左递归查询
this.left.delNode(no);
// 如果向右节点没有匹配就开始向右递归查询
this.right.delNode(no);
}
public BinaryNode() {
}
public BinaryNode(final int no, final String name) {
this.no = no;
this.name = name;
}
public BinaryNode getLeft() {
return left;
}
public void setLeft(final BinaryNode left) {
this.left = left;
}
public BinaryNode getRight() {
return right;
}
public void setRight(final BinaryNode right) {
this.right = right;
}
@Override
public String toString() {
return "BinaryNode{" +
"no=" + no +
", name='" + name + '\'' +
'}';
}
public int getNo() {
return no;
}
public void setNo(final int no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
}
/**
* 只是用与完全二叉树
* 用数组存储编号,用二叉树实现 前序 ,中序,后序遍历查询
* 注意:==因为用数组存储了数据,只是用二叉树遍历,那么就不需要规定left和right节点了
*/
class BinaryTree{
private int [] arr;
/**
* 前序查找方法,只是用与完全二叉树,也就是子节点都是同级节点
* // 元素左子节点下角标=2*n+1
* // 元素右子节点下角标 = 2*n+2
* // 元素父节点下角标 = (n-1)/2
* n表示当前节点的下角标,
* 上面说的都是在数组中的下角标
*/
public void preSearchSort(int n){
System.out.println(arr[n]);
// 向左递归判断首先2n+1一定要小于arr数组长度
if ((2*n+1)<arr.length) {
// 元素左子节点下角标=2*n+1
preSearchSort(2*n+1);
}
// 向右递归判断首先2n+1一定要小于arr数组长度
if ((2*n+2)<arr.length) {
// 元素右子节点下角标 = 2*n+2
preSearchSort(2*n+2);
}
}
public BinaryTree(int[] arr) {
this.arr = arr;
}
public int[] getArr() {
return arr;
}
public void setArr(final int[] arr) {
this.arr = arr;
}
}
排序算法
1:冒泡排序算法
// 冒泡排序规则,首先比较前两个数,找出最大的数,让一个变量接收,然后调换两个位置,然后再一次类推比下去,最后确定出最后一位一定是最大的值, // 然后下一次循环,不用在比较最后一位了,类似还是和上面一样比较。所以冒泡排序循环的次数是length-1次,并且时间复杂度是O(n^2) , // 也就是n的平方,因为是两个for嵌套循环:@:
/**
* 冒泡排序算法
* 时间复杂度是O(n^2)
* @author mgq
* @create 2021-03-09 14:39
*/
public class MaoPaoSort {
public static void main(String[] args) {
// 冒泡排序规则,首先比较前两个数,找出最大的数,让一个变量接收,然后调换两个位置,然后再一次类推比下去,最后确定出最后一位一定是最大的值,
// 然后下一次循环,不用在比较最后一位了,类似还是和上面一样比较。所以冒泡排序循环的次数是length-1次,并且时间复杂度是O(n^2) ,
// 也就是n的平方,因为是两个for嵌套循环
// ===============冒泡排序算法实现步骤:
// 定义乱序数组
int [] arr = {2,1,4,3,8,7,6,5 };
// 有多少元素,就循环length-1 次,因为最后就剩下一个最前面的元素,也不用比较了
for (int j = 0; j < arr.length-1; j++) {
// -----------------一次小循环
// 实现两个元素对比,然后位置互换,这个试一次循环
int temp = 0;
for (int i = 0; i < arr.length-1-j; i++) {
// 如果当前元素大于后面的元素,那么付给temp 变量,然后调换位置
if (arr[i]>arr[i+1]) {
temp=arr[i]; // 暂时付给变量
arr[i]=arr[i+1]; //后面元素赋值给当前位置元素,也就是调换位置
arr[i+1]=temp;
}
}
//---------------------------end
}
// 打印结果
System.out.println(Arrays.toString(arr));
}
}
2:选择排序算法
// 选择排序:在一个数组中,先把两个元素相互比较,然后把小的那个值,赋值给temp变量,还有坐标给index变量赋值,然后用这个temp的值, // 依次去和后面的比,如果有比他小的,就把小的值和索引值,付给temp和index变量,循环完然后,把temp和index 和 第一个元素位置互换, // z这是一次循环,那么后面再次循环依次类推,再从第二个元素开始,再次找到最小的,和第二个元素互换位置,以此类推!
/**
* 选择排序算法
* 时间复杂度是O(n^2)
* @author mgq
* @create 2021-03-09 14:56
*/
public class XuanZeSort {
public static void main(String[] args) {
// 选择排序:在一个数组中,先把两个元素相互比较,然后把小的那个值,赋值给temp变量,还有坐标给index变量赋值,然后用这个temp的值,
// 依次去和后面的比,如果有比他小的,就把小的值和索引值,付给temp和index变量,循环完然后,把temp和index 和 第一个元素位置互换,
// z这是一次循环,那么后面再次循环依次类推,再从第二个元素开始,再次找到最小的,和第二个元素互换位置,以此类推!
// ===============选择排序算法实现步骤:
// 定义乱序数组
int [] arr = {2,1,4,3,8,7,6,5 };
// 外面循环只需要循环length-1次,因为最后一个值一定是最大的,就不用了在循环了
for (int j = 0; j < arr.length-1; j++) {
// ---------------------一次小循环-----
int temp= arr[j]; //先取出第一个元素,然后和后面所有元素对比
int index=j; // 第一个元素的索引是 0
for (int i = j; i < arr.length; i++) {
//如果当前元素小于temp变量中的元素,就把当前元素值和索引放入temp和index中
if (arr[i]<temp) {
temp = arr[i];
index=i;
}
}
// 把temp中的值给 第一个元素,把一个元素和 index位置互换,这样最小值就在第一个位置了
arr[index]=arr[j];
arr[j]=temp;
//-------------------------------end------------
}
// 打印结果
System.out.println(Arrays.toString(arr));
}
}
3:插入排序算法
时间复杂度是O(n^2)// 插入排序:插入排序是把一个数组,默认分成两段去看待,一个是有序数组,一个是无序数组,例如【【4,5】【7,8,9,4】】,但其实还是一个数组, // 那么有序数组中默认数据中第一个就是有序数组的元素,那么剩下无序的,循环从无序数组中取一个,然后和有序中的数组比较,然后放到相应位置, // 那么,当所有元素都到有序数组中的时候,就排序成功了
4:希尔排序==插入升级版
5:快速排序算法
* 快速排序是基于冒泡排序的一种改进,基本的实现步骤: * 1:定义数组,然后取数组的一个中间的元素的值,以中间值把数组分为左侧和右侧,然后定义为左侧是小于中间值得,右边大于中间值, * 然后递归 ,如果左边数据有大于中间值得,并且右边有小于中间值得话,那么我们就把两个数相互交换,依次类推下去。 * 2:时间复杂度O(nlogn) 属于线性对数阶
/**
* 快速排序算法
* java里/和%的区别
* 1》 / 取得是两个数相除后的整数部分。
* 2》 % 取得是两个数相除后的余数部分。
* @author mgq
* @create 2021-03-09 23:06
*/
public class KuaiSuSort {
public static void main(String[] args) {
int [] arr ={-9,78,0,23,-567,70};
// int [] arr ={2,-9,6,3,5,4,-1};
// int [] arr ={2,2,2};
//[-1, 2, 4, 5, 6, 7, 9]
//[9, 7, 6, 5, 4, 2, -1]
kuaisu(arr,0,arr.length-1);
System.out.println(Arrays.toString(arr));
}
/**
* 快速排序是基于冒泡排序的一种改进,基本的实现步骤:
* 1:定义数组,然后取数组的一个中间的元素的值,以中间值把数组分为左侧和右侧,然后定义为左侧是小于中间值得,右边大于中间值,
* 然后递归 ,如果左边数据有大于中间值得,并且右边有小于中间值得话,那么我们就把两个数相互交换,依次类推下去。
* 2:时间复杂度O(nlogn) 属于线性对数阶
*/
/**
*
* @param arr 数组
* @param left 左边开始坐标
* @param right 右边开始坐标
*/
public static void kuaisu(int [] arr , int left ,int right){
int l =left; //左边开始索引值
int r = right; // 右边开始索引值
int middle = (left+right)/2; //求出r中间得位置索引
int temp =0; //临时交换变量
// l<r 那么就是一直没有循环到中间值,那么就一直循环下去r,什么时候等l==right,那么就停止
while (l<r){
// 如果左边的值一直小于中间值,那么就一直++索引,一直到中间值停止,或者有大于中间值得就停止,然后l==当前值得坐标
while (arr[l]<arr[middle]){
l++;
}
// 如果右边的值一直大于中间值,那么就一直--索引,一直到中间值停止,或者有小于中间值得就停止,然后r==当前值得坐标
while (arr[r]>arr[middle]){
r--;
}
// 左边都是大于中间值得这种情况,这种情况r--,会变成负数,这种情况在下面互换位置时候会出现 数组索引 异常
if(l>=r){
break;
}
// 交换位置,把左边数据大于中间值得数据,和 右边数据小于中间值得数据,进行数据的交换位置,通过一个temp临时变量进行转换
temp = arr[l];
arr[l]= arr[r];
arr[r]=temp;
// 这种情况是 当l<right,但是左边的arr[l]==arr[middle]和中间的值相等了,那么这种情况就是,在左边出现了和中间值相等情况。。
// 为了避免死循环,那么我们就直接往前--,不理会这种数据
// == 这个意思是当左边l 或者 右边r 挪到中间了,那么剩下的就让另一边自己挪动
if (arr[l]==arr[middle]) {
// l++;
// 这块注意一定不要写反了,这块出的问题找了半天
r--;
}
// 那么同理,右边也是
if (arr[r]==arr[middle]) {
// r--;
// 这块注意一定不要写反了,这块出的问题找了半天
l++;
}
}
// 否则会出现栈溢出
if (l==r) {
l++;
r--;
}
// 必须要放在for循环外边,因为上面操作是把左边所有元素和右边所有元素 和中间值对比后相互互换的过程,所以放在外面
// 然后递归左边,因为左边[2, -9, -1, 3, 5, 4, 6]中的2, -9, -1,只能保证比3小,并不能保证顺序,所以,要递归上面的操作最后保证左边顺寻
// 传入三个参数,第一个还是整个数组,但是由于第二遍是做百年2, -9, -1,那么我们只需要限制索引范围即可
// left是左边界
// if(left<l){
// kuaisu(arr,left,l);
// }
// 这块注意一定不要写反了,这块出的问题找了半天
// r是--
if(left<r){
kuaisu(arr,left,r);
}
// 递归右边,因为左边[2, -9, -1, 3, 5, 4, 6]中的 5, 4, 6,只能保证比3大,并不能保证顺序,所以,要递归上面的操作最后保证右边顺寻
// 传入三个参数,第一个还是整个数组,但是由于第二遍是做百年5, 4, 6,那么我们只需要限制索引范围即可
// right是右边界
// if(right>r){
// kuaisu(arr,r,right);
// }
// 这块注意一定不要写反了,这块出的问题找了半天
// 因为l是++
if(right>l){
kuaisu(arr,l,right);
}
}
}
6:归并排序
时间复杂度O(nlogn)* 归并排序,是经典的 先 分 后 合的算法,其中先把数组中元素,递归分成一个,然后再比较大小后,再合并,然后再比较,然后再合并,再比较,以此类推
/**
* 归并排序
* 时间复杂度O(nlogn)
* @author mgq
* @create 2021-03-10 1:57
*/
public class GuiBingSort {
public static void main(String[] args) {
int [] arr ={2,-9,6,3,5,4,-1};
int [] temp=new int[arr.length];
fenlie(arr,0,arr.length-1,temp);
System.out.println(Arrays.toString(arr));
}
/**
* 归并排序,是经典的 先 分 后 合的算法,其中先把数组中元素,递归分成一个,然后再比较大小后,再合并,然后再比较,然后再合并,再比较,以此类推
*/
/**
* 归并步骤之一:分裂过程
*/
public static void fenlie(int [] arr ,int left,int right,int [] temp){
if (left<right){
// 分裂过程
int mid = (left+right)/2; //中间索引
// 递归调用本方法,分裂左边,向左递归
fenlie(arr,left,mid,temp);
// 递归调用本方法,分裂右边,向右递归
fenlie(arr,mid+1,right,temp);
//合并过程
merge(arr,left,mid,right,temp);
}
}
/**
* 归并步骤之二: 合并过程
* @param arr 原数组
* @param left 左边的起索引
* @param mid 中间索引
* @param right 右边索引
* @param temp 临时数组,用来接收排序后的数组,然后再复制给arr
*/
public static void merge(int [] arr ,int left ,int mid , int right,int [] temp ){
//数组[1,2,3,4,5,6],其中123 属于左边,456属于右边 ,然后把左边和右边元素一个个对比,元素小的放到temp中
// temp []
int l=left; //左边元素起位置
int r=mid+1; //右边元素起位置
int t =0; //temp 数组从0开始
// 1: 限制l小于mid中间索引 ,r<右边界索引
while (l<=mid && r<=right){
// 如果
if (arr[l]<=arr[r]) {
// 把对比小的元素 赋值 到 temp中,然后t++,l++,继续下一个元素对比
temp[t]=arr[l];
t++;
l++;
}else{
// 反之,则把arr[r]放到temp中,然后 ++
temp[t]=arr[r];
t++;
r++;
}
}
// 2: 如果左边的没有放完到temp中,但是右边已经没有元素可以对比了,那么把左边剩余的都放到temp中
while (l<=mid){
temp[t]=arr[l];
t++;
l++;
}
// 3: 如果右边的没有放完到temp中,但是左边已经没有元素可以对比了,把右边剩余的都放到remp中
while (r<=right){
temp[t]=arr[r];
t++;
r++;
}
// 4:把临时数组temp放入到arrz中
t=0;
int tempIndex = left;
// tempIndex 0,right 1; tempIndex 2,right 3;tempIndex 0,right 3,
while (tempIndex<=right){
arr[tempIndex] = temp[t];
tempIndex++;
t++;
}
}
}
7:基数排序(基于桶排序实现)
基数排序== * 时间复杂度O(n*k) 线性阶, * 特点: * 速度非常快, * 缺点:典型的空间换时间,因为需定义一个二维数组来表示组桶,也就是数组,很占用内存,并且很难处理排序数组中出现负数的情况,一般有负数就不用了桶排序
* 基数排序是根据桶排序实现的算法: * --实现步骤: 假如有一组数组arr=【1,11,123,22,55】,那么数组中最大位数是123三位,所有,我们定义三组桶,每组桶都有十个桶, * 第一组桶,0-9循序桶号,然后根据每个元素的个位是什么就放到哪个桶去,然后取出再放到arr数组中,此时数组中为arr=【1,11,22,123,55】,这个是根据个位数排序了 * 第二组桶,0-9循序通号,然后根据每个元素的十位去放入桶中,然后放入后,在全部取出来放到arr中,此时arr【1,11,22,123,55】,这是根据十位排序 * 第三组桶,0-9通号,然后根据百位去放入桶中,再取出来,arr[1,11,22,55,123] ,那么就全部排序成功 * 这种时间复杂度是对数阶的,速度很快,单是由于每个桶是一个数组,会很占用内存,典型的空间换时间
/**
* 基数排序==
* 时间复杂度O(n*k) 线性阶,
* 特点:
* 速度非常快,
* 缺点:典型的空间换时间,因为需定义一个二维数组来表示组桶,也就是数组,很占用内存,并且很难处理排序数组中出现负数的情况,一般有负数就不用了桶排序
* @author mgq
* @create 2021-03-10 3:13
*/
public class JiShuSort {
public static void main(String[] args) {
// 要排序的数组
int [] arr={1,11,123,22,55};
bucketSort(arr);
System.out.println(Arrays.toString(arr));
}
/**
* 基数排序是根据桶排序实现的算法:
* --实现步骤: 假如有一组数组arr=【1,11,123,22,55】,那么数组中最大位数是123三位,所有,我们定义三组桶,每组桶都有十个桶,
* 第一组桶,0-9循序桶号,然后根据每个元素的个位是什么就放到哪个桶去,然后取出再放到arr数组中,此时数组中为arr=【1,11,22,123,55】,这个是根据个位数排序了
* 第二组桶,0-9循序通号,然后根据每个元素的十位去放入桶中,然后放入后,在全部取出来放到arr中,此时arr【1,11,22,123,55】,这是根据十位排序
* 第三组桶,0-9通号,然后根据百位去放入桶中,再取出来,arr[1,11,22,55,123] ,那么就全部排序成功
* 这种时间复杂度是对数阶的,速度很快,单是由于每个桶是一个数组,会很占用内存,典型的空间换时间
*/
public static void bucketSort(int [] arr){
/**
* 0 0 0 0 0 0 0 0 0 0
* 1 0 0 0 0 0 0 0 0 0
* 2 0 0 0 0 0 0 0 0 0
* 3 0 0 0 0 0 0 0 0 0
* 4 0 0 0 0 0 0 0 0 0
* 5 0 0 0 0 0 0 0 0 0
* 6 0 0 0 0 0 0 0 0 0
* 7 0 0 0 0 0 0 0 0 0
* 8 0 0 0 0 0 0 0 0 0
* 9 0 0 0 0 0 0 0 0 0
*/
// 定义一个二维数组,用来管理十个桶,Y表示桶序号 0-9 序号,X表示一个桶,桶的深度为元素的长度,
// 0-9如上所示 ,每一个横行表示一个桶
int y =10; // Y表示桶序号 0-9 序号
int x =arr.length; // X表示一个桶,桶的深度为元素的长度
int [][] bucketNumber = new int[y][x];
int indx = arr[0];
// 求出arr中,位数最大的值
for (int i = 0; i < arr.length; i++) {
if( indx<arr[i] ){
indx=arr[i]; // 把位数大的数字放到临时变量ind中
}
}
// 计算出最大位数,然后就循环定义几组桶,每组十个桶
int weishuLength = (indx + "").length();
System.out.println("最大位数::"+weishuLength);
// 根据 位数 循环 个位数,十位数 ,百位数 组桶
for (int m = 0,n=1; m < weishuLength; m++,n*=10) {
//=============================================分割线,记录第一组根据个位数十个桶的方法==================================
// 定义一个桶,用来统计临时存放每个桶中元素的个数的,因为这样才下一个元素要存放的话,才能找到存放在二维数组中桶内元素要存放的位置
// 这表示0-9 十个桶,0-9索引 临时统计 0-9桶中有多少个元素,每插入一个元素,就往相应桶号的位置,记录++
int [] temp = new int[10];
// 拿出arr所有元素的个位数,然后依次按照个位数找到对应的桶,放入里面
for (int i = 0; i < arr.length; i++) {
// int number = arr[i] % 10; // 对元素取余过后就是 ==个位数,也就是==桶序号
int number = arr[i] / n % 10; // 这块根据位数循环,动态去取个位还是十位后者百位的那组桶序号
// 找到序号后,把元素放入到对应序号的桶中
bucketNumber[number][temp[number]]=arr[i];
// 记录本次number序号桶中元素 ++ ,那么下次在存放,在二维数组中的位置就是这个元素的下一个位置了
temp[number]++;
}
// 所有的元素都放入桶中后,从桶中在取出元素,重新放入到arr数组中
// 循环二维数组,依次从桶中取出数据,放入arr数组中
int tempIndex =0;
for (int i = 0; i < bucketNumber.length; i++) {
for (int j = 0; j < bucketNumber[i].length; j++) {
// 判断如果桶中有数据,就存放,不判断的话,就出现数组越界,会把所有二维数组元素都给arr[]
if (bucketNumber[i][j]!=0){
// 从0号桶开始取元素,直到第9号桶,然后依次放入arr[]数组中
arr[tempIndex] = bucketNumber[i][j];
// arr中每放入一个元素,就++,放下一个
tempIndex++;
// 注意:每次大循环后,到了下一组桶前,需要先把二维数组中的数都清干净,以便于下一组桶存数据
bucketNumber[i][j]=0;
}
}
}
// System.out.println(Arrays.toString(arr));
//=============================================分割线,记录第一组根据个位数十个桶的方法==================================
}
}
}
8:堆排序(基于二叉树实现)
* 堆排序--基于完全二叉树实现 * 满二叉树:除了叶子结点,每个节点的都有两个子节点,是满二叉树 * 完全二叉树:在满二叉树基础上,其叶子结点是从左到右分布的是完全二叉树 * 时间复杂度是 线性常数阶 O(nlogn),没有桶排序快,但是相对于桶排序省内存
/**
* 堆排序--基于完全二叉树实现
* 满二叉树:除了叶子结点,每个节点的都有两个子节点,是满二叉树
* 完全二叉树:在满二叉树基础上,其叶子结点是从左到右分布的是完全二叉树
* 时间复杂度是 线性常数阶 O(nlogn),没有桶排序快,但是相对于桶排序省内存
*
* @author mgq
* @create 2021-03-12 11:10
*/
public class DumpSort {
public static void main(String[] args) {
int [] arr={2,5,9,4,8,3,1};
// int [] arr={4,6,8,5,9};
froTree(arr,arr.length);
System.out.println(Arrays.toString(arr));
}
/**
* 循环完全二叉树,把所有的子树都像dumpTree方法一样,对比左右节点,然后大的一个子节点和父节点进行比较,大于父节点就进行交换
*/
public static void froTree(int arr[],int length){
// 获取最后一个非叶子节点
// dumpTree(arr,1,arr.length);
// 获取第一个非叶子节点
// dumpTree(arr,0,arr.length);
/**
* 公式:arr.length/2-1 能够获取最后一个非叶子节点,那么只需要i--,就可以依次从最后一个非叶子节点开始到最后顶节点,
* 然后循环对他们进行dumpTree()方法 ,也就是子节点比较,然后和父节点交换
*/
for (int j=length/2-1 ; j>=0;j--) {
dumpTree(arr,j,length);
}
// 临时变量接收数组长度,通过最顶的节点值和最低端的叶子结点互换,然后length--,这样数组的最后一位就是最大值,以此类推,直到排序成功
int tempLength = arr.length;
int tempMax =0;
for (int i = tempLength-1; i >0 ; i--) {
// 将顶端节点和叶子节点交换;
tempMax=arr[i];
// 将最小值赋值到定点
arr[i] =arr[0];
// 将最大值放到tempLength ,tempLength--,依次放入最大值
arr[0]=tempMax;
// System.out.println(Arrays.toString(arr));
// 将顶端节点和叶子节点交换,也就是将数组的最前面和最后面的交换
dumpTree(arr,0,i);
}
}
/**
* 找到二叉树的一个子树,先让左右子节点对比,其中大的一个子节点和当前父节点在比较,如果比父节点大,那么就交换,以此类推
* @param arr 原数组
* @param i 数组下标(先从最后一个节点开始遍历i= arr.length/2-1)
* @param length 数组长度,每次进行依次交换,就讲树定元素和树最后节点交换,那么对应的数组中最后的值就是最大的,然后以此类推
*/
public static void dumpTree(int arr[], int i, int length){
int temp = arr[i]; //临时变量先接收目前栈顶数据,用来后面和子节点交换使用
//注意 :这步很重要 :遍历当前树的左子节点,并且当左子节点还有子节点时候,就继续遍历
for (int k = 2*i+1; k <length ; k=2*k+1) {
// 2*n+1 获取当前节点的左侧子节点下角标,2*n+2 右侧子节点下角标
if ((k<length)) {
// 对比左侧和右侧值,哪侧值大就和当前节点交换
// k+1<length 判断父节点下还有没有右节点
if(k+1<length && arr[k]<arr[k+1]){
// 如果右子节点比父节点大的话,那么就把右子节点和父节点交换
if (arr[k+1]>temp) {
// 右侧比左侧大,把右侧赋值到当前节点,然后交换
arr[i]=arr[k+1];
// arr[k+1]=temp;
// 注意:这步很重要,这步保证了子节点转为父节点再去循环去上面同样操作,
i=k+1;
// 再把当前右子节点作为父节点给temp变量
// temp=arr[k+1];
}
}else{
// 如果右子节点比父节点大的话,那么就把右子节点和父节点交换
if (arr[k]>temp) {
// 左侧比右侧大,把左侧和当前节点交换
arr[i]=arr[k];
// arr[k]=temp;
// 注意:这步很重要,这步保证了子节点的子节点找到自己的父节点
i=k;
// 再把当前左子节点作为父节点给temp变量
// temp=arr[k];
}
}
}else{
break;
}
arr[i]=temp;
/*// 2*n+1 获取当前节点的左侧子节点下角标,2*n+2 右侧子节点下角标
if ((2*i+2<length)) {
// 对比左侧和右侧值,哪侧值大就和当前节点交换
if(arr[2*i+1]<arr[2*i+2]){
// 如果右子节点比父节点大的话,那么就把右子节点和父节点交换
if (arr[2*i+2]>temp) {
// 右侧比左侧大,把右侧赋值到当前节点,然后交换
arr[i]=arr[2*i+2];
arr[2*i+2]=temp;
}
}else{
// 如果右子节点比父节点大的话,那么就把右子节点和父节点交换
if (arr[2*i+1]>temp) {
// 左侧比右侧大,把左侧和当前节点交换
arr[i]=arr[2*i+1];
arr[2*i+1]=temp;
}
}
}*/
}
}
}
查找算法
1:线性查找
线性查找(顺序查找), * 数组可以是有序的也可以是无序的
/**
* 线性查找(顺序查找),
* 数组可以是有序的也可以是无序的
* @author mgq
* @create 2021-03-11 11:26
*/
public class XunXuSearch {
public static void main(String[] args) {
int [] arr={1,5,4,7,8,3,2};
int value =5; //需要找到的值
for (int i = 0; i < arr.length; i++) {
if (arr[i]==value) {
System.out.println("找到了,下角标为:index:"+i);
}
}
}
}
2:二分查找算法(二分递归算法)(二分非递归查找算法)
* 二分查找算法(二分递归算法)(二分非递归查找算法) * 需要查找的数组是有序的前提下, * 并且是根据数组的中间下标去查找的,先递归左边,在递归右边 * // 二分查找 和 插值查找 和 黄金分割查找 都是对 mid中间值优化的查找算法,其实现原理差不多 * // 二分查找 确定中间值 mid= (right+left)/2
// 二分查找 和 插值查找 和 黄金分割点查找 都是对 mid中间值优化的查找算法,其实现原理差不多 /** * 步骤: * 1:先确定中间值mid * 2:因为arr[]是有序数组,那么当要查找的值如果大于中间值,就往右递归查找 * 3:因为arr[]是有序数组,那么当要查找的值如果小于中间值,就向左递归查找 * 4:最后返回mid,就是最后匹配值后的下标* 二分查找非递归方法 * 根据mid, while循环向左和向右查找,直到reruen mid 返回下角标 * @param arr 数组 * @param findValue 需要查找的数值
/**
* 二分查找算法(二分递归算法)(二分非递归查找算法)
* 需要查找的数组是有序的前提下,
* 并且是根据数组的中间下标去查找的,先递归左边,在递归右边
* // 二分查找 和 插值查找 和 黄金分割查找 都是对 mid中间值优化的查找算法,其实现原理差不多
* // 二分查找 确定中间值 mid= (right+left)/2
* @author mgq
* @create 2021-03-11 11:30
*/
public class ErFenSearch {
public static void main(String[] args) {
// 二分递归
// int [] arr ={1,3,4,6,9,11,14,18};
// int findValue=18;
// int i = erFen(arr, 0, arr.length - 1, findValue);
// System.out.println(i);
// 二分不递归
int [] arr ={1,3,4,6,9,11,14,18};
int findValue=18;
int i = erFenNoDiGui(arr, findValue);
System.out.println(i);
}
/**
* 二分递归查找方法
* 根据mid,然后分别向左和向右递归,return mid,就是返回了下角标值
* @param arr 原数组
* @param left 左角标
* @param right 右角标
* @param findValue 要查找的值
*/
public static int erFen(int [] arr ,int left ,int right,int findValue){
System.out.println("二分算法查找了多少遍");
// 二分查找 和 插值查找 和 黄金分割点查找 都是对 mid中间值优化的查找算法,其实现原理差不多
/**
* 步骤:
* 1:先确定中间值mid
* 2:因为arr[]是有序数组,那么当要查找的值如果大于中间值,就往右递归查找
* 3:因为arr[]是有序数组,那么当要查找的值如果小于中间值,就向左递归查找
* 4:最后返回mid,就是最后匹配值后的下标
*/
// 1:确定中间值
int mid = (right+left)/2; // 结果取整
if(findValue>arr[mid]){ //因为arr[]是有序数组,那么当要查找的值如果大于中间值,就往右递归查找
return erFen(arr,mid+1,right,findValue);
}else if(findValue<arr[mid]){
//因为arr[]是有序数组,那么当要查找的值如果小于中间值,就向左递归查找
return erFen(arr,left,mid-1,findValue);
}else{
// 最后返回mid,就是最后匹配值后的下标
return mid;
}
}
/**
* 二分查找非递归方法
* 根据mid, while循环向左和向右查找,直到reruen mid 返回下角标
* @param arr 数组
* @param findValue 需要查找的数值
* @return
*/
public static int erFenNoDiGui(int arr [] ,int findValue){
int left=0;
int right=arr.length-1;
while (left<=right){
int mid = (left+right)/2;
// 如果不等于mid
if (arr[mid]==findValue) {
return mid;
}
// 首先数组是有序的,那么就可以根据值去判断是向左循环还是向右循环
if(arr[mid]>findValue){
// 向右旋转
right=mid-1;
}else {
//向左旋转
left=mid+1;
}
}
return -1;
}
}
3:插值查找算法:
* 需要查找的数组是有序的前提下 * 并且是根据数组的中间下标去查找的,先递归左边,在递归右边 * // 二分查找 和 插值查找 和 黄金分割查找 都是对 mid中间值优化的查找算法,其实现原理差不多 * 插值算法中间值规则 公式: * // int mid= left+(right-left)*(findVal-arr[left])/(arr[right]-arr[left])
/**
* 插值算法:
* 需要查找的数组是有序的前提下
* 并且是根据数组的中间下标去查找的,先递归左边,在递归右边
* // 二分查找 和 插值查找 和 黄金分割查找 都是对 mid中间值优化的查找算法,其实现原理差不多
* 插值算法中间值规则:
* // int mid= left+(right-left)*(findVal-arr[left])/(arr[right]-arr[left])
* @author mgq
* @create 2021-03-11 11:56
*/
public class ChaZhiSearch {
public static void main(String[] args) {
int [] arr ={1,3,4,6,9,11,14,18};
int findValue=18;
int i = chaRu(arr, 0, arr.length - 1, findValue);
System.out.println(i);
}
/**
* 二分查找方法
* @param arr 原数组
* @param left 左角标
* @param right 右角标
* @param findValue 要查找的值
*/
public static int chaRu(int [] arr ,int left ,int right,int findValue){
System.out.println("插值算法查找了多少遍");
// 二分查找 和 插值查找 和 黄金分割点查找 都是对 mid中间值优化的查找算法,其实现原理差不多
/**
* 步骤:
* 1:先确定中间值mid
* 2:因为arr[]是有序数组,那么当要查找的值如果大于中间值,就往右递归查找
* 3:因为arr[]是有序数组,那么当要查找的值如果小于中间值,就向左递归查找
* 4:最后返回mid,就是最后匹配值后的下标
*/
// 1:确定中间值
// int mid = (right+left)/2; // 结果取整
int mid= left+(right-left)*(findValue-arr[left])/(arr[right]-arr[left]);
if(findValue>arr[mid]){ //因为arr[]是有序数组,那么当要查找的值如果大于中间值,就往右递归查找
return chaRu(arr,mid+1,right,findValue);
}else if(findValue<arr[mid]){
//因为arr[]是有序数组,那么当要查找的值如果小于中间值,就向左递归查找
return chaRu(arr,left,mid-1,findValue);
}else{
// 最后返回mid,就是最后匹配值后的下标
return mid;
}
}
}
4:黄金分割查找算法
* 黄金分割查找算法 * 1:定义黄金分割数列 * 2:根据公式 mid=low+F(K-1)-1,计算出mid中间值,、 * 3:在循环根据mid向左边和向右边循环找到符合查找的值得下角标
5:递归查找算法
* 1、递归调用就是一个循环调用本方法,每次调用都把本身方法进行压栈操作,然后再依次根据return返回结果做计算出栈方法,并把计算的结果交给下一个栈顶方法 * 2、递归实现迷宫求出路线,和最短路线 * 3、递归 实现: 路径的 '上北下南'字符串 的 排列组合
/**
* 递归算法,
* 1、递归调用就是一个循环调用本方法,每次调用都把本身方法进行压栈操作,然后再依次根据return返回结果做计算出栈方法,并把计算的结果交给下一个栈顶方法
* 2、递归实现迷宫求出路线,和最短路线
* 3、递归 实现: 路径的 '上北下南'字符串 的 排列组合
* @author mgq
* @create 2021-03-08 10:59
*/
public class RecursiveDemo {
// 统计步数
static int count =0;
// 统计最短步数
static int ruleCount =0;
// 统计最短距离
static int min =0;
static List<String> ruleslist = new ArrayList();
public static void main(String[] args) {
// System.out.println(print(4));
// System.out.println(print2(4));
mazeDemo();
}
/**
* 简单测试递归调用方法
* @param n
* @return
*/
public static int print(int n){
if (n==1){
return 1;
}else{
System.out.println(n);
return print(n-1)*n;
// 对于结果分析,假如print(4),n参数传的是4,那么根据递归规则,层层调用,最终到n==1,然后return 1 ,
// 在到n=2,由于n=1返回结果是1,那么print(n-1)*n就应该是:print(2-1)*2=1*2=2,此时print(2-1)就是n=1 return返回的结果
// 在到n=3,由于n=2返回结果是2,那么print(n-1)*n就应该是:print(3-1)*3=2*3=6,此时print(3-1)*3就是n=2 return的结果
// 再到n=4,以此类推,print(4-1)*4=6*4=24,那么最终结果就是24.
// 注意: print(4-1) 是表达式的结果,也就是上一个方法print(3)调用return返回的结果
}
}
public static int print2(int n){
if (n==1){
return 1;
}else{
// System.out.println(n);
return print2(n-1)+4;
// 假如调用方法print2(4),那么执行循序:1--->print2(2-1)+4=1+4=5--->print2(3-1)+4=5+4=9--->print2(4-1)+4=9+4=13
}
}
/**
* 递归实现迷宫的路线问题
*/
public static void mazeDemo(){
// int[][] maze = initMaze();
// route(maze,1,1);
//
// for (int i = 0; i < maze.length; i++) {
// for (int j = 0; j < maze[i].length; j++) {
// System.out.print(maze[i][j]+" ");
// }
// System.out.println();
// }
// System.out.println("起点到终点总的步数:"+count);
System.out.println("=========================分割线,求出最短距离");
/**
* 递归查询上下左右排列组合 个数
*/
List list = new ArrayList();
list.add("上");
list.add("下");
list.add("左");
list.add("右");
arrayList(list,"");
System.out.println("=========================分割线,求出最短距离2");
// 统计所有走完地图终点所用的步数
List<Integer> listCount = new ArrayList();
/**
* 求出最短距离
*/
for (int i = 0; i < ruleslist.size(); i++) {
// 记录每次步数
ruleCount=0;
String rule = ruleslist.get(i);
char[] chars = rule.toCharArray();
// 获取初始化的地图
int[][] maze1 = initMaze();
route2(maze1,1,1,chars);
System.out.println("打印的地图路径的第+"+i+"+遍=============统计:用了"+ruleCount+"步走到终点");
listCount.add(ruleCount);
for (int x = 0; x < maze1.length; x++) {
for (int j = 0; j < maze1[x].length; j++) {
System.out.print(maze1[x][j]+" ");
}
System.out.println();
}
}
System.out.println("步数排序");
Collections.sort(listCount);
listCount.stream().limit(1).forEach((data)->{
System.out.println("最短距离为:"+data );
});
}
/**
* 地图初始化
* @return
*/
public static int [][] initMaze(){
/**
* 1 1 1 1 1 1 1 1
* 1 0 0 0 0 0 0 1
* 1 0 0 0 0 0 0 1
* 1 0 0 0 0 0 0 1
* 1 0 0 0 0 0 0 1
* 1 0 0 0 0 0 0 1
* 1 0 0 0 0 0 0 1
* 1 1 1 1 1 1 1 1
*/
//定义二维数组,创建二维地图
int [][] maze = new int[8][8];
for (int i = 0; i < maze.length; i++) {
for (int j = 0; j < maze[i].length; j++) {
// 创建迷宫,初始化围墙,围墙设置成1,
if(i==0){
maze[i][j]=1;
}
if(i==7){
maze[i][j]=1;
}
if (j==0){
maze[i][j]=1;
}
if (j==7){
maze[i][j]=1;
}
// System.out.print(maze[i][j]+" ");
}
// System.out.println();
}
// System.out.println("=================分割线");
// 定义路线的起点坐标 int[1][1]
// 定义路线的终点坐标 int[6][6]
// 那么求出从起点到终点的路线
// 加上围墙
maze[3][1]=1;
maze[3][2]=1;
// maze[1][2]=1;
maze[2][2]=1;
return maze;
}
/**
* // 迷宫中,求出从起点到终点的 路线
* 重要:递归,从起点开始就尝试往下去走,然后只要遇到死了,那么就会把走过的路线都会标记为3,这是利用递归方法后,出栈的方法返回boolean值,
* 从而判断路线是否可走,打上标记;那么只要路线中有一个坐标能走通,那么就尝试递归去走新的路线,沿途都把标志设置为2
* @param maze 代表迷宫
* @param x 列坐标
* @param y 行坐标
*/
public static boolean route(int [][] maze,int x,int y){
// 先定义 0:是未走过的路线,1:是围墙,2:走过成功的路线 3:未成功的路线
// maze[6][6]终点等于2,说已经走到了终点,那么我们就结束方法
if (maze[6][6] ==2){
// count++;
return true;
}
// 如果是没有走过的路线,那么就可以走
if (maze[x][y]==0){
// 先设置走的路线为2,后面判断走不了的话,在设置为3
maze[x][y]=2;
// 制定路线规则,走 :下 、右、上,左
// 向下:坐标行加一,x+1
if(route(maze,x+1,y)){
count++;
return true;
}
// 向右
if(route(maze,x,y+1)){
count++;
return true;
}
// 向上
if(route(maze,x-1,y)){
count++;
return true;
}
// 向左
if(route(maze,x,y-1)){
count++;
return true;
}else{
// 说明上下左右都走不通了,死路设置为3
maze[x][y]=3;
}
}
// 除了为0 那么123都返回false
return false;
}
/**
* 求出最短距离算法
* @param maze
* @param x
* @param y
* @return
*/
public static boolean route2(int [][] maze,int x,int y,char[] chars){
// 先定义 0:是未走过的路线,1:是围墙,2:走过成功的路线 3:未成功的路线
// maze[6][6]终点等于2,说已经走到了终点,那么我们就结束方法
if (maze[6][6] ==2){
// count++;
return true;
}
// 如果是没有走过的路线,那么就可以走
if (maze[x][y]==0){
// 先设置走的路线为2,后面判断走不了的话,在设置为3
maze[x][y]=2;
//动态从list中获取路线排列方式
for (int j = 0; j < chars.length; j++) {
// 制定路线规则,走 :下 、右、上,左
if("下".equals(String.valueOf(chars[j]))){
// 向下:坐标行加一,x+1
if(route2(maze,x+1,y,chars)){
ruleCount++;
return true;
}
}
if("右".equals(String.valueOf(chars[j]))){
// 向右
if(route2(maze,x,y+1,chars)){
ruleCount++;
return true;
}
}
if("上".equals(String.valueOf(chars[j]))){
// 向上
if(route2(maze,x-1,y,chars)){
ruleCount++;
return true;
}
}
if("左".equals(String.valueOf(chars[j]))){
// 向上
if(route2(maze,x,y-1,chars)){
ruleCount++;
return true;
}
}
}
// else{
// 说明上下左右都走不通了,死路设置为3
maze[x][y]=3;
// }
}
// 除了为0 那么123都返回false
return false;
}
/**
* 递归查询 上下左右 路径 的所有排列方式
* @param list
* @param str
*/
public static void arrayList(List list,String str)
{
if(list.size()==0){
System.out.println("第" + min++ + "个:" + str);
ruleslist.add(str);
}
for(int i=0;i<list.size();i++)
{
// 这步就是把对象赋值给second,后续在操作second的remove移除元素,就不会影响list本身元素了
List second = new ArrayList(list);
arrayList(second,str + second.remove(i));
}
}
}