-----参考-----
1)网址
https://blog.csdn.net/oneby1314/category_10231585.html
2)视频
https://www.bilibili.com/video/BV1E4411H73v?p=108&spm_id_from=pageDriver
一.稀疏数组与队列
1)稀疏数组
1.举例说明
2.代码实现
public class LessArr {
public static void main(String[] args) {
//11*11棋盘,1白棋,2黑棋
int[][] chessArr = new int[11][11];
chessArr[1][2] = 1;
chessArr[2][3] = 2;
//1.遍历有效数量
int validNums = 0;
for (int[] ints : chessArr) {
for (int item : ints) {
System.out.print(item + "\t");
if (item != 0){
validNums++;
}
}
System.out.println();
}
System.out.println("有效数据个数:" + validNums);
//2.创建稀疏数组
int[][] arr2 = new int[validNums + 1][3];
arr2[0][0] = chessArr.length;
arr2[0][1] = chessArr.length;
arr2[0][2] = validNums;
int sort = 1;
for (int i = 0; i < chessArr.length; i++) {
for (int j = 0; j < chessArr.length; j++) {
if (chessArr[i][j] != 0){
arr2[sort][0] = i;
arr2[sort][1] = j;
arr2[sort][2] = chessArr[i][j];
sort++;
}
}
}
//遍历
for (int i = 0; i < arr2.length; i++) {
System.out.printf("%d\t%d\t%d\t\n", arr2[i][0], arr2[i][1], arr2[i][2]);
}
System.out.println("================");
//3.恢复为二维数组
int[][] originArr = new int[arr2[0][0]][arr2[0][1]];
for (int i = 1; i < arr2.length; i++) {
originArr[arr2[i][0]][arr2[i][1]] = arr2[i][2];
}
for (int[] ints : originArr) {
for (int item : ints) {
System.out.print(item + "\t");
}
System.out.println();
}
}
}
//输出
0 0 0 0 0 0 0 0 0 0 0
0 0 1 0 0 0 0 0 0 0 0
0 0 0 2 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 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
0 0 0 0 0 0 0 0 0 0 0
有效数据个数:2
11 11 2
1 2 1
2 3 2
================
0 0 0 0 0 0 0 0 0 0 0
0 0 1 0 0 0 0 0 0 0 0
0 0 0 2 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 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
0 0 0 0 0 0 0 0 0 0 0
2)队列
1.队列介绍
- maxSize :队列容量(数组的长度)
- arr :模拟队列的数组
- front :指向队列头部元素的前一个元素,初始值为 -1
- rear :指向队列尾部元素,初始值为 -1
队列判空:front == rear
队列判满:rear == (maxSize - 1) ,即 rear 是否已经指向了数组的最后一个位置
队列元素个数:rear - front
队列入队:队列不满才能入队,arr[++rear] = value
队列出队:队列不空才能出队,return arr[front++]
2.代码实现
public class ArrQueue {
public static void main(String[] args) {
Queue queue = new Queue(3);
Scanner scanner = new Scanner(System.in);
while (true){
System.out.print("输入操作(+或者-):");
String s = scanner.nextLine();
if ("+".equals(s)){
System.out.print("输入入队数据:");
String data = scanner.nextLine();
System.out.println(queue.pushQueue(data));
}else {
System.out.println(queue.popQueue());
}
queue.showQueue();
}
}
}
class Queue{
private int maxSize; // 表示数组的最大容量
private int front; // 队列头
private int rear; // 队列尾
private String[] arr; // 该数据用于存放数据, 模拟队列
//1.初始化队列
public Queue(int maxSize) {
this.maxSize = maxSize;
this.front = -1;
this.rear = -1;
this.arr = new String[maxSize];
}
//2.判满
public boolean isFull(){
return rear == maxSize - 1;
}
//3.判空
public boolean isEmpty(){
return rear == front;
}
//4.入队
public String pushQueue(String data){
if (isFull()){
return "队列已满!!!";
}
rear++;
arr[rear] = data;
return "入队成功!!";
}
//5.出队
public String popQueue(){
if (isEmpty()){
return "队列为空!!";
}
front++;
String i = arr[front];
return i + ":出队成功!!";
}
//6.显示所有数据
public void showQueue(){
if (isEmpty()){
return ;
}
System.out.print("队列数据:\t");
for (int i = front+1; i <= rear; i++) {
System.out.print(arr[i] + "\t");
}
System.out.println();
}
}
3.循环队列
public class CircleQueue {
public static void main(String[] args) {
CirQueue queue = new CirQueue(4);
Scanner scanner = new Scanner(System.in);
while (true){
System.out.print("输入操作(+或者-):");
String s = scanner.nextLine();
if ("+".equals(s)){
System.out.print("输入入队数据:");
String data = scanner.nextLine();
System.out.println(queue.pushQueue(data));
}else {
System.out.println(queue.popQueue());
}
queue.showQueue();
}
}
}
class CirQueue{
private int maxSize; // 表示数组的最大容量
private int front; // 队列头
private int rear; // 队列尾
private String[] arr; // 该数据用于存放数据, 模拟队列
//1.初始化队列
public CirQueue(int maxSize) {
this.maxSize = maxSize;
this.front = 0;
this.rear = 0;
this.arr = new String[maxSize];
}
//2.判满
public boolean isFull(){
return (rear+1) % maxSize == front;
}
//3.判空
public boolean isEmpty(){
return rear == front;
}
//4.入队
public String pushQueue(String data){
if (isFull()){
return "队列已满!!!";
}
arr[rear] = data;
rear = (rear+1)%maxSize;
return "入队成功!!";
}
//5.出队
public String popQueue(){
if (isEmpty()){
return "队列为空!!";
}
String i = arr[front];
front = (front+1)%maxSize;
return i + ":出队成功!!";
}
//6.显示所有数据
public void showQueue(){
if (isEmpty()){
return ;
}
System.out.print("队列数据:\t");
for (int i = front; i < front+(rear+maxSize-front)%maxSize; i++) {
System.out.print(arr[i%maxSize] + "\t");
}
System.out.println();
}
}
二.链表
1)单向链表
1.示意图
2.代码
public class SingleLinkedListDemo {
public static void main(String[] args) {
// 先创建节点
HeroNode hero1 = new HeroNode(1, "宋江", "及时雨");
HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟");
HeroNode hero3 = new HeroNode(3, "吴用", "智多星");
HeroNode hero03 = new HeroNode(3, "吴用", "智多星");
HeroNode hero4 = new HeroNode(4, "林冲", "豹子头");
// // 创建要给链表
// SingleLinkedList singleLinkedList = new SingleLinkedList();
// //1. 尾插法
// singleLinkedList.add(hero1);
// singleLinkedList.add(hero2);
// singleLinkedList.add(hero3);
// singleLinkedList.add(hero4);
//
// singleLinkedList.showList();
System.out.println("-----------------------");
SingleLinkedList singleLinkedList1 = new SingleLinkedList();
//2. 顺序添加
singleLinkedList1.addByOrder(hero3);
singleLinkedList1.addByOrder(hero1);
singleLinkedList1.addByOrder(hero4);
singleLinkedList1.addByOrder(hero03);
singleLinkedList1.addByOrder(hero2);
singleLinkedList1.showList();
//3.修改
singleLinkedList1.update(new HeroNode(3, "无用--", "智多星"));
System.out.println("修改后:");
singleLinkedList1.showList();
//4.删除
singleLinkedList1.delete(2);
System.out.println("删除后:");
singleLinkedList1.showList();
//5.求节点个数
System.out.println("节点个数:");
System.out.println(count(SingleLinkedList.header));
//6.反转单链表
System.out.println("反转:");
reverse(SingleLinkedList.header);
singleLinkedList1.showList();
//7.反转单链表(栈)
System.out.println("反转(栈):");
reverseStack(SingleLinkedList.header);
}
public static int len = 0;
public static int count(HeroNode header){
if (header.next == null){
return len;
}
header = header.next;
len++;
count(header);
return len;
}
public static void reverse(HeroNode oldLinked){
if (oldLinked.next == null || oldLinked.next.next == null){
return ;
}
HeroNode newLinked = new HeroNode(0, "", "");
HeroNode curr = oldLinked.next;
HeroNode next = null;
while (curr != null){
next = curr.next;//保存下一个节点
curr.next = newLinked.next;
newLinked.next = curr;
curr = next;
}
oldLinked.next = newLinked.next;
}
public static void reverseStack(HeroNode oldLinked){
if (oldLinked.next == null){
return ;
}
Stack<HeroNode> stack = new Stack<>();
HeroNode curr = oldLinked.next;
while (curr != null){
stack.push(curr);
curr = curr.next;
}
while (stack.size()>0){
HeroNode pop = stack.pop();
System.out.println(pop);
}
}
}
class SingleLinkedList{
public static HeroNode header;
public SingleLinkedList(){
header = new HeroNode(0, "", "");
}
public void add(HeroNode heroNode){
HeroNode temp = header;
while (true){
if (temp.next == null){
break;
}
temp = temp.next;
}
temp.next = heroNode;
}
public void addByOrder(HeroNode heroNode){
HeroNode temp = header;
while (true){
if (temp.next == null){
break;
}
if (temp.next.id > heroNode.id) {
heroNode.next = temp.next;
break;
}
if (temp.next.id == heroNode.id){
System.out.println("%d重复不能添加!!" + heroNode);
return;
}
temp = temp.next;
}
temp.next = heroNode;
}
public void update(HeroNode heroNode){
if (header.next == null){
System.out.println("链表为空!!");
return;
}
HeroNode temp = header;
while (true){
if (temp.next == null){
System.out.println("修改的数据没找到");
return;
}
if (temp.next.id == heroNode.id){
temp.next.name = heroNode.name;
temp.next.nickName = heroNode.nickName;
return;
}
temp = temp.next;
}
}
public void delete(int id){
if (header.next == null){
System.out.println("链表为空!!");
return;
}
HeroNode temp = header;
while (true){
if (temp.next == null){
System.out.println("删除数据没找到!!!");
return;
}
if (temp.next.id == id){
temp.next = temp.next.next;
return;
}
temp = temp.next;
}
}
public void showList(){
if (header.next == null){
System.out.println("链表为空!!");
return;
}
HeroNode temp = header.next;
while (true){
if (temp.next == null){
System.out.println(temp);
break;
}
System.out.println(temp);
temp = temp.next;
}
}
}
//定义HeroNode , 每个HeroNode 对象就是一个节点
class HeroNode {
public int id;
public String name;
public String nickName;
public HeroNode next; // 指向下一个节点
public HeroNode(int id, String name, String nickName) {
this.id = id;
this.name = name;
this.nickName = nickName;
}
@Override
public String toString() {
return "HeroNode{" +
"id=" + id +
", name='" + name + '\'' +
", nickName='" + nickName + '\'' +
'}';
}
}
//输出
-----------------------
%d重复不能添加!!HeroNode{id=3, name='吴用', nickName='智多星'}
HeroNode{id=1, name='宋江', nickName='及时雨'}
HeroNode{id=2, name='卢俊义', nickName='玉麒麟'}
HeroNode{id=3, name='吴用', nickName='智多星'}
HeroNode{id=4, name='林冲', nickName='豹子头'}
修改后:
HeroNode{id=1, name='宋江', nickName='及时雨'}
HeroNode{id=2, name='卢俊义', nickName='玉麒麟'}
HeroNode{id=3, name='无用--', nickName='智多星'}
HeroNode{id=4, name='林冲', nickName='豹子头'}
删除后:
HeroNode{id=1, name='宋江', nickName='及时雨'}
HeroNode{id=3, name='无用--', nickName='智多星'}
HeroNode{id=4, name='林冲', nickName='豹子头'}
节点个数:
3
反转:
HeroNode{id=4, name='林冲', nickName='豹子头'}
HeroNode{id=3, name='无用--', nickName='智多星'}
HeroNode{id=1, name='宋江', nickName='及时雨'}
反转(栈):
HeroNode{id=1, name='宋江', nickName='及时雨'}
HeroNode{id=3, name='无用--', nickName='智多星'}
HeroNode{id=4, name='林冲', nickName='豹子头'}
2)双向链表
1.示意图
2.代码
package day2_linkedList;
import java.util.Stack;
public class DoubleLinkedListDemo {
public static void main(String[] args) {
// 先创建节点
DoubleHeroNode hero1 = new DoubleHeroNode(1, "宋江", "及时雨");
DoubleHeroNode hero2 = new DoubleHeroNode(2, "卢俊义", "玉麒麟");
DoubleHeroNode hero3 = new DoubleHeroNode(3, "吴用", "智多星");
DoubleHeroNode hero03 = new DoubleHeroNode(3, "吴用", "智多星");
DoubleHeroNode hero4 = new DoubleHeroNode(4, "林冲", "豹子头");
System.out.println("-----------------------");
DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
//2. 顺序添加
doubleLinkedList.addByOrder(hero3);
doubleLinkedList.addByOrder(hero1);
doubleLinkedList.addByOrder(hero4);
doubleLinkedList.addByOrder(hero03);
doubleLinkedList.addByOrder(hero2);
doubleLinkedList.showList();
//3.修改
doubleLinkedList.update(new DoubleHeroNode(3, "无用--", "智多星"));
System.out.println("修改后:");
doubleLinkedList.showList();
//4.删除
doubleLinkedList.delete(2);
System.out.println("删除后:");
doubleLinkedList.showList();
//5.求节点个数
System.out.println("节点个数:");
System.out.println(count(DoubleLinkedList.header));
//6.反转单链表
System.out.println("反转:");
reverse(DoubleLinkedList.header);
doubleLinkedList.showList();
//7.反转单链表(栈)
System.out.println("反转(栈):");
reverseStack(DoubleLinkedList.header);
}
public static int len = 0;
public static int count(DoubleHeroNode header){
if (header.next == null){
return len;
}
header = header.next;
len++;
count(header);
return len;
}
public static void reverse(DoubleHeroNode oldLinked){
if (oldLinked.next == null || oldLinked.next.next == null){
return ;
}
DoubleHeroNode newLinked = new DoubleHeroNode(0, "", "");
DoubleHeroNode curr = oldLinked.next;
DoubleHeroNode next = null;
while (curr != null){
next = curr.next;//保存下一个节点
curr.next = newLinked.next;
newLinked.next = curr;
curr = next;
}
oldLinked.next = newLinked.next;
}
public static void reverseStack(DoubleHeroNode oldLinked){
if (oldLinked.next == null){
return ;
}
Stack<DoubleHeroNode> stack = new Stack<>();
DoubleHeroNode curr = oldLinked.next;
while (curr != null){
stack.push(curr);
curr = curr.next;
}
while (stack.size()>0){
DoubleHeroNode pop = stack.pop();
System.out.println(pop);
}
}
}
class DoubleLinkedList{
public static DoubleHeroNode header;
public DoubleLinkedList(){
header = new DoubleHeroNode(0, "", "");
}
public void add(DoubleHeroNode heroNode){
DoubleHeroNode temp = header;
while (true){
if (temp.next == null){
break;
}
temp = temp.next;
}
temp.next = heroNode;
}
public void addByOrder(DoubleHeroNode heroNode){
DoubleHeroNode temp = header;
while (true){
if (temp.next == null){
break;
}
if (temp.next.id > heroNode.id) {
heroNode.next = temp.next;
heroNode.pre =temp;
break;
}
if (temp.next.id == heroNode.id){
System.out.println("%d重复不能添加!!" + heroNode);
return;
}
temp = temp.next;
}
temp.next = heroNode;
temp.next.pre = heroNode;
}
public void update(DoubleHeroNode heroNode){
if (header.next == null){
System.out.println("链表为空!!");
return;
}
DoubleHeroNode temp = header;
while (true){
if (temp.next == null){
System.out.println("修改的数据没找到");
return;
}
if (temp.next.id == heroNode.id){
temp.next.name = heroNode.name;
temp.next.nickName = heroNode.nickName;
return;
}
temp = temp.next;
}
}
public void delete(int id){
if (header.next == null){
System.out.println("链表为空!!");
return;
}
DoubleHeroNode temp = header.next;
while (true){
if (temp.next == null){
System.out.println("删除数据没找到!!!");
return;
}
if (temp.next.id == id){
temp.pre.next = temp.next;
if (temp.next != null){
temp.next.pre = temp.pre;
}
return;
}
temp = temp.next;
}
}
public void showList(){
if (header.next == null){
System.out.println("链表为空!!");
return;
}
DoubleHeroNode temp = header.next;
while (true){
if (temp.next == null){
System.out.println(temp);
break;
}
System.out.println(temp);
temp = temp.next;
}
}
}
//定义HeroNode , 每个HeroNode 对象就是一个节点
class DoubleHeroNode {
public int id;
public String name;
public String nickName;
public DoubleHeroNode next; // 指向下一个节点
public DoubleHeroNode pre;
public DoubleHeroNode(int id, String name, String nickName) {
this.id = id;
this.name = name;
this.nickName = nickName;
}
@Override
public String toString() {
return "HeroNode{" +
"id=" + id +
", name='" + name + '\'' +
", nickName='" + nickName + '\'' +
'}';
}
}
3)约瑟夫环
1.说明
- Josephu 问题为: 设编号为 1, 2, … n 的 n 个人围坐一圈, 约定编号为 k(1<=k<=n) 的人从 1 开始报数, 数到 m 的那个人出列, 它的下一位又从 1 开始报数, 数到 m 的那个人又出列, 依次类推, 直到所有人出列为止, 由此产生一个出队编号的序列。
2.代码
public class JosephuDemo {
public static void main(String[] args) {
Josephu josephu = new Josephu();
//创建18人的循环单链表
josephu.create(18);
//每次报数到3打印
josephu.outMan(3);
}
}
class Josephu{
private static Man first;
private Man current;
public Man create(int count){
Man man1 = new Man(1);
first = man1;
current = first;
for (int i = 2; i <= count; i++) {
Man man = new Man(i);
current.next = man;
current = current.next;
}
current.next = first;
return first;
}
public void outMan(int num){
Man result = first;
while (result.next != result){
for (int i = 1; i <= num; i++) {
if (i == num-1){
System.out.print(result.next.no + " => ");
result.next = result.next.next;
result = result.next;
break;
}
result = result.next;
}
}
System.out.print(result.no);
}
}
class Man{
int no;
Man next;
public Man(int no){
this.no = no;
}
@Override
public String toString() {
return "Man{" +
"no=" + no +
'}';
}
}
三.栈
1)基本使用
1.栈的应用场景
1子程序的调用: 在跳往子程序前, 会先将下个指令的地址存到堆栈中, 直到子程序执行完后再将地址取出, 以回到原来的程序中。
2处理递归调用: 和子程序的调用类似, 只是除了储存下一个指令的地址外, 也将参数、 区域变量等数据存入栈中。
3表达式的转换:[中缀表达式转后缀表达式]与求值(实际解决)。
4二叉树的遍历。
5图形的深度优先(depth 一 first)搜索法。
2.数组 栈
maxSize :栈的大小(数组的大小)
arr :用来模拟栈的数组
top :指向当前栈顶元素,初始值为 -1 ,表示栈空
判断栈满:top == maxSize ,即已经到达数组最后一个位置
判断栈空:top == -1
入栈:arr[++top] = arr;
出栈:return arr[top–] ;
public class ArrayStackDemo {
public static void main(String[] args) {
ArrayStack stack = new ArrayStack(3);
Scanner scanner = new Scanner(System.in);
while (true){
System.out.print("操作(1.pop 2.push):");
int i = scanner.nextInt();
switch (i){
case 1:
stack.pop();
stack.show();
break;
case 2:
System.out.println("输入入栈数据:");
stack.push(scanner.nextInt());
stack.show();
break;
}
System.out.println();
}
}
}
class ArrayStack{
int maxSize;
int[] stack;
int top;
public ArrayStack(int maxSize){
top = -1;
this.maxSize = maxSize;
this.stack = new int[maxSize];
}
public boolean isEmpty(){
if (top == -1){
return true;
}
return false;
}
public boolean isFull(){
if (top == maxSize - 1){
return true;
}
return false;
}
public void push(int data){
if (isFull()) {
System.out.println("栈满!!!!");
return;
}
top++;
stack[top] = data;
}
public int pop(){
if (isEmpty()) {
System.out.println("栈空!!!!");
return -1;
}
int value = stack[top];
top--;
return value;
}
public void show(){
if (isEmpty()) return;
System.out.print("栈数据:");
for (int i = 0; i <= top; i++) {
System.out.print(stack[i] + "\t");
}
}
}
2).表达式计算(中缀表达式)
1.示意图
2.代码
public class ComputeStackDemo {
public static void main(String[] args) {
String expre = "1+212/3/10*1-10";
Method method = new Method();
System.out.println(method.useStack(expre));
}
}
class Method{
public int getPriority(char value){
if ('*'==value || '/'==value){
return 2;
}else if ('+'==value || '-'==value){
return 1;
}else return 0;
}
public boolean isOperator(char value){
return '*'==value || '/'==value || '+'==value || '-'==value;
}
public int compute(int a, int b, char operator){
switch (operator){
case '+':
return b + a;
case '-':
return b - a;
case '*':
return b * a;
case '/':
return b / a;
}
return -1;
}
public int useStack(String expression) {
ComputeStack intStack = new ComputeStack(10);
ComputeStack operStack = new ComputeStack(10);
String str = "";
for (int i = 0; i < expression.length(); i++) {
char c = expression.charAt(i);
if (isOperator(c)) {
if (operStack.isEmpty()) {
operStack.push(c);
} else {
if (getPriority(operStack.getTopData()) >= getPriority(c)) {
int compute = compute(intStack.pop(), intStack.pop(), operStack.getTopData());
intStack.push(compute);
operStack.pop();
}
operStack.push(c);
}
} else {
if (i == expression.length() - 1 && str.equals("")) {
intStack.push(c-48);
break;
}
if (i!=expression.length()-1 && !isOperator(expression.charAt(i + 1))) {
str += c;
} else {
if ("".equals(str)){
intStack.push(c-48);
}else {
intStack.push(Integer.parseInt(str + c));
str = "";
}
}
}
}
while (true) {
if (operStack.isEmpty()) {
break;
}
int compute = compute(intStack.pop(), intStack.pop(), (char) operStack.pop());
intStack.push(compute);// 入栈
}
return intStack.pop();
}
}
class ComputeStack {
int maxSize;
int[] stack;
int top;
public ComputeStack(int maxSize) {
top = -1;
this.maxSize = maxSize;
this.stack = new int[maxSize];
}
public char getTopData(){
return (char)stack[top];
}
public boolean isEmpty() {
if (top == -1) {
return true;
}
return false;
}
public boolean isFull() {
if (top == maxSize - 1) {
return true;
}
return false;
}
public void push(int data) {
if (isFull()) {
System.out.println("栈满!!!!");
return;
}
top++;
stack[top] = data;
}
public int pop() {
if (isEmpty()) {
System.out.println("栈空!!!!");
return -1;
}
int value = stack[top];
top--;
return value;
}
public void show() {
if (isEmpty()) return;
System.out.print("栈数据:");
for (int i = 0; i <= top; i++) {
System.out.print(stack[i] + "\t");
}
}
}
3)逆波兰表达式(后缀表达式)
1.介绍
2.代码
public class SuffixExpressionDemo {
public static void main(String[] args) {
String exp = "1+((2+3)*4)-5";
//1.字符串转list
List<String> list = toList(exp);
System.out.println(list);
//2.前缀转后缀
List<String> sufList = toSufExpress(list);
System.out.println(sufList);
//3.输出结果
System.out.println(compute(sufList));
}
public static List<String> toSufExpress(List<String> preExp){
Stack<String> expStack = new Stack<>();
ArrayList<String> resList = new ArrayList<>();
for (String s : preExp) {
if (isNum(s.charAt(0))){
resList.add(s);
}else if ("(".equals(s)){
expStack.push(s);
} else if (")".equals(s)){
while (!"(".equals(expStack.peek())){
resList.add(expStack.pop());
}
expStack.pop();
}else {
while (true){
if (expStack.isEmpty() ||
"(".equals(expStack.peek()) || getPriority(s)>getPriority(expStack.peek())){
expStack.push(s);
break;
}else {
resList.add(expStack.pop());
}
}
}
}
while (!expStack.isEmpty()){
resList.add(expStack.pop());
}
return resList;
}
public static int getPriority(String s){
if ("*".equals(s) || "/".equals(s)){
return 1;
}else {
return 0;
}
}
public static List<String> toList(String exp){
ArrayList<String> list = new ArrayList<>();
int temp = 0;
for (int i = 0; i < exp.length(); i++) {
if (!isNum(exp.charAt(i)) && i!=temp){
list.add(exp.substring(temp, i));
list.add(String.valueOf(exp.charAt(i)));
temp = i+1;
}else if (!isNum(exp.charAt(i))){
list.add(String.valueOf(exp.charAt(i)));
temp++;
}else if (i == exp.length()-1){
list.add(exp.substring(temp));
}
}
return list;
}
public static int compute(List<String> sufExp){
Stack<String> stack = new Stack<>();
for (String s : sufExp) {
if (isNum(s.charAt(0))){
stack.push(s);
}else {
int num1 = Integer.parseInt(stack.pop());
int num2 = Integer.parseInt(stack.pop());
int res = 0;
if ("+".equals(s)){
res = num2+num1;
}else if ("-".equals(s)){
res = num2-num1;
}else if ("*".equals(s)){
res = num2*num1;
}else if ("/".equals(s)){
res = num2/num1;
}
stack.push(String.valueOf(res));
}
}
return Integer.parseInt(stack.pop());
}
public static boolean isNum(char c){
return c>=48 && c<=57;
}
}
四.递归
1)简介
1.递归能解决什么问题
- 各种数学问题如: 8 皇后问题,汉诺塔,阶乘问题,迷宫问题,球和篮子的问题(google 编程大赛)
- 各种算法中也会使用到递归, 比如快排, 归并排序, 二分查找, 分治算法等.
- 将用栈解决的问题 --> 递归代码比较简洁
2.递归需遵循的规则
- 执行一个方法时, 就创建一个新的受保护的独立空间(一个线程有自己独立的一个栈空间,每个方法调用对应着一个栈帧)
- 方法的局部变量是独立的, 不会相互影响, 比如 n 变量
- 如果方法中使用的是引用类型变量(比如数组), 就会共享该引用类型的数据
- 递归必须向退出递归的条件逼近, 否则就是无限递归,出现 StackOverflowError, 死龟了 😃
- 当一个方法执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时当方法执行完毕或者返回时,该方法也就执行完毕。
2)迷宫问题(待完善)
1.代码思路
- 使用二维数组 map[][] 模拟迷宫
- 约定: 当 map[i][j] 为 0 表示该点没有走过;当为 1 表示墙;2 表示通路可以走 ;3 表示该点已经走过,但是走不通
- setWay() 方法用于找路,true 表示该路可以走通,false 表示该路走不通
- 在走迷宫时,需要确定一个策略(方法) 下->右->上->左 , 一步一步向前试探,如果该点走不通,再回溯
- 每当走到一个点时,将该点置为 2 ,暂时假设该路能走通,至于到底走不走得通,得看后面有没有找到通路
- 如果后面的路能走通,从最后一个点开始返回,整个 setWay() 递归调用链都返回 true
- 如果后面的路不能走通,那么将当前的点设置为 3 ,表示是死路,走不通,回溯至上一个点,看看其他方向能不能走通
2.代码
public class MazeGameDemo {
public static void main(String[] args) {
//1.创建迷宫
int[][] maze = {{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,1,1,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}};
swap(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();
}
}
public static boolean swap(int[][] maze, int i, int j){
if (maze[6][6] == 2){
return true;
}else {
if (maze[i][j] == 0) {
maze[i][j] = 2;
//下右上左
if (swap(maze, i+1, j)){
return true;
}else if (swap(maze, i, j+1)){
return true;
}else if (swap(maze, i-1, j)){
return true;
}else if (swap(maze, i, j-1)){
return true;
}else{
maze[i][j] = 3;
return false;
}
}else {
return false;
}
}
}
}
3)八皇后问题(回溯-待优化)
1.思路
2.代码
public class EightQueenDemo {
static int num = 0;
static int count = 0;
public static void main(String[] args) {
int[] queens = new int[8];
check(queens, 0);
System.out.println("总共有:" + num);
System.out.println("冲突次数:" + count);
}
public static boolean isConflict(int[] queens, int n){
for (int i = 0; i < n; i++) {
//说明:判断列&斜线是否冲突
if (queens[n]==queens[i] || Math.abs(n-i)==Math.abs(queens[n]-queens[i])){
count++;
return false;
}
}
return true;
}
//递归调用
public static void check(int[] queens, int n){
if (n == queens.length){
System.out.println(Arrays.toString(queens));
num++;
}else {
for (int i = 0; i < queens.length; i++) {
queens[n] = i;
if (isConflict(queens, n)){
check(queens, n+1);
}
}
}
}
}
五.排序算法
1)介绍
1.排序算法的分类
2.平均和最坏时间复杂度
2)算法的复杂度
1.时间复杂度
①时间频度
一个算法中的语句执行次数称为语句频度或时间频度。 记为 T(n)
②时间复杂度
- 一般情况下, 算法中的基本操作语句的重复执行次数是问题规模 n 的某个函数, 用 T(n)表示, 若有某个辅助函数 f(n), 使得当 n 趋近于无穷大时, T(n) / f(n) 的极限值为不等于零的常数, 则称 f(n)是 T(n)的同数量级函数。记作 T(n)=O ( f(n) ), 称O ( f(n) ) 为算法的渐进时间复杂度, 简称时间复杂度。
- T(n) 不同, 但时间复杂度可能相同。 如: T(n)=n²+7n+6 与 T(n)=3n²+2n+2 它们的 T(n) 不同, 但时间复杂度相同, 都为 O(n²)。
- 计算时间复杂度的方法:
1.用常数 1 代替运行时间中的所有加法常数 T(n)=n²+7n+6 => T(n)=n²+7n+1
2.修改后的运行次数函数中, 只保留最高阶项 T(n)=n²+7n+1 => T(n) = n²
3.去除最高阶项的系数 T(n) = n² => T(n) = n² => O(n²)
③常见时间复杂度
1常数阶 O(1)
2对数阶 O(log2n)
3线性阶 O(n)
4线性对数阶 O(nlog2n)
5平方阶 O(n^2)
6立方阶 O(n^3)
7k 次方阶 O(n^k)
8指数阶 O(2^n)
结论:
常见的算法时间复杂度由小到大依次为: Ο (1)<Ο (log2n)<Ο (n)<Ο (nlog2n)<Ο (n2)<Ο (n3)< Ο (nk) < Ο (2n) , 随着问题规模 n 的不断增大, 上述时间复杂度不断增大, 算法的执行效率越低
从图中可见, 我们应该尽可能避免使用指数阶的算法
2.空间复杂度
- 类似于时间复杂度的讨论, 一个算法的空间复杂度(Space Complexity)定义为该算法所耗费的存储空间, 它也是问题规模 n 的函数。
- 空间复杂度(Space Complexity)是对一个算法在运行过程中临时占用存储空间大小的量度。 有的算法需要占用的临时工作单元数与解决问题的规模 n 有关, 它随着 n 的增大而增大, 当 n 较大时, 将占用较多的存储单元, 例如快速排序和归并排序算法, 基数排序就属于这种情况
- 在做算法分析时, 主要讨论的是时间复杂度。 从用户使用体验上看, 更看重的程序执行的速度。 一些缓存产品(redis, memcache)和算法(基数排序)本质就是用空间换时间
3)冒泡排序
1.介绍
2.代码
public class BubbleSort {
public static void main(String[] args) {
int[] arr = {4,5,66,33,-23,0,4};
bubbleSort(arr);
System.out.println(Arrays.toString(arr));
}
public static void bubbleSort(int[] arr){
for (int i = 0; i < arr.length-1; i++) {
for (int j = 0; j < arr.length-i-1; j++) {
if (arr[j+1]<arr[j]){
int temp = arr[j+1];
arr[j+1] = arr[j];
arr[j] = temp;
}
}
}
}
}
3)优化代码
public class BubbleSort {
public static void main(String[] args) {
int[] arr = {4,5,66,33,-23,0,4};
bubbleSort(arr);
System.out.println(Arrays.toString(arr));
}
public static void bubbleSort(int[] arr){
boolean flag = true;
for (int i = 0; i < arr.length-1; i++) {
for (int j = 0; j < arr.length-i-1; j++) {
if (arr[j+1]<arr[j]){
flag = false;
int temp = arr[j+1];
arr[j+1] = arr[j];
arr[j] = temp;
}
}
if (flag){
break;
}else {
flag = true;
}
}
}
}
4)选择排序
1.介绍
2.代码
public class SelectSort {
public static void main(String[] args) {
int[] arr = {4,5,66,33,-23,0,4};
selectSort(arr);
System.out.println(Arrays.toString(arr));
}
public static void selectSort(int[] arr){
for (int i = 0; i < arr.length-1; i++) {
int temp = arr[i];
int index = i;
for (int j = i; j < arr.length; j++) {
if (temp > arr[j]){
temp = arr[j];
index = j;
}
}
arr[index] = arr[i];
arr[i] = temp;
}
}
}
5)插入排序
1.介绍
2.代码
public class InsertSort {
public static void main(String[] args) {
int[] arr = {4,5,66,33,-23,0, 3,4};
insertSort(arr);
System.out.println(Arrays.toString(arr));
}
public static void insertSort(int[] arr){
for (int i = 1; i < arr.length; i++) {
int temp = arr[i];
int index = i;
while (index > 0 && arr[index-1] > temp){
arr[index] = arr[index-1];
index--;
}
arr[index] = temp;
}
}
}
6)希尔排序
1.介绍
2.代码
public class ShellSort {
public static void main(String[] args) {
int[] arr = {4,5,66,33,-23,0,4};
shellSort(arr, 2);
System.out.println(Arrays.toString(arr));
}
//优化:插入希尔
public static void shellSort(int[] arr, int step){
for (int i = arr.length/step; i > 0 ; i /= step) {
for (int j = i; j < arr.length; j++) {
int index = j;
int temp = arr[j];
while (index - i >= 0 && arr[index - i] > temp){
arr[index] = arr[index - i];
index -= i;
}
arr[index] = temp;
}
}
}
}
7)快速排序
1.介绍
2.代码
public class QuickSort {
public static void main(String[] args) {
int[] arr = {4,5,66,33,-23,0,4};
quickSort(arr, 0, arr.length-1);
System.out.println(Arrays.toString(arr));
}
public static void quickSort(int[] arr, int start, int end){
if (start < end){
int partition = partition(arr, start, end);
quickSort(arr, start, partition-1);
quickSort(arr, partition+1, end);
}
}
public static int partition(int[] arr, int start, int end){
int temp = arr[start];
while (start < end){
while (start < end && arr[end] >= temp){
end--;
}
arr[start] = arr[end];
while (start < end && arr[start] <= temp){
start++;
}
arr[end] = arr[start];
}
arr[start] = temp;
return start;
}
}
8)归并排序
1.介绍
2.代码
public class MargeSort {
public static void main(String[] args) {
int[] arr = {4,5,66,33,-23,0,4};
mergeSort(arr, 0, arr.length - 1, new int[arr.length]);
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);
}
}
public static void merge(int[] arr, int left, int mid, int right, int[] temp) {
int i = left; // 初始化i, 左边有序序列的初始索引
int j = mid + 1; // 初始化j, 右边有序序列的初始索引
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数组
temp[t] = arr[j];
t += 1;
j += 1;
}
}
// (二)
// 把有剩余数据的一边的数据依次全部填充到temp
while (i <= mid) { // 左边的有序序列还有剩余的元素,就全部填充到temp
temp[t] = arr[i];
t += 1;
i += 1;
}
while (j <= right) { // 右边的有序序列还有剩余的元素,就全部填充到temp
temp[t] = arr[j];
t += 1;
j += 1;
}
// (三)
// 将temp数组的元素拷贝到arr
// 注意,并不是每次都拷贝所有
t = 0;
int tempLeft = left; //
// 第一次合并 tempLeft = 0 , right = 1 //第二次: tempLeft = 2 right = 3 //第三次: tL=0 ri=3
// 最后一次 tempLeft = 0 right = 7
while (tempLeft <= right) {
arr[tempLeft] = temp[t];
t += 1;
tempLeft += 1;
}
}
}
9)基数排序
1.介绍
2.代码
public class RadixSort {
public static void main(String[] args) {
int arr[] = { 53, 3, 542, 748, 14, 214 };
radixSort(arr);
System.out.println("基数排序后 " + Arrays.toString(arr));
}
// 基数排序方法
public static void radixSort(int[] arr) {
//根据前面的推导过程,我们可以得到最终的基数排序代码
//1. 得到数组中最大的数的位数
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.length
//3. 名明确,基数排序是使用空间换时间的经典算法
int[][] bucket = new int[10][arr.length];
//为了记录每个桶中,实际存放了多少个数据,我们定义一个一维数组来记录各个桶的每次放入的数据个数
//可以这里理解
//比如:bucketElementCounts[0] , 记录的就是 bucket[0] 桶的放入数据个数
int[] bucketElementCounts = new int[10];
// n=1 表示处理个位,n=10表示处理十位,n=100表示处理百位 ......
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;
//放入到对应的桶中
bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
bucketElementCounts[digitOfElement]++;
}
//按照这个桶的顺序(一维数组的下标依次取出数据,放入原来数组)
int index = 0;
//遍历每一桶,并将桶中的数据,放入到原数组
for(int k = 0; k < bucketElementCounts.length; k++) {
//如果桶中,有数据,我们才放入到原数组
// 遍历第k个桶(即第k个一维数组), 将桶中的数据放回原数组中
for (int l = 0; l < bucketElementCounts[k]; l++) {
// 取出元素放入到arr
arr[index++] = bucket[k][l];
}
//第i+1轮处理后,需要将每个 bucketElementCounts[k] = 0 !!!!
bucketElementCounts[k] = 0;
}
System.out.println("第"+(i+1)+"轮,对个位的排序处理 arr =" + Arrays.toString(arr));
}
}
}
10)堆排序(见九树应用)
11)排序总结
1.介绍
六.查找算法
1)线性查找
1.代码
public class LineSearch {
public static void main(String[] args) {
int[] arr = { 1, 2, 3, 4, 5 };
System.out.println(lineSearch(arr, 4));
}
public static int lineSearch(int[] arr, int value){
for (int i = 0; i < arr.length; i++) {
if (arr[i] == value){
return i;
}
}
return -1;
}
}
2)二分查找
1.介绍
2.代码
public class BinarySearch {
public static void main(String[] args) {
int[] arr = { 1, 2, 3, 4, 5 };
System.out.println(binarySearch(arr, 0, arr.length-1, 1));
}
public static int binarySearch(int[] arr, int left, int right, int value){
if (left > right) {
return -1;
}
int mid = (left+right)/2;
int midVal = arr[mid];
if (value > midVal){
return binarySearch(arr, mid+1, right, value);
}else if (value < midVal){
return binarySearch(arr, left, mid-1, value);
}else {
return mid;
}
}
}
3)插值查找
1.介绍
2.代码
public class InsertSearch {
public static void main(String[] args) {
int[] arr = { 1, 2, 3, 4, 5 };
System.out.println(insertSearch(arr, 0, arr.length-1, 4));
}
public static int insertSearch(int[] arr, int left, int right, int value){
if (left > right){
return -1;
}
int mid = left + (right - left) * (value - arr[left]) / (arr[right] - arr[left]);;
int midVal = arr[mid];
if (value > midVal){
return insertSearch(arr, mid+1, right, value);
}else if (value < midVal) {
return insertSearch(arr, left, mid - 1, value);
}else {
return mid;
}
}
}
4)斐波那契查找
1.介绍
2.代码
public class FibonacciSearch {
public static int maxSize = 20;
public static void main(String[] args) {
int[] arr = { 1, 2, 3, 4, 5 };
System.out.println("index=" + fibSearch(arr, 5));
}
// 因为后面我们mid=low+F(k-1)-1,需要使用到斐波那契数列,因此我们需要先获取到一个斐波那契数列
// 非递归方法得到一个斐波那契数列
public static int[] fib() {
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(); // 获取到斐波那契数列
// 获取到斐波那契分割数值的下标
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]
// 因为 前面有 f[k-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-1] = 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;
}
}
}
if(a[low]==key) {
return low;
}
else {
return -1;
}
}
}
七.哈希表
1)介绍
1.哈希表
-
散列表(Hash table, 也叫哈希表) ,是根据关键**码值(Key value)**而直接进行访问的数据结构。
-
它通过把关键码值映射到表中一个位置来访问记录, 以加快查找的速度。 这个映射函数叫做散列函数, 存放记录的数组叫做散列表。
-
哈希表的核心:private EmpLinkedList[] empLinkedListArray;
-
哈希表编程思路:
1.先根据对象的信息将其散列,得到 hashCode
2.根据对象的 hashCode 值,找到对应的数组下标,其实就是找到存储对象的链表
3.在链表中进行相应的增删改查操作
2.示意图
2)应用
1.说明
-
看一个实际需求, google 公司的一个上机题:
-
有一个公司,当有新的员工来报道时,要求将该员工的信息加入(id, 性别, 年龄, 住址…),当输入该员工的 id 时,要求查找到该员工的所有信息
-
要求:不使用数据库,尽量节省内存,速度越快越好 => 哈希表(散列)
2.代码
public class HashTableDemo {
public static void main(String[] args) {
HashTable hashTable = new HashTable(5);
Scanner scanner = new Scanner(System.in);
while (true){
System.out.print("请输入操作(1.添加 2.查找):");
int i = scanner.nextInt();
if (i == 1){
System.out.print("请输入id:");
int id = scanner.nextInt();
System.out.print("请输入name:");
String name = scanner.next();
Emp emp = new Emp(id, name);
hashTable.add(emp);
hashTable.show();
}else {
System.out.print("请输入id:");
int id = scanner.nextInt();
hashTable.selectEmpById(id);
}
}
}
}
class HashTable{
private EmpLinkedList[] empLinkedLists;
public int size;
public HashTable(int size){
this.size = size;
empLinkedLists = new EmpLinkedList[size];
for (int i = 0; i < empLinkedLists.length; i++) {
empLinkedLists[i] = new EmpLinkedList();
}
}
public void add(Emp emp){
int pos = getHashCode(emp.id);
empLinkedLists[pos].add(emp);
}
public void show(){
for (int i = 1; i <= empLinkedLists.length; i++) {
empLinkedLists[i-1].show(i);
}
}
public void selectEmpById(int id){
int pos = getHashCode(id);
empLinkedLists[pos].selectEmpById(id);
}
public int getHashCode(int id){
return id%size;
}
}
class EmpLinkedList{
private Emp head;
public void add(Emp emp){
if (head == null){
head = emp;
}else {
Emp temp = head;
while (temp.next != null){
temp = temp.next;
}
temp.next = emp;
}
}
public void show(int no){
if (head == null){
System.out.println("空链表!!");
return;
}
System.out.println(no + "号链表:" + head);
}
public void selectEmpById(int id){
if (head == null){
System.out.println("空链表!!");
return;
}
Emp temp = head;
while (temp != null){
if (temp.id == id){
System.out.println("id=" + temp.id + ", name=" + temp.name);
return;
}
temp = temp.next;
}
System.out.println("没找到!!");
}
}
class Emp{
public int id;
public String name;
public Emp next;
public Emp(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "Emp{" +
"id=" + id +
", name='" + name + '\'' +
", next=" + next +
'}';
}
}
八.树结构基础
1)二叉树介绍
1.为什么需要二叉树
-
数组存储方式的分析
- 优点: 通过下标方式访问元素, 速度快。 对于有序数组, 还可使用二分查找提高检索速度
- 缺点: 如果要检索具体某个值, 或者插入值(按一定顺序)会整体移动,效率较低
-
链式存储方式的分析
- 优点: 在一定程度上对数组存储方式有优化(比如: 插入一个数值节点, 只需要将插入节点, 链接到链表中即可,删除效率也很好)。
- 缺点: 在进行检索时, 效率仍然较低, 比如(检索某个值, 需要从头节点开始遍历)
-
能提高数据存储, 读取的效率, 比如利用 二叉排序树(Binary Sort Tree), 既可以保证数据的检索速度, 同时也可以保证数据的插入, 删除, 修改的速度。
2.树的常用术语
- 节点
- 根节点
- 父节点
- 子节点
- 叶子节点 (没有子节点的节点)
- 节点的权(节点值)
- 路径(从 root 节点找到该节点的路线)
- 层
- 子树
- 树的高度(最大层数)
- 森林 :多颗子树构成森林
- 满二叉树:如果该二叉树的所有叶子节点都在最后一层, 并且结点总数= 2^n -1, n 为层数
- 完全二叉树:如果该二叉树的所有叶子节点都在最后一层或者倒数第二层, 而且最后一层的叶子节点在左边连续, 倒数第二层的叶子节点在右边连续
3.二叉树的遍历
①前中后序遍历思路
- 前序遍历: 先输出父节点, 再遍历左子树和右子树
- 中序遍历: 先遍历左子树, 再输出父节点, 再遍历右子树
- 后序遍历: 先遍历左子树, 再遍历右子树, 最后输出父节点
②代码
public class BinaryTreeDemo {
public static void main(String[] args) {
Node root = new Node(1, "宋江");
Node node2 = new Node(2, "吴用");
Node node3 = new Node(3, "卢俊义");
Node node4 = new Node(4, "林冲");
root.left = node2;
root.right = node3;
node3.right = node4;
BinaryTree binaryTree = new BinaryTree(root);
System.out.println("-----先序遍历------");
binaryTree.preOrder();
System.out.println("------中序遍历-----");
binaryTree.inOrder();
System.out.println("-----后序遍历------");
binaryTree.postOrder();
System.out.println("-----先序查找------");
System.out.println(binaryTree.preOrderSearch(4));
System.out.println("-----删除------");
binaryTree.delete(2);
binaryTree.preOrder();
}
}
class BinaryTree{
public Node root;
public BinaryTree(Node root){
this.root = root;
}
public void preOrder(){
if (root != null){
root.preOrder();
}else {
System.out.println("二叉树为空!!!");
}
}
public void inOrder(){
if (root != null){
root.inOrder();
}else {
System.out.println("二叉树为空!!!");
}
}
public void postOrder(){
if (root != null){
root.postOrder();
}else {
System.out.println("二叉树为空!!!");
}
}
public Node preOrderSearch(int no){
if (root != null){
return root.preOrderSearch(no);
}
return null;
}
public void delete(int no){
if (root != null){
if (root.no == no){
root = null;
}else {
root.delete(no);
}
}else {
System.out.println("空二叉树!!");
}
}
}
class Node{
public int no;
public String name;
public Node left;
public Node right;
public Node(int no, String name){
this.no = no;
this.name = name;
}
public void preOrder(){
System.out.println(this);
if (this.left != null){
this.left.preOrder();
}
if (this.right != null){
this.right.preOrder();
}
}
public void inOrder(){
if (this.left != null){
this.left.inOrder();
}
System.out.println(this);
if (this.right != null){
this.right.inOrder();
}
}
public void postOrder(){
if (this.left != null){
this.left.postOrder();
}
if (this.right != null){
this.right.postOrder();
}
System.out.println(this);
}
public Node preOrderSearch(int no){
//1.根
if (no == this.no){
return this;
}
//1.左
Node temp = null;
if (this.left != null){
temp = this.left.preOrderSearch(no);
}
if (temp != null){
return temp;
}
//3.右
if (this.right != null){
temp = this.right.preOrderSearch(no);
}
return temp;
}
public void delete(int no){
if (this.left != null && this.left.no == no){
this.left = null;
return;
}
if (this.right != null && this.right.no == no){
this.right = null;
return;
}
if (this.left != null){
this.left.delete(no);
}
if (this.right != null){
this.right.delete(no);
}
}
@Override
public String toString() {
return "Node{" +
"no=" + no +
", name='" + name + '\'' +
'}';
}
}
4.二叉树的查找
①思路
②代码
- 见 八 - 1)- 3:二叉树的遍历
5.二叉树的删除
①思路
②代码
- 见 八 - 1)- 3:二叉树的遍历
2)顺序存储二叉树
1.思路
2.代码
public class ArrBinTreeDemo {
public static void main(String[] args) {
int[] arr = { 1, 2, 3, 4, 5, 6, 7 };
ArrBinTree arrBinTree = new ArrBinTree(arr);
arrBinTree.preOrder(0);
}
}
class ArrBinTree{
private int[] arr;
public ArrBinTree(int[] arr){
this.arr = arr;
}
public void preOrder(int rootIndex){
if (arr == null || arr.length == 0){
System.out.println("二叉树为空!!!");
}
System.out.println(arr[rootIndex]);
if (rootIndex*2+1 < arr.length){
preOrder(rootIndex*2+1);
}
if (rootIndex*2+2 < arr.length){
preOrder(rootIndex*2+2);
}
}
}
3)线索二叉树
1.介绍
- n 个结点的二叉链表中含有 n+1 【公式 2n-(n-1)=n+1】 个空指针域。 利用二叉链表中的空指针域, 存放指向该结点在某种遍历次序下的前驱和后继结点的指针(这种附加的指针称为"线索")
2.代码
public class ThreadBinTreeDemo {
public static void main(String[] args) {
ThreadNode root = new ThreadNode(1, "tom");
ThreadNode node2 = new ThreadNode(3, "jack");
ThreadNode node3 = new ThreadNode(6, "smith");
ThreadNode node4 = new ThreadNode(8, "mary");
ThreadNode node5 = new ThreadNode(10, "king");
ThreadNode node6 = new ThreadNode(14, "dim");
root.left = node2;
root.right = node3;
node2.left = node4;
node2.right = node5;
node3.left = node6;
// 中序线索化
ThreadBinTree threadedBinaryTree = new ThreadBinTree(root);
threadedBinaryTree.inOrderThread(root);
// 测试
ThreadNode leftNode = node5.left;
ThreadNode rightNode = node5.right;
System.out.println("10号结点的前驱结点是 " + leftNode); // 3
System.out.println("10号结点的后继结点是 " + rightNode); // 1
//中序遍历
threadedBinaryTree.inOrderResult(root); // 8, 3, 10, 1, 14, 6
}
}
class ThreadBinTree{
public ThreadNode root;
public ThreadNode pre; //前驱指针
public ThreadBinTree(ThreadNode root){
this.root = root;
this.pre = null;
}
public void inOrderResult(ThreadNode root){
ThreadNode temp = root;
while (temp !=null){
while (temp.leftThread != 1){
temp = temp.left;
}
System.out.println(temp);
while (temp.rightThread == 1){
temp = temp.right;
System.out.println(temp);
}
temp = temp.right;
}
}
public void inOrderThread(ThreadNode node){
if (node == null){
return;
}
inOrderThread(node.left);
if (node.left == null){
node.left = pre;
node.leftThread = 1;
}
if (pre != null && pre.right == null){
pre.right = node;
pre.rightThread = 1;
}
pre = node;
inOrderThread(node.right);
}
}
class ThreadNode{
public int no;
public String name;
public ThreadNode left;
public ThreadNode right;
public int leftThread; //0左子树,1前驱
public int rightThread; //0右子树,1后继
public ThreadNode(int no, String name){
this.no = no;
this.name = name;
this.leftThread = 0;
this.rightThread = 0;
}
@Override
public String toString() {
return "ThreadNode{" +
"no=" + no +
", name='" + name + '\'' +
", leftThread=" + leftThread +
", rightThread=" + rightThread +
'}';
}
}
九.树结构应用
1) 堆排序
1.说明
- ①首先构造初始堆,将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆),假设原始数组为 [4, 6, 8, 5, 9]
- ②此时我们从最后一个非叶子结点开始(叶子结点自然不用调整,第一个非叶子结点arr.length/2-1=5/2-1=1,也就是下面的 6 结点),从左至右,从下至上进行调整。
- ③找到第二个非叶节点 4,由于[4,9,8]中 9 元素最大,4 和 9 交换
- ④这时,交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中 6 最大,交换 4 和 6
- ⑤将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换,步骤如下:
- ⑥将堆顶元素 9 和末尾元素 4 进行交换
- ⑦照着之前的方法重新调整结构:将栈顶元素 4 与节点 8 互换,使其继续满足堆定义
- ⑧再将堆顶元素 8 与末尾元素 5 进行交换,得到第二大元素 8
- ⑨后续过程,继续进行调整,交换,如此反复进行,最终使得整个序列有序
2.代码
public class HeapSortDemo {
public static void main(String[] args) {
int arr[] = {4, 6, 8, 5, 9, 5325, 46, 646};
heapSort(arr);
System.out.println("排序后=" + Arrays.toString(arr));
}
public static void heapSort(int[] arr){
for (int i = arr.length/2 - 1; i >= 0 ; i--) {
adjustHeap(arr, i, arr.length);
}
int temp = 0;
for (int j = arr.length - 1; j > 0 ; j--) {
temp = arr[j];
arr[j] = arr[0];
arr[0] = temp;
adjustHeap(arr, 0, j);
}
}
public static void adjustHeap(int[] arr, int index, int len){
int temp = arr[index];
for (int i = index * 2 + 1; i < len; i = index * 2 + 1) {
if (i+1 < len && arr[i] < arr[i+1]){
i++;
}
if (arr[i] > temp){
arr[index] = arr[i];
index = i;
}else {
break;
}
}
arr[index] = temp;
}
}
2) 赫夫曼树
1.说明
2.代码
3)赫夫曼编码
1.
2.代码
4)二叉排序树
1.说明
- 二叉排序树:BST(Binary Sort(Search) Tree) ,对于二叉排序树的任何一个非叶子节点,要求左子节点的值比当前节点的值小,右子节点的值比当前节点的值大。
删除:
-
第一种情况:待删除的节点为叶子节点,直接删除该叶子节点即可
- 怎么样才算是叶子节点?targetNode.left == null && targetNode.right == null:左右节点都为空
- 怎么删除?
- 如果 parentNode.left != null && parentNode.left.value == value:即待删除的节点是 parentNode 的左子节点,则删除 parentNode 的左节点:parentNode.left = null;
- 如果 parentNode.right!= null && parentNode.right.value == value:即待删除的节点是 parentNode 的右子节点,则删除 parentNode 的右节点:parentNode.right= null;
-
第二种情况:待删除的节点只有一颗子树,直接将其子树接在 parentNode 左边或右边即可
-
怎么判断节点只有一颗子树?targetNode.left 和 targetNode.right 中有且仅有一个为 null
-
怎么删除?四种情况
- 如果 targetNode 只有左子结点,则证明子树挂在 targetNode 的左边,现在来看看 target 挂在 parentNode 的哪边?
- 如果 target 挂在 parentNode 的左边,直接将 target 的子树挂在 parentNode 的左边:parentNode.left = target.left
- 如果 target 挂在 parentNode 的右边,直接将 target 的子树挂在 parentNode 的右边:parentNode.right = target.left
- 如果 targetNode 只有右子结点,则证明子树挂在 targetNode 的右边,现在来看看 target 挂在 parentNode 的哪边?
- 如果 target 挂在 parentNode 的左边,直接将 target 的子树挂在 parentNode 的左边:parentNode.left = target.right
- 如果 target 挂在 parentNode 的右边,直接将 target 的子树挂在 parentNode 的右边:parentNode.right = target.right
- 如果 targetNode 只有左子结点,则证明子树挂在 targetNode 的左边,现在来看看 target 挂在 parentNode 的哪边?
-
以上逻辑有个 Bug ~~~ 当待删除的节点为根节点时 , parentNode == null,这时候我们直接用根节点 root 来操作即可
-
-
第三种情况:待删除的节点具有两棵颗子树
- 从 targetNode 的左子树种找到值最大的节点(一直往右遍历),或者从从 targetNode 的右树种找到值最小的节点(一直往左遍历),假设最小值为 temp ,最小值所在的节点为 minNode
- 此时 minNode 肯定为叶子节点,删除 minNode 节点
- 将 targetNode.value 设置为 temp ,这样以 targetNode 根节点的子树又是一棵二叉排序树
2.代码(未完成)
public class BinSortTreeDemo {
public static void main(String[] args) {
int[] arr = { 7, 3, 10, 12, 5, 1, 9, 2 };
BinSortTree binSortTree = new BinSortTree();
for (int i = 0; i < arr.length; i++) {
binSortTree.add(new Node(arr[i]));
}
binSortTree.inOrder();
}
}
class BinSortTree{
Node root;
public void add(Node node){
if (root == null){
root = node;
}else {
root.add(node);
}
}
public void inOrder(){
if (root == null){
System.out.println("空二叉树!");
}else {
root.inOrder();
}
}
}
class Node{
int value;
Node left;
Node right;
public Node(int value){
this.value = value;
}
@Override
public String toString() {
return "Node{" +
"value=" + value +
'}';
}
public void add(Node node){
if (node == null){
return;
}
if (node.value < this.value){
if (this.left == null){
this.left = node;
}else {
this.left.add(node);
}
}else {
if (this.right == null){
this.right = node;
}else {
this.right.add(node);
}
}
}
public void inOrder(){
if (this.left != null){
this.left.inOrder();
}
System.out.println(this);
if (this.right != null){
this.right.inOrder();
}
}
}
5)平衡二叉树
1.
2.代码
十.多路查找树
1)
1.
2.
2)
1.
2.
十一.图
1)
1.
2.
2)
1.
2.
十二.常见算法
1)
1.
2.
2)
1.
2.
t = target.left
- 如果 target 挂在 parentNode 的右边,直接将 target 的子树挂在 parentNode 的右边:parentNode.right = target.left
- 如果 targetNode 只有右子结点,则证明子树挂在 targetNode 的右边,现在来看看 target 挂在 parentNode 的哪边?
- 如果 target 挂在 parentNode 的左边,直接将 target 的子树挂在 parentNode 的左边:parentNode.left = target.right
- 如果 target 挂在 parentNode 的右边,直接将 target 的子树挂在 parentNode 的右边:parentNode.right = target.right
-
以上逻辑有个 Bug ~~~ 当待删除的节点为根节点时 , parentNode == null,这时候我们直接用根节点 root 来操作即可
-
第三种情况:待删除的节点具有两棵颗子树
- 从 targetNode 的左子树种找到值最大的节点(一直往右遍历),或者从从 targetNode 的右树种找到值最小的节点(一直往左遍历),假设最小值为 temp ,最小值所在的节点为 minNode
- 此时 minNode 肯定为叶子节点,删除 minNode 节点
- 将 targetNode.value 设置为 temp ,这样以 targetNode 根节点的子树又是一棵二叉排序树
2.代码(未完成)
public class BinSortTreeDemo {
public static void main(String[] args) {
int[] arr = { 7, 3, 10, 12, 5, 1, 9, 2 };
BinSortTree binSortTree = new BinSortTree();
for (int i = 0; i < arr.length; i++) {
binSortTree.add(new Node(arr[i]));
}
binSortTree.inOrder();
}
}
class BinSortTree{
Node root;
public void add(Node node){
if (root == null){
root = node;
}else {
root.add(node);
}
}
public void inOrder(){
if (root == null){
System.out.println("空二叉树!");
}else {
root.inOrder();
}
}
}
class Node{
int value;
Node left;
Node right;
public Node(int value){
this.value = value;
}
@Override
public String toString() {
return "Node{" +
"value=" + value +
'}';
}
public void add(Node node){
if (node == null){
return;
}
if (node.value < this.value){
if (this.left == null){
this.left = node;
}else {
this.left.add(node);
}
}else {
if (this.right == null){
this.right = node;
}else {
this.right.add(node);
}
}
}
public void inOrder(){
if (this.left != null){
this.left.inOrder();
}
System.out.println(this);
if (this.right != null){
this.right.inOrder();
}
}
}
5)平衡二叉树
1.
2.代码