文章目录

  • Java数据结构和算法基础知识
    • 一、Java数据结构
      • 1. 线性结构:数组、队列、链表和栈
        • 1.1 数组(Array)
        • 1.2 稀疏数组
        • 1.3 队列(Queue)
        • 1.4 链表(Linked List)
        • 1.5 栈(Stack)
      • 2. 非线性结构:二维数组,多维数组,广义表,树结构,图结构
      • 4. 堆
      • 6. 树
      • 7. 散列表(Hash)
    • 二、Java算法知识
      • 1. 排序算法
        • 1. 冒泡排序
        • 2. 选择排序
        • 3. 直接插入排序
        • 4. 希尔排序
        • 5. 快速排序
      • 2. 递归
      • 3. 二分查找

Java数据结构和算法基础知识

一、Java数据结构

数据结构是计算机存储、组织数据的方式,指相互之间存在一种或多种特定关系的数据元素的集合。

1. 线性结构:数组、队列、链表和栈

  1. 分类:
  • 线性结构:数据元素之间存在一一对应关系(arr[0] = 1);
    存储方式:
    顺序存储(数组):线性存储表,存储元素是连续的;
    链式存储(链表):元素不一定连续,元素节点存储数据元素和相邻元素地址信息。
1.1 数组(Array)
  1. 定义:在Java中,数组是用来存放同一种数据类型的集合,注意只能存放同一种数据类型(Object类型数组除外),既可以存储基本数据类型,也可以存储引用数据类型。。
  2. 格式:
    数据类型 [] 数组名称 = new 数据类型[数组长度]; (推荐格式)
    数据类型 数组名称 [] = new 数据类型[数组长度];
    数据类型 [] 数组名称 = new int[]{数组元素1,数组元素2,...}; = 数据类型 [] 数组名称 = {数组元素1,数组元素2,...}; (简化)
    例如:定义一个名为myArrary的 int 型的数组,数组长度为6; int [] myArray = new int [6];
  3. 数组赋值和访问元素:
// 声明数组,声明一个长度为6,只能存放int类型的数据
int [] myArray = new int[3];
// 给myArray第一个元素赋值为1
myArray[0] = 1;
// 访问数组的第2个元素
System.out.println(myArray[1]);
  1. 数组遍历:
int[] arr={1,2,3,4,5,6,7,8,9};
for (int i = 0; i < arr.length; i++) {System.out.print(arr[i]+",");
}
//遍历结果为:1,2,3,4,5,6,7,8,9,

数组反向遍历:

// 定义数组
int[] arr = {1, 2, 3, 4, 5, 6, 7};
// 1.将数组前后元素互换
for (int i = 0; i < arr.length / 2; i++) {// 引入中间变量t进行反转;int t = arr[i];arr[i] = arr[arr.length - 1 - i];arr[arr.length - 1 - i] = t;
}
// 2. 遍历互换后的新数组
for (int i = 0; i < arr.length; i++) {System.out.print(arr[i] + ",");
}
//结果:7,6,5,4,3,2,1,
  1. 数组的局限性分析:
    ①、插入快,对于无序数组,即元素没有按照从大到小或者某个特定的顺序排列,只是按照插入的顺序排列。无序数组增加一个元素很简单,只需要在数组末尾添加元素即可,但是有序数组却不一定了,它需要在指定的位置插入;
    ②、查找慢,当然如果根据下标来查找是很快的。但是通常我们都是根据元素值来查找,给定一个元素值,对于无序数组,我们需要从数组第一个元素开始遍历,直到找到那个元素。有序数组通过特定的算法查找的速度会比无需数组快;
    ③、删除慢,根据元素值删除,我们要先找到该元素所处的位置,然后将元素后面的值整体向前面移动一个位置。也需要比较多的时间;
    ④、数组一旦创建后,大小就固定了,不能动态扩展数组的元素个数。如果初始化给了一个很大的数组大小,会浪费内存空间,如果给小了,后面数据个数增加了又添加不进去了。
  • 很显然,数组虽然插入快,但是查找和删除都比较慢,而且扩展性差,所以我们一般不会用数组来存储数据。
1.2 稀疏数组

存储一个11*11的棋盘需要一个二维数组(黑子表示为1、蓝子表示为2 )int arr[][] = new int[11][11],但如果转化为稀疏数组,只需要存放下面的数组:
第一行:总行数、总列数、棋子的个数
第二行:第一个棋子的行数、第一个棋子的列数、棋子表示的数字

  • 代码:
public class SparseArray {public static void main(String[] args) {// 1. 原始数组int arr[][] = new int[11][11];arr[1][2] = 1;arr[2][3] = 2;for (int[] row : arr) {for (int data : row) {System.out.printf("%d\t",data);}System.out.println();}// 2. 统计数组非0个数(棋子总数)int sum = 0;for (int i = 0; i < 11; i++) {for (int j = 0; j < 11; j++) {if (arr[i][j] != 0){sum++;}}}System.out.println("sum=" + sum);// 3. 创建稀疏数组int sparseArray[][] = new int[sum+1][3];// 4. 稀疏数组赋值sparseArray[0][0] = 11;sparseArray[0][1] = 11;sparseArray[0][2] = sum;// 5. 遍历二维数组,将非0的数据放入稀疏数组int count = 0; // 定义一个计数的数字,用来记录非0数据for (int i = 0; i < 11; i++) {for (int j = 0; j < 11; j++) {if (arr[i][j] != 0){count++;sparseArray[count][0] = i; // 稀疏数组行sparseArray[count][1] = j; // 稀疏数组列sparseArray[count][2] = arr[i][j];  // 非0数据}}}// 6. 输出稀疏数组System.out.println("==========Sparse Array==========");for (int i = 0; i < sparseArray.length; i++) {System.out.printf("%d\t%d\t%d\t\n",sparseArray[i][0],sparseArray[i][1],sparseArray[i][2]);}System.out.println();// 7. 从稀疏数组恢复为原始数组// 7.1 读取稀疏数组第一行的数据,用作新数组的行列int arr2[][] = new int[sparseArray[0][0]][sparseArray[0][1]];// 7.2 读取稀疏数组的非0元素,赋值到恢复的数组上for (int i = 1; i < sparseArray.length; i++) {arr2[sparseArray[i][0]][sparseArray[i][1]] = sparseArray[i][2];}System.out.println("==========Sparse Array-->arr==========");for (int[] row : arr2) {for (int data : row) {System.out.printf("%d\t",data);}System.out.println();}}
}

结果:

1.3 队列(Queue)
  1. 定义:原则:先进先出,可以用数组或链表实现;
    rear:队尾,front:队首,初始化为 -1,maxSize:队列的最大容量
    数据入队时:front索引不变,rear索引增大;即存数据从队尾加入
    数据出队时:rear索引不变,front索引增大;即取数据从队首取出
  2. 数组模拟队列:
class ArrayQueue{private int rear;   // 队尾指针private int front;  // 队首指针private int maxSize;// 队列最大值private int[] arr;  // 定义数组模拟队列// 初始化队列public ArrayQueue(int arrMaxSize){maxSize = arrMaxSize;arr = new int[maxSize];rear = -1;   // 指向队尾元素front = -1;  // 指向队首元素前一个位置}// 判断队列是否为空public boolean isEmpty(){return rear == front;}// 判断队列是否满public boolean isFull(){return rear == maxSize-1;}// 入队,添加数据public void addQueue(int n){if (isFull()){System.out.println("队列已满,不能加入数据!");return;}rear++;arr[rear] = n;}// 出队,取出数据public int getQueue(){if (isEmpty()){throw new RuntimeException("队列为空,不能取数据!");}front++;return arr[front];}//显示队列的所有元素public void showQueue(){if (isEmpty()){System.out.println("队列为空,没有数据!");}for (int i = 0; i < arr.length; i++) {System.out.printf("arr[%d]=%d\t",i,arr[i]);}}// 显示队列头数据public int showHead(){if (isEmpty()){System.out.println("队列为空,没有头元素!");}return arr[front+1];}
}

为什么rear和front初始值取 -1?
因为当存入一个数据后,rear+1,刚好等于0,即指向0索引处的数据。

  • 问题:上面实现的队列,取出数据后空间还是被占用,不能再存数据,即队列不能复用!
  • 改进:改为循环队列,将rear、front初始值变为0(即直接指向元素),此时队列中有效元素的个数为:
    (rear + maxSize - front) % maxSize,当rear=0,front=0,有效元素个数为0;当存入一个元素后。rear=1,front=0,有效元素个数为1;
class CircularQueue{private int rear;   // 队尾指针private int front;  // 队首指针private int maxSize;// 队列最大值private int[] arr;  // 定义数组// 初始化队列public CircularQueue(int arrMaxSize){maxSize = arrMaxSize;arr = new int[maxSize];}// 判断队列是否为空public boolean isEmpty(){return rear == front;}// 判断队列是否满public boolean isFull(){return (rear + 1) % maxSize == front;}// 入队,添加数据public void addQueue(int n){if (isFull()){System.out.println("队列已满,不能加入数据!");return;}arr[rear] = n;// rear后移:取余原因当rear指向队列最后时,前面队列还有位置,取余后rear可以移动到前面rear = (rear + 1) % maxSize;}// 出队,取出数据public int getQueue(){if (isEmpty()){throw new RuntimeException("队列为空,不能取数据!");}// 分析front是指向队列第一个元素,先将front指向的值保留为临时变量,再将front往后移动int temp = arr[front];front = (front + 1) % maxSize;return temp;}//显示队列的所有元素public void showQueue(){if (isEmpty()){System.out.println("队列为空,没有数据!");}// 从front开始遍历,遍历个数:front + (rear + maxSize -front)% maxSizefor (int i = front; i < front + ((rear + maxSize -front)% maxSize); i++) {System.out.printf("arr[%d]=%d\n",i % maxSize,arr[i % maxSize]);}}// 显示队列头数据public int showHead(){if (isEmpty()){System.out.println("队列为空,没有头元素!");}return arr[front];}
}
  • 注意:
    循环数组队列为什么要留一个空间?
    随着元素存储 rear 指针慢慢上移,最终移到 maxSize-1 的位置,上面留一个空位,用来判断 是不是该将 rear指针向下面的空位置移动,即队列已满,也就是 (rear + 1) % maxSize == front 刚好成立。
1.4 链表(Linked List)
  • 单向链表
  1. 是有序的列表,头指针,根据需求创建;链表由节点组成,节点分为两个区域(data、next),next指向下一个节点。
  2. 实际应用:
    客户端传递给服务端一堆乱序的id,需要服务端按顺序返回给客户端,不能查数据库,在内存层完成操作,此时链表就是很好的选择,即将按顺序插入单项链表中,再返回。
  3. 链表逻辑结构:不一定是连续的(即a1不一定指向a2)
  4. 实现单向链表
  • 不考虑顺序,只将节点添加到链表末尾即可,也就是按照你插入的顺序决定链表的结构。
    模拟水浒传英雄,创建一个对象(英雄)作为节点,data包括(id,name,nickname),next指向下一个英雄;
    思路:创建头节点,然后定义一个temp辅助变量,指向头节点;
    怎么实现插入节点?
    插入节点时,需要找到链表的末尾,因为链表由next连接;
    怎么找到最后一个节点?
    即链表的末尾,用temp去查找,当找到 next=null 时,即是链表的末尾。
public class SingleLinkedListTest{public static void main(String[] args) {HeroNode heroNode1 = new HeroNode(1,"宋江","及时雨");HeroNode heroNode2 = new HeroNode(2,"卢俊义","玉麒麟");HeroNode heroNode3 = new HeroNode(3,"林冲","豹子头");SingleLinkedList linkedList = new SingleLinkedList();// 加入链表linkedList.addNode1(heroNode1);linkedList.addNode1(heroNode2);linkedList.addNode1(heroNode3);// 显示链表linkedList.showList();}
}class SingleLinkedList{// 创建头节点private HeroNode head = new HeroNode(0,"","");// 添加节点public void addNode1(HeroNode heroNode){// 定义辅助变量HeroNode temp = head;// 遍历链表while (true){// 找到链表的最后if (temp.next == null){break;}// 没有找到将temp后移temp = temp.next;}// 退出while后,temp指向链表最后,此时需要将temp指向新的节点继续操作temp.next = heroNode;}// 显示链表public void showList(){//判断链表是否为空if (head.next == null){System.out.println("链表为空");return;}//因为头节点不能动,所以通过辅助变量HeroNode temp = head.next;// 遍历while (true){// 如果 = null,即为链表的末尾了if (temp == null){break;}// 如果不为null,输出节点信息(已经toString了),记住:将temp后移继续输出System.out.println(temp);temp = temp.next;}}
}class HeroNode{public int id;public String name;public String nikeName;public HeroNode next;public HeroNode(int id,String name,String nikeName){this.id = id;this.name = name;this.nikeName = nikeName;}@Overridepublic String toString() {return "HeroNode{" +"id=" + id +", name='" + name + '\'' +", nikeName='" + nikeName + '\'' +'}';}
}
  • 考虑顺序:按照排名插入到指定的位置,如果没有按照水浒传英雄排名插入,给出提示(必须按照宋江1,卢俊义2、吴用3…插入)
    如图:a2必须插入a1和a3之间
    思路:通过遍历并且使用辅助变量找到a1的位置,然后将 temp.next = 新节点新节点.next = temp.next
public class SingleLinkedListTest{public static void main(String[] args) {HeroNode heroNode1 = new HeroNode(1,"宋江","及时雨");HeroNode heroNode2 = new HeroNode(2,"卢俊义","玉麒麟");HeroNode heroNode3 = new HeroNode(3,"林冲","豹子头");SingleLinkedList linkedList = new SingleLinkedList();// 乱序加入链表linkedList.addNode2(heroNode1);linkedList.addNode2(heroNode3);linkedList.addNode2(heroNode2);// 显示链表linkedList.showList();  // 结果还是1、2、3}
}class SingleLinkedList{// 创建头节点private HeroNode head = new HeroNode(0,"","");// 添加节点:按顺序插入public void addNode2(HeroNode heroNode){// 定义辅助变量HeroNode temp = head;// 定义flag:检测id是否存在,默认为falseboolean flag = false;// 遍历链表while (true){// 找到链表的最后if (temp.next == null){break;}if (temp.next.id == heroNode.id){  // id存在flag = true;break;}else if (temp.next.id > heroNode.id){ // 说明新节点不在这插入break;}// 将temp继续后移temp = temp.next;}if (flag){System.out.println("id已存在不能添加!");} else {heroNode.next = temp.next;temp.next = heroNode;}}// 显示链表public void showList(){//判断链表是否为空if (head.next == null){System.out.println("链表为空");return;}//因为头节点不能动,所以通过辅助变量HeroNode temp = head.next;// 遍历while (true){// 如果 = null,即为链表的末尾了if (temp == null){break;}// 如果不为null,输出节点信息(已经toString了),记住:将temp后移继续输出System.out.println(temp);temp = temp.next;}}
}class HeroNode{public int id;public String name;public String nikeName;public HeroNode next;public HeroNode(int id,String name,String nikeName){this.id = id;this.name = name;this.nikeName = nikeName;}@Overridepublic String toString() {return "HeroNode{" +"id=" + id +", name='" + name + '\'' +", nikeName='" + nikeName + '\'' +'}';}
}
  1. 修改单链表节点
    根据节点id进行查找并修改
// 修改节点
public void updateNode(HeroNode newHeroNode){//判断链表是否为空if (head.next == null){System.out.println("链表为空");return;}// 定义辅助变量HeroNode temp = head.next;boolean flag = false;while (true) {if (temp.id == newHeroNode.id){ // 找到需要修改的节点flag = true;break;}if (temp == null){break;}}if (flag){System.out.println("修改成功,结果如下:");temp.name = newHeroNode.name;temp.nikeName = newHeroNode.nikeName;}else {System.out.printf("没有找到id = %d 的节点,修改失败",newHeroNode.id);}
}
  1. 删除节点
    删除节点a2,定义辅助变量 temp,找到a2前面的节点a1,即根据 id 判断: temp.id == 需要删除的id,然后让a1节点指向a3 temp.next == temp.next.next,a2自然被GC处理;
    为什么找前节点?
    如果直接找需删除的节点删除,没法删除,因为是链表,每个节点之间都有联系。
public void delNode(int delId){//判断链表是否为空if (head.next == null){System.out.println("链表为空");return;}// 定义辅助变量HeroNode temp = head;boolean flag = false;while (true) {if (temp == null){break;}// 找到需要删除的节点前一个节点if (temp.next.id == delId){flag = true;break;}temp = temp.next;}if (flag){temp.next = temp.next.next;System.out.println("删除成功!");}else {System.out.printf("没有找到id = %d 的节点,删除失败", delId);}
}
  1. 单向链表问题:查找方向只能在一个方向;单项链表节点的删除:依靠辅助节点找到需要删除节点的前一个节点;
  • 双向链表
    相比单向链表而言,多了一个pre域:指向前一个节点;可以自我删除。

    删除节点:
/*** 删除节点:自我删除,直接找到需删除的节点作为 temp* @param: [delId]* @return: void*/
public void delNode(int delId){//判断链表是否为空if (head.next == null){System.out.println("链表为空");return;}// 定义辅助变量:不需要定义到头节点HeroNode2 temp = head.next;boolean flag = false;while (true) {if (temp == null){break;}if (temp.id == delId){flag = true;break;}temp = temp.next;}// 找到节点if (flag){temp.pre.next = temp.next; // 删除节点的前节点指向后节点// 如果删除的是最后一个节点,需要判断,不然会有空指针异常if (temp.next != null){temp.next.pre = temp.pre;}System.out.println("删除成功!");}else {System.out.printf("没有找到id = %d 的节点,删除失败", delId);}
}
  • 环形单向链表
  1. 实际应用问题:Josephus (约瑟夫环)
    设带有标号的一群人,1、2、3…n;围成一圈坐着,设定从第k(1<= k <= n)个人从1开始报数,数到 m 的人出列,从出列的这个人的下一个人继续数,直到全部出列。
    比如:当 n = 5, k = 2,m = 3;如下图:
  2. 创建并遍历环形单向链表的思路:
    先创建第一个节点并标记为 first ,让 first指向该节点,即自己指向自己的环形;

    当下一个节点需要加入环形链表时,先定义一个辅助指针 temp指向第一个节点,让 temp.next 指向新节点,再让新节点指向 first:

    遍历链表:
// 环形单向链表
class CircularList{// 定义 first 节点private Person first = null;/*** 添加节点:参数为需要添加的节点个数* @param: [nums]* @return: void*/public void addPerson(int nums){// 校验节点的有效性if (nums < 1){System.out.println("请输入正确的节点数");return;}// 定义辅助指针Person temp = null;for (int i = 1; i <= nums; i++){Person person = new Person(i);// 创建单节点的环形链表if (i == 1){first = person;      // first指向第一个人first.setNext(first);// 构成环型temp = first;        // 辅助指针也指向第一个人}else {temp.setNext(person); // 让辅助指针指向的节点,指向新节点person.setNext(first);// 新节点指向firsttemp = person;        // 将辅助指针再指向新节点准备下一次添加}}}/*** 遍历链表* @param: []* @return: void*/public void showList(){if (first == null){System.out.println("链表为空~");return;}// 定义辅助指针用来遍历Person temp = first;while (true){System.out.printf("遍历出了第 %d 人 \n",temp.getNo());if (temp.getNext() == first){  // 遍历结束break;}temp = temp.getNext();  // 将辅助指针继续后移}}}// 创建人节点
class Person{private int no;      // 定义每个人的编号private Person next; // 定义next节点指向下一个人,默认为nullpublic Person(int no){this.no = no;}public int getNo() {return no;}public Person getNext() {return next;}public void setNo(int no) {this.no = no;}public void setNext(Person next) {this.next = next;}
}
  1. 根据用户输入,解决Josephus问题:
/*** Josephus问题* @param: [startNum:从哪个人开始, count:数几次, nums:人的总数]* @return: void*/
public void outPerson(int startNum, int count, int nums){// 先对数据校验if (first == null || startNum < 1 || startNum > nums){System.out.println("输入数据有误~");return;}// 辅助指针Person temp = first;// 通过遍历使辅助指针指向链表的最后节点while (true){if (temp.getNext() == first){break;}temp = temp.getNext();}// 在报数前让 temp、first移动到 startNum节点,即移动:startNum-1次for (int i = 0; i < startNum - 1; i++) {first = first.getNext();temp = temp.getNext();}// 开始出局:开始报数时,temp、first移动 count-1 次,然后出局while (true){if (temp == first){  // 环形链表只剩一个节点break;}for (int i = 0; i < count - 1; i++) {first = first.getNext();temp = temp.getNext();}// 此时first 指向的人就是该出局的人System.out.printf("出局者为 %d \n",first.getNo());// 出局first = first.getNext();temp.setNext(first);}// 跳出循环后,剩最后一个人System.out.println("最后的幸存者为" + first.getNo());
}
1.5 栈(Stack)

底层数据结构是数组,所有的基本数据类型全部是存储在栈里面,因为栈存储速度快,在程序执行期间它们才被创建,执行完成之后,它们就被销毁。

  1. 定义:在同一端进行插入和删除操作的特殊线性表,它按照先进后出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据,栈具有记忆作用,对栈的插入与删除操作中,不需要改变栈底指针。
    允许进行插入和删除操作的一端称为栈顶(top),另一端为栈底(bottom);栈底固定,而栈顶浮动;栈中元素个数为零时称为空栈。插入一般称为进栈(PUSH),删除则称为退栈(POP)。
  2. 举例:羽毛球筒就是一个栈,刚开始羽毛球筒是空的,也就是空栈,然后我们一个一个放入羽毛球,也就是一个一个push进栈,当我们需要使用羽毛球的时候,从筒里面拿,也就是pop出栈,但是第一个拿到的羽毛球是我们最后放进去的。
  3. 数组模拟栈:
class ArrayStack{private int maxSize;  // 栈的大小private int top = -1; // 栈顶private int[] stack;  // 栈public ArrayStack (int maxSize){this.maxSize = maxSize;stack = new int[maxSize];}// 栈满:随着元素的入栈,栈顶上移,top增大,当top == maxSize时,栈就满了public boolean isFull(){return top == maxSize - 1;}// 栈空public boolean isEmpty(){return top == -1;}/*** 入栈* @param: [value]* @return: void*/public void push(int value){if (isFull()){System.out.println("栈已满,不能入栈~");return;}top++;stack[top] = value;}/*** 出栈* @param: []* @return: int*/public int pop(){if (isEmpty()){throw new RuntimeException("栈为空");}int value = stack[top];  // 取出栈顶元素top--;return value;}// 遍历栈元素public void showStack(){if (isEmpty()){System.out.println("栈为空");return;}// 从栈顶开始往下遍历for (int i = top; i >= 0; i--) {System.out.printf("stack[%d]=%d\n",i,stack[i]);}}
}
  1. Java中的栈
// 创建一个栈
Stack<Object> stack = new Stack<>();
// 将一个字符串压入栈中
String string = "I love you";
char[] chars = string.toCharArray();
for (char c : chars) {stack.push(c);
}
// 弹出栈顶值
System.out.println(stack.peek());
// 弹出每一个元素
while (!stack.isEmpty()){System.out.print(stack.pop());
}
// 结果:uoy evol I
  1. 栈的应用
  • 使用栈完成括号的规范匹配()、[ ]、{}
public class Test {public static void main(String[] args) {Test t = new Test();boolean valid = t.isValid("{[()]}");System.out.println(valid);          // 结果:true}public boolean isValid(String s) {// 判断传进来的是否对称if (s.length() % 2 != 0) return false;// 创建 stack 对象Stack<Character> stack = new Stack<>(); // 入栈for (int i = 0; i < s.length(); i++) {char c = s.charAt(i);if (c == '(' || c == '[' || c == '{')    stack.push(c);//针对首先出现的右侧符号进行判断else {if (stack.isEmpty())     return false;// 出栈char top = stack.pop();     // 对应匹配if (c == ')' && top != '(') return false;if (c == ']' && top != '[')return false;if (c == '}' && top != '{')return false;}}// 检查栈是否为空return stack.isEmpty(); }
}
  • 实现字符串格形式表达式的计算
    如:给你一个 :String s = "2+2*3-1+2*3" 字符串形式的表达式(运算符仅包含:+、-、*、/),利用栈完成计算,注意运算符的优先级。

实现思路:

  1. 创建两个栈(数字栈、符号栈),利用索引 index 遍历字符串;
  2. 如果是数字就入数字栈,如果是符号则分两种情况:
    如果符号栈为空,直接入栈;如果符号栈存在符号则需要根据运算符的优先级进行比较:如果准备入栈的运算符优先级小于或等于栈内的运算符,则需要从数字栈pop出两个数字,从符号栈pop出一个运算符,进行运算,将结果重新push入数字栈,再将符号入符号栈;如果准备入栈的运算符优先级高于栈内运算符,直接入栈即可;
  3. 如果表达式扫描完毕,就从两个栈顺序pop元素进行运算即可;
  4. 最后一个留在数字栈的元素就是结果。
// 使用上面自定义的栈,扩展其功能即可完成
public class StackDemo {public static void main(String[] args) {String s = "10*3-2+6*2-8/4";CustomStack numStack = new CustomStack(10); // 数字栈CustomStack operStack = new CustomStack(10); // 符号栈int index = 0; // 扫描索引int num1 = 0;int num2 = 0;int oper = 0;int res = 0;char ch = ' '; // 用来存放字符串遍历出的字符String ss = ""; // 用于拼接多位数:因为遍历是一个字符一个字符的遍历,所以存在多位数的情况(10,100...)需要进行拼接while (true){ch = s.substring(index, index + 1).charAt(0);  // 遍历字符串// 如果遍历的是符号if (operStack.isOperator(ch)){if (!operStack.isEmpty()){ // 如果符号栈不为空,判断运算符优先级if (operStack.priority(ch) <= operStack.priority(operStack.peek())){ // 运算符优先级低于栈中符号num1 = numStack.pop();num2 = numStack.pop();oper = operStack.pop();res = numStack.result(num1,num2,oper);numStack.push(res); // 将结果入数字栈operStack.push(ch); // 将符号入符号栈} else {operStack.push(ch);}}else {  // 如果符号栈为空,直接入栈operStack.push(ch);}} else { // 如果是数字:不能直接入栈,需要判断多位数的情况// 拼接ss += ch;// 如果遍历到最后一位,直接入数字栈即可if (index == s.length()-1){numStack.push(Integer.parseInt(ss));} else {// 判断下一位是不是为运算符,如果是说明num不是多位数,可以直接入栈if (operStack.isOperator(s.substring(index+1,index+2).charAt(0))){numStack.push(Integer.parseInt(ss));// 注意:将拼接者清空ss = "";}}}index++;if (index >= s.length()){break;}}// 最后的结果while (true){if (operStack.isEmpty()){break;}num1 = numStack.pop();num2 = numStack.pop();oper = operStack.pop();res = numStack.result(num1,num2,oper);numStack.push(res);}int result = numStack.pop();System.out.printf("%s = %d",s,result);}
}class CustomStack{private int maxSize;  // 栈的大小private int top = -1; // 栈顶private int[] stack;  // 栈public CustomStack (int maxSize){this.maxSize = maxSize;stack = new int[maxSize];}// 不pop,只查看栈顶元素public int peek(){return stack[top];}// 栈满:随着元素的入栈,栈顶上移,top增大,当top == maxSize时,栈就满了public boolean isFull(){return top == maxSize - 1;}// 栈空public boolean isEmpty(){return top == -1;}/*** 入栈* @param: [value]* @return: void*/public void push(int value){if (isFull()){System.out.println("栈已满,不能入栈~");return;}top++;stack[top] = value;}/*** 出栈* @param: []* @return: int*/public int pop(){if (isEmpty()){throw new RuntimeException("栈为空");}int value = stack[top];  // 取出栈顶元素top--;return value;}// 遍历栈元素public void showStack(){if (isEmpty()){System.out.println("栈为空");return;}// 从栈顶开始往下遍历for (int i = top; i >= 0; i--) {System.out.printf("stack[%d]=%d\n",i,stack[i]);}}// 判断是否为运算符public boolean isOperator(char val){return val == '+' || val == '-' || val == '*' || val == '/';}// 设置运算符的优先级public int priority(int val){if (val == '*' || val == '/'){return 1;}else if (val == '+' || val == '-'){return 0;}else {return -1;}}// 定义计算方法public int result(int num1,int num2,int ope){int res = 0;  // 运算结果switch (ope){case '+':res = num1 + num2;break;case '-':res = num2 - num1;break;case '*':res = num1 * num2;break;case '/':res = num2 / num1;break;default:break;}return res;}
}
  1. 前缀、中缀、后缀表达式

以 3+4-5为例:

①、前缀表达式:操作符在操作数的前面,比如 + - 5 4 3,从右向左扫描进行运算,不用考虑运算符的优先级;
②、中缀表达式:操作符在操作数的中间,这也是人类最容易识别的算术表达式 3 + 4 - 5
③、后缀表达式:操作符在操作数的后面,比如 3 4 + 5 -,从左向右扫描进行运算,不用考虑运算符的优先级
  
前后缀表达式为了计算机更容易识别,提高运算效率。

  • 中缀表达式转后缀表达式:
    转化规则:


    计算机如何使用后缀表达式进行计算?
1. 从左向右扫描表达式进行运算;
2. 遇到数字,压入栈中;
3. 遇到运算符,弹出栈顶的两个数字进行运算,将结果压入栈中;
4. 重复2、3步骤,直到扫描完,最后的值即为运算结果。

2. 非线性结构:二维数组,多维数组,广义表,树结构,图结构

4. 堆

  1. 定义:堆就是将一个集合的数据按照完全二叉树的顺序结构存储在一个一维数组中,堆在逻辑上是一棵完全二叉树,在物理结构上是一个一维数组。
  2. 分类:
  • 小堆
  • 大堆

6. 树

  • 为什么引入树这个数据结构?
    上面讲的数组,虽然通过下标提升了检索速度,有序数组通过二分查找也提升检索速度,但当你想要访问某个具体的数据或者插入数据时,数组还需要整体移动,效率会显得不太行;还有链表,链表的删除、插入数据效率都很高,但检索数据还需要从头节点开始;树结构既能提升检索速度、也可以提升存储速度。
  • 二叉树
    字节点最多只能有两个节点的树,分为:左节点和右节点;
    满二叉树:所有的叶子节点都在最后一层,并且满足 节点总数=2^n-1 (n为二叉树的层数)

    完全二叉树:所有的叶子节点都在最后一层或倒数第二层,并且最后一层的叶子节点在左边连续,倒数第二层的叶子节点右边连续

  1. 二叉树的前、中、后序遍历
    前序遍历:先遍历父节点,再遍历左子树和右子树;
    中序遍历:先遍历左子树,再遍历父节点,再遍历右子树;
    后续遍历:先遍历左子树,再遍历右子树,再遍历父节点。

    代码实现:

创建二叉树

class BinaryTree {private MyNode root;   // 根节点public void setRoot (MyNode root) {this.root = root;}// 前序遍历public void preList() {if (root != null) {this.root.preList();} else {System.out.printf("二叉树为空");}}// 中序遍历public void midList(){if (root != null) {this.root.midList();} else {System.out.printf("二叉树为空");}}// 后序遍历public void postList(){if (root != null) {this.root.postList();} else {System.out.printf("二叉树为空");}}
}

创建树节点

class MyNode{private int id;private String name;private MyNode left;  // 左节点:默认nullprivate MyNode right; // 右节点public MyNode(int id, String name) {this.id = id;this.name = name;}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 MyNode getLeft() {return left;}public void setLeft(MyNode left) {this.left = left;}public MyNode getRight() {return right;}public void setRight(MyNode right) {this.right = right;}@Overridepublic String toString() {return "MyNode{" +"id=" + id +", name='" + name + '\'' +'}';}/*** 前序遍历*/public void preList(){// 输出父节点System.out.println(this);// 左子树递归遍历if (left != null) {this.left.preList();}// 右子树递归遍历if (right != null) {this.right.preList();}}/*** 中序遍历*/public void midList(){// 左子树递归遍历if (left != null) {this.left.midList();}// 输出父节点System.out.println(this);// 右子树递归遍历if (right != null) {this.right.midList();}}/*** 后序遍历*/public void postList(){// 左子树递归遍历if (left != null) {this.left.postList();}// 右子树递归遍历if (right != null) {this.right.postList();}// 输出父节点System.out.println(this);}
}

测试:

public class BinaryTreeTest {public static void main(String[] args) {// 创建二叉树BinaryTree binaryTree = new BinaryTree();// 创建节点MyNode root = new MyNode(1,"node1");MyNode myNode2 = new MyNode(2,"node2");MyNode myNode3 = new MyNode(3,"node3");MyNode myNode4 = new MyNode(4,"node4");MyNode myNode5 = new MyNode(5,"node5");root.setLeft(myNode2);root.setRight(myNode3);myNode2.setLeft(myNode4);myNode2.setRight(myNode5);binaryTree.setRoot(root);System.out.println("=====pre======");binaryTree.preList();System.out.println("=====mid======");binaryTree.midList();System.out.println("=====post=====");binaryTree.postList();}
}

结果:

=====pre======
MyNode{id=1, name='node1'}
MyNode{id=2, name='node2'}
MyNode{id=4, name='node4'}
MyNode{id=5, name='node5'}
MyNode{id=3, name='node3'}
=====mid======
MyNode{id=4, name='node4'}
MyNode{id=2, name='node2'}
MyNode{id=5, name='node5'}
MyNode{id=1, name='node1'}
MyNode{id=3, name='node3'}
=====post======
MyNode{id=4, name='node4'}
MyNode{id=5, name='node5'}
MyNode{id=2, name='node2'}
MyNode{id=3, name='node3'}
MyNode{id=1, name='node1'}

7. 散列表(Hash)

根据关键码值(Key value)直接进行访问的数据结构,它将数据通过一定的映射关系,映射到表中的某个位置,映射函数叫做散列函数,映射表叫做映射表或者散列表。

  1. 班级要求新加入班级的同学,录入信息(id、name),当输入id时查找到该同学的所有信息,在不使用数据库的情况下,使用哈希表实现,保证id由低到高插入。

代码实现:

  • 哈希表:管理多条链表
class HashTab {private StudentLinkedList[] studentListArray;  // 定义存放链表的数组private int size; // 定义哈希表存放链表的大小public HashTab(int size) {this.size = size;studentListArray = new StudentLinkedList[size];// 记得初始化链表: 构造studentListArray只是将哈希表创建起来,里面的每条链表还是null,所以需要初始化for (int i = 0; i < size; i++) {studentListArray[i] = new StudentLinkedList();}}// 添加学生public void add(Student student) {int studentLinkedListNo = hashFun(student.id);studentListArray[studentLinkedListNo].add(student);}// 定义hash函数:传入的学生id对哈希表的size取余,决定该映射到那个链表public int hashFun(int id) {return id % size;}// 遍历public void list() {for (int i = 0; i < size; i++) {studentListArray[i].list(i);}}// 根据id查找public void findById(int id){int no = hashFun(id);Student student = studentListArray[no].selectById(id);if (student != null) { //找到System.out.println("在第" +  no + "链表" + "查找到了id=" + id + "的学生,信息为:姓名=>" + student.name);}else {System.out.println("没有此id的学生");}}// 根据id删除public void deletById(int id){int no = hashFun(id);studentListArray[no].deletById(id);System.out.println("删除了" + no + "链表中的元素" + id);}
}
  • 学生类
class Student {public int id;public String name;public Student next;public Student(int id, String name) {this.id = id;this.name = name;}
}
  • 哈希表中的链表
class StudentLinkedList {// 头指针默认为nullpublic Student head; /*** 添加学生,id自增实现方式:将新学生添加到链表的最后节点即可** @param: [student]*/public void add(Student student) {// 添加第一个学生if (head == null) {head = student;return;}Student temp = head;  // 定义辅助指针,用来寻找链表的最后节点while (true) {if (temp.next == null) { // 找到链表最后break;}temp = temp.next;  // 后移指针继续查找}// 退出循环,将新节点加入即可temp.next = student;}/*** 遍历链表*/public void list(int no) {if (head == null) {System.out.println("链表" + no + "为空");return;}System.out.print("链表" + no + "信息为:");Student temp = head;while (true) {System.out.print("学生id=>" + temp.id + "," + "学生姓名=>" + temp.name + " ");if (temp.next == null) {  // 遍历结束break;}temp = temp.next; // 后移指针}System.out.println();}/*** 根据id查找* @param id* @return*/public Student selectById(int id){if (head == null) {System.out.println("链表为空");return null;}Student temp = head;while (true) {if (temp.id == id) {break;}if (temp.next == null) {temp = null;  // 遍历结束,指针位于链表末尾,所以将它变为null}temp = temp.next;}return temp;}public void deletById(int id){if (head == null) {System.out.println("链表为空");return;}Student temp = head;boolean flag = false;while (true) {if (temp.next.id == id) {flag = true;break;}temp = temp.next;if (temp.next == null) {break;}}if (flag){temp.next = temp.next.next;}else {System.out.printf("没有找到id = %d 的节点,删除失败", id);}}
}

二、Java算法知识

相关知识点:算法的时间复杂度
通过判断算法的时间复杂度确定算法的优势。

1. 时间频度
算法执行的时间和算法中语句的执行次数成正比例;一个算法中语句执行次数越多,其执行时间越长,算法中语句的执行次数成为语句频度或时间频度,记为 T(n)。

  • 举例:计算 1~100 所有数字之和
// 1. 使用 for 循环计算
int sum = 0;
int end = 100;
for (int i = 1; i <= end; i++) {sum += i;
}

时间频度为:T(n) = n + 1
这里的加1:因为还有一条语句为最终的判断语句

// 2. 直接计算
int sum = 0;
int end = 100;
sum = (1+end)*end/2

时间频度为:T(n) = 1

  • 结论:
    1)关于时间频度,常数项可以忽略:如 T(n) = 2n+20、T(n) = 2n,可以忽略20,因为随着n增大,两者的时间频度是无限接近的,故可以忽略常数项;
    2)低次项可以忽略:如 T(n) = 2n^2+2n+20、T(n) = 2n^2,可以忽略2n+20,因为随着n增大,两者的时间频度是无限接近的,故可以忽略低次项;
    3)时间频度和次数有关,如 T(n) = n^3+2n+20、T(n) = 5n^3,随着n增大,两者分离,最终 T(n) 成系数比,但如 T(n) = 3n^2+2n、T(n) = 5n^2+3n,随着n增大,两者渐渐接近,此时可忽略系数。

2. 时间复杂度

1)若有一个辅助函数 f(n),当n趋于无穷大时,T(n)/ f(n)(T(n):时间频度)的极限值为不为零的常数,则称 O(f(n)) 为算法的时间渐进复杂度,简称时间复杂度。
2)T(n)不同,时间复杂度可能相同
T(n) = 2n^2+2n+20、T(n) = 3n^2+3n+6,它们的时间复杂度为 O(n^2);
3)计算时间复杂度的方法:

1. 将常数1代替T(n)中的所有加法常数项; T(n) = 2n^2+2n+20 => T(n) = 2n^2+2n+1
2. 保留最高次数项;                  T(n) = 2n^2+2n+1 => T(n) = 2n^2
3. 去除最高项的系数                  T(n) = n^2 => T(n) = n^2

4)常见的算法时间复杂度

上述算法事件复杂度依次增大,算法的运算效率依次降低。

1. 常数阶O(1)
当算法中,没有循环等复杂结构时,算法的时间复杂度都为 O(1);
2. 对数阶O(log2n)
如:int i = 1;while(i<n){i = i*2;}
随着i慢慢增大,靠近n,假设 i 循环 x 次后,跳出循环,即 2^x = n,那么 x = log2n,即时间复杂度为 O(log2n);
若循环体中为:i = i*3; 时间复杂度即为: O(log3n);
3. 线性阶O(n)
当算法消耗的时间随着 n 的变化而变化时,其时间复杂度为:O(n)
如:一个算法主体是有for循环构成,并且循环 10 次结束,故该算法时间复杂度为:O(10)
4. 线性对数阶O(nlog2n)
将时间复杂度为对数阶O(log2n)的代码循环了 n 次
5. 平方阶O(n^2)
将时间复杂度为 O(n)的代码再嵌套一次
如:两层for循环嵌套且 i<n,如果一层循环为m一层为n,时间复杂度为:O(m*n)
6. 立方阶、k次方阶类似

1. 排序算法

1. 冒泡排序

通过比较将较小的元素慢慢的交换到数组最前面,直到排序完成,第一轮比较完后,最大的元素就到最后了。

private static void BubbleSorting(int[] arr) {int temp;     // 定义用于交换的临时变量boolean flag; // 定义标志位//外层循环控制轮数:for (int i = 0; i < arr.length - 1; i++) {flag = false; // 默认标志位为falsefor (int j = 0; j < arr.length - 1 - i; j++) {if (arr[j] > arr[j+1]) {//交换位置temp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;flag = true;      // 只要发生了交换就为true}}if (!flag) break;}
}

时间复杂度:
最好:O(n),最坏:O(n^2)
适用于数据量很小的排序场景,因为冒泡原理简单

2. 选择排序

从0索引开始,依次和后面元素互相比较,小的往前放,第一轮比较完毕,最小值出现在了最小索引处,以此循环。

private static void selectSort(int[] arr) {//外层循环控制轮数:for (int j = 0; j < arr.length - 1; j++) {for (int i = 1 + j; i < arr.length; i++) {if (arr[j] > arr[i]) {//交换位置int t = arr[j];arr[j] = arr[i];arr[i] = t;}}System.out.println("第" + (i+1) + "次排序结果");System.out.println(Arrays.toString(arr));}
}

时间复杂度:
最好:O(n),最坏:O(n^2)
适用于大多数排序场景,虽然它的对比次数较多,但是数据量大的时候,他的效率明显优于冒泡,数据移动是非常耗时的,选择排序移动次数少。

3. 直接插入排序

假设有一组元素{k1,k2…,kn},我们需要从小到大排序:
排序开始以 k1 为一个有序序列,让 k2 同 k1 比较,按从小到大排序并插入长度为1的有序序列,使之成为一个长度为2的有序序列;然后让 k3 同 k2 比较,插入上述表长为2的有序序列,使之成为一个表长为3的有序序列…以此类推,最后让 kn 插入表长为 n-1 的有序序列,得到一个表长为n的有序序列。

public static void insertSort(int[] arr) {for (int i = 1; i < arr.length; i++) {int j=i;while (j > 0 && arr[j] < arr[j - 1]) {  // 让新插入进来的数字和前面的数依次比较int t=arr[j];arr[j]=arr[j-1];arr[j-1]=t;j--;}System.out.println("第" + i + "次排序结果");System.out.println(Arrays.toString(arr));}
}

时间复杂度:
最好:O(n),最坏:O(n^2)

4. 希尔排序

在上面的直接插入排序中,我们发现,如果将一个小于已存在有序数组中的任意一个元素,需要执行太多的比较再插入:如下图

希尔排序,也是插入排序的一种,它按照下标设置一定的增量,再将数组进行一定的分组,对分组后的数组再进行直接插入排序,当增量为1即分组只剩一组时,算法结束。

希尔排序时间复杂度:O(n^(1.3—2))

5. 快速排序

选择一个数作为基准数,以基准数作为分界,将数组分为两部分,然后开始选择,规定比基准数大的全部放到右边,比基准数小的全部放到左边;再在两边的数组中继续选择基准数,继续进行右边大,左边小的操作,直到排序结束。

public static void quickSort(int[] arr,int left,int right){if (left >= right || arr == null || arr.length == 0){return;}int l = left;   // 左边索引指针int r = right;  // 右边索引指针int base = arr[left];  // 定义基准数// 循环和对比基准数,大的放右边,小的放左边while (l < r) {while (l < r && arr[r] >= base) { // 查找右边有没有小于基准数的数,如果没有将右边索引左移,继续查找r--;}if (l < r) {arr[l] = arr[r];l++;}while (l < r && arr[l] <= base) { // 查找左边有没有大于基准数的数,如果没有将左边索引右移,继续查找l++;}if (l < r) {arr[r] = arr[l];r--;}}// 跳出循环后,将基准数放到索引遍历停下来的地方 (这里 arr[l] = arr[r])arr[l] = base;// 将基准数左侧数组继续排序quickSort(arr,left,l-1);// 将基准数右侧数组继续排序quickSort(arr,l+1,right);
}

快速排序算法时间复杂度:O (nlogn)

2. 递归

简单说:递归就是方法中调用方法,每次传入不同的参数;递归有助于代码的简洁。

  1. “不死神兔” 问题(斐波那契数列)
    有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问第二十个月的兔子对数为多少?

思路分析:
先来看看兔子的对数:1、1、2、3、5、8、13、21…即从第三个数开始,后面的每个数等于他前两个数之和:(n-1) + (n-2)

/**
* @param: [n:月数]
* @return: int:兔子对数
*/
private static int rabbit(int n) {if (n == 1 || n == 2) {return 1;} else {return rabbit(n - 1) + rabbit(n - 2);}
}

图解:

  1. 递归回溯解决迷宫问题
    迷宫地图为一个二维数组:8*8,四周和中间某区域设有边界,设定走的路径规则为:下、右、上、左,在找出口过程中,如果碰到边界即此路不通,最终目的走到标记位。
  • 代码实现
  1. 构建地图
public class RecursionDemo {public static void main(String[] args) {int[][] Maze = new int[8][8]; // 定义二维数组// 设置边界for (int i = 0; i < 8; i++) {Maze[0][i] = 1;Maze[7][i] = 1;Maze[i][0] = 1;Maze[i][7] = 1;}Maze[3][1] = 1;Maze[3][2] = 1;Maze[3][3] = 1;System.out.println("======迷宫地图======");for (int i = 0; i < 8; i++) {for (int j = 0; j < 8; j++) {System.out.print(Maze[i][j] + " ");}System.out.println();}}
}

  1. 递归回溯
    1)方法参数:map:地图、i、j:表示起始位置;
    2)如果走到 (7,7) 位置即结束;
    3)设定的数字:1:表示边界、2:表示可以通过、3:表示已经走过但走不通;
    4)执行我们的规则:先往下走,如果不通再向右,不通过再向上,不通再向左
public static boolean findWay(int[][] map,int i,int j){if (map[6][6] == 2) {  // 设置 (6,6) 位置为终点return true;} else {if (map[i][j] == 0) { // 0表示没走过,下面按照规则开始走map[i][j] = 2;if (findWay(map,i + 1,j)) {       // 向下走return true;} else if (findWay(map,i,j+1)) {  // 向右走return true;} else if (findWay(map,i-1,j)) {   // 向上走return true;} else if (findWay(map,i,j-1)) {   // 向左走return true;} else {  // 如果按照上面的规则都没走通,则设置为3map[i][j] = 3;return false;}} else {return false;}}
}

结果:

3. 二分查找

  1. 在一个有序数组查找某元素的位置,如果找到返回该元素的索引,如果没有就返回-1;
/** @param: [arr, left:左边索引, right:右边索引, findVal:需要查找的值]* @return: int*/
public static int dichotomySearch(int[] arr,int left,int right,int findVal){// 定义跳出递归的条件if (left > right) {return -1;}// 定义二分点int mid = (left+right)/2;int midVal = arr[mid];if (midVal == findVal) {return mid;}else if (findVal > midVal) {return dichotomySearch(arr,mid+1,right,findVal);} else {return dichotomySearch(arr,left,mid-1,findVal);}
}
  1. 当一个数组存在重复元素时,如果查找的刚好时该重复元素则返回重复元素的所有下标:
/*** 找到元素后不要立刻返回,再向索引的左边、右边遍历扫描,找到元素后,将所有的下标放进集合中,最后返回集合即可** @param: [arr, left, right, findVal]* @return: int*/
public static ArrayList<Integer> dichotomySeach(int[] arr, int left, int right, int findVal) {// 定义跳出递归的条件if (left > right) {return new ArrayList();}// 定义二分点int mid = (left + right) / 2;int midVal = arr[mid];if (findVal > midVal) {return dichotomySeach(arr, mid + 1, right, findVal);} else if (findVal < midVal) {return dichotomySeach(arr, left, mid - 1, findVal);} else {// 找到元素,存入集合ArrayList<Integer> list = new ArrayList<>();// 向左遍历int temp = mid - 1;while (true) {if (temp < 0 || arr[temp] != findVal) {  // 扫描到头或没有找到则跳出循环break;}// 否则加入集合list.add(temp);temp = temp - 1; // 将索引左移}list.add(mid); // 查找的值刚好是二分点// 向右遍历temp = mid + 1;while (true) {if (temp > arr.length - 1 || arr[temp] != findVal) {  // 扫描到头或没有找到则跳出循环break;}// 否则加入集合list.add(temp);temp = temp + 1; // 将索引左移}return list;}
}

【Java面试高频问题】Java数据结构和算法基础知识汇总相关推荐

  1. 1.数据结构与算法 基础知识

    文章目录 数据结构与算法 (Python 版) 知识点一 : 算法 概念 知识点二 : 时间复杂度 知识点三 : 空间复杂度 知识点四 : 递归 知识点五 : 递归 汉诺塔问题 1.题目 2.思路 3 ...

  2. 数据结构与算法基础知识集锦

    程序设计 = 数据结构 + 算法 数据结构分为逻辑结构与物理结构 逻辑结构:是指数据对象中数据元素之间的相互关系:物理结构:是指数据的逻辑结构在计算机中的存储形式. 逻辑结构可以分为:集合结构.线性结 ...

  3. java算法概述,Java数据结构与算法基础(一)概述与线性结构

    Java数据结构与算法基础(二)递归算法 Java数据结构与算法基础(一)概述与线性结构 学习目的:为了能更顺畅的读很多底层API代码和拓宽解决问题的思路 一.数据结构概述 1.数据结构是什么?数据与 ...

  4. 数据结构与算法基础(java版)

    目录 数据结构与算法基础(java版) 1.1数据结构概述 1.2算法概述 2.1数组的基本使用 2.2 数组元素的添加 2.3数组元素的删除 2.4面向对象的数组 2.5查找算法之线性查找 2.6查 ...

  5. 【数据结构与算法基础】并查集原理、封装实现及例题解析(C和java)

    前言 数据结构,一门数据处理的艺术,精巧的结构在一个又一个算法下发挥着他们无与伦比的高效和精密之美,在为信息技术打下坚实地基的同时,也令无数开发者和探索者为之着迷. 也因如此,它作为博主大二上学期最重 ...

  6. 数据结构中为什么输入数据还没输入完全就结束了_我岂能忍!面试官居然用数据结构和算法“羞辱”我...

    如果大家是因为标题而进来,那我实话告诉你吧,真事!以后我会分享数据结构与算法相关的文章.为什么要学数据结构与算法,我总结了如下几点: 锻炼思维和问题处理能力 提高代码效率 大厂面试必备 情怀(大学没认 ...

  7. Java面试官:java的跨平台原理

    京东一面凉经 object的方法,7大方法 synchronized方法讲解 synchronized方法实现原理 volatile关键字的原理 锁的分类 偏向锁讲解 NoClassDefFoundE ...

  8. java如何创造一个整数的类_【技术干货】Java 面试宝典:Java 基础部分(1)

    原标题:[技术干货]Java 面试宝典:Java 基础部分(1) Java基础部分: 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语法,集合的语法,io 的 ...

  9. 视频教程-Java面试Offer直通车-Java

    Java面试Offer直通车 10年大厂工作经验,有IBM,花旗,平安等一线外企和互联网工作经验,8年Java面试官经验,了解面试java初级开发高级开发和架构师的背后一面,更熟悉各种在面试中展示亮点 ...

最新文章

  1. 继承演练 动物 狗 哮天犬 c# 1613703354
  2. 轻松云上揽胜中华,靠的就是这份聪明的“地图”!
  3. .Net事件委托备忘
  4. 四边形可以分为几类_大件物流有哪些公司?大件物流公司的业务可以分为哪几类...
  5. python导入turtle报错_由于“未定义宽度错误”,我无法将turtle模块导入Python2.7.10...
  6. java的源文件和字节码文件_javaweb项目源文件与字节码文件目录结构
  7. vue组件中传值遇到的一些问题
  8. 计算机考研专业课408什么意思,科普:考研408是什么意思
  9. 分享数百个 HT 工业互联网 2D 3D 可视化应用案例之 2019
  10. 借助WinPE进行Windows系统安装
  11. Robocup 仿真2D 学习笔记(一) ubuntu16.04 搭建 robocup 仿真2D环境
  12. JAVA 开发规范
  13. ac算法 java_Aho-Corasick算法的Java实现与分析
  14. c++直方图匹配终极版,支持任意通道数(opencv版本)
  15. [RK3288][Android6.0] 调试笔记 --- Audio的Voice Call无法静音问题
  16. mysql按升序创建索引_MySQL 降序索引 (Descending Indexes)
  17. ANSI 标准是为了确保 C++ 的便携性
  18. 163邮箱的格式怎么写,如何申请电子邮箱?
  19. PostgreSQL regress test
  20. c1TrueDBGrid在C#中的研究

热门文章

  1. 从MicroPython到TPYBoard 萝卜教育引领青少年学科式编程新标杆
  2. 瑞盟 MS41929 步进电机驱动IC 一些使用心得
  3. 达人评测 酷睿i7 12700h和锐龙r9 6900hx差多少 i712700h和r9 6900hx对比
  4. Docker-常用命令
  5. 表格:表格作用,展示数据
  6. stateflow基础知识之(时序逻辑)
  7. 施密特正交化_考研数学答疑210施密特正交化
  8. linux screen 命令的使用教程
  9. Pro/E二次开发入门详细教程
  10. SU-03T语音模块烧录及mini开发板使用