文章目录
前提须知
为了尽快的找到一个好实习,我不得不翻出来基础知识好好复习,并且从头到位把代码都敲了一遍!!!!。复习课程数据结构和算法:尚硅谷av54029771 ,这是尚硅谷的课程链接,我把知识总结全部做了笔记,我在下面的博客会写道,想要更全的可以私信我。本妹子超可又自恋。本文有点长,大家可以通过目录点到你需要看的地方。代码全部正确可以直接用哦!!
前方高能!!!!!!!!!!!!!!!!
嘻嘻嘻嘻嘻自恋成功,开始正文。
队列
队列介绍
- 队列时一个有序列表,可以用数组或是链表来实现
- 遵循先入先出原则。即先存入队列的数据,要先去除,后存入的要后取出
- 以下是用数组来建立队列模型和环形队列模型,也就是环形队列。
数组队列模型
数组模拟队列分析说明
-
队列本身时有序列表,若使用数据的结构来存储队列的数据,maxSize时队列的最大容量
-
队列的输出、输入时分别从前后端来处理,因此需要两个变量front和rear分别记录队列前后端的下标,front会随着数据输出而改变,而rear则时随着数据输入而改变,如下图所示:
-
当我们将数据存入队列是称“addQyeye",addQueue的处理需要有两个步骤
-
将尾指针向后移:rear+1,当front==rear【空】
-
若尾指针rear小于队列的最大下标maxSize-1,则将数据存入rear所指的数组元素中,否则无法存入数据。rear==maxSize-1【队列满】
-
问题分析并优化
-
目前数组使用一次就不能用,没有达到复用的效果。
-
将这个数组使用算法,改进成一个环形的队列 【取模】%
代码实现
class LineQueue{
private int rear;
private int front;
private int maxSize;
private int[] arr;
public LineQueue(int maxSize) {
this.rear=-1;
this.front=-1;
this.maxSize = maxSize;
this.arr=new int[this.maxSize];
}
//判断队列是否满
private boolean isFull(){
return rear==maxSize-1;
}
//判断队列是否空
private boolean isEmpty(){
return rear==front;
}
//添加队列
public void addQueue(int n){
if(isFull()){
System.out.println("队列满,不能加入数据");
return;
}
rear++;
arr[rear]=n;
}
//出队列
public void deleteQueue(){
if(isEmpty()){
System.out.println("队列空,不能再取出数据");
return;
}
front++;
System.out.println(arr[front]);
}
//打印队列
public void printQueue(){
if(isEmpty()){
System.out.println("队列空");
return;
}
for(int i=front+1;i<=rear;i++){
System.out.print(arr[i]+"->");
}
System.out.println();
}
}
数组模拟环形
数组模拟环形队列分析说明:
- 尾索引的下一个为头索引时表示队列满,即将队列容量空出一个作为约定,这个再做判断队列满的时候需要注意**(rear+1)%maxSize==front**【满】
- rear==front【空】
- 分析示意:
1)front做变量的含义做一个调整:front就指向队列的第一个元素,也就是说arr[front]就是队列的第一个元素front的初始值=0
2)rear变量的含义做一个调整:rear指向队列的最后一个元素的后一个位置,因为希望空出一个空间作为约定,rear的初始值=0
3) 当队列满时,条件是 (rear+1)%maxSize=front【满】
4)对队列为空的 rear==front【空】
5)当我们这样分析,队列中有效的数据的个数为 (rear+maxSize-front)%maxSize
6) 我们就可以再原来的队列上修改得到,一个环形队列
代码实现
class CircleQueue{
private int rear;
private int front;
private int maxSize;
private int[] arr;
public CircleQueue(int maxSize){
this.maxSize=maxSize;
this.arr=new int[maxSize];
this.rear=0;
this.front=0;
}
//判断是否满
private boolean isFull(){
return (rear+1)%maxSize==front;
}
//判断是否为空
private boolean isEmpty(){
return rear==front;
}
//增加队列
public void addCircleQueue(int n){
if(isFull()){
System.out.println("队列已经满,不能插入");
return;
}
arr[rear]=n;
rear=(rear+1)%maxSize;
}
//删除队列
public void deleteCircleQueue(){
if(isEmpty()){
System.out.println("该队列是空的,不能删除");
return;
}
System.out.println(arr[front]);
front=(front+1)%maxSize;
}
//打印队列
public void printCircleQueue(){
if(isEmpty()){
System.out.println("该队列是空的");
return;
}
for(int i=0;i<(rear+maxSize-front)%maxSize;i++){
int k=(front+i)%maxSize;
System.out.print(arr[k]+"->");
}
}
}
栈
栈的介绍
- 栈是一个先入后出的有序列表
- 栈是限制线性表中元素的插入和删除只能在线性表的同一端进行的一种特殊的线性表。允许插入和删除的一端,为变化的一段,成为栈顶 (Top)另一端为固定的一段,成为栈底 (Botton).
- 根据栈的定义可知,最先放入栈中的元素在栈底,最后放入的元素在栈顶,而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除。
栈的应用场景
- 子程序的调用:在眺往子程序前,会先将下个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。
- 处理递归调用:和子程序的调用类似,只是除了储存下一个指令的地址外,也将参数,区域变量等数据存入堆栈中。
- 表达式的转换【中缀表达式转后缀表达式】与求值(实际解决)
- 二叉树的遍历
- 图形深度优先(depth-first)搜索法
数组模拟思路
此图为课程中的截图,课程在前提中有链接
栈的经典练习
链表实现栈代码
package com.data;
import java.util.Scanner;
/**
* 用链表表示栈
*/
public class StackDemo {
public static void main(String[] args) {
Stack listStack=new Stack();
while (true) {
System.out.println("请选择你需要的命令,a添加,d删除,p打印队列,b表示退出");
Scanner scanner = new Scanner(System.in);
String comment = scanner.next();
if(comment.equals("b"))break;
switch (comment) {
case "a":
System.out.println("请输入你要插入的元素");
int n = scanner.nextInt();
listStack.push(n);
System.out.println("该栈为:");
listStack.list();
break;
case "d":
System.out.println(listStack.pop());
//listStack.list();
break;
case "p":
listStack.list();
break;
default:
System.out.println("您输入的命令有误,请重新输入");
}
}
}
}
class Stack{
ListStack head=new ListStack(4,1,0);
ListStack temp=head.next;
ListStack cur=head;
//判断栈是否为空
public boolean isEmpty(){
return head.next==null;
}
//判断栈是否为满
public boolean isFull(){
return cur.num==head.maxSize-1;
}
//入栈操作
public void push(int data){
if(isFull()){
System.out.println("栈已满");
return;
}
ListStack newNode=new ListStack(cur.maxSize,cur.num+1,data);
temp=head.next;
cur=head;
while (true){
if(temp==null){
temp=newNode;
cur.next=temp;
break;
}
cur=temp;
temp=temp.next;
}
}
//出栈操作
public ListStack pop(){
if(isEmpty()){
return null;
}
temp=head.next;
cur=head;
while (true){
if(temp.next==null)break;
temp=temp.next;
cur=cur.next;
}
cur.next=null;
return temp;
}
//遍历栈
public void list(){
ListStack temp=head.next;
while (temp!=null){
System.out.print(temp+"->" );
temp=temp.next;
}
}
}
class ListStack{
public int num;
public int data;
public ListStack next;
public int maxSize;
public ListStack(int maxSize,int num,int data){
this.maxSize=maxSize;
this.num=num;
this.data=data;
}
@Override
public String toString() {
return "ListStack{" +
"data=" + data +
'}';
}
}
使用栈完成计算一个表达式结果 如:722-5+1=?
此图依然为截图
简单运算器代码实现
package com.data;
import java.util.Scanner;
import java.util.Stack;
public class Calculate {
//返回操作符号优先级
private int priority(char oper){
if(oper=='*'||oper=='/')return 1;
else if(oper=='+'||oper=='-')return 0;
return -1;
}
//判断是不是一个运算符
private boolean isOper(char val){
return val == '+'||val == '-'||val == '*'||val == '/';
}
//计算方法
private int cal(int num1,int num2,char oper){
int rel=0;
switch (oper){
case '+':
rel=num1+num2;
break;
case '-':
rel=num2-num1;
break;
case '*':
rel=num2*num1;
break;
case '/':
rel=num2/num1;
break;
default:
break;
}
return rel;
}
//返回最后index,增加下一个数据
private int addNext(String expression, int i, Stack data){
//先判断有多少个数字字符
StringBuffer sb=new StringBuffer();
boolean flag=false;
while (i<expression.length()&&expression.charAt(i)<58&&expression.charAt(i)>47&&i<expression.length()){
sb.append(expression.charAt(i));
i++;
flag=true;
}
if(flag){
int num=Integer.parseInt(sb.toString());
data.push(num);//将所得到的值存入数据栈
}
return i;
}
public static void main(String[] args) {
Calculate calculate=new Calculate();
System.out.println("请输入你要计算的式子");
Scanner scanner=new Scanner(System.in);
String expression=scanner.next();
//存放数据的栈
Stack<Integer> data=new Stack<>();
//存放运算符的栈
Stack<String> operator=new Stack<>();
//开始遍历这个字符串
for(int i=0;i<expression.length();i++){
i=calculate.addNext(expression,i,data);
if(i<expression.length()&&calculate.isOper(expression.charAt(i))){
//判断运算符栈中栈顶是否有数据,如果是空,直接加入
//否则判断优先级,如果栈顶的优先级小于或者等于该优先级,那么跳出开始运算。
if(operator.isEmpty()){
operator.push(expression.substring(i,i+1));
if(calculate.priority(expression.charAt(i))==1){
char opr=expression.charAt(i);
i++;
i=calculate.addNext(expression,i,data)-1;
int rel=calculate.cal(data.pop(),data.pop(),opr);
//得到的结果在将结果放到数据栈中
data.push(rel);
operator.pop();
}
}else if(calculate.priority(expression.charAt(i))>calculate.priority(operator.peek().charAt(0))){
char opr=expression.charAt(i);
i++;
i=calculate.addNext(expression,i,data)-1;
int rel=calculate.cal(data.pop(),data.pop(),opr);
//得到的结果在将结果放到数据栈中
data.push(rel);
}else {
operator.push(expression.substring(i,i+1));
}
}else if(i>=expression.length()){
//System.out.println("运行结束");
}else{
System.out.println("不支持该运算,表达式有误");
}
}
//将字符串遍历完,开始取剩下栈里面的数据
while (true){
if(operator.isEmpty())break;
int rel=calculate.cal(data.pop(),data.pop(),operator.pop().charAt(0));
data.push(rel);
}
System.out.print("="+data.pop());
System.out.println();
}
}
前缀、中缀、后缀表达式(逆波兰表达式)
前缀表达式
- 前缀表达式又成为波澜式,前缀表达式的运算符位于操作数之前
- 举例说明:
3+4)*5-6对应的前缀表达式就是 - * + 3456
前缀表达式的计算机求值
- 从右到左扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对他们组哦相应的计算,并将结果入栈,重复上述过程直到表达式最左端,最后运算符得到的值即为表达式的结果
- 上述例子为(3+4)5-6对应的前缀表达式就是- * + 3456
1)从右到左扫描,将6,5,4,3压入堆栈
2)遇到+运算符,因此弹出3和4(3为栈顶元素,4为次顶元素),计算出3+4的值,得到7再将7入栈
3)接下来是运算符,因此弹出7和5,计算7*5=35,将35入栈
4)最后是-运算符,计算出35-6,得到29入栈得到最终结果。
中缀表达式
- 中缀表达式就是常见的运算表达式 如(3+4)*5-6
- 中缀表达式的求值是我们人最熟悉的,但是对计算机来说却不好操作,因此,在计算结果时,往往会将中缀表达式转换成其他表达式来操作(一般转化为后缀表达式)
后缀表达式(逆波兰表达式)
- 后缀表达式又称逆波兰表达式,与前缀表达式相似,只是运算符位于操作数之后
- 举例说明:(3+4)*5-6对应的后缀表达式为34+5*6-
后缀表达式的计算机求值
- 从左至右扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对他们左相应的计算,开始将结果入栈;重复上面的过程直到表达式结束,最后运算得出的数值即为表达式的结果
- 举例说明:(3+4)5-6对应的后缀表达式为34+56-
1)从左到右扫描,将3和4压入堆栈。
2)遇到+,弹出3,4,计算并将得到7入栈
3)5入栈
4)遇到*,运算7*5,35入栈
5)6入栈
6)运算符,计算35-6得到35入栈。
逆波兰计算器代码实现
- 输入一个逆波兰表达式,使用栈计算其结果
思路
- 先把3 4+5*6-放到ArrayList
- 将list传递给一个放大,遍历ArrayList配合栈完成计算
- 举例说明:(3+4)5-6对应的后缀表达式为34+56-
- 从左到右扫描,将3和4压入堆栈。
- 遇到+,弹出3,4,计算并将得到7入栈
- 5入栈
- 遇到*,运算7*5,35入栈
- 6入栈
- 运算符,计算35-6得到35入栈。
代码实现
package com.data;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.Stack;
public class PolishCalculate {
/**
* 将表达式存入list中
* @param s
* @return
*/
public static List<String> multidigit(String s){
List<String> infix=new ArrayList<>();
//多位数的判断
String[] a=s.split("");
String rel="";
for(String value:a){
if(value.matches("\\d+")){
rel=rel+value;
continue;
}else{
if(rel!="") infix.add(rel);
infix.add(value);
rel="";
}
}
if(rel!="") infix.add(rel);
return infix;
}
/**
* 中缀转后缀
* @param s 表达式
* @return 后缀形式的list
*/
public static List<String> InfixToSuffix(String s){
List<String> infix=multidigit(s);
System.out.println("中缀为"+infix);
List<String> suffix=new ArrayList<>();
Stack<String> operator=new Stack<>();
for(String value:infix){
if(value.matches("\\d+")){
suffix.add(value);
}else if(isOperator(value)){
//如果操作符栈为空,则将运算符入栈
if(operator.isEmpty()||operator.peek().equals("(")){
operator.push(value);
}
//如果优先级比栈顶运算符高,也将运算符压入栈
else if(operator.peek().equals(")")!=false&&priority(value)>priority(operator.peek())){
operator.push(value);
}
//其他情况弹出运算符栈中的运算符,将运算符加入后缀表中
else {
suffix.add(operator.pop());
operator.push(value);
}
}else if(value.equals("(")||value.equals(")")){
//如果是右括号,直接压入栈顶,如果是左括号,弹出栈顶元素直到遇到右括号为止
if(value.equals("(")){
operator.push(value);
}
else{
while (!operator.peek().equals("(")){
suffix.add(operator.pop());
}
operator.pop();
}
} else {
throw new RuntimeException("操作符有误");
}
}
while (!operator.isEmpty()){
suffix.add(operator.pop());
}
return suffix;
}
/**
* 返回操作符号优先级
* @param oper
* @return
*/
public static int priority(String oper){
if(oper=="*"||oper=="/")return 1;
else if(oper=="+"||oper=="-")return 0;
return -1;
}
public static boolean isOperator(String oper){
return oper.equals("+")||oper.equals("-")||oper.equals("*")||oper.equals("/");
}
public static void main(String[] args) {
System.out.println("请输入一个表达式");
Scanner scanner=new Scanner(System.in);
String expression=scanner.next();
List<String> suffix=InfixToSuffix(expression);
System.out.println("后缀为"+suffix);
}
}
中缀表达式转换为后缀表达式
具体步骤
- 初始化两个栈:运算符栈s1和存贮空间结果的栈s2;
- 从左至右扫描中缀表达式
- 遇到操作数时,将其压入s2
- 遇到运算符时,比较其与s1栈顶运算符优先级
4.1如果s1为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈
4.2否则,若优先级比栈顶运算符的高,也将运算符压入s1
4.3否则,将s1栈顶的运算符弹出并压入s2中,再次转到(4-1)与s1中的新的栈顶运算符比较 - 遇到括号时
5.1如果时左括号,则直接压入s1
5.2如果是右括号,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一堆括号丢弃 - 重复步骤2-5,直到表达式的最右边
- 将s1中剩余的运算符依次弹出并压入s2
- 依次弹出 s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式。
举例:中缀表达式1+((2+3)*4)-5->后缀表达式
代码实现
在波兰计算机中有实现,请跳转到上面的代码查看。
哈希表
哈希表(散列)
(散列)-Google上机题
- 有一个公司,当有新员工来报道时,要求将该员工的信息加入(id,性别,年龄,住址…),当输入该员工的id时候,要求查找到该员工的所有信息。要求:不使用数据库,尽量节省内存,速度越快越好——哈希表(散列)
具体介绍
Hashtable 散列表,是根据关键值 (Key value) 而直接进行访问的数据结构。也就是说,它通过把关键之映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫散列函数,存放记录的数组叫散列表。
哈希表与缓存的关系
哈希表代码实现
package com.data;
import java.util.Scanner;
public class HashtableDemo {
public static void main(String[] args) {
Hash hash=new Hash(5);
//写一个简单的菜单
String key="";
Scanner scanner=new Scanner(System.in);
while (true){
System.out.println("add:添加成员");
System.out.println("list:显示成员");
System.out.println("select:查找成员");
System.out.println("exit:退出系统");
key=scanner.next();
switch (key){
case "add":
System.out.println("请输入id");
int id=scanner.nextInt();
System.out.println("请输入名字");
String name=scanner.next();
Employee employee=new Employee(id,name);
hash.add(employee);
break;
case "list":
hash.list();
break;
case "select":
System.out.println("请输入员工id");
int idd=scanner.nextInt();
hash.select(idd);
break;
case "exit":
scanner.close();
System.exit(0);
break;
default:
break;
}
}
}
}
class Hash{
private LinkList[] employeeList;
private int size;
//构造器
public Hash(int size){
this.size=size;
employeeList=new LinkList[size];
//这里有一个坑。
for (int i=0;i<size;i++){
employeeList[i]=new LinkList();
}
}
//添加雇员
public void add(Employee employee){
//获取第几个链表
int key=(employee.id)%size;
employeeList[key].add(employee);
}
//遍历函数
public void list(){
for (int i=0;i<size;i++){
employeeList[i].list(i);
}
}
//查找员工
public void select(int id){
int key=id%size;
employeeList[key].select(id);
}
}
class LinkList{
Employee head=new Employee(0,null);
//增加Employee
public void add(Employee newNode){
Employee temp=head;
//直到找到最后一个null
while (temp.next!=null){
temp=temp.next;
}
temp.next=newNode;
}
//遍历
public void list(int n){
Employee temp=head.next;
System.out.print("第"+(n+1)+"个链表内容为");
while (temp!=null){
System.out.print(temp+"->");
temp=temp.next;
}
System.out.println();
}
//查找
public void select(int id){
Employee temp=head.next;
while (temp!=null){
if(temp.id==id){
System.out.println("找到该员工");
break;
}
temp=temp.next;
}
if(temp==null)System.out.println("没有找到该员工");
}
}
class Employee{
public int id;
public String name;
public Employee next;
public Employee(int id,String name){
this.id=id;
this.name=name;
}
@Override
public String toString() {
return "Employee{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
小结
以上对于堆,栈,前缀,中缀,后缀以及哈希表进行了简单介绍和总结,这是一场面试前的突击,要想拿到好的offer一定得基础过硬,因为各种大厂都是很看重我们的基础滴。今后我会将我整理的,学习到的分享到博客来,以及即将迎接的各种面试经历,希望在这观看的你和我都不断努力,争取拿到高薪!!!如有问题,欢迎留言。