目录
数组与稀疏数组
队列:自己用数组模拟Queue
环形队列,取模【取余】实现.
单链表(LinkList)
双向链表(Next 、Pre)
单向环形链表
线性结构
-
数组与稀疏数组
稀疏数组,很多0值,可用于压缩
特点:共n+1行3列,n为不同值的个数(0除外)
第一行:数组的行数、列数、不同值的个数
第二行:行号、列号、值
public class SparseArray {
public static void main(String[] args) {
convert();
}
private static void convert(){
// 01.array2sparse
//定义7*7的二维数组,arr[6][6]=7、arr[2][2]=3 、arr[4][4]=5,其余位置为0
int[][] arr = new int[7][7];//别😅,7为行列数不是索引!
arr[2][2]=3;
arr[4][4]=5;
arr[6][6]=7;
//增强for遍历
for (int[]row:arr) {
for (int i :row) {
System.out.printf("%d\t",i);
}
System.out.println();
};
//获取非0值个数确定数组行数
int notzeroNumber=0;
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr.length; j++) {
if(arr[i][j]!=0){
notzeroNumber=notzeroNumber+1;
}
}
}
int count = 0;//稀疏数组中第几行
int[][] arr2 = new int[notzeroNumber+1][3];
arr2[0][0]= arr.length;
arr2[0][1]=arr.length;
arr2[0][2]=notzeroNumber;
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr.length; j++) {
if(arr[i][j]!=0){
count++;
arr2[count][0]=i;
arr2[count][1]=j;
arr2[count][2]=arr[i][j];
}
}
}
//遍历打印稀疏数组
for (int[]row:arr2) {
for (int i :row) {
System.out.printf("%d\t",i);
}
System.out.println();
};
// 02sparse2array
// 获取数组行列数
int[][] arr3 =new int[arr2[0][0]][arr2[0][1]];
// 设置数组中非0值
for (int i = 1; i < arr2.length; i++) {
arr3[arr2[i][0]][arr2[i][1]]=arr2[i][2];
}
for (int[]row :arr3) {
for (int i :row) {
System.out.printf("%d\t",i);
}
System.out.println();
}
};
}
***
-
队列
自己用数组模拟Queue
public class Queue {
private int maxSize;
private int front;
private int rear;
private int[]arr;//利用数组模拟!你搞得数组都没有
public Queue(int maxSize) {
this.maxSize = maxSize;
arr = new int[this.maxSize];
front=-1;
rear=-1;
}
public void addQueue(int n){
//先判断队列是否已满
if(isFull()){
System.out.println("队列满了哦~");
}
//rear初始值为-1
rear = rear+1;
arr[rear]=n;
}
public int getQueue(){
//先判断队列是否为空
if (isEmpty()){
throw new RuntimeException("队列为空");
}
front++;
return arr[front];//front默认为-1,见构造函数
}
public void showQueue(){
if (isEmpty()){
throw new RuntimeException("队列为空");
}
for (int i = 0; i < arr.length; i++) {
System.out.printf("arr[%d]=%d\n",i,arr[i]);//%d十进制整型
}
}
public int getHeader(){
if (isEmpty()){
throw new RuntimeException("队列为空");
}
return arr[front+1];
}
public boolean isFull(){
return rear==maxSize-1;
}
public boolean isEmpty(){
return rear==front;
}
public int getMaxSize() {
return maxSize;
}
public void setMaxSize(int maxSize) {
this.maxSize = maxSize;
}
public int getFront() {
return front;
}
public void setFront(int front) {
this.front = front;
}
public int getRear() {
return rear;
}
public void setRear(int rear) {
this.rear = rear;
}
}
key:
1.通过构造函数初始化队列
public Queue(int maxSize) {
this.maxSize = maxSize;
arr = new int[this.maxSize];
front=-1;
rear=-1;
}
2.规定:front=rear初始化值为-1,取头元素时为arr[rear+1]
3.队列为空【front==rear】、队列满时条件[rear=maxsize-1]
问题:目前数组使用一次就不能用,没有达到复用的效果
附加:环形队列,取模【取余】实现,优化解决上述问题.
key:why取模,见下图
size=(rear-front+max)%max
full:(rear+1)%max==front
empty:rear==front
show:for(i=front,i<front+size,i++){
"arr[%d]=d%\n",i%maxSize,arr[i%maxSize]
}
public class CircleQueue {
private int maxSize;
private int front;
private int rear;//rear 指向队列的最后一个元素的后一个位置
private int[]arr;
public CircleQueue(int maxSize) {
this.maxSize = maxSize;
front=0;
rear=0;
arr=new int[maxSize];
}
public void addQueue(int a){
if (isFull()){
System.out.println("已满");
return;
}
//rear指向队列的最后一个元素的后一个位置
arr[rear]=a;//先赋值
rear=(rear+1)%maxSize;//后++
};
public int getQueue(){
if(isEmpty())
{
throw new RuntimeException("队列为空");
}
// 这里需要分析出 front 是指向队列的第一个元素
// 1. 先把 front 对应的值保留到一个临时变量
// 2. 将 front 后移, 考虑取模
// 3. 将临时保存的变量返回
int value =arr[front];
front=(front+1)%maxSize;
return value;
}
public int getHeader(){
if(isEmpty()){
throw new RuntimeException("空的");
}
return arr[front];
}
//key!考虑front在后面
public void showQueue(){
if(isEmpty()){
throw new RuntimeException("空的");
}
//遍历!!!取模思想,front也是变的【取了的】
for (int i = front; i < front+size(); i++) {
System.out.printf("arr[%d]=d%\n",i%maxSize,arr[i%maxSize]);
}
}
public boolean isEmpty(){
return front==rear;
}
public boolean isFull(){
return (rear + 1 ) % maxSize==front;
}
public int size(){
return (rear+maxSize-front)%maxSize;
}
public int getMaxSize() {
return maxSize;
}
public void setMaxSize(int maxSize) {
this.maxSize = maxSize;
}
public int getFront() {
return front;
}
public void setFront(int front) {
this.front = front;
}
public int getRear() {
return rear;
}
public void setRear(int rear) {
this.rear = rear;
}
public int[] getArr() {
return arr;
}
public void setArr(int[] arr) {
this.arr = arr;
}
}
单链表(LinkList)
//链表的Node节点
public class Node {
private int no;
private String name;
private String nickname;
private Node next;
public Node(int no, String name, String nickname) {
this.no = no;
this.name = name;
this.nickname = nickname;
}
}
public class LinkList {
//头节点属性
private Node headNode = new Node(0,"","");
// 01添加节点
// 1.找到当前链表最后节点
// 2.将最后节点next指向新的节点
public void add(Node node){
//思想:
//头节点不能动,因此需借助一个辅助变量temp
Node temp = headNode;
while(true){
if (temp.getNext() == null){
break;
}
//如果有就后移
temp = temp.getNext();
}
temp.setNext(node);
};
//02循环遍历打印节点
public void show(){
if (headNode.getNext()==null){
System.out.println("链表为空");
return;
}
Node temp = headNode.getNext();
while (true){
if (temp==null){
break;
}
System.out.println(temp);
temp=temp.getNext();
}
}
//03按顺序编号插入节点
public void addbyOrder(Node node){
Node temp = headNode;
boolean flag = false;
while (true){
if(temp.getNext()==null){
break;
}
if(temp.getNext().getNo()>node.getNo()){
break;
}else if (temp.getNext().getNo()==node.getNo()) {
flag=true;
break;
}
temp = temp.getNext();
}
if(flag){
System.out.printf("准备插入的英雄的编号 %d 已经存在了\n",node.getNo());
}else{
//改变相应节点的next指针
node.setNext(temp.getNext());
temp.setNext(node);
}
}
//04修改节点信息
//根据节点编号no修改节点信息【no编号不能改】
public void updateNode(Node node){
if (headNode.getNext()==null){
System.out.println("链表为空~");
return;
}
Node temp = headNode.getNext();
boolean flag=false;//表示是否找到该节点,思想,代码分离
while (true){
if(temp==null){
break;
}
if(temp.getNo()==node.getNo()){
flag=true;
break;
}
temp=temp.getNext();
};
if (flag){
temp.setName(node.getName());
temp.setNickname(node.getName());
}
}
//05删除节点,根据编号no删除指定节点
public void deleteNode(int no){
Node temp = headNode;
boolean flag = false;//是否找到所删节点
if(temp.getNext()==null){
System.out.println("空链表");
return;
}
while (true){
if(temp.getNext()==null){
System.out.println("空链表");
break;
}
if (temp.getNext().getNo()==no){
flag=true;
break;
}
temp=temp.getNext();
}
if (flag){
//修改next指针
temp.setNext(temp.getNext().getNext());
}
}
链表反转三种方式:
//01借助pre\cur\next反转链表,需注意的是反转后需改变头指针的指向
public void reverseList(){
if(headNode.getNext()==null||headNode.getNext().getNext()==null){
return;
}
Node pre = null;
Node cur =headNode.getNext();
Node next =null;
while (cur!=null){
next=cur.getNext();
cur.setNext(pre);
pre=cur;
cur=next;
}
//头节点指针需指向反转后的首节点
// 仍指向反转前的首节点会导致后面的节点被垃圾回收【没有指针指向】
headNode.setNext(pre);
}
//02创建新指针头节点
// 思路
// 1)创建一个新的头指针newHead。
// 2)创建一个结点p遍历反转前的链表,每遍历一个结点,把newHead指向当前结点。同时创建一个结点cur,用来保存p的下一个结点,以此来保证p遍历整个反转前的链表。
// 3)最后原来头指针head,指向新的链表。
public void reverseList2(){
Node cur = headNode.getNext();
Node next=null;
Node reverseHead = new Node(0,"","");
if (cur==null||cur.getNext()==null){return;}
while (cur!=null){
next=cur.getNext();
cur.setNext(reverseHead.getNext());
reverseHead.setNext(cur);
cur=next;
}
headNode.setNext(reverseHead.getNext());//指向当前链表
}
//本函数的headNode参数为链表的第一个元素,本函数返回逆序的第一个节点(也就是原来的倒数第一个节点)
// 需将headNodey头指针指向函数返回值
public Node reverseList3(Node head){
if(head==null||head.getNext()==null){
return head;
};
Node newNode = reverseList3(head.getNext());//初次返回链表最后一个节点,条件为headNode为倒数第二个节点时
head.getNext().setNext(head);
head.setNext(null);
headNode.setNext(newNode);
return newNode;//返回上一层的递归函数,保证指针指向反链表头部
}
public void reversePrint(){
if (headNode.getNext()==null){return;}
Stack<Node> stack = new Stack<Node>();
Node cur = headNode.getNext();
while (cur!=null){
stack.add(cur);
cur=cur.getNext();
}
while (stack.size()>0) {
System.out.println(stack.pop());
}
}
-
双向链表(Next 、Pre)
按顺序插入
keys:
1.temp=headNode考虑到添加节点为最前面的节点
2.temp.getNext()!=null考虑到添加节点是否为尾节点
3.设置双向链表首先设置Node指向其他节点,以防添加节点为首节点temp为headNode指向Node,导致原本链表直接垃圾回收
node.setNext(temp.getNext())
if(temp.getNext()!=null){temp.getNext().setPre(node)}
temp.setNext(Node)
Node.setPre(temp)
//03按顺序编号插入节点!!!
//插入节点总与前一节点《=》,后一节点需考虑
public void addbyOrder(Node node){
Node temp = headNode;
boolean flag = false;
while (temp.getNext()!=null){
if(temp.getNext().getNo()>node.getNo()){
break;
}else if (temp.getNext().getNo()==node.getNo()) {
flag=true;
break;
}
temp = temp.getNext();
}
if(flag) {
System.out.printf("准备插入的英雄的编号 %d 已经存在了\n", node.getNo());}
else{
//改变顺序,头节点指向都变了,可能导致垃圾回收和【循环链表】
// temp.setNext(node);
// node.setPre(temp);
// node.setNext(temp.getNext());
// if(temp.getNext()!=null){
// temp.getNext().setPre(node);
// }
//the order matters!
node.setNext(temp.getNext());
if(temp.getNext()!=null){
temp.getNext().setPre(node);
}
temp.setNext(node);
node.setPre(temp);
}
}
删除:考虑是否为尾节点
if (flag){
cur.getPre().setNext(cur.getNext());
//判断删除节点是否为最后一个节点
if(cur.getNext()!=null) {
cur.getNext().setPre(cur.getPre());
}
}
-
单向环形链表
1.环形链表生成
//生成环
private Boy first = null;
public void addBoy(int num){
if(num<1){
System.out.println("num值不正确");
return;
}
Boy curBoy = null;
for (int i = 1; i <=num ; i++) {
Boy boy =new Boy(i);
if(i==1){
//一个自己形成环
first=boy;
boy.setNext(first);
curBoy=boy;
}else {
//形成环
curBoy.setNext(boy);
boy.setNext(first);
curBoy=boy;
}
}
2.Josephu问题
/**
*
* @param startNo:表示从第几个小孩开始数数
* @param countNum:表示数几下
* @param nums:表示最初有多少小孩在圈中
*/
//根据用户输入,计算小孩出圈
public void countBoy(int startNo ,int countNum , int nums){
//先进行数据校验
if(first==null||startNo<1||startNo>nums){
System.out.println("para_Error");
return;
}
Boy helper= first;
//helper转圈圈直至转到链表最后
//helper为指向链表最后一个元素,始终在first前一位置,直至遍历完成【最后一个元素,相等】
//!!!!
while (true){
if (helper.getNext()==first){
break;
}
helper=helper.getNext();
};
//第几个开始
for (int i = 0; i < startNo-1; i++) {
first=first.getNext();
helper=helper.getNext();
};
while (true){
if (helper==first){break;}
for (int i = 0; i < countNum-1; i++) {
first=first.getNext();
helper=helper.getNext();
}
System.out.printf("小孩%d出圈\n",first.getNo());
first=first.getNext();
helper.setNext(first);
}
System.out.printf("最后留在圈中的小孩编号%d\n",first.getNo());
}
目录
1.栈
1.1基本概念
编辑
1.2应用场景
1.3数组模拟栈
2.中序计算器
3.中序转后序
4.逆波兰计算器
1.栈
1.1基本概念
1.2应用场景
1.3数组模拟栈
public class ArrayStack {
private int maxSize;
private int [] stack;
private int top =-1;
public ArrayStack(int maxSize) {
this.maxSize = maxSize;
stack = new int[this.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++;//初始为-1,所以先++
stack[top]=value;
}
//出栈
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]);
}
}
//返回运算符的优先级,优先级是程序员来确定,优先级使
//数字越大,则优先级就越高.
//'',int类型?
//自动类型转换:由低字节向高字节的转换 byte->short-> char –>int->long->float->double
//char->int,ASCII,*(42),0(48),A(65),a(97)
public int priority(int oper){
if (oper=='*'||oper=='/'){
return 1;
} else if (oper=='+'||oper=='-') {
return 0;
}else {
return -1;
}
}
//判断是否为运算符
public boolean isOpera(char val){
return val=='+'||val=='-'||val=='*'||val=='/';
}
//弹出栈顶
public int peek(){
return stack[top];
}
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;
break;
default:
break;
}
return res;
}
}
2.中序计算器
//多多看看
public class Calculator {
//中序计算器
public static void main(String[] args) {
String expression = "7*2*2-5+1-5+3-4";
ArrayStack numStack = new ArrayStack(10);
ArrayStack operStack = new ArrayStack(10);
int index = 0;//用于扫描
int num1 =0;
int num2 =0;
int oper = 0;
int res = 0;
char ch = ' ';//每次扫描得到char保存至ch
String keepNum="";//用于拼接多位数
//开始循环扫描expression
while (true){
//依次得到每个字符
ch = expression.substring(index,index+1).charAt(0);
if (operStack.isOpera(ch)){//如果是运算符
//符号栈中的符号优先级大于需加入的符号时,先pop两个数计算,计算结果push进numStack
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 {
//1.当处理多位数时,不能发现是一个数就立即入栈,因为他可能是多位数
//2.在处理数,需要向expression的表达式的index后再看一位,如果是数就进行扫描,如果是符号才入栈
//3.因此我们需要定义一个变量字符串,用于拼接处理多位数
//处理多位数
keepNum+=ch;
//如果ch是表达式最后一位就直接入栈
if (index==expression.length()-1){
numStack.push(Integer.parseInt(keepNum));
}else {
//判断下一位字符是不是数字,如果是数字就继续扫描,如果是运算符就入栈
//注意是看后一位,不是index++
if (operStack.isOpera(expression.substring(index+1,index+2).charAt(0))){
numStack.push(Integer.parseInt(keepNum));
//keepNum清空!!!!
keepNum="";
}
}
}
//index++,并判断是否扫描到expression最后
index++;
if (index>=expression.length()){
break;
}
}
//当表达式扫描完毕,就顺序的从数栈和符号栈中pop出相应的数和符号,并运行
while (true){
//符号栈为空时
if (operStack.isEmpty()){
break;
}
num1 = numStack.pop();
num2 = numStack.pop();
oper = operStack.pop();
res = numStack.cal(num1,num2,oper);
numStack.push(res);
}
//将数栈最后数,pop出,就是结果
int res2 = numStack.pop();
System.out.printf("表达式%s=%d",expression,res2);
}
}
3.中序转后序
public class Mid2Pos {
public static void main(String[] args) {
String expression = "1+((2+3)*10)-5";
List<String> infixExpressionList =toInfixExpressionList(expression);
System.out.println("中缀表达式对应List"+infixExpressionList);
List<String> suffixExpresssionList =parseSuffixExpresssionList(infixExpressionList);
System.out.println(suffixExpresssionList);
int result =calculate(suffixExpresssionList);
System.out.println(result);
}
//将中缀表达式转换为
public static List<String> toInfixExpressionList(String s){
List<String> ls =new ArrayList<String>();
int i = 0;
String str;
char c;
do {
//非数字,ASCII 48-57对应0-9
if ((c=s.charAt(i))<48||(c=s.charAt(i))>57){
ls.add(c+"");
i++;//i往后移
}else {
str="";
//考虑到了为,例如:12多个数
while (i<s.length()&&(c=s.charAt(i))>=48&&(c=s.charAt(i))<=57){
str+=c;
i++;
}
ls.add(str);
}
}while (i<s.length());
return ls;
}
public static List<String> parseSuffixExpresssionList(List<String>ls){
Stack<String> s1 =new Stack<String>();//符号栈
List<String> s2 = new ArrayList<String>();//存储中间结果
for (String item:ls){
System.out.println(item);
//正则表达式,匹配【多个】数字
if (item.matches("\\d+")){
s2.add(item);
} else if (item.equals("(")) {
s1.push(item);
} else if (item.equals(")")) {
//为未匹配到(,前面的数pop到s2
while (!s1.peek().equals("(")){
s2.add(s1.pop());
}
s1.pop();//!!!将(弹出,消除小括号
}else {
//有符号且其符号优先级大于所需要加的符号
while (s1.size()!=0&&Operation.getvalue(s1.peek())>=Operation.getvalue(item)){
s2.add(s1.pop());
}
s1.push(item);
}
}
//将s1中剩余的符号压入s2中
while (s1.size()!=0){
s2.add(s1.pop());
}
return s2;//注意这是将其存入List,顺序输出就是后缀表达式对应的List
}
public class Operation {
private static int ADD =1;
private static int SUB =1;
private static int MUL =2;
private static int DIV =2;
public static int getvalue(String operation){
int result =0;
switch (operation){
case "+":
result=ADD;
break;
case "-":
result=SUB;
break;
case "*":
result=MUL;
break;
case "/":
result=DIV;
break;
default:
System.out.println("不存在运算符号");
break;
}
return result;
}
}
4.逆波兰计算器
public static int calculate(List<String>ls){
Stack<String> stack = new Stack<String>();
for (String item :ls){
//正则表达式,匹配数字
if (item.matches("\\d+")){
stack.push(item);
}else {
int num2 = Integer.parseInt(stack.pop());//栈顶,后面的数”被减、被除“
int num1 = Integer.parseInt(stack.pop());
int res =0;
if (item.equals("+")){
res=num1+num2;
} else if (item.equals("-")) {
res = num1-num2;
} else if (item.equals("*")) {
res = num1*num2;
} else if (item.equals("/")) {
res=num1/num2;
}else {
throw new RuntimeException("运算符有误");
}
stack.push(""+res);
}
}
return Integer.parseInt(stack.pop());
}
}
树
1.why:
数组&链表&树
2. 大纲
2.1前中后序
public class HeroNode {
private int no;
private String name;
private HeroNode left;//默认为null
private HeroNode right;//默认为null
public HeroNode(int no, String name) {
this.no = no;
this.name = name;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public HeroNode getLeft() {
return left;
}
public void setLeft(HeroNode left) {
this.left = left;
}
public HeroNode getRight() {
return right;
}
public void setRight(HeroNode right) {
this.right = right;
}
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
'}';
}
//遍历
//编写前序遍历方法,被谁调用this就是谁
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 postOrder(){
//递归先左子树前遍历
if(this.left!=null){
this.left.postOrder();
}
//递归向右子树前序遍历
if (this.right!=null){
this.right.postOrder();
}
//最后输出父节点
System.out.println(this);
};
}
2.2查找节点
//查找
//前序查找
public HeroNode preSearch(int HeroNo){
//判断当前节点是不是
if (this.getNo()==HeroNo){
return this;
};
//左子树前序查找
//如果左递归前序查找节点,找到结点,则返回
HeroNode resNode = null;//辅助节点
if (this.getLeft()!=null) {
resNode =this.getLeft().preSearch(HeroNo);
}
//resNode不为空才返回,因为右子树仍未查找
if (resNode!=null){
return resNode;
}
if (this.getRight()!=null){
resNode = this.getRight().preSearch(HeroNo);
}
return resNode;
}
//中序查找
public HeroNode infixSearch(int HeroNo){
//左子树前序查找
//如果左递归前序查找节点,找到结点,则返回
HeroNode resNode = null;//辅助节点
if (this.getLeft()!=null) {
resNode =this.getLeft().preSearch(HeroNo);
}
//resNode不为空才返回,因为右子树仍未查找
if (resNode!=null){
return resNode;
};
//判断当前节点是不是
if (this.getNo()==HeroNo){
return this;
}
//查找右子树
if (this.getRight()!=null){
resNode = this.getRight().preSearch(HeroNo);
}
return resNode;
}
//后序查找
public HeroNode postSearch(int HeroNo){
//左子树前序查找
//如果左递归前序查找节点,找到结点,则返回
HeroNode resNode = null;//辅助节点
if (this.getLeft()!=null) {
resNode =this.getLeft().preSearch(HeroNo);
}
//resNode不为空才返回,因为右子树仍未查找
if (resNode!=null){
return resNode;
};
if (this.getRight()!=null){
resNode = this.getRight().preSearch(HeroNo);
}
if (resNode!=null){
return resNode;
};
//最后判断当前节点是不是
if (this.getNo()==HeroNo){
return this;
};
return resNode;
}
2.3删除节点(基本)
//删除
public void deleteNode(int HeroNo){
//判断左子节点
if (this.left!=null && this.left.getNo()==HeroNo){
this.left=null;
return;
}
//判断右子节点
if (this.right!=null&&this.right.getNo()==HeroNo){
this.right=null;
return;
}
//遍历左子树
if (this.left!=null){
this.left.deleteNode(HeroNo);
}
if(this.right!=null){
this.right.deleteNode(HeroNo);
}
}
2.4二叉树 (root节点)
public class BinaryTree {
//root
private HeroNode root;
public void setRoot(HeroNode root) {
this.root = root;
};
//遍历二叉树
//前序遍历
public void preOrder(){
if (this.root!=null){
this.root.preOrder();
}else {
System.out.println("二叉树为空");
}
}
//中序遍历
public void infixOrder(){
if (this.root!=null){
this.root.infixOrder();
}else {
System.out.println("二叉树为空");
}
}
//后续遍历
public void postOrder(){
if (this.root!=null){
this.root.postOrder();
}else {
System.out.println("二叉树为空");
}
}
//查找二叉树指定节点
//前序查找
public HeroNode preSearch(int HeroNo){
if (this.root!=null){
return this.root.preSearch(HeroNo);
}else {
return null;
}
}
//中序查找
public HeroNode infixSearch(int HeroNo){
if (this.root!=null){
return this.root.infixSearch(HeroNo);
}else {
return null;
}
}
//后序查找
public HeroNode postSearch(int HeroNo){
if (this.root!=null){
return this.root.postSearch(HeroNo);
}else {
return null;
}
}
public void delNode(int HeroNo){
if(root!=null){
if (root.getNo()==HeroNo){
root=null;
}else {
root.deleteNode(HeroNo);
}
}else {
System.out.println("空树,无法删除");
}
}
}
2.5顺序存储二叉树 (为完全二叉树,公式)
public class ArrBinaryTree {
private int [] arr;//存储结点的数组
public ArrBinaryTree(int[] arr) {
this.arr = arr;
}
//重载
public void preOrder(){
preOrder(0);
}
public void infixOrder(){
infixOrder(0);
}
/**
*
* @param index arr[index]=i
*/
//前序
public void preOrder(int index){
if(arr == null ||arr.length==0){
System.out.println("数组为空");
}
System.out.println(arr[index]);
//向左递归遍历
if ((index*2+1)<arr.length){
preOrder(2*index+1);
}
//向右递归遍历
if ((index*2+2)<arr.length){
preOrder(2* index+2);
}
}
//中序
public void infixOrder(int index){
if(arr == null ||arr.length==0){
System.out.println("数组为空");
}
//向左递归遍历
if ((index*2+1)<arr.length){
infixOrder(index*2+1);
}
System.out.println(arr[index]);
//向右递归遍历
if ((index*2+2)<arr.length){
infixOrder(2* index+2);
}
}
}
2.6线索化二叉树(节点左右节点类型、前驱指针)
public class ThreadBinaryTree {
private HeroNode root;
//为了实现线索化,需要创建要给指向当前结点的前驱结点的指针
// 在递归进行线索化时,pre总是保留前一个结点
//pre指针
private HeroNode pre =null;
public HeroNode getRoot() {
return root;
}
public HeroNode getPre() {
return pre;
}
public void setPre(HeroNode pre) {
this.pre = pre;
}
public void setRoot(HeroNode root) {
this.root = root;
};
//重载threadNodes()
public void threadedNodes(){
this.threadedNodes(root);
}
/*多回顾*/
// //中序线索化二叉树
public void threadedNodes(HeroNode node){
//递归找到最左节点,然后返回
if (node == null) {
return;
}
//先线索化左子树
threadedNodes(node.getLeft());
//线索化当前节点
if (node.getLeft()==null){
node.setLeft(pre);
node.setLeftType(1);
}
//key!!!
if (pre!=null&&pre.getRight()==null){
pre.setRight(node);
pre.setRightType(1);
}
pre=node;
//线索化右子树
threadedNodes(node.getRight());
};
//***提高:中序、后序***
//遍历线索化二叉树
public void threadedList(){
HeroNode node = root;
while (root!=null){
while(node!=null){
//循环的找到leftType ==1的结点,第一个找到就是8结点
// 后面随着遍历而变化,因为当leftType==1时,说明该结点是按照线索化
// 处理后的有效结点
while (node.getLeftType()==0){
node=node.getLeft();
}
System.out.println(node);
while (node.getRightType()==1){
node=node.getRight();
System.out.println(node);
}
//替换这个遍历点(对于原本就有右结点的)!!!
node=node.getRight();
}
}
}
//遍历二叉树
//前序遍历
public void preOrder(){
if (this.root!=null){
this.root.preOrder();
}else {
System.out.println("二叉树为空");
}
}
//中序遍历
public void infixOrder(){
if (this.root!=null){
this.root.infixOrder();
}else {
System.out.println("二叉树为空");
}
}
//后续遍历
public void postOrder(){
if (this.root!=null){
this.root.postOrder();
}else {
System.out.println("二叉树为空");
}
}
//查找二叉树指定节点
//前序查找
public HeroNode preSearch(int HeroNo){
if (this.root!=null){
return this.root.preSearch(HeroNo);
}else {
return null;
}
}
//中序查找
public HeroNode infixSearch(int HeroNo){
if (this.root!=null){
return this.root.infixSearch(HeroNo);
}else {
return null;
}
}
//后序查找
public HeroNode postSearch(int HeroNo){
if (this.root!=null){
return this.root.postSearch(HeroNo);
}else {
return null;
}
}
public void delNode(int HeroNo){
if(root!=null){
if (root.getNo()==HeroNo){
root=null;
}else {
root.deleteNode(HeroNo);
}
}else {
System.out.println("空树,无法删除");
}
}
}
线索化二叉树
1024先发一个Blog再说
加油,励志成为一位全栈工程师->架构师的GISer_Jinger
/*多回顾*/
//线索化二叉树
public void threadedNodes(HeroNode node){
if (node == null) {
return;
}
//先线索化左子树
threadedNodes(node.getLeft());
//线索化当前节点
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 threadedList(){
HeroNode node = root;
while (root!=null){
while(node!=null){
//循环的找到leftType ==1的结点,第一个找到就是8结点
// 后面随着遍历而变化,因为当leftType==1时,说明该结点是按照线索化
// 处理后的有效结点
while (node.getLeftType()==0){
node=node.getLeft();
}
System.out.println(node);
while (node.getRightType()==1){
node=node.getRight();
System.out.println(node);
}
node=node.getRight();
}
}
}
图
图的深度优先遍历(Depth First Search, DFS)和广度优先遍历(Breadth First Search, BFS)是图论中两种非常重要的算法,它们广泛应用于拓扑排序、道路搜索(如迷宫问题)、搜索引擎、爬虫等领域。以下是对这两种遍历算法的详细解析:
深度优先遍历(DFS)
1. 基本思想
深度优先遍历的思想是从图中某个顶点v出发,沿着一条路一直走到底,然后从这条路尽头的节点回到上一个节点,尝试另一条路,直到所有的顶点都被访问过为止。其特点是不撞南墙不回头,先尽可能深地搜索一个分支,然后再回溯搜索其他分支。
2. 实现方式
深度优先遍历主要有递归和迭代(使用栈)两种实现方式:
- 递归实现:对于每个节点,先访问该节点,然后递归地访问其所有未被访问的邻接点。递归的优点是代码简洁,易于理解,但缺点是如果图的层次过深,可能会导致栈溢出。
- 迭代实现:使用栈来模拟递归过程。将初始节点压入栈中,然后不断从栈中弹出节点并访问其所有未被访问的邻接点,将邻接点压入栈中。迭代实现避免了递归可能导致的栈溢出问题。
3. 应用场景
深度优先遍历常用于解决需要遍历所有可能路径的问题,如迷宫问题、图的连通性问题等。
广度优先遍历(BFS)
1. 基本思想
广度优先遍历的思想是从图中某个顶点v出发,先访问v的所有邻接点,然后再依次访问这些邻接点的邻接点,即按层次遍历图中的所有顶点。其特点是从起始点开始,一层一层地向外扩展,直到访问完所有顶点。
2. 实现方式
广度优先遍历主要使用队列来实现:
- 初始化一个队列,将起始节点加入队列。
- 当队列不为空时,从队列中取出一个节点并访问它。
- 将该节点的所有未被访问的邻接点加入队列。
- 重复上述过程,直到队列为空。
3. 应用场景
广度优先遍历常用于解决最短路径问题、层次遍历二叉树等问题。由于它按层次遍历图中的所有顶点,因此可以很容易地找到从起始点到其他所有顶点的最短路径(如果图是无向图或所有边的权重都相等的话)。
总结
深度优先遍历和广度优先遍历是图论中两种重要的遍历算法,它们各有优缺点和适用场景。深度优先遍历适用于需要遍历所有可能路径的问题,而广度优先遍历则更适用于解决最短路径等层次化问题。在实际应用中,可以根据问题的具体需求选择合适的遍历算法。
package org.example;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
public class Graph {
private ArrayList<String> vertexList;//存储顶点集合
private int[][] edges;//存储图对应的邻接矩阵
private int numOfEdges;//表示边的数目
private boolean[] isVisited;//是否已被访问
public static void main(String[] args) {
int n = 5;
String Vertexs[] = {"A","B","C","D","E"};
Graph graph = new Graph(n);
for (String vertex:Vertexs){
graph.insertVertex(vertex);
}
graph.insertEdge(0,1,1);
graph.insertEdge(0,2,1);
graph.insertEdge(1,2,1);
graph.insertEdge(1,4,1);
graph.insertEdge(1,3,1);
graph.showGraph();
// System.out.println("===深度优先===");
// graph.dfs();
System.out.println("===广度优先===");
graph.bfs();
}
public ArrayList<String> getVertexList() {
return vertexList;
}
public void setVertexList(ArrayList<String> vertexList) {
this.vertexList = vertexList;
}
public int[][] getEdges() {
return edges;
}
public void setEdges(int[][] edges) {
this.edges = edges;
}
public int getNumOfEdges() {
return numOfEdges;
}
public void setNumOfEdges(int numOfEdges) {
this.numOfEdges = numOfEdges;
}
public boolean[] getIsVisited() {
return isVisited;
}
public void setIsVisited(boolean[] isVisited) {
this.isVisited = isVisited;
}
//构造函数
public Graph(int n) {
edges = new int[n][n];
vertexList = new ArrayList<String>(n);
numOfEdges = 0;
}
/**
*
* @param index
* @return 存在则返回,不存在返回-1
*/
//获取第一个邻接节点的下标
public int getFirstNeighbor(int index){
for (int j = 0; j < vertexList.size(); j++) {
if (edges[index][j]>0){
return j;
}
}
return -1;
}
//根据前一个邻接结点的下标来获取下一个邻接结点
public int getNextNeighbor(int v1 , int v2){
for (int j = v2 + 1; j < vertexList.size(); j++) {
if (edges[v1][j]>0){
return j;
}
}
return -1;
}
//深度优先遍历算法
/**
*
* @param isVisited
* @param i
*/
private void dfs(boolean[]isVisited , int i){
//输出访问的结点
System.out.println(getValueByIndex(i)+"->");
//设置已访问
isVisited[i]=true;
int w =getFirstNeighbor(i);
while (w!=-1){
if (!isVisited[w]){
dfs(isVisited,w);
}
//如果w结点已经被访问过
w = getNextNeighbor(i,w);
}
}
//对dfs进行一个重载,遍历我们所有结点,并进行dfs
public void dfs(){
isVisited = new boolean[vertexList.size()];
//遍历所有结点,进行dfs【回溯】
for (int i = 0; i < getNumOfVertex(); i++) {
if (!isVisited[i]){
dfs(isVisited,i);
}
}
}
//广度优先算法
private void bfs(boolean[]isVisited,int i){
int u;//表示队列的头结点对应下标
int w;//邻接结点w
//队列,记录结点访问的顺序
LinkedList queue = new LinkedList();
System.out.print(getValueByIndex(i)+"=>");
isVisited[i]=true;
//将结点加入队列
queue.addLast(i);
while (!queue.isEmpty()){
//取出队列的头结点下标
u=(Integer)queue.removeFirst();
w=getFirstNeighbor(u);
while (w!=-1){
if (!isVisited[w]){
System.out.print(getValueByIndex(w)+"=>");
isVisited[w]=true;
queue.addLast(w);
}
w=getNextNeighbor(u,w);
}
}
}
public void bfs(){
isVisited = new boolean[vertexList.size()];
for (int i = 0; i < getNumOfVertex(); i++) {
if (!isVisited[i]){
bfs(isVisited,i);
}
}
}
//图中常用的方法
//返回结点的个数
public int getNumOfVertex(){
return vertexList.size();
}
//显示图对应的矩阵
public void showGraph(){
//增强for
for(int[] link : edges){
System.err.println(Arrays.toString(link));
}
}
//返回结点i(下标)对应的数据
public String getValueByIndex(int i){
return vertexList.get(i);
}
//返回vl和 v2的权值
public int getWeight(int v1 ,int v2){
return edges[v1][v2];
}
//插入结点
public void insertVertex(String vertex){
vertexList.add(vertex);
}
//添加边
public void insertEdge(int v1 , int v2 , int weight){
edges[v1][v2] = weight;
edges[v2][v1] = weight;
numOfEdges++;
}
}
递归
1.基本概念
1.1应用场景
1.2遵守原则
1.3常见结构
void backtracking(){
if(终止条件) {
收集结果
return
}
for(集合的元素集,类似子节点的个数)
{
处理结点
递归函数;
回溯操作
(撤销处理结点12, 2撤销 ,13 撤销3, 14)
}
}
2.常见应用场景
1.迷宫问题
public class Mystery {
public void design(){
int[][] map = new int[8][7];
//约定: 当 map[i][j] 为 0 表示该点没有走过 当为 1 表示墙 ; 2 表示通路可以走 ; 3 表示该点已经走过,但是走不通
map[3][2]=1;
map[3][1]=1;
map[1][2]=1;
map[2][2]=1;
for (int i = 0; i < 7; i++) {
map[0][i] = 1;
map[7][i]=1;
}
for (int i = 0; i < 8; i++) {
map[i][0]=1;
map[i][6]=1;
}
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 7; j++) {
System.out.print(map[i][j]);
}
System.out.println();
}
System.out.println("===走迷宫后===");
setWay(map,1,1);
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 7; j++) {
System.out.print(map[i][j]);
}
System.out.println();
}
}
/**
*
* @param map
* @param i:初始位置行号
* @param j:初始位置列号
* @return
*/
public boolean setWay(int[][]map,int i ,int j){
// 在走迷宫时,需要确定一个策略(方法) 下->右->上->左 , 如果该点走不通,再回溯
// 约定: 当 map[i][j] 为 0 表示该点没有走过 当为 1 表示墙 ; 2 表示通路可以走 ; 3 表示该点已经走过,但是走不通
// 假定int[i][j]能走得通,
if(map[6][5]==2){
return true;
} else{
if(map[i][j]==0){
map[i][j]=2;
if(setWay(map,i+1,j)){
return true;
}else if (setWay(map,i,j+1)) {
return true;
}else if(setWay(map,i-1,j)){
return true;
}else if(setWay(map,i,j-1)){
return true;
}else {
map[i][j]=3;
return false;
}
}else {
return false;
}
}
}
2.八皇后问题
public class Queen {
int max = 4;
//定义数组 array, 保存皇后放置位置的结果,比如 arr = {0 , 4, 7, 5, 2, 6, 1, 3 }
// arr[index]=val,raw=index+1,column=val+1
int[] array = new int[max];
static int count = 0;
static int judgeCount = 0;
public static void main(String[] args) {
Queen queen = new Queen();
queen.check(0);
System.out.printf("一共有%d解法",count);
System.out.printf("一个判断冲突%d次",judgeCount);
}
//n为第几行,需要和前几行进行检查是否符合条件
//不同行,但也要确保不同行元素也不同列并且不在对角线上
private void check(int n) {
if (n == max) {
print();
return;
}
//遍历列,保证不同行元素也不在同一列或者对角线上
for (int i = 0; i < max; i++) {
array[n] = i;
if (judge(n)){
check(n+1);
}
}
}
private boolean judge(int n) {
judgeCount++;
//和上面的行比较判断,不能同列array[i] == array[n],不能同斜线Math.abs(array[n] - array[i]) == Math.abs(n - i)
for (int i = 0; i < n; i++) {
//!!!判断是否在斜线: Math.abs(array[n] - array[i]) == Math.abs(n - i)
if (array[i] == array[n] || Math.abs(array[n] - array[i]) == Math.abs(n - i)) {
return false;
}
}
return true;
}
private void print(){
count++;
for (int i = 0; i < array.length; i++) {
System.out.print(array[i]+" ");
}
System.out.println();
}
排序算法
排序:
插入排序:
直接插入排序
- 插入排序(Insertion Sorting)的基本思想是:把n个待排序的元素看成为一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含有n-1个元素,
- 排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表。
public static void insertSort(int[]arr){
int insertVal = 0;
int insertIndex=0;
for (int i = 1; i < arr.length; i++) {
insertVal=arr[i];
insertIndex=i-1;//即插入数前面的那个数
//1. insertIndex >=0保证在给insertVal找插入位置,不越界
// 2. insertVal < arr[insertIndex]待插入的数,还没有找到插入位置
//实现工作:整体后移,找到合适位置
//!!!!问题:当需要插入的数是较小的数时,后移的次数明显增多,对效率有影响.
while (insertIndex>=0&&insertVal<arr[insertIndex]){
arr[insertIndex+1]=arr[insertIndex];
insertIndex--;
}
//这里我们判断是否需要赋值
if (insertIndex+1!=i){
arr[insertIndex+1]=insertVal;
}
System.out.println("第"+i+"轮插入");
System.out.println(Arrays.toString(arr));
}
}
希尔排序
交换法
//交换法
public static void shellSort(int[]arr){
int temp =0;
int count = 0;
for (int gap = arr.length/2; gap >0 ; gap/=2) {
//这层循环,可用下面移动法替换,原理类似
for (int i =gap ; i <arr.length ; i++) {
//分组,两两一组,步长gap
//条件j>=0,每次遍历
for (int j = i-gap; j >=0 ; j-=gap) {
if (arr[j]>arr[j+gap]){
temp=arr[j];
arr[j]=arr[j+gap];
arr[j+gap]=temp;
}
}
}
}
}
移动法
//移动法
public static void shellSort2(int[]arr){
for (int gap = arr.length/2; gap >0 ; gap/=2) {
for (int i = gap; i < arr.length; i++) {
int j =i;
int temp =arr[j];
if (arr[j]<arr[j-gap]){
while (j-gap>=0&&temp<arr[j-gap]){
//移动
arr[j]=arr[j-gap];
j-=gap;
}
//当退出while后,就给temp找到插入的位置
arr[j]=temp;
}
}
}
}
交换排序
冒泡排序
public static void bubbleSort(int[]arr){
int temp = 0;//临时变量
boolean flag =false;//标识变量是否进行过交换
//时间复杂都为O(n^2)
for (int i = 0; i < arr.length-1; i++) {
for (int j = 0; j < arr.length-1-i; j++) {
//如果前面的数比后面的数大,则交换
if (arr[j]>arr[j+1]){
flag=true;
temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
System.out.println("第"+(i+1)+"趟排序后的数组");
if (!flag){
break;
}else {
flag=false;//重置flag!!!进行下次判断使用
}
}
}
快速排序
//chatgpt
public class FastSort {
public static void quickSort(int[] arr) {
if (arr == null || arr.length == 0) {
return;
}
quickSort(arr, 0, arr.length - 1);
}
private static void quickSort(int[] arr, int low, int high) {
if (low < high) {
// 分区,返回基准元素的最终位置
int pivotIndex = partition(arr, low, high);
// 递归排序基准元素左右的两个子数组
quickSort(arr, low, pivotIndex - 1);
quickSort(arr, pivotIndex + 1, high);
}
}
private static int partition(int[] arr, int low, int high) {
int pivot = arr[high];
int i = low - 1;
// 将小于基准的元素交换到左边
for (int j = low; j < high; j++) {
if (arr[j] < pivot) {
i++;
swap(arr, i, j);
}
}
// 将基准元素放到正确的位置
swap(arr, i + 1, high);
// 返回基准元素的最终位置
return i + 1;
}
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public static void main(String[] args) {
int[] arr = {38, 27, 43, 3, 9, 82, 10};
quickSort(arr);
// 输出排序结果
for (int num : arr) {
System.out.print(num + " ");
}
}
}
public static void quickSort(int [] arr,int left,int right){
int l = left;
int r = right;
//pivot中轴值
int pivot = arr[(left+right)/2];
int temp = 0;//临时变量,作为交换使用
while (l<r){
//左侧一直找,找到大于等于pivot值才退出
while (arr[l]<pivot){
l+=1;
}
//右侧一直找,找到小于等于pivot值才退出
while (arr[r]>pivot){
r-=1;
};
//如果1>=r说明pivot的左右两的值,已经按照左边全部是l小于等于pivot值,右边全部是大于等于pivot值
if (l>=r){
break;
};
//交换
temp=arr[l];
arr[l]=arr[r];
arr[r]=temp;
//如果交换完后,发现arr[l]==pivot,r--前移
if(arr[l]==pivot){
r-=1;
}
//如果交换完后,发现arr[r]==pivot,l++后移
if(arr[r]==pivot){
l+=1;
}
}
if (l==r){
l+=1;
r-=1;
}
//向左递归
if (left<r){
quickSort(arr,left,r);
}
//向右递归
if (right > l) {
quickSort(arr,l,right);
}
}
选择排序
简单选择排序
//选择排序,从头开始选,选的元素与剩下元素比较,保证当前元素排序后小于其后所以数据
public static void selectSort(int[]arr){
//时间复杂都为O(n^2)
//i<arr.length-1,与后面比较,最大可能为倒数第二
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;
}
}
//判断当前数是否为剩下数的最小值,若是才进行交互
if (minIndex!=i){
arr[minIndex]=arr[i];
arr[i]=min;
}
System.out.println("第"+(i+1)+"轮后~");
System.out.println(Arrays.toString(arr));
}
}
堆排序
归并排序
//分治方法:分+合方法
public static void mergeSort(int [] arr,int left,int right,int[]temp){
if(left<right){
int mid =(left+right)/2;
//递归分解
mergeSort(arr,left,mid,temp);
mergeSort(arr,mid+1,right,temp);
//合并
merge(arr,left,mid,right,temp);
}
}
//合并方法
/**
*
* @param arr
* @param left
* @param mid
* @param right
* @param temp 做中转的数组
*/
public static void merge(int [] arr,int left,int mid,int right,int[]temp){
int i = left;
int j = mid+1;
int t = 0;//指向temp数组的当前索引
//(一)
//先把左右两边(有序)的数据按照规则填充到temp数组
// 直到左右两边的有序序列,有一边处理完毕为止
while (i<=mid&&j<=right){
//如果左边的有序序列的当前元素,小于等于右边有序序列的当前元素
// 即将左边的当前元素,填充到temp数组
//然后 t++, i++
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;
}
}
基数排序
基数排序是桶排序的扩展
//基数排序
public static void radixSort(int[]arr){
//01 获取最大数
// int maxNum =Arrays.stream(arr).max().getAsInt();
// int digit = 1;
// //获取最大数位数
// while (maxNum/Math.pow(10,digit)>0){
// digit++;
// }
//02获取在最大数
int max = arr[0];//假设为第一个数
for (int i = 1; i <arr.length ; i++) {
if (arr[i]>max){
max=arr[i];
}
}
//获取最大数长度
int maxLength =(max+"").length();
//定义一个二维数组,表示10个桶,每个桶就是一个一维数组
// 说明
//1.二维数组包含10个一维数组
// 2.为了防止在放入数的时候,数据溢出,则每个一维数组(桶),大小定为arr.1ength
// 3.明确,基数排序是使用空间换时间的经典算法
int [][]bucket = new int[10][arr.length];
//为了记录每个桶中,实际存放了多少个数据,我们定义一个一维数组来记录各个桶的每次放入的数据个数
// 可以这里理解
//比如: bucketElementCounts[0],记录的就是bucket[0]桶的放入数据个数
int [] bucketElementCounts = new int[10];
for (int i = 0,n=1; i <maxLength ; i++,n*=10) {
for (int j = 0; j < arr.length; j++) {
//取出每个元素的对于位的值
int digitOfElement = arr[j]/ n % 10;//key1
//放入对应桶中
bucket[digitOfElement][bucketElementCounts[digitOfElement]]=arr[j];
bucketElementCounts[digitOfElement]++;
}
//按照桶的顺序(以为数组的下标一次取出的数据,放入原来数组)
int index = 0;
//遍历每个桶,并将桶中数据放入原数组
for (int k = 0; k < bucketElementCounts.length; k++) {
//如果桶中有数据,我们才放入原数组
if (bucketElementCounts[k]!=0){
for (int l = 0; l < bucketElementCounts[k]; l++) {
//取出元素放入arr
arr[index++]=bucket[k][l];
}
}
//第i+1处理后,休要将每个bucketElementCounts置为0
bucketElementCounts[k]=0;
}
System.out.println("第"+(i+1)+"轮,排序处理后arr="+Arrays.toString(arr));
}
}
查找算法
二分🔍
//二分查找
//前提:先需进行排序
//关键:递归查找
//递归退出条件:1.找到2.递归完整个数组,仍然没有找到,也需结束递归,条件:left>right
public static List<Integer> binarySearch(int[]arr, int left, int right, int findVal){
//递归整个数组,没找到
//条件left>right
if (left>right){
return new ArrayList<Integer>();
}
int mid =(left+right)/2;
int midVal =arr[mid];
if (findVal>midVal){
return binarySearch(arr,mid+1,right,findVal);
} else if (findVal<midVal) {
return binarySearch(arr,left,mid-1,findVal);
}else {
//*思路分析
//*1.在找到mid索引值,不要马上返回
//*2.向mid索引值的左边扫描,将所有满足值,的元素的下标,加入到集合ArrayList
//*3.向mid索引值的右边扫描,将所有满足值,的元素的下标,加入到集合ArrayList
//*4.将Arraylist返回
List<Integer> resIndexlist = new ArrayList<Integer>();
//向左扫描
int temp = mid-1;
while (true){
if (temp<0||arr[temp]!=findVal){
break;//退出
}
//否则就将temp放入到resIndexlist
resIndexlist.add(temp);
temp-=1;//temp左移
}
resIndexlist.add(mid);
//向右扫描
temp=mid+1;
while (true){
if (temp>arr.length-1||arr[temp]!=findVal){
break;
}
//否则就将temp放入到resIndexlist
resIndexlist.add(temp);
temp+=1;
}
return resIndexlist;
}
}
插值🔍
key:
Low+(High-Low)*(findVal-arr[Low])/(arr[High]-arr[Low])//其中arr[Low]<=findVal<=arr[High]
//插值查找
//前提:先需进行排序
//关键1:递归查找
//关键2:mid自适应求解算法:Low+(High-Low)*(findVal-arr[Low])/(arr[High]-arr[Low])//其中arr[Low]<=findVal<=arr[High]
//递归退出条件:1.找到2.递归完整个数组,仍然没有找到,也需结束递归,条件:left>right
public static List<Integer> InterpSearch(int[]arr, int left, int right, int findVal){
//递归整个数组,没找到
if (left>right||findVal<arr[0]||findVal>arr[arr.length-1]){
return new ArrayList<Integer>();
}
int mid =left+(right-left)*(findVal-arr[left])/(arr[right]-arr[left]);
int midVal =arr[mid];
if (findVal>midVal){
return InterpSearch(arr,mid+1,right,findVal);
} else if (findVal<midVal) {
return InterpSearch(arr,left,mid-1,findVal);
}else {
//*思路分析
//*1.在找到mid索引值,不要马上返回
//*2.向mid索引值的左边扫描,将所有满足值,的元素的下标,加入到集合ArrayList
//*3.向mid索引值的右边扫描,将所有满足值,的元素的下标,加入到集合ArrayList
//*4.将Arraylist返回
List<Integer> resIndexlist = new ArrayList<Integer>();
//向左扫描
int temp = mid-1;
while (true){
if (temp<0||arr[temp]!=findVal){
break;//退出
}
//否则就将temp放入到resIndexlist
resIndexlist.add(temp);
temp-=1;//temp左移
}
resIndexlist.add(mid);
//向右扫描
temp=mid+1;
while (true){
if (temp>arr.length-1||arr[temp]!=findVal){
break;
}
//否则就将temp放入到resIndexlist
resIndexlist.add(temp);
temp+=1;
}
return resIndexlist;
}
}
斐波那契查找算法
public class FibonacciSearch {
//因为后面我们mid=low+F(k-1)-1,需要使用到斐波那契数列,因此我们需要先获取到一个斐波那契数列
// 非递归方法得到一个斐波那契数列
public static int[] fib(int maxSize){
int [] f =new int[maxSize];
f[0]=1;
f[1]=1;
for (int i = 2; i < maxSize; i++) {
f[i]=f[i-1]+f[i-2];
}
return f;
}
//编写斐波那契查找算法
//使用非递归的方式编写算法
/**
*
* @param a 数组
* @param key
* @return 返回对应下标,如果没有-1
*/
public static int fibSearch(int[] a,int key){
int low = 0;
int high =a.length-1;
int k = 0;//表示斐波那契分割数值的下标
int mid = 0;//存放mid值
int f[]=fib(20);
//获取到斐波那契分割数值的下标
while (high>f[k]-1){
k++;
}
//因为f[k]值可能大于a的长度,因此我们需要使用Arrays类,构造一个新的数组,并指向temp[]
//不足的部分会使用0填充
int[] temp = Arrays.copyOf(a,f[k]);
//实际上需求使用a数组最后的数填充temp
// 举例:
//temp= {1,8,10,89,1000,1234,0,0}=>{1,8,10,89,1000,1234,1234,1234,}
for (int i = high+1; i <temp.length ; i++) {
temp[i]=a[high];
}
//使用while循环找到key
while (low<=high){
mid=low+f[k-1]-1;
if (key<temp[mid]){
//继续向左边查找
high=mid-1;
//为甚是k--
// 说明
// 1.全部元素=前面的元素+后边元素
// 2.f[k]= f[k-1]+ f[k-2]
//因为前面有fk-1]个元素,所以可以继续拆分f[k-1]=f[k-2]+ f[k-3]
//即在f[k-1]的前面继续查找k--
// 即下次循环mid= f[k-1-1]-1
k--;
} else if (key>temp[mid]) {
//向右边继续查找
low=mid+1;
//*为什么是k-2
//说明
//1.全部元素=前面的元素+后边元素
// 2.f[k]=f[k-1]+ f[k-2]
//3.因为后面我们有f[k-2]所以可以继续拆分f[k-2]= f[k-3]+ f[k-4]
// 4.即在f[k-2]的前面进行查找k -=2
//5.即下次循环mid= f[k-1-2]- 1
k-=2;
}else {
//找到
if (mid<=high){
return mid;
}else {
return high;
}
}
}
return -1;
}
分治算法
定义:
分治法是一种很重要的算法。字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。这个技巧是很多高效算法的基础,如排序算法(快速排序,归并排序),傅立叶变换(快速傅立叶变换)
基本步骤
1) 分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题
2) 解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题 3) 合并:将各个子问题的解合并为原问题的解。
应用:
1. 汉诺塔问题
public static void hanoiTower(int num ,char a,char b,char c){
//如果只有一个盘
if(num == 1){
System.out.println("第一个从 " + a + " 移动到 " + c);
}else {
//如果num>=2,总是可以看成两个盘1.最下边的一个盘2.上面所有盘
//1.把最上边的所有盘A,移动到B,使用C做辅助
hanoiTower(num - 1, a, c, b);
//2.把最下边的盘A,移动到C
System.out.println("第 " + num + " 个从 " + a + " 移动到 " + c);
//3.把B所有的盘,移动到C,使用A做辅助
hanoiTower(num - 1, b, a, c);
}
}