数据结构与算法
1.数组模拟循环队列
1.1. 实现的关键步骤:
1、front:指向队列的第一个元素
2、rear:指向队列的最后一个元素的后一个位置
3、maxSize:队列的最大容量,可以在初始化时定义;如果没定义,可设置默认值。
4、front 和 rear 的初始值为0
5、判空条件:(rear + 1)% maxSize == front
6、判满条件:rear == front
7、入队:判满、rear指针后移、赋值
8、出队:判空、front指针后移
9、获取队首元素:获取 front 指向的元素
10、打印队列:从 front指向的元素到 front + 队列元素的个数
11、队列元素的个数:(rear + maxSize - front) % maxSize
1.2. 实现代码
class ArrayQueue{
// 表示数组的最大容量
private int maxSize;
//front 指向队列的第一个元素
private int front;
//rear 指向队列的最后一个元素的后一个位置
private int rear;
// 模拟队列的数组,用于存放元素
private int[] arr;
public ArrayQueue(int arrMaxSize) {
// 初始化时,front 和 rear的初始值都为0,但由于int 本身默认值为 0 ,因此这里可以不用写
//最大值由外部指定(由于牺牲掉一个位置,所以例如传入4实际上只有3个位置)
maxSize = arrMaxSize;
// 创建数组存储
arr = new int[maxSize];
}
// 判断队列是否满
public boolean isFull() {
return (rear + 1) % maxSize == front;
}
// 判断队列是否为空
public boolean isEmpty() {
return rear == front;
}
// 添加数据到队列
public void addQueue(int n) {
// 判断队列是否满
if (isFull()) {
System.out.println("队列满,不能加入数据~");
return;
}
//直接将数据加入
arr[rear] = n;
//将 rear 后移, 这里必须考虑取模
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 void showQueue() {
// 遍历
if (isEmpty()) {
System.out.println("队列空的,没有数据~~");
return;
}
// 思路:从front开始遍历,遍历多少个元素
// 动脑筋
for (int i = front; i < front + size() ; i++) {
System.out.printf("arr[%d]=%d\n", i % maxSize, arr[i % maxSize]);
}
}
// 求出当前队列有效数据的个数
public int size() {
// rear = 2
// front = 1
// maxSize = 3
return (rear + maxSize - front) % maxSize;
}
// 显示队列的头数据, 注意不是取出数据
public int headQueue() {
// 判断
if (isEmpty()) {
throw new RuntimeException("队列空的,没有数据~~");
}
return arr[front];
}
}
2. 单链表
2.1. 实现的关键步骤:
1、需要创建一个节点类(属性包括 数据域 和 指针域 next )
2、需要创建一个链表类(属性包括头节点,初始化链表时,需要创建一个头节点,头节点没有实际意义)
3、增加–找到要插入位置的节点的前一个节点,然后先将节点与后一个节点连接,再将前面的节点连接。关键代码:node.next = temp.next;temp.next = node;
4、删除–找到要删除的节点的前一个节点,然后将引用修改为下下个节点。关键代码:temp.next = temp.next.next;
5、查看–找到要查看的节点并返回。关键代码:return temp;
6、修改–找到要修改的节点,然后修改其值即可。关键代码:temp.值 = 新值;
7、打印链表:先跳过头节点,然后遍历链表直到 temp 指向空时,打印链表的信息。
2.2. 实现代码
package com.cs.testt;
import java.util.Scanner;
/**
* @ClassName SingleLinkedListDemo
* @Description TODO
* @Author jiaqi
* @Date 2022/2/22 14:27
* @Version 1.0
**/
public class SingleLinkedListDemo {
public static void main(String[] args) {
SingleLinkedList singleLinkedList = new SingleLinkedList();
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.println("------------------------");
System.out.println("请输入以下字母:");
System.out.println("a(add):添加");
System.out.println("f(find):查找");
System.out.println("u(update):更新");
System.out.println("d(delete):删除");
System.out.println("p(print):打印");
System.out.println("o(out):退出程序");
System.out.println("------------------------");
String next = scanner.next();
if (next.charAt(0) == 'o') {
break;
}
switch (next.charAt(0)) {
case 'a':
System.out.println("请输入要添加的值:");
int item = scanner.nextInt();
singleLinkedList.add(item);
break;
case 'f':
System.out.println("请输入要查找的值:");
int item1 = scanner.nextInt();
Node node = singleLinkedList.find(item1);
if (node == null) {
System.out.println("没有找到对应的值");
} else {
System.out.println("找到的值为:" + node.num);
}
break;
case 'u':
System.out.println("请输入要旧的值:");
int oldN = scanner.nextInt();
System.out.println("请输入要新的值:");
int newN = scanner.nextInt();
boolean update = singleLinkedList.update(oldN, newN);
String s = update ? "更新成功" : "要更新的值根本不存在";
System.out.println(s);
break;
case 'd':
System.out.println("请输入要删除的值:");
int item3 = scanner.nextInt();
Node delete = singleLinkedList.delete(item3);
if (delete == null) {
System.out.println("没有找到对应的值");
} else {
System.out.println("已删除的值为:" + delete.num);
}
break;
case 'p':
String s1 = singleLinkedList.printList();
System.out.println(s1);
break;
default:
System.out.println("-------------------------------");
System.out.println("|您的输入格式有误,请重新输入: |");
System.out.println("-------------------------------");
break;
}
}
System.out.println("程序已退出...");
}
}
//链表类
class SingleLinkedList {
private Node head;
public SingleLinkedList() {
this.head = new Node(0, null);
}
//再链表的尾部添加一个节点
public void add(int num) {
Node temp = head;
while (true) {
if (temp.next == null) {
break;
}
temp = temp.next;
}
Node node = new Node(num, null);
temp.next = node;
}
// //根据编号添加一个节点(根据num从小到大排序)
// public void addByOrder(Node node){
// Node temp = head;
// while (true){
// if (temp.next == null){
// break;
// }
// if (temp.next.num >= node.num){
// break;
// }
// temp = temp.next;
// }
//
// node.next = temp.next;
// temp.next = node;
// }
public Node find(int num) {
Node temp = head.next;
while (true) {
if (temp == null) {
break;
}
if (temp.num == num) {
break;
}
temp = temp.next;
}
return temp;
}
public boolean update(int oldnum, int newNum) {
Node temp = head.next;
boolean flag = false;//是否找到旧值
while (true) {
if (temp == null) {
break;
}
if (temp.num == oldnum) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
temp.num = newNum;
}
return flag;
}
public Node delete(int num) {
Node del = null;
Node temp = head;
while (true) {
if (temp.next == null) {//没有要找的节点
break;
}
if (temp.next.num == num) {
del = temp.next;
temp.next = temp.next.next;
break;
}
temp = temp.next;
}
return del;
}
public String printList() {
StringBuffer stringBuffer = new StringBuffer();
Node temp = head.next;
int count = 1;
while (true) {
if (temp == null) {
break;
}
stringBuffer.append("第" + count + "个值: " + temp.num + "\n");
temp = temp.next;
count++;
}
return stringBuffer.toString();
}
//计算节点的个数
public int NodeNum(){
int count = 0;
Node temp = head.next;
while (true){
if (temp == null){
break;
}
temp = temp.next;
count++;
}
return count;
}
//查找单链表中的倒数第k个节点(双指针法)
public Node findOrderByLast(int n){
//设置两个指针
Node temp1 = head.next;
Node temp2 = head.next;
while (true){
if (temp1 == null){
break;
}
if (n<1){
break;
}
temp1 = temp1.next;
n--;
}
while (true){
if (temp1 == null){
break;
}
temp1 = temp1.next;
temp2 = temp2.next;
}
return temp2;
}
//反转链表
public void reserve(Node head){
Node reserveHead = new Node(0,null);
Node temp = head.next;
Node cur;
while (true){
if (temp == null){
break;
}
cur = temp;
temp = temp.next;
cur.next = reserveHead.next;
reserveHead.next = cur;
}
head.next = reserveHead.next;
}
}
//节点类
class Node {
public int num;
public Node next;
public Node(int num, Node next) {
this.num = num;
this.next = next;
}
}
2.3. 拓展:
题目1:求一个单链表中节点的个数
方法:先创建一个变量作为计数器存储个数,初始值为 0 ,接着创建临时指针,循环遍历直到指针指向的节点为 null ,计数不断增加,最后跳出循环返回计数器。
题目2:查找单链表中的倒数第k个节点
方法1:双指针法。先让指针1和指针2指向第一个节点,接着指针1先走k个节点,然后让两个指针同时走,知道指针1走到null之后,指针2所指的节点即为所求。
方法2:先求出节点的个数(题目1),然后用节点个数减掉k得到需要走的步数,接着让指针走这么多步,即可。
题目3:单链表的反转。
方法:先创建一个新的头节点,然后遍历链表,当遍历到一个节点时将其放到新的头节点的后面的那个位置(每一个都如此),这样到最后的 temp == null 时停止,此时除了头节点外,其余都为所求,最后将旧的头节点指向新的头节点的下一个节点即可(即:head.next = nowHead.next;),然后head。
题目4:从头到尾打印单链表(不可破坏原先链表的结构)
方法:用栈结构 Stack ,遍历链表,并将每个节点压入栈(stack.push),接着循环栈,当栈不为空时,出栈(stack.pop),打印出栈的元素即可。
题目5:合并两个有序的单链表,并且合并之后还是有序的
方法:先创建一个新的头节点,然后创建两个指针指向两个链表的第一个节点(非头节点),然后再创建一个指针记录指向新的头节点(用户每次添加),循环直到有两个链表有一个指针为空为止,当哪个值比较小时,则让新链表的指针指向那个节点,并且新链表向后移动一个,指向的那个节点的指针也要向右移动,退出循环后,要将新链表的指针指向未结束的链表的指针的节点。最后返回头节点。
3. 双链表
3.1. 实现的关键步骤
1、需要创建一个节点类(属性包括 数据域 和指针域 next 和 pre )
2、需要创建一个链表类(属性包括头节点,与单链表类似)
3、增加–节点到链表尾部:与单链表类似,通过遍历,然后连接
4、增加–节点到指定的位置:与单链表类似,找到前一个节点,然后改变指针域
5、删除–可以自我删除,这个是双链表的特点,指针 temp 可以直接指向待删除的节点,然后通过改变指针指向,即可。
6、查看和修改–与单链表类似,直接找到指定的节点进行操作
7、打印链表–与单链表一样
3.2. 实现代码:
package com.cs.testt;
/**
* @ClassName DoubleLinkedListDemo
* @Description TODO
* @Author jiaqi
* @Date 2022/2/23 14:27
* @Version 1.0
**/
//双向链表
public class DoubleLinkedListDemo {
}
class DoubleLinkedList{
private TNode head;
public DoubleLinkedList(){
this.head = new TNode(0,null,null);
}
//添加节点到尾部
public void addLast(TNode tNode){
TNode temp = head;
while (true){
if (temp.next == null){
break;
}
temp = temp.next;
}
temp.next = tNode;
tNode.pre = temp;
}
//根据值的大小(从小到大)添加节点
public void addOrder(TNode tNode){
TNode temp = head;
while (true){
if (temp.next == null){
break;
}
if (temp.next.num >=tNode.num){
break;
}
temp = temp.next;
}
if (temp.next == null){
temp.next = tNode;
tNode.pre = temp;
}else {
//没名字的先来
temp.next.pre = tNode;
tNode.next = temp.next;
temp.next = tNode;
tNode.pre = temp;
}
}
//删除指定值的节点(双向链表删除可以自删除)
public boolean delete(int num){
TNode temp = head.next;
//记录是否找到要删除的值
boolean flag = false;
while (true){
if (temp == null){
break;
}
if (temp.num == num){
flag = true;
break;
}
temp = temp.next;
}
if (flag){
temp.pre.next = temp.next;
if (temp.next != null){
temp.next.pre = temp.pre;
}
}
return flag;
}
//修改节点
public boolean update(int oNum,int nNum){
//记录是否找到旧值
boolean flag = false;
TNode temp = head.next;
while (true){
if (temp == null){
//找不到
break;
}
if (temp.num == oNum){
//找到了
flag = true;
break;
}
temp = temp.next;
}
if (flag){
temp.num = nNum;
}
return flag;
}
//查看
public TNode select(int num){
TNode temp = head.next;
while (true){
if (temp == null || temp.num == num){
return temp;
}
temp = temp.next;
}
}
//打印链表
public String printList(){
StringBuffer sb = new StringBuffer();
TNode temp = head.next;
int count = 1;
while (true){
if (temp == null){
break;
}
//打印节点
sb.append("第"+count+"个节点:"+temp.num+"\n");
//指针移动1
temp = temp.next;
count++;
}
return sb.toString();
}
}
//(双向)节点类
class TNode{
public int num;
public TNode next;
public TNode pre;
public TNode(int num,TNode next,TNode pre){
this.num = num;
this.next = next;
this.pre = pre;
}
}
4. 单向环形链表
分为有头节点和无头节点。
经典问题:约瑟夫问题(可以用不带头节点的单向环形链表实现,用链表模拟,然后记录出圈顺序,然后改变节点的指向。)
5. 数组模拟栈(stack)
5.1. 实现的关键步骤
1、自己实现一个栈类,属性包括(栈的最大空间,栈顶指针(指向栈顶),模拟栈的数组)
2、初始化时,传入一个最大空间,然后创建一个这么大的数组,栈顶指针值为 -1
3、出栈:先 判断栈是否为空,再 int value = arr[top]; top --; return value;
4、入栈:先 判断栈是否为满,再 top++;arr[top] = value;
5、判满:top == maxSize - 1
6、判空:top == -1
7、打印栈:从栈顶打印到栈底,因此 for 循环从 位置 top 打印到 位置 0,即可。
5.2. 实现代码
略
5.3. 拓展
题目一:实现综合计算器题目。传入一个表达式字符串,返回表达式的计算结果。例如:传入字符串 7*2+5+1*5+3-4
,返回结果为4
。
方法:
1、通过一个 index 指针,遍历这个字符串表达式
2、创建一个数字栈存数字,一个符号栈存符号
3、如果遍历到是一个数字,就直接入数栈
4、如果遍历到是一个符号,就分情况:
4.1、如果符号栈为空,直接入栈
4.2、如果符号栈不为空,就将当前符号的优先级与栈顶符号的优先级比较
4.2.1、如果当前操作符的优先级小于或等于栈顶操作符,则从数栈pop出两个数,符号栈pop出一个符号,进行运算,将运算结果存到数栈中,然后当前操作符入符号栈
4.2.2、如果当前操作符的优先级大于栈顶操作符,则直接入符号栈
5、当表达式扫描完毕,就顺序从数栈和符号栈中pop出相应的数和符号进行运算
6、最后,符号栈为空,数栈剩下一个数,这个数就是表达式的结果。
代码实现:(一位数的加减乘除)
package com.cs.testt;
/**
* @ClassName Test1
* @Description TODO
* @Author jiaqi
* @Date 2022/2/28 11:24
* @Version 1.0
**/
public class Test1 {
public static void main(String[] args) throws Exception {
//给你一个字符串表达式,返回一个表达式结果。
String s = "3+4*5+4-9-6*5";
ArrayStack numStcak = new ArrayStack(10);
ArrayStack operStack = new ArrayStack(10);
int index = 0;
while (true){
//判断index是否结束,结束就退出
if (index == s.length()){
break;
}
//先判断是否是操作符
if (isoper(s.charAt(index))){
//是操作符判断栈是否为空
if (operStack.isEmpty()){
//直接入栈
operStack.push(s.charAt(index));
}else {
//判断操作符的优先级
if (operf(s.charAt(index))>operf(operStack.peek())){
operStack.push(s.charAt(index));
}else {
int suan = suan(numStcak.pop(), numStcak.pop(), operStack.pop());
numStcak.push(suan);
operStack.push(s.charAt(index));
}
}
}else{
//是数字直接进数字栈
numStcak.push(s.charAt(index) -'0');
}
index++;
}
//index 走完之后
while (true){
if (operStack.isEmpty()){
break;
}
int suan = suan(numStcak.pop(), numStcak.pop(), operStack.pop());
numStcak.push(suan);
}
System.out.printf("%s = %d",s,numStcak.pop());
}
//判断传进来的是运算符还是数字
public static boolean isoper(int a){
if (a == '+' || a == '-' || a== '*'||a == '/'){
return true;
}else {
return false;
}
}
//传入两个数字和一个运算符,返回结果
public static int suan(int num1,int num2,int oper){
if (oper == '+'){
return num1 + num2;
}else if (oper == '-'){
return num2 - num1;
}else if (oper == '*'){
return num1 * num2;
}else if (oper == '/'){
return num2 / num1;
}else {
return -1;
}
}
//判断操作符的优先级
public static int operf(int oper){
if (oper == '*' || oper == '/'){
return 1;
}else if (oper == '+' || oper == '-'){
return 0;
}else {
return -1;
}
}
}
class ArrayStack{
private int top;
private int[] arr;
private int maxSize;
//初始化
public ArrayStack(int maxSize){
arr = new int[maxSize];
this.maxSize = maxSize;
top = -1;
}
//判断满
public boolean isFull(){
return top == maxSize - 1;
}
public boolean isEmpty(){
return top == -1;
}
//入栈
public void push(int num) throws Exception {
if (isFull()){
throw new Exception("栈满");
}
top++;
arr[top] = num;
}
//出栈
public int pop() throws Exception {
if (isEmpty()){
throw new Exception("栈空");
}
int temp = arr[top];
top--;
return temp;
}
//查看栈顶元素
public int peek() throws Exception {
if (isEmpty()){
throw new Exception("栈空");
}
return arr[top];
}
}
代码实现:(多位数的加减乘除)
package com.cs.testt;
/**
* @ClassName Test1
* @Description TODO
* @Author jiaqi
* @Date 2022/2/28 11:24
* @Version 1.0
**/
public class Test1 {
public static void main(String[] args) throws Exception {
//给你一个字符串表达式,返回一个表达式结果。
String s = "30+4*5+4-9-6*5";
ArrayStack numStcak = new ArrayStack(10);
ArrayStack operStack = new ArrayStack(10);
int index = 0;
while (true){
//判断index是否结束,结束就退出
if (index == s.length()){
break;
}
//先判断是否是操作符
if (isoper(s.charAt(index))){
//是操作符判断栈是否为空
if (operStack.isEmpty()){
//直接入栈
operStack.push(s.charAt(index));
}else {
//判断操作符的优先级
if (operf(s.charAt(index))>operf(operStack.peek())){
operStack.push(s.charAt(index));
}else {
int suan = suan(numStcak.pop(), numStcak.pop(), operStack.pop());
numStcak.push(suan);
operStack.push(s.charAt(index));
}
}
index++;
}else{
//是数字直接进数字栈
//numStcak.push(s.charAt(index) -'0');
int sum = 0;
while (true){
if (s.length() == index || isoper(s.charAt(index))){
break;
}
sum = sum * 10 + (s.charAt(index) - '0');
index++;
}
numStcak.push(sum);
}
}
//index 走完之后
while (true){
if (operStack.isEmpty()){
break;
}
int suan = suan(numStcak.pop(), numStcak.pop(), operStack.pop());
numStcak.push(suan);
}
System.out.printf("%s = %d",s,numStcak.pop());
}
//判断传进来的是运算符还是数字
public static boolean isoper(int a){
if (a == '+' || a == '-' || a== '*'||a == '/'){
return true;
}else {
return false;
}
}
//传入两个数字和一个运算符,返回结果
public static int suan(int num1,int num2,int oper){
if (oper == '+'){
return num1 + num2;
}else if (oper == '-'){
return num2 - num1;
}else if (oper == '*'){
return num1 * num2;
}else if (oper == '/'){
return num2 / num1;
}else {
return -1;
}
}
//判断操作符的优先级
public static int operf(int oper){
if (oper == '*' || oper == '/'){
return 1;
}else if (oper == '+' || oper == '-'){
return 0;
}else {
return -1;
}
}
}
class ArrayStack{
private int top;
private int[] arr;
private int maxSize;
//初始化
public ArrayStack(int maxSize){
arr = new int[maxSize];
this.maxSize = maxSize;
top = -1;
}
//判断满
public boolean isFull(){
return top == maxSize - 1;
}
public boolean isEmpty(){
return top == -1;
}
//入栈
public void push(int num) throws Exception {
if (isFull()){
throw new Exception("栈满");
}
top++;
arr[top] = num;
}
//出栈
public int pop() throws Exception {
if (isEmpty()){
throw new Exception("栈空");
}
int temp = arr[top];
top--;
return temp;
}
//查看栈顶元素
public int peek() throws Exception {
if (isEmpty()){
throw new Exception("栈空");
}
return arr[top];
}
}
前缀表达式(波兰表达式,从右向左扫描):- * + 3 4 5 6
计算机容易理解的形式,人类不容易理解
中缀表达式:(3 + 4)*5-6
人类容易理解的形式,计算机不容易理解
后缀表达式(逆波兰表达式,从左向右扫描):3 4 + 5 * 6 -
计算机容易理解的形式,人类不容易理解
前面的扩展就是我们将中缀表达式表达给计算机,很复杂,又得考虑优先级,计算机很难理解,因此编码难度大。因此一般我们是先将中缀表达式转换成其它表达式(一般是后缀表达式,因为最容易转换)的,然后再给计算机计算。
一般是先将中缀表达式转换为后缀表达式,然后再从左到右进行计算后缀表达式,计算的规矩是:从左到右扫描表达式,遇到数字就压入栈,遇到运算符就弹出两个数字,用运算符对他们进行运算,并将结果入栈,重复上诉操作直到表达式的最右端,最后运算得到的值则为结果。
6. 递归
6.1. 递归在实际开发中可以解决什么问题?
八皇后问题,汉诺塔,阶乘问题,迷宫问题,球和篮子问题,快排,归并排序,二分查找,分治查找 等等。
6.2. 递归的常见形式
个人理解:通常的递归都是 链式 或者 发散式。例如:打印 1- n 就是链式,打印二叉树就是 发散式。一般来说,一个方法包含链式或发散式中的一个节点(发散式只是多了可以选择的路径而已)。
链式:
public 返回值 方法名(第一个节点需要的数据){
//退出条件,一般是结束条件
if(结束的标志){
return xx;
}
返回值 = 方法名(一般是第一个节点的后续);
return 返回值
}
发散式:
public 返回值 方法名(第一个节点需要的数据){
//退出条件,一般是结束条件
if(结束的标志){
return xx;
}
if(满足链路1){
返回值 = 方法名(一般是第一个节点的后续);
return 返回值
}
if(满足链路2){
返回值 = 方法名(一般是第一个节点的后续);
return 返回值
}
}
6. 排序算法
6.1. 冒泡排序
package com.atguigu.sort;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
public class BubbleSort {
public static void main(String[] args) {
// int arr[] = {3, 9, -1, 10, 20};
//
// System.out.println("排序前");
// System.out.println(Arrays.toString(arr));
//为了容量理解,我们把冒泡排序的演变过程,给大家展示
//测试一下冒泡排序的速度O(n^2), 给80000个数据,测试
//创建要给80000个的随机的数组
int[] arr = new int[80000];
for(int i =0; i < 80000;i++) {
arr[i] = (int)(Math.random() * 8000000); //生成一个[0, 8000000) 数
}
Date data1 = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date1Str = simpleDateFormat.format(data1);
System.out.println("排序前的时间是=" + date1Str);
//测试冒泡排序
bubbleSort(arr);
Date data2 = new Date();
String date2Str = simpleDateFormat.format(data2);
System.out.println("排序后的时间是=" + date2Str);
//System.out.println("排序后");
//System.out.println(Arrays.toString(arr));
/*
// 第二趟排序,就是将第二大的数排在倒数第二位
for (int j = 0; j < arr.length - 1 - 1 ; j++) {
// 如果前面的数比后面的数大,则交换
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
System.out.println("第二趟排序后的数组");
System.out.println(Arrays.toString(arr));
// 第三趟排序,就是将第三大的数排在倒数第三位
for (int j = 0; j < arr.length - 1 - 2; j++) {
// 如果前面的数比后面的数大,则交换
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
System.out.println("第三趟排序后的数组");
System.out.println(Arrays.toString(arr));
// 第四趟排序,就是将第4大的数排在倒数第4位
for (int j = 0; j < arr.length - 1 - 3; j++) {
// 如果前面的数比后面的数大,则交换
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
System.out.println("第四趟排序后的数组");
System.out.println(Arrays.toString(arr)); */
}
// 将前面额冒泡排序算法,封装成一个方法
public static void bubbleSort(int[] arr) {
// 冒泡排序 的时间复杂度 O(n^2), 自己写出
int temp = 0; // 临时变量
boolean flag = false; // 标识变量,表示是否进行过交换
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) + "趟排序后的数组");
//System.out.println(Arrays.toString(arr));
if (!flag) { // 在一趟排序中,一次交换都没有发生过
break;
} else {
flag = false; // 重置flag!!!, 进行下次判断
}
}
}
}
6.2. 插入排序
package com.atguigu.sort;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
public class InsertSort {
public static void main(String[] args) {
//int[] arr = {101, 34, 119, 1, -1, 89};
// 创建要给80000个的随机的数组
int[] arr = new int[80000];
for (int i = 0; i < 80000; i++) {
arr[i] = (int) (Math.random() * 8000000); // 生成一个[0, 8000000) 数
}
System.out.println("插入排序前");
Date data1 = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date1Str = simpleDateFormat.format(data1);
System.out.println("排序前的时间是=" + date1Str);
insertSort(arr); //调用插入排序算法
Date data2 = new Date();
String date2Str = simpleDateFormat.format(data2);
System.out.println("排序前的时间是=" + date2Str);
//System.out.println(Arrays.toString(arr));
}
//插入排序
public static void insertSort(int[] arr) {
int insertVal = 0;
int insertIndex = 0;
//使用for循环来把代码简化
for(int i = 1; i < arr.length; i++) {
//定义待插入的数
insertVal = arr[i];
insertIndex = i - 1; // 即arr[1]的前面这个数的下标
// 给insertVal 找到插入的位置
// 说明
// 1. insertIndex >= 0 保证在给insertVal 找插入位置,不越界
// 2. insertVal < arr[insertIndex] 待插入的数,还没有找到插入位置
// 3. 就需要将 arr[insertIndex] 后移
while (insertIndex >= 0 && insertVal < arr[insertIndex]) {
arr[insertIndex + 1] = arr[insertIndex];// arr[insertIndex]
insertIndex--;
}
// 当退出while循环时,说明插入的位置找到, insertIndex + 1
// 举例:理解不了,我们一会 debug
//这里我们判断是否需要赋值
if(insertIndex + 1 != i) {
arr[insertIndex + 1] = insertVal;
}
//System.out.println("第"+i+"轮插入");
//System.out.println(Arrays.toString(arr));
}
/*
//使用逐步推导的方式来讲解,便利理解
//第1轮 {101, 34, 119, 1}; => {34, 101, 119, 1}
//{101, 34, 119, 1}; => {101,101,119,1}
//定义待插入的数
int insertVal = arr[1];
int insertIndex = 1 - 1; //即arr[1]的前面这个数的下标
//给insertVal 找到插入的位置
//说明
//1. insertIndex >= 0 保证在给insertVal 找插入位置,不越界
//2. insertVal < arr[insertIndex] 待插入的数,还没有找到插入位置
//3. 就需要将 arr[insertIndex] 后移
while(insertIndex >= 0 && insertVal < arr[insertIndex] ) {
arr[insertIndex + 1] = arr[insertIndex];// arr[insertIndex]
insertIndex--;
}
//当退出while循环时,说明插入的位置找到, insertIndex + 1
//举例:理解不了,我们一会 debug
arr[insertIndex + 1] = insertVal;
System.out.println("第1轮插入");
System.out.println(Arrays.toString(arr));
//第2轮
insertVal = arr[2];
insertIndex = 2 - 1;
while(insertIndex >= 0 && insertVal < arr[insertIndex] ) {
arr[insertIndex + 1] = arr[insertIndex];// arr[insertIndex]
insertIndex--;
}
arr[insertIndex + 1] = insertVal;
System.out.println("第2轮插入");
System.out.println(Arrays.toString(arr));
//第3轮
insertVal = arr[3];
insertIndex = 3 - 1;
while (insertIndex >= 0 && insertVal < arr[insertIndex]) {
arr[insertIndex + 1] = arr[insertIndex];// arr[insertIndex]
insertIndex--;
}
arr[insertIndex + 1] = insertVal;
System.out.println("第3轮插入");
System.out.println(Arrays.toString(arr)); */
}
}
6.3. 选择排序
package com.atguigu.sort;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
//选择排序
public class SelectSort {
public static void main(String[] args) {
//int [] arr = {101, 34, 119, 1, -1, 90, 123};
//创建要给80000个的随机的数组
int[] arr = new int[80000];
for (int i = 0; i < 80000; i++) {
arr[i] = (int) (Math.random() * 8000000); // 生成一个[0, 8000000) 数
}
System.out.println("排序前");
//System.out.println(Arrays.toString(arr));
Date data1 = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date1Str = simpleDateFormat.format(data1);
System.out.println("排序前的时间是=" + date1Str);
selectSort(arr);
Date data2 = new Date();
String date2Str = simpleDateFormat.format(data2);
System.out.println("排序前的时间是=" + date2Str);
//System.out.println("排序后");
//System.out.println(Arrays.toString(arr));
}
//选择排序
public static void selectSort(int[] arr) {
//在推导的过程,我们发现了规律,因此,可以使用for来解决
//选择排序时间复杂度是 O(n^2)
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]; // 重置min
minIndex = j; // 重置minIndex
}
}
// 将最小值,放在arr[0], 即交换
if (minIndex != i) {
arr[minIndex] = arr[i];
arr[i] = min;
}
//System.out.println("第"+(i+1)+"轮后~~");
//System.out.println(Arrays.toString(arr));// 1, 34, 119, 101
}
/*
//使用逐步推导的方式来,讲解选择排序
//第1轮
//原始的数组 : 101, 34, 119, 1
//第一轮排序 : 1, 34, 119, 101
//算法 先简单--》 做复杂, 就是可以把一个复杂的算法,拆分成简单的问题-》逐步解决
//第1轮
int minIndex = 0;
int min = arr[0];
for(int j = 0 + 1; j < arr.length; j++) {
if (min > arr[j]) { //说明假定的最小值,并不是最小
min = arr[j]; //重置min
minIndex = j; //重置minIndex
}
}
//将最小值,放在arr[0], 即交换
if(minIndex != 0) {
arr[minIndex] = arr[0];
arr[0] = min;
}
System.out.println("第1轮后~~");
System.out.println(Arrays.toString(arr));// 1, 34, 119, 101
//第2轮
minIndex = 1;
min = arr[1];
for (int j = 1 + 1; j < arr.length; j++) {
if (min > arr[j]) { // 说明假定的最小值,并不是最小
min = arr[j]; // 重置min
minIndex = j; // 重置minIndex
}
}
// 将最小值,放在arr[0], 即交换
if(minIndex != 1) {
arr[minIndex] = arr[1];
arr[1] = min;
}
System.out.println("第2轮后~~");
System.out.println(Arrays.toString(arr));// 1, 34, 119, 101
//第3轮
minIndex = 2;
min = arr[2];
for (int j = 2 + 1; j < arr.length; j++) {
if (min > arr[j]) { // 说明假定的最小值,并不是最小
min = arr[j]; // 重置min
minIndex = j; // 重置minIndex
}
}
// 将最小值,放在arr[0], 即交换
if (minIndex != 2) {
arr[minIndex] = arr[2];
arr[2] = min;
}
System.out.println("第3轮后~~");
System.out.println(Arrays.toString(arr));// 1, 34, 101, 119 */
}
}
6.4. 快速排序
package com.atguigu.sort;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
public class QuickSort {
public static void main(String[] args) {
//int[] arr = {-9,78,0,23,-567,70, -1,900, 4561};
//测试快排的执行速度
// 创建要给80000个的随机的数组
int[] arr = new int[8000000];
for (int i = 0; i < 8000000; i++) {
arr[i] = (int) (Math.random() * 8000000); // 生成一个[0, 8000000) 数
}
System.out.println("排序前");
Date data1 = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date1Str = simpleDateFormat.format(data1);
System.out.println("排序前的时间是=" + date1Str);
quickSort(arr, 0, arr.length-1);
Date data2 = new Date();
String date2Str = simpleDateFormat.format(data2);
System.out.println("排序前的时间是=" + date2Str);
//System.out.println("arr=" + Arrays.toString(arr));
}
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循环的目的是让比pivot 值小放到左边
//比pivot 值大放到右边
while( l < r) {
//在pivot的左边一直找,找到大于等于pivot值,才退出
while( arr[l] < pivot) {
l += 1;
}
//在pivot的右边一直找,找到小于等于pivot值,才退出
while(arr[r] > pivot) {
r -= 1;
}
//如果l >= r说明pivot 的左右两的值,已经按照左边全部是
//小于等于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;
}
}
// 如果 l == r, 必须l++, r--, 否则为出现栈溢出
if (l == r) {
l += 1;
r -= 1;
}
//向左递归
if(left < r) {
quickSort(arr, left, r);
}
//向右递归
if(right > l) {
quickSort(arr, l, right);
}
}
}
7.查找
7.1. 二分查找
package com.atguigu.search;
import java.util.ArrayList;
import java.util.List;
//注意:使用二分查找的前提是 该数组是有序的.
public class BinarySearch {
public static void main(String[] args) {
//int arr[] = { 1, 8, 10, 89,1000,1000, 1234 };
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 , 11, 12, 13,14,15,16,17,18,19,20 };
//
// int resIndex = binarySearch(arr, 0, arr.length - 1, 1000);
// System.out.println("resIndex=" + resIndex);
List<Integer> resIndexList = binarySearch2(arr, 0, arr.length - 1, 1);
System.out.println("resIndexList=" + resIndexList);
}
// 二分查找算法
/**
*
* @param arr
* 数组
* @param left
* 左边的索引
* @param right
* 右边的索引
* @param findVal
* 要查找的值
* @return 如果找到就返回下标,如果没有找到,就返回 -1
*/
public static int binarySearch(int[] arr, int left, int right, int findVal) {
// 当 left > right 时,说明递归整个数组,但是没有找到
if (left > right) {
return -1;
}
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 {
return mid;
}
}
//完成一个课后思考题:
/*
* 课后思考题: {1,8, 10, 89, 1000, 1000,1234} 当一个有序数组中,
* 有多个相同的数值时,如何将所有的数值都查找到,比如这里的 1000
*
* 思路分析
* 1. 在找到mid 索引值,不要马上返回
* 2. 向mid 索引值的左边扫描,将所有满足 1000, 的元素的下标,加入到集合ArrayList
* 3. 向mid 索引值的右边扫描,将所有满足 1000, 的元素的下标,加入到集合ArrayList
* 4. 将Arraylist返回
*/
public static List<Integer> binarySearch2(int[] arr, int left, int right, int findVal) {
System.out.println("hello~");
// 当 left > right 时,说明递归整个数组,但是没有找到
if (left > right) {
return new ArrayList<Integer>();
}
int mid = (left + right) / 2;
int midVal = arr[mid];
if (findVal > midVal) { // 向 右递归
return binarySearch2(arr, mid + 1, right, findVal);
} else if (findVal < midVal) { // 向左递归
return binarySearch2(arr, left, mid - 1, findVal);
} else {
// * 思路分析
// * 1. 在找到mid 索引值,不要马上返回
// * 2. 向mid 索引值的左边扫描,将所有满足 1000, 的元素的下标,加入到集合ArrayList
// * 3. 向mid 索引值的右边扫描,将所有满足 1000, 的元素的下标,加入到集合ArrayList
// * 4. 将Arraylist返回
List<Integer> resIndexlist = new ArrayList<Integer>();
//向mid 索引值的左边扫描,将所有满足 1000, 的元素的下标,加入到集合ArrayList
int temp = mid - 1;
while(true) {
if (temp < 0 || arr[temp] != findVal) {//退出
break;
}
//否则,就temp 放入到 resIndexlist
resIndexlist.add(temp);
temp -= 1; //temp左移
}
resIndexlist.add(mid); //
//向mid 索引值的右边扫描,将所有满足 1000, 的元素的下标,加入到集合ArrayList
temp = mid + 1;
while(true) {
if (temp > arr.length - 1 || arr[temp] != findVal) {//退出
break;
}
//否则,就temp 放入到 resIndexlist
resIndexlist.add(temp);
temp += 1; //temp右移
}
return resIndexlist;
}
}
}
8. 哈希表
8.1. 模拟HashTable代码
package com.cs.testt;
/**
* @ClassName HashDemo
* @Description TODO
* @Author jiaqi
* @Date 2022/3/7 0:03
* @Version 1.0
**/
public class HashDemo {
public static void main(String[] args) {
MyHashTable myHashTable = new MyHashTable(7);
System.out.println(myHashTable.print());
myHashTable.add(new Student(1,"jack",21));
myHashTable.add(new Student(2,"jack",21));
myHashTable.add(new Student(3,"oo",21));
myHashTable.add(new Student(4,"jack",21));
myHashTable.add(new Student(5,"jack",21));
myHashTable.add(new Student(6,"jack",21));
myHashTable.add(new Student(7,"jack",21));
myHashTable.add(new Student(8,"jack",21));
myHashTable.deleteById(7);
myHashTable.updete(new Student(4,"tom",11));
System.out.println(myHashTable.get(3));
System.out.println(myHashTable.print());
}
}
//数组类
class MyHashTable{
private LinkedList[] linkedLists;
private int size;//多少条链表
public MyHashTable(int size){
this.size = size;
linkedLists = new LinkedList[size];
}
//添加元素
public void add(Student stu){
//根据id获取到散列的位置
int i = myHash(stu.getId());
if (linkedLists[i] == null){
linkedLists[i] = new LinkedList();
}
linkedLists[i].add(stu);
}
//根据id获取到学生的值(默认id是不重复的)
public Student get(int id){
int i = myHash(id);
if (linkedLists[i] == null){
return null;
}
Student student = linkedLists[i].get(id);
return student;
}
//遍历元素
public String print(){
StringBuffer stringBuffer = new StringBuffer();
for (int i = 0; i < size; i++) {
if (linkedLists[i] == null){
continue;
}
stringBuffer.append(linkedLists[i].print());
}
return stringBuffer.toString();
}
//散列方法
private int myHash(int id){
return id % size;
}
//根据id删除一个学生
public Student deleteById(int id){
int i = myHash(id);
if (linkedLists[i] == null){
return null;
}
Student delete = linkedLists[i].delete(id);
return delete;
}
//修改一个学生的数据
public boolean updete(Student stu){
//根据id找到数据并更新,找到返回true,找不到返回false
int i = myHash(stu.getId());
if (linkedLists[i] == null){
return false;
}
boolean upd = linkedLists[i].upd(stu);
return upd;
}
}
//链表类
class LinkedList{
//头节点:表示第一个数据,默认为空
private Student head;
public LinkedList(){
head = null;
}
//添加数据:根据一个值,添加到链表的尾部
public void add(Student stu){
//临时指针
Student temp = head;
//头节点都为空
if (temp == null){
head = stu;
return;
}
while (true){
if (temp.next == null){
temp.next = stu;
break;
}
temp = temp.next;
}
}
//根据id获取数据
public Student get(int id){
//临时指针
Student temp = head;
while (true){
if (temp == null || temp.getId() == id){
return temp;
}
temp = temp.next;
}
}
public StringBuffer print(){
StringBuffer sb = new StringBuffer();
Student temp = head;
while (true){
if (temp == null){
break;
}
sb.append(temp.toString());
temp = temp.next;
}
return sb;
}
//根据id删除一个学生
public Student delete(int id){
Student temp = head;
//如果头节点为空
if (temp == null){
return temp;
}
//如果删除的是头节点
if (temp.getId() == id){
Student result = temp;
head = temp.next;
return result;
}
while (true){
if (temp.next == null){
return temp.next;
}
if (temp.next.getId() == id){
Student result = temp.next;
temp.next =temp.next.next;
return result;
}
temp = temp.next;
}
}
public boolean upd(Student stu){
Student temp = head;
while (true){
if (temp == null){
return false;
}
if (temp.getId() == stu.getId()){
temp.setName(stu.getName());
temp.setAge(stu.getAge());
return true;
}
temp = temp.next;
}
}
}
//封装学生数据
class Student{
private int id;
private String name;
private int age;
public Student next;
public Student(int id,String name,int age){
this.id = id;
this.name = name;
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}' +
'\n';
}
}
9. 二叉树
9.1. 代码实现二叉树/中前后序遍历/打印二叉树
package com.cs.testt;
import java.util.ArrayList;
import java.util.List;
/**
* @ClassName BinaryTree
* @Description TODO 二叉树
* @Author jiaqi
* @Date 2022/3/7 15:26
* @Version 1.0
**/
public class BinaryTree {
private TreeNode root; //根节点
public BinaryTree(){
root = null;
}
//按关键字查找节点
public TreeNode find(int key){
TreeNode cur = root; //从根节点开始查找
if(cur == null){ //如果树为空,直接返回null
return null;
}
while(cur.age != key){
if(key < cur.age){
cur = cur.leftChild; //如果关键字比当前节点小,转向左子节点
}else{
cur = cur.leftChild; //如果关键字比当前节点大,转向右子节点
}
if(cur == null){ //没有找到结果,搜索结束
return null;
}
}
return cur;
}
//插入新节点
public void insert(TreeNode node){
if(root == null){
root = node; //如果树为空,则新插入的节点为根节点
}else{
TreeNode cur = root;
while(true){
if(node.age < cur.age){
if(cur.leftChild == null){ //找到了要插入节点的父节点
cur.leftChild = node;
return;
}
cur = cur.leftChild;
}else{
if(cur.rightChild == null){ //找到了要插入节点的父节点
cur.rightChild = node;
return;
}
cur = cur.rightChild;
}
}
}
}
//删除指定节点
public boolean delete(TreeNode node){
if(root == null){
return false; //如果为空树,直接返回false
}
boolean isLeftChild = true; //记录目标节点是否为父节点的左子节点
TreeNode cur= root; //要删除的节点
TreeNode parent = null; //要删除节点的父节点
while(cur.age != node.age){ //确定要删除节点和它的父节点
parent = cur;
if(node.age < cur.age){ //目标节点小于当前节点,跳转左子节点
cur = cur.leftChild;
}else{//目标节点大于当前节点,跳转右子节点
isLeftChild = false;
cur = cur.rightChild;
}
if(cur == null){
return false; //没有找到要删除的节点
}
}
if(cur.leftChild == null && cur.rightChild == null){ //目标节点为叶子节点(无子节点)
if(cur == root){ //要删除的为根节点
root = null;
}else if(isLeftChild){
//要删除的不是根节点,则该节点肯定有父节点,该节点删除后,需要将父节点指向它的引用置空
parent.leftChild = null;
}else{
parent.rightChild = null;
}
}else if(cur.leftChild == null){ //只有一个右子节点
if(cur == root){
root = cur.rightChild;
}else if(isLeftChild){
parent.leftChild = cur.rightChild;
}else{
parent.rightChild = cur.rightChild;
}
}else if(cur.rightChild == null){ //只有一个左子节点
if(cur == root){
root = cur.leftChild;
}else if(isLeftChild){
parent.leftChild = cur.leftChild;
}else{
parent.rightChild = cur.leftChild;
}
}else{ //有两个子节点
//第一步要找到欲删除节点的后继节点
TreeNode successor = cur.rightChild;
TreeNode successorParent = null;
while(successor.leftChild != null){
successorParent = successor;
successor = successor.leftChild;
}
//欲删除节点的右子节点就是它的后继,证明该后继无左子节点,则将以后继节点为根的子树上移即可
if(successorParent == null){
if(cur == root){ //要删除的为根节点,则将后继设置为根,且根的左子节点设置为欲删除节点的做左子节点
root = successor;
root.leftChild = cur.leftChild;
}else if(isLeftChild){
parent.leftChild = successor;
successor.leftChild = cur.leftChild;
}else{
parent.rightChild = successor;
successor.leftChild = cur.leftChild;
}
}else{ //欲删除节点的后继不是它的右子节点
successorParent.leftChild = successor.rightChild;
successor.rightChild = cur.rightChild;
if(cur == root){
root = successor;
root.leftChild = cur.leftChild;
}else if(isLeftChild){
parent.leftChild = successor;
successor.leftChild = cur.leftChild;
}else{
parent.rightChild = successor;
successor.leftChild = cur.leftChild;
}
}
}
return true;
}
public static final int PREORDER = 1; //前序遍历
public static final int INORDER = 2; //中序遍历
public static final int POSTORDER = 3; //中序遍历
//遍历
public void traverse(int type){
switch(type){
case 1:
System.out.print("前序遍历:\t");
preorder(root);
System.out.println();
break;
case 2:
System.out.print("中序遍历:\t");
inorder(root);
System.out.println();
break;
case 3:
System.out.print("后序遍历:\t");
postorder(root);
System.out.println();
break;
}
}
//前序遍历
public void preorder(TreeNode currentRoot){
if(currentRoot != null){
System.out.print(currentRoot.age+"\t");
preorder(currentRoot.leftChild);
preorder(currentRoot.rightChild);
}
}
//中序遍历,这三种遍历都用了迭代的思想
public void inorder(TreeNode currentRoot){
if(currentRoot != null){
inorder(currentRoot.leftChild); //先对当前节点的左子树对进行中序遍历
System.out.print(currentRoot.age+"\t"); //然后访问当前节点
inorder(currentRoot.rightChild); //最后对当前节点的右子树对进行中序遍历
}
}
//后序遍历
public void postorder(TreeNode currentRoot){
if(currentRoot != null){
postorder(currentRoot.leftChild);
postorder(currentRoot.rightChild);
System.out.print(currentRoot.age+"\t");
}
}
//私有方法,用迭代方法来获取左子树和右子树的最大深度,返回两者最大值
private int getDepth(TreeNode currentNode,int initDeep){
int deep = initDeep; //当前节点已到达的深度
int leftDeep = initDeep;
int rightDeep = initDeep;
if(currentNode.leftChild != null){ //计算当前节点左子树的最大深度
leftDeep = getDepth(currentNode.leftChild, deep+1);
}
if(currentNode.rightChild != null){ //计算当前节点右子树的最大深度
rightDeep = getDepth(currentNode.rightChild, deep+1);
}
return Math.max(leftDeep, rightDeep);
}
//获取树的深度
public int getTreeDepth(){
if(root == null){
return 0;
}
return getDepth(root,1);
}
//返回关键值最大的节点
public TreeNode getMax(){
if(isEmpty()){
return null;
}
TreeNode cur = root;
while(cur.rightChild != null){
cur = cur.rightChild;
}
return cur;
}
//返回关键值最小的节点
public TreeNode getMin(){
if(isEmpty()){
return null;
}
TreeNode cur = root;
while(cur.leftChild != null){
cur = cur.leftChild;
}
return cur;
}
//以树的形式打印出该树
public void displayTree(){
int depth = getTreeDepth();
ArrayList<TreeNode> currentLayerNodes = new ArrayList<TreeNode> ();
currentLayerNodes.add(root); //存储该层所有节点
int layerIndex = 1;
while(layerIndex <= depth){
int NodeBlankNum = (int)Math.pow(2, depth-layerIndex)-1; //在节点之前和之后应该打印几个空位
for(int i = 0;i<currentLayerNodes.size();i++){
TreeNode node = currentLayerNodes.get(i);
printBlank(NodeBlankNum); //打印节点之前的空位
if(node == null){
System.out.print("*\t"); //如果该节点为null,用空位代替
}else{
System.out.print("* "+node.age+"\t"); //打印该节点
}
printBlank(NodeBlankNum); //打印节点之后的空位
System.out.print("*\t"); //补齐空位
}
System.out.println();
layerIndex++;
currentLayerNodes = getAllNodeOfThisLayer(currentLayerNodes); //获取下一层所有的节点
}
}
//获取指定节点集合的所有子节点
private ArrayList getAllNodeOfThisLayer(List parentNodes){
ArrayList list = new ArrayList<Node>();
TreeNode parentNode;
for(int i=0;i<parentNodes.size();i++){
parentNode = (TreeNode)parentNodes.get(i);
if(parentNode != null){
if(parentNode.leftChild != null){ //如果上层的父节点存在左子节点,加入集合
list.add(parentNode.leftChild);
}else{
list.add(null); //如果上层的父节点不存在左子节点,用null代替,一样加入集合
}
if(parentNode.rightChild != null){
list.add(parentNode.rightChild);
}else{
list.add(null);
}
}else{ //如果上层父节点不存在,用两个null占位,代表左右子节点
list.add(null);
list.add(null);
}
}
return list;
}
//打印指定个数的空位
private void printBlank(int num){
for(int i=0;i<num;i++){
System.out.print("*\t");
}
}
//判空
public boolean isEmpty(){
return (root == null);
}
//判断是否为叶子节点
public boolean isLeaf(TreeNode node){
return (node.leftChild != null || node.rightChild != null);
}
//获取根节点
public TreeNode getRoot(){
return root;
}
}
//树节点的封装类
class TreeNode{
int age;
String name;
TreeNode leftChild; //左子节点的引用
TreeNode rightChild; //右子节点的引用
public TreeNode(int age,String name){
this.age = age;
this.name = name;
}
//打印该节点的信息
public void displayNode(){
System.out.println("name:"+name+",age:"+age);
}
}
参考自:https://www.jianshu.com/p/bb0c12b34f2a
9.2. 树的相关操作
树的前中后序遍历
树的前中后序查找
打印树
二叉排序树
霍夫曼树
AVL树
B树
B+树
顺序存储二叉树(求他的前中后序的数组)
10. 分治算法
经典的:汉诺塔问题、快排、二分查找等等。
11.动态规划
0 - 1背包问题(限制每个物品只能一个)
完全背包问题(每个物品不限个数)
区别:分治算法分解得到的小问题是相互独立的,而动态规划算法分解的问题是相互不独立的。
12. 字符串匹配问题
12.1. 暴力匹配
package Algorithm;
public class ViolenceMatch {
public static void main(String[] args) {
String str1="addgadfhfgsfgs";
String str2="fhf";
int index = violenceMatch(str1, str2);
System.out.println("index="+index);
}
//暴力匹配算法实现
public static int violenceMatch(String str1,String str2){
char[] s1 = str1.toCharArray();
char[] s2 = str2.toCharArray();
int s1len = s1.length;
int s2len = s2.length;
//两个索引
int i = 0;//指向字符串str1
int j = 0;//指向字符串str2
while (i<s1len && j<s2len) {//保证匹配不越界
if (s1[i] == s2[j]) {
i++;
j++;
}else {
i = i-(j-1);
j=0;
}
}
//判断是否匹配成功
if (j == s2len) {
return i-j;
}else {
return -1;
}
}
}
12.2. KMP 算法
13. 贪心算法
13.1. 集合覆盖问题
动态规划、贪心、回溯、递归、分治、深度优先/广度优先搜索
几个排序算法(冒泡、插入、选择、快排)、二分查找、双指针、滑动窗口、数据结构
五大常用算法之一:分治算法
五大常用算法之二:动态规划算法
五大常用算法之三:贪心算法
五大常用算法之四:回溯法
五大常用算法之五:分支限界法
14. 额外
如何理解「分治算法」?
比如说二分查找这个问题的思路是如下:
1、我们的目的是什么:找到一个数组中的某个指定的值,那么方法的参数和返回值我们就可以确定了(参数是数组,返回值是找到的值)
2、如何分解:每个分治题目的分解方式都不太一样,在这道题的分治是通过对半分(原因是数组有序),那么对半分后的数组需要做什么呢?答案是重复 1 的操作,即我们的目的还是一样的:找到对半分的数组的指定值,那么方法还是第1中的方法,那么这里就是写递归方法的地方。
3、递归方法写完之后,我们需要获取其返回值,根据左右两个半数组的返回值,择优返回一个最优的返回。
4、当然在方法之初,需要一个退出条件(配合return使用),这个条件一般是「不可再分的情况」。
基本上所有的分治算法都遵循上面的思路,不同的地方主要是在第 2 点,难点也是在第 2 点,因为你不知道对于一道陌生的题,你到底应该怎么分解(对半?还是去一?等等)而且第 3 有时候某些分支也不需要递归(因为分解的足够小后已经成为了特殊情况,可以直接处理)。
如何理解「动态规划」?
动态规划和分治的区别:分治是把大问题「分裂」成若干个小问题,直到小问题不可再分,直接处理后返回,所以,思想上是自大到小处理,用到递归。动态规划也是把大问题「分裂」成若干个小问题,但是它是从小问题处理到大问题的过程,会有一个状态方程和一个维护的数组(可能一维或者二维)以及几个初始条件,状态方程反映了前后步骤的关系,数组存储各个步骤的数据。
冒泡排序:让第k个和第k+1个进行比较,然后交换,一直交换,到最后一个就是最大值(最小值),然后再重新比较。(联想:最后一个就像泡泡一样冒出来了)。
选择排序:先假设第一个就是最小的,接着遍历后面,找到一个比第一个还小的,就交换,这样,第一个就是最小的。接着第二个。。
插入排序:假定第一个是排好的,接着从第二个开始,往前找,看要插入到哪里。
快速排序