数据结构与算法

  • 线性结构和非线性结构
    • 基本概念
      • 线性结构
      • 非线性结构
    • 稀疏数组
      • 基本概念
      • 稀疏数组的处理方法
      • 举例说明
      • 应用实例
      • 代码实例
    • 队列
      • 基本介绍
      • 示意图
      • 数组模拟队列
        • 思路分析
        • 代码实现
      • 数组模拟环形队列
        • 思路分析
        • 代码实现
    • 链表
      • 链表(Linked List)介绍
      • 添加节点-直接在链表尾部添加
        • 思路分析
        • 代码实例
      • 添加节点-根据id插入到链表中
        • 思路分析
        • 代码实例
      • 修改节点-根据no修改name和nickname
        • 实例代码
      • 删除节点-根据no删除节点
        • 思路分析
        • 代码实例
      • 单链表基本面试题
        • 求单链表中节点得个数
        • 查找单链表中得倒数第k个节点
        • 单链表得反转
          • 示意图
          • 思路分析
          • 代码实例
        • 从尾到头打印单链表
          • 思路分析
          • 代码实例
        • 合并两个有序单链表,合并之后的链表依然有序
      • 双向链表
        • 简要分析单链表和双向链表的区别
        • 示意图
        • 思路分析
        • 代码实例
      • 单向环形链表
        • 基本介绍
        • 示意图
        • 思路分析
        • 代码实例
      • 栈的介绍
      • 数组模拟栈
        • 实现栈的思路分析
        • 代码实例
      • 利用栈实现综合计算器
        • 思路分析
        • 代码实例
      • 前缀、中缀、后缀表达式(逆波兰表达式)
        • 前缀表达式
          • 基本介绍
          • 思路分析
        • 中缀表达式
          • 基本介绍
        • 后缀表达式
          • 基本介绍
          • 思路分析
      • 逆波兰计算器
        • 基本介绍
          • 代码实例
        • 中缀表达式转换为后缀表达式
          • 思路分析
          • 代码实例

线性结构和非线性结构

数据结构主要包括线性结构和非线性结构。

基本概念

线性结构

  1. 线性结构作为最常用的数据结构,其特点是数据元素之间存在一对一的线性关系
  2. 线性结构有两种不同的存储结构,即顺序存储结构(数组)和链式存储结构(链表)。顺序存储的线性表称为顺序表,顺序表中的存储元素是连续的(地址是连续的)
  3. 链式存储的线性表称为链表,链表中得存储元素不一定是连续的,元素节点中存放数据元素以及相邻元素的地址信息
  4. 线性结构常见的有:数据、队列、链表和栈。

非线性结构

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

稀疏数组

基本概念

当一个数组中大部分元素为0,或者为同一个值的数组时,可以使用稀疏数组来保存该数组。

稀疏数组的处理方法

1.记录数组一共有几行几列,有多少个不同的值
2.把具有不同值的元素的行列及值记录在一个小规模的数组中,从而缩小程序的规模

举例说明

应用实例

1.使用稀疏数组,来保留类似前面的二维数组(棋盘、地图等等)
2. 把稀疏数组存盘,并且可以重新恢复原来的二维数组。
3. 思路分析:

二维数组转稀疏数组的思路

  1. 遍历原始的二维数组,得到有效数据的个数
  2. 根据sum创建稀疏数组 sparseArr int[sum+1][3]
  3. 将二维数组的有效数据存入到稀疏数组

稀疏数组转原始的二维数组的思路

  1. 先读取稀疏数组的第一行,根据第一行的数据,创建原始的二维数组chessArr2=int[11][11]
    2.在读取稀疏数组后几行的数据,并赋给原始的二维数组。

代码实例

//稀疏数组
public class sparsearray {public static void main(String[] args) {//创建一个原始的二维数组11*11// 0:表示没有棋子 1:表示黑子 2:表示白子int nums[][] = new int[11][11];nums[1][2] = 1;nums[2][3] = 2;//输出原始的二维数组System.out.println("原始的二维数组:");for (int[] row : nums) {for (int data : row) {System.out.print(data + "   ");}System.out.println();}//将二维数组转稀疏数组的思路//1. 先遍历二维数组 得到非0数据的个数int sum = 0;for (int[] row : nums) {for (int data : row) {if (data != 0) {sum++;}}}//2. 创建对应的稀疏数组int sparseArray[][] = new int[sum + 1][3];//给稀疏数组赋值sparseArray[0][0] = nums.length;sparseArray[0][1] = nums[0].length;sparseArray[0][2] = sum;//遍历二维数组,将非0数据存放到sparseArray数组中int count = 1;for (int i = 0; i < nums.length; i++) {for (int j = 0; j < nums[i].length; j++) {if (nums[i][j] != 0) {sparseArray[count][0] = i;sparseArray[count][1] = j;sparseArray[count][2] = nums[i][j];count++;}}}//输出稀疏数组的形式System.out.println();System.out.println("得到稀疏数组为~~~~~");for (int[] ints : sparseArray) {for (int data : ints) {System.out.print(" " + data + "    ");}System.out.println();}//1.先读取稀疏数组的第一行,根据第一行的数据,创建原始的二维数组int newNums[][] = new int[sparseArray[0][0]][sparseArray[0][1]];//2. 在读取稀疏数组后几行的数据,并赋给原始的二维数组即可for (int i = 1; i < sparseArray.length; i++) {newNums[sparseArray[i][0]][sparseArray[i][1]] = sparseArray[i][2];}for (int[] row : newNums) {for (int data : row) {System.out.print(data + "   ");}System.out.println();}}
}

队列

基本介绍

  • 队列是一个有序列表,可以使用数组或是链表来实现。
  • 遵循先入先出的原则,即:先存入队列的数据,要先取出。后存入的要后取出

示意图


示意图说明

  1. rear代表队列的尾部,初始化为-1。
  2. front代表队列的头部,初始化为-1。
  3. 当数据加入的时候,rear+1,front不会变化。
  4. 当数据减少的时候,front-1,rear不会变化。
  5. 加数据是在队列的尾部添加,而取数据是在队列的头部取出。

数组模拟队列

思路分析
  • 队列本身是有序列表,若使用数组的结构来存储队列的数据,则队列数组的声明如上图,其中maxSize是该队列的最大容量。
  • 因为队列的输出、输入是分别从前后端来处理的,因此需要两个变量front及rear分别记录队列前后端的下标,front会随着数据输出而改变,而rear泽斯随着数据输入而改变。
  • 当我们将数据存入队列时称为"addQueue",addQueue的处理需要有两个步骤:
  1. 将尾指针往后移:rear+1,当front==rear【空】
  2. 若尾指针rear小于队列的最大下标maxSize=1,则将数据存入rear所指的数组元素中,否则无法存入数据。rear==maxSize-1【队列满】
代码实现
public class ArrayQueueDemo {public static void main(String[] args) {ArrayQueue queue = new ArrayQueue(3);queue.addQueue(1);System.out.println(queue.headQueue());System.out.println(queue.getQueue());}
}//使用数组模拟队列-编写一个ArrayQueue
class ArrayQueue {private int maxSize; //表示数组的最大容量private int front; //队列头private int rear;  //队列尾private int[] arr; //该数组用于存放数据,模拟队列//创建队列的构造器public ArrayQueue(int arrMaxSize) {this.maxSize = arrMaxSize;this.arr = new int[this.maxSize];this.front = -1; //指向队列头部,分析出front是指向队列头的前一个位置this.rear = -1;  //指向队列尾,指向队列尾的数据(即就是队列最后一个数据)}//判断队列是否满public boolean isFull() {return rear == maxSize - 1;}//判断队列是否为空public boolean isEmpty() {return rear == front;}//添加数据到队列public void addQueue(int n) {if (isFull()) {System.out.println("队列已满无法添加新的数据");return;}arr[++rear] = n;}//获取队列中的数据,出队列public int getQueue() {if (isEmpty()) {//抛出异常throw new RuntimeException("队列空,不能取数据");}return arr[++front];}//显示队列中的所有数据public void showQueue() {if (isEmpty()) {System.out.println("队列为空,没有数据");}for (int i = 0; i < arr.length; i++) {System.out.println("arr[" + i + "]==" + arr[i]);}}//显示队列的头数据,注意不是取数据public int headQueue() {if (isEmpty()) {throw new RuntimeException("队列空,不能取数据");}return arr[front + 1];}}

数组模拟环形队列

思路分析
  1. front变量的含义做一个调整:front就指向队列的第一个元素,也就是说arr[front] 就是队列的第一个元素,front的初始值=0
  2. rear变量的含义做一个调整:rear指向队列的最后一个元素的后一个位置.因为希望空出一个空间作为约定.
  3. 当队列满时,条件是 (rear+1)%maxSize=front【满】,rear的初始值=0
  4. 当队列空时,条件是rear==front空
  5. 队列中有效地数据的个数是 (rear+maxSize-front)%maxSize
代码实现
public class CircleArrayQueueDemo {public static void main(String[] args) {System.out.println("测试数组模拟环形队列~");CircleArray queue = new CircleArray(4);//有效数据为3个,因为希望腾出一个空间作为约定。rear+1为了数组不越界,且永远指向元素的后一个位置。queue.addQueue(10);queue.showQueue();queue.addQueue(20);queue.showQueue();queue.addQueue(30);queue.showQueue();}
}class CircleArray {private int maxSize; //表示数组的最大容量//front变量的含义做一个调整:front就指向队列的第一个元素,也就是说arr[front] 就是队列的第一个元素,// front的初始值=0private int front; //队列头// rear变量的含义做一个调整:rear指向队列的最后一个元素的后一个位置.因为希望空出一个空间作为约定.// rear变量的初始值 = 0;private int rear;  //队列尾private int[] arr; //该数组用于存放数据,模拟队列public CircleArray(int arrMaxSize) {this.maxSize = arrMaxSize;this.arr = new int[this.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.println("arr[" + i % maxSize + "]==" + arr[i % maxSize]);}}//求出当前队列有效数据的个数public int size() {return (rear + maxSize - front) % maxSize;}//显示队列的头数据,注意不是取数据public int headQueue() {if (isEmpty()) {throw new RuntimeException("队列空,不能取数据");}return arr[front];}
}

链表

链表(Linked List)介绍

  1. 链表是以节点的方式来存储的
  2. 每个节点包含data域,next域指向下一个节点.
  3. 链表的各个节点不一定是连续存储
  4. 链表分带头节点的链表和没有头结点的链表,根据实际的需求来确定

添加节点-直接在链表尾部添加

思路分析


示意图说明

  • 添加(创建)

    1. 先创建一个head头节点,作用就是表示单链表的头
    2. 后面每添加一个节点,就直接加入到链表的最后
  • 遍历:
    1. 通过一个辅助变量,来方便遍历整个单链表。
代码实例
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 hero4 = new HeroNode(4, "林冲", "豹子头");SingleLinkedList list = new SingleLinkedList();list.add(hero1);list.add(hero2);list.add(hero3);list.add(hero4);list.list();}
}//定义SingleLinkedList 管理我们的英雄
class SingleLinkedList {// 先初始化一个头节点,头节点不要动// 1.不存放具体的数据// 2.作用就是表示单链表头nextprivate HeroNode head = new HeroNode(0, "", "");//添加节点到单向链表//思路:当不考虑编号顺序时//1. 找到当前链表的最后节点//2. 将最后这个节点的next 指向新的节点public void add(HeroNode heroNode) {//因为head节点不能动,因此需要一个辅助指针temp//相当于有一个temp指针指向头节点.HeroNode temp = head;//遍历链表,找到最后while (true) {//找到链表的最后if (temp.next == null) {break;}//如果没有找到,就将temp后移;temp = temp.next;}//当退出while循环后,temp就指向了链表最后一个元素temp.next = heroNode;}//显示链表[遍历]public void list() {//判断链表是否为空if (head.next == null) {System.out.println("链表为空");return;}//因为头节点不能动,因此我们需要一个辅助变量来遍历HeroNode temp = head;while (true) {if (temp == null) {break;}//输出节点的信息System.out.println(temp);//需要注意,将temp后移temp = temp.next;}}}//定义HeroNode,每个HeroNode对象就是一个节点
class HeroNode {public int no;public String name;public String nickName;public HeroNode next; //指向下一个节点//构造器public HeroNode(int no, String name, String nickName) {this.no = no;this.name = name;this.nickName = nickName;}//为了显示方便,我们重新toString@Overridepublic String toString() {return "HeroNode{" +"no=" + no +", name='" + name + '\'' +", nickName='" + nickName + '\'' +'}';}
}

添加节点-根据id插入到链表中

思路分析

代码实例
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 hero4 = new HeroNode(4, "林冲", "豹子头");SingleLinkedList list = new SingleLinkedList();//加入按照编号的顺序list.addByOrder(hero4);list.addByOrder(hero3);list.addByOrder(hero1);list.addByOrder(hero2);list.list();}
}//定义SingleLinkedList 管理我们的英雄
class SingleLinkedList {// 先初始化一个头节点,头节点不要动// 1.不存放具体的数据// 2.作用就是表示单链表头nextprivate HeroNode head = new HeroNode(0, "", "");// 第二种方式根据id顺序插入到链表中// 如果已经有该id,则添加失败,并给出提示public void addByOrder(HeroNode heroNode) {//因为head节点不能动,因此需要一个辅助指针temp//相当于有一个temp指针指向头节点.HeroNode temp = head;boolean flag = false; //标识添加的编号是否存在,默认为falsewhile (true) {if (temp.next == null) {//说明temp已经在链表的最后break;}if (temp.next.no > heroNode.no) {//位置找到,就在temp的后面插入break;} else if (temp.next.no == heroNode.no) {//说明希望添加的heroNode的编号已然存在flag = true;break;}temp = temp.next; //后移,遍历当前链表}//判断flag的值if (flag) {//不能添加,说明id已经存在System.out.println("准备插入的英雄的编号:" + heroNode.no + " 已经存在,不能加入");} else {//插入到链表中.heroNode.next = temp.next;temp.next = heroNode;}}//显示链表[遍历]public void list() {//判断链表是否为空if (head.next == null) {System.out.println("链表为空");return;}//因为头节点不能动,因此我们需要一个辅助变量来遍历HeroNode temp = head;while (true) {if (temp == null) {break;}//输出节点的信息System.out.println(temp);//需要注意,将temp后移temp = temp.next;}}
}//定义HeroNode,每个HeroNode对象就是一个节点
class HeroNode {public int no;public String name;public String nickName;public HeroNode next; //指向下一个节点//构造器public HeroNode(int no, String name, String nickName) {this.no = no;this.name = name;this.nickName = nickName;}//为了显示方便,我们重新toString@Overridepublic String toString() {return "HeroNode{" +"no=" + no +", name='" + name + '\'' +", nickName='" + nickName + '\'' +'}';}
}

修改节点-根据no修改name和nickname

实例代码
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 hero4 = new HeroNode(4, "林冲", "豹子头");SingleLinkedList list = new SingleLinkedList();//加入按照编号的顺序list.addByOrder(hero4);list.addByOrder(hero3);list.addByOrder(hero1);list.addByOrder(hero2);list.addByOrder(hero2);list.list();System.out.println("==================修改前==================");HeroNode hero5 = new HeroNode(2, "小卢", "麒麟");list.update(hero5);list.list();}
}//定义SingleLinkedList 管理我们的英雄
class SingleLinkedList {// 先初始化一个头节点,头节点不要动// 1.不存放具体的数据// 2.作用就是表示单链表头nextprivate HeroNode head = new HeroNode(0, "", "");// 第二种方式根据id顺序插入到链表中// 如果已经有该id,则添加失败,并给出提示public void addByOrder(HeroNode heroNode) {//因为head节点不能动,因此需要一个辅助指针temp//相当于有一个temp指针指向头节点.HeroNode temp = head;boolean flag = false; //标识添加的编号是否存在,默认为falsewhile (true) {if (temp.next == null) {//说明temp已经在链表的最后break;}if (temp.next.no > heroNode.no) {//位置找到,就在temp的后面插入break;} else if (temp.next.no == heroNode.no) {//说明希望添加的heroNode的编号已然存在flag = true;break;}temp = temp.next; //后移,遍历当前链表}//判断flag的值if (flag) {//不能添加,说明id已经存在System.out.println("准备插入的英雄的编号:" + heroNode.no + " 已经存在,不能加入");} else {//插入到链表中.heroNode.next = temp.next;temp.next = heroNode;}}//修改节点的信息,根据no编号修改,即no编号不能修改.//说明//1. 根据newHeroNode的no来修改即可public void update(HeroNode heroNode) {if (head.next == null) {System.out.println("链表为空");return;}//找到需要修改的节点,根据no编号//定义一个辅助变量HeroNode temp = head;boolean flag = false;while (true) {if (temp == null) { //说明temp指向了链表的最后一个节点break;}if (temp.no == heroNode.no) {flag = true;break;}temp = temp.next;}//根据flag 判断是否找到要修改的节点if (flag) {temp.name = heroNode.name;temp.nickName = heroNode.nickName;} else { //没有找到System.out.println("没有找到编号:" + heroNode.no);}}//显示链表[遍历]public void list() {//判断链表是否为空if (head.next == null) {System.out.println("链表为空");return;}//因为头节点不能动,因此我们需要一个辅助变量来遍历HeroNode temp = head;while (true) {if (temp == null) {break;}//输出节点的信息System.out.println(temp);//需要注意,将temp后移temp = temp.next;}}}//定义HeroNode,每个HeroNode对象就是一个节点
class HeroNode {public int no;public String name;public String nickName;public HeroNode next; //指向下一个节点//构造器public HeroNode(int no, String name, String nickName) {this.no = no;this.name = name;this.nickName = nickName;}//为了显示方便,我们重新toString@Overridepublic String toString() {return "HeroNode{" +"no=" + no +", name='" + name + '\'' +", nickName='" + nickName + '\'' +'}';}
}

删除节点-根据no删除节点

思路分析

代码实例
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 hero4 = new HeroNode(4, "林冲", "豹子头");SingleLinkedList list = new SingleLinkedList();//加入按照编号的顺序list.addByOrder(hero4);list.addByOrder(hero3);list.addByOrder(hero1);list.addByOrder(hero2);list.addByOrder(hero2);list.list();     System.out.println("==================删除前==================");list.del(1);list.list();}
}//定义SingleLinkedList 管理我们的英雄
class SingleLinkedList {// 先初始化一个头节点,头节点不要动// 1.不存放具体的数据// 2.作用就是表示单链表头nextprivate HeroNode head = new HeroNode(0, "", "");// 第二种方式根据id顺序插入到链表中// 如果已经有该id,则添加失败,并给出提示public void addByOrder(HeroNode heroNode) {//因为head节点不能动,因此需要一个辅助指针temp//相当于有一个temp指针指向头节点.HeroNode temp = head;boolean flag = false; //标识添加的编号是否存在,默认为falsewhile (true) {if (temp.next == null) {//说明temp已经在链表的最后break;}if (temp.next.no > heroNode.no) {//位置找到,就在temp的后面插入break;} else if (temp.next.no == heroNode.no) {//说明希望添加的heroNode的编号已然存在flag = true;break;}temp = temp.next; //后移,遍历当前链表}//判断flag的值if (flag) {//不能添加,说明id已经存在System.out.println("准备插入的英雄的编号:" + heroNode.no + " 已经存在,不能加入");} else {//插入到链表中.heroNode.next = temp.next;temp.next = heroNode;}}//删除节点//思路//1. head 不能动,因此我们需要一个temp辅助节点找到待删除节点的前一个节点//2. 说明在比较时,是temp.next.no 和需要删除的节点的比较public void del(int no) {if (head.next == null) {System.out.println("该链表为空");return;}//指定一个辅助节点HeroNode temp = head;boolean flag = false;while (true) {if (temp.next == null) { //说明temp指向了链表的最后一个节点break;}if (temp.next.no == no) {//找到的待删除节点的前一个节点tempflag = true;break;}temp = temp.next;}//判断flagif (flag) {temp.next = temp.next.next;} else {System.out.println("要删除的节点no=" + no + "不存在");}}//显示链表[遍历]public void list() {//判断链表是否为空if (head.next == null) {System.out.println("链表为空");return;}//因为头节点不能动,因此我们需要一个辅助变量来遍历HeroNode temp = head;while (true) {if (temp == null) {break;}//输出节点的信息System.out.println(temp);//需要注意,将temp后移temp = temp.next;}}}//定义HeroNode,每个HeroNode对象就是一个节点
class HeroNode {public int no;public String name;public String nickName;public HeroNode next; //指向下一个节点//构造器public HeroNode(int no, String name, String nickName) {this.no = no;this.name = name;this.nickName = nickName;}//为了显示方便,我们重新toString@Overridepublic String toString() {return "HeroNode{" +"no=" + no +", name='" + name + '\'' +", nickName='" + nickName + '\'' +'}';}
}

单链表基本面试题

求单链表中节点得个数
public class SingleLinkedListDemo {//方法:获取到单链表得节点得个数(如果是带头节点得链表,需求不统计头节点)/*** @param head 链表得头节点* @return 返回得就是有效节点得个数*/public static int getLength(HeroNode head) {if (head.next == null) {return 0;}int length = 0;//定义一个辅助得变量,这里不统计头节点HeroNode cur = head.next;while (cur != null) {length++;cur = cur.next;}return length;}
}
查找单链表中得倒数第k个节点
public class SingleLinkedListDemo {//查找单链表中得倒数第k个节点//思路//1. 编写一个方法,接受head节点,同时接收一个index//2. index 表示是倒数第index个节点//3. 先把链表从头到尾遍历,得到链表得总的长度//4. 得到size后,我们从链表得第一个开始遍历(size-index)个,就可以得到//5. 如果找到了,则返回该节点,否则返回nullpublic static HeroNode findLastIndexNode(HeroNode head, int index) {//判断如果链表为空,返回nullif (head.next == null) {return null;}//第一次遍历得到链表得长度(节点个数)int size = getLength(head);//第二次遍历 size-index 位置,就是我们倒数得第K个节点//先做一个index 得校验if (index < 0 || index > size) {return null;}//定义辅助变量HeroNode cur = head.next;int length = 0;while (cur != null) {if (length == size - index) {break;}length++;cur = cur.next;}return cur;}
}
单链表得反转
示意图

思路分析
  1. 先定义一个节点 reverseHead=new HeroNode();
  2. 从头到尾遍历原来得链表,每遍历一个节点,就将其取出,并放在新的链表得最前端。
  3. 原来得链表得head.next=reverseHead.next
代码实例
//将单链表反转
public class SingleLinkedListDemo {public static void reversetList(HeroNode head) {//如果当前链表为空,或者只有一个节点,无需反转,直接返回if (head.next == null || head.next.next == null) {return;}//定义一个辅助指针(变量),帮助我们遍历原来得链表HeroNode cur = head.next;HeroNode next = null; //指向当前节点得下一个节点HeroNode reverseHead = new HeroNode(0, "", "");//遍历原来得链表,每遍历一个节点,就将其取出,并放在新的链表reverseHead得最前端//头插法while (cur != null) {next = cur.next;//先暂时保存当前节点得下一个节点,因为后面需要使用System.out.println(cur.next);//这里将cur得下一个节点指向reverseHead得第一个节点,此时cur链表已经断开,next=cur.next起到作用,cur=next,则是将链表重新连接cur.next = reverseHead.next;//将cur得下一个节点指向新的链表得最前端reverseHead.next = cur;//将 cur连接到新的链表.System.out.println(reverseHead.next);cur = next; //让cur后移}//将head.next 指向reverseHead.next,实现单链表的反转head.next = reverseHead.next;}
}
从尾到头打印单链表
思路分析
  1. 方式1:先将单链表进行反转操作,然后再遍历即可,这样做会破坏原来的链表结构,不建议。
  2. 方式2:可以利用栈这个数据结构,将各个节点压入到栈中,然后利用栈的先进后出的特点,实现逆序打印的效果
代码实例
//方式2:
//可以利用栈这个数据结构,将各个节点压入到栈中,然后利用栈的先进后出的特点,就实现了逆序打印的效果
public class SingleLinkedListDemo {public static void reversePrint(HeroNode head) {if (head.next == null) {return;//空链表,不打印}//创建一个栈,将各个节点压入栈Stack<HeroNode> stack = new Stack<HeroNode>();HeroNode cur = head.next;while (cur != null) {stack.push(cur);cur = cur.next;}while (stack.size()>0){System.out.println(stack.pop());}}
}
合并两个有序单链表,合并之后的链表依然有序
待更新

双向链表

简要分析单链表和双向链表的区别
  1. 单向链表,查找的方向只能是一个方向,而双向链表可以向前或者向后查找。
  2. 单向链表不能自我删除,需要依靠辅助节点,而双向链表则可以自我删除,所以前一章单链表删除节点时,总是找到temp(temp是待删除节点的前一个节点)的下一个节点来删除的。
示意图

思路分析

分析双向链表的遍历,添加,修改,删除的操作思路===> 代码实现

  1. 遍历方式和单链表一样,只是可以向前,也可以向后查找
  2. 添加(默认添加到双向链表的最后)
  1. 先找到双向链表的最后这个节点
  2. temp.next=lastNode (lastNode 是要插入链表尾部的节点)
  3. lastNode.pre=temp (lastNode的pre要指向链表的前一个节点)
  1. 修改思路和原先的单向链表一样
  2. 删除
  1. 因为是双向链表,因此,我们可以实现自我删除某个节点。
  2. 直接找到要删除的这个节点,比如temp
  3. temp.pre.next=temp.next
  4. temp.next.pre=temp.pre
代码实例
public class DoubleLinkedListDemo {public static void main(String[] args) {//测试System.out.println("双向链表得测试");HeroNode2 hero1 = new HeroNode2(1, "宋江", "及时雨");HeroNode2 hero2 = new HeroNode2(2, "卢俊义", "玉麒麟");HeroNode2 hero3 = new HeroNode2(3, "吴用", "智多星");HeroNode2 hero4 = new HeroNode2(4, "林冲", "豹子头");//创建一个双向链表DoubleLinkedList doubleLinkedList = new DoubleLinkedList();doubleLinkedList.add(hero1);doubleLinkedList.add(hero2);doubleLinkedList.add(hero3);doubleLinkedList.add(hero4);doubleLinkedList.list();//修改HeroNode2 newHeroNode = new HeroNode2(4, "公孙胜", "入云龙");doubleLinkedList.update(newHeroNode);doubleLinkedList.list();// 删除doubleLinkedList.del(4);doubleLinkedList.list();}
}//创建一个双向链表的类
class DoubleLinkedList {// 先初始化一个头节点,头节点不要动// 1.不存放具体的数据// 2.作用就是表示单链表头nextprivate HeroNode2 head = new HeroNode2(0, "", "");//返回头节点public HeroNode2 getHead() {return head;}//显示链表[遍历]public void list() {//判断链表是否为空if (head.next == null) {System.out.println("链表为空");}//因为头节点不能动,因此我们需要一个辅助变量来遍历HeroNode2 temp = head;while (true) {if (temp == null) {break;}//输出节点的信息System.out.println(temp);//需要注意,将temp后移temp = temp.next;}}//添加一个节点到双向链表得最后public void add(HeroNode2 heroNode) {//因为head节点不能动,因此需要一个辅助指针temp//相当于有一个temp指针指向头节点.HeroNode2 temp = head;//遍历链表,找到最后while (true) {//找到链表的最后if (temp.next == null) {break;}//如果没有找到,就将temp后移;temp = temp.next;}//当退出while循环后,temp就指向了链表最后一个元素//让所添加得节点得pre指向前一个节点形成一个双向链表temp.next = heroNode;heroNode.pre = temp;}//修改一个节点得内容,可以看到双向链表得节点内容修改和单向链表一样public void update(HeroNode2 heroNode) {if (head.next == null) {System.out.println("链表为空");return;}//找到需要修改的节点,根据no编号//定义一个辅助变量HeroNode2 temp = head;boolean flag = false;while (true) {if (temp == null) { //说明temp指向了链表的最后一个节点break;}if (temp.no == heroNode.no) {flag = true;break;}temp = temp.next;}//根据flag 判断是否找到要修改的节点if (flag) {temp.name = heroNode.name;temp.nickName = heroNode.nickName;} else { //没有找到System.out.println("没有找到编号:" + heroNode.no);}}//从双向链表中删除一个节点//说明//1 对于双向链表,可以直接找到要删除得这个节点//2 找到后自我删除即可public void del(int no) {if (head.next == null) {System.out.println("该链表为空");return;}//指定一个辅助节点HeroNode2 temp = head.next;boolean flag = false;while (true) {if (temp == null) { //说明temp指向了链表的最后一个节点break;}if (temp.no == no) {//找到的待删除节点的前一个节点tempflag = true;break;}temp = temp.next;}//判断flagif (flag) {temp.pre.next = temp.next;//如果是最后一个节点,就不需要执行下面这句话,否则会出现空指针if (temp.next != null) {temp.next.pre = temp.pre;}} else {System.out.println("要删除的节点no=" + no + "不存在");}}
}//定义HeroNode,每个HeroNode对象就是一个节点
class HeroNode2 {public int no;public String name;public String nickName;public HeroNode2 next; //指向下一个节点,默认为Nullpublic HeroNode2 pre; //指向前一个节点,默认为Null//构造器public HeroNode2(int no, String name, String nickName) {this.no = no;this.name = name;this.nickName = nickName;}//为了显示方便,我们重新toString@Overridepublic String toString() {return "HeroNode{" +"no=" + no +", name='" + name + '\'' +", nickName='" + nickName + '\'' +'}';}
}

单向环形链表

基本介绍

Josephu(约瑟夫环)问题
Josephu 问题为:设编号为1,2,…得n个人围坐在一圈,约定编号为k(1<=k<=n)得人从1开始报数,数到m得那个人出列,它得下一位又从1开始报数,数到m得那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号得序列。

提示:用一个不带头节点得循环链表来处理Josephu问题:先构成一个有n个结点得单循环链表,然后由k节点起从1开始技术,记到m时,对应节点从链表中删除,然后再从被删除节点得下一个节点又从1开始计数,直到最后一个节点从链表中删除算法结束。

示意图

出队列得顺序

2号出队列

4号出队列

1号出队列

5号出队列

思路分析

构建一个单向得环形链表思路

  1. 先创建第一个节点,让first指向该节点,并形成环形
  2. 后门当每创建一个新的节点,就把该节点加入到已有得环形链表中即可

遍历环形链表

  1. 先让一个辅助指针(变量)curBoy,指向first节点
  2. 然后通过一个while循环遍历该环形链表即可 curBoy.next == first 结束

根据用户得输入,生成一个节点出环形得顺序

  1. 需求创建一个辅助指针(变量)helper,事先应该指向环形链表得最后这个节点。

补充:节点报数前,先让first和helper移动k-1次

  1. 当遍历m次时,让first和helper指针同时得移动m-1次
  2. 这时就可以将first指向得节点出圈
    first=first.next
    helper.next=first
    原来first指向得节点就没有任何引用,就会被回收
代码实例
public class Josephu {public static void main(String[] args) {//测试System.out.println("测试~~~~~~~~~~~");CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();circleSingleLinkedList.addBoy(5);//加入5个节点circleSingleLinkedList.showBoy();circleSingleLinkedList.countBoy(1,2,5);}
}//创建一个环形得单向链表
class CircleSingleLinkedList {//创建一个first节点,当前没有编号private Boy first = new Boy(-1);//添加节点,构建成一个环形链表public void addBoy(int nums) {//nums 做一个数据校验if (nums < 1) {System.out.println("nums得值不正确");return;}Boy curBoy = null; //辅助指针,帮助构建环形链表//使用for循环来创建环形链表for (int i = 1; i <= nums; i++) {//根据编号创建节点Boy boy = new Boy(i);//如果是第一个节点if (i == 1) {first = boy;first.setNext(first);//构成环curBoy = first; //让curBoy指向第一个节点} else {curBoy.setNext(boy);boy.setNext(first);curBoy = boy;}}}//遍历当前得环形链表public void showBoy() {//判断链表是否为空if (first == null) {System.out.println("没有任何节点~~~");return;}//因为first不能动,因此我们仍然使用一个辅助指针完成遍历Boy curBoy = first;while (true) {System.out.println("当前节点得编号:" + curBoy.getNo());if (curBoy.getNext() == first) { //说明已经遍历完毕break;}curBoy = curBoy.getNext();}}//根据用户得输入,计算出节点出圈得顺序/*** @param startNo  表示从第几个节点开始报数* @param countNum countNum 表示移动次数* @param nums     表示最初由多少节点在圈中*/public void countBoy(int startNo, int countNum, int nums) {//先对数据进行校验if (first == null || startNo < 1 || startNo > nums) {System.out.println("参数输入有误,请重新输入");return;}//创建一个辅助指针,帮助完成节点出圈Boy helper = first;//需求创建一个辅助指针(变量)helper,事先应该指向环形链表得最后这个节点。while (true) {if (helper.getNext() == first) { //说明helper指向最后小孩节点break;}helper = helper.getNext();}for (int j = 0; j < startNo - 1; j++) {first = first.getNext();helper = helper.getNext();}//当遍历m次时,让first和helper指针同时得移动m-1次//这里是一个循环操作,直到圈中只有一个节点while (true) {if (helper == first) { //说明圈中只有一个节点break;}//让first和helper指针同事的移动countNum-1for (int j = 0; j < countNum - 1; j++) {first = first.getNext();helper = helper.getNext();}//这时first指向的节点,就是要出圈的小孩节点System.out.println("小孩出圈的编号:" + first.getNo());//这时将first指向的节点出圈first = first.getNext();helper.setNext(first);}System.out.println("最后留在圈中的节点编号是:" + first.getNo());}}//创建一个Boy类,表示一个节点
class Boy {private int no; //编号private Boy next;//指向下一个节点,默认nullpublic Boy(int no) {this.no = no;}public int getNo() {return no;}public void setNo(int no) {this.no = no;}public Boy getNext() {return next;}public void setNext(Boy next) {this.next = next;}
}

栈的介绍

  1. 栈(stack)的特点是先入后出(FILO-First In Last Out)的有序列表
  2. 栈(stack)是限制线性表中元素的插入和删除只能在线性表的同一端进行的一种特殊线性表。允许插入和删除的一端为变化的一端,称为栈顶(Top),另一端为固定的一端,称为栈底(Bottom)
  3. 通俗的讲,最先放入栈中元素在栈底,最后放入的元素在栈顶,而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除。

数组模拟栈

实现栈的思路分析
  1. 使用数组来模拟栈
  2. 定义一个top来表示栈顶,初始化为-1
  3. 入栈的操作,当有数据加入到栈时,top++;stack[top]=data;
  4. 出栈的操作,int value=stack[top];top–;return value;
代码实例
public class ArrayStackDemo {public static void main(String[] args) {//测试一下ArrayStack 是否正确//先创建一个ArrayStack对象->表示栈ArrayStack stack = new ArrayStack(4);String key = "";boolean loop = true; //控制是否退出菜单Scanner scanner = new Scanner(System.in);while (loop) {System.out.println("show : 显示栈");System.out.println("exit : 退出程序");System.out.println("push : 添加数据到栈(入栈)");System.out.println("pop : 表示从栈取出数据(出栈)");key = scanner.next();switch (key) {case "show":stack.list();break;case "push":System.out.print("请输入一个数 :");int value = scanner.nextInt();stack.push(value);break;case "pop":try {int res = stack.pop();System.out.println("出栈的数据是 " + res);} catch (Exception e) {System.out.println(e.getMessage());}break;case "exit":scanner.close();loop = false;System.exit(0);break;default:break;}}System.out.println("程序退出");}
}//定义一个ArrayStack 表示栈
class ArrayStack {private int maxSize; //栈的大小private int[] stack; //数组,数组模拟栈,数据就放在该数组中private int top = -1;//top表示栈顶,初始化为-1;//构造器public ArrayStack(int maxSize) {this.maxSize = maxSize;stack = new int[this.maxSize];}//栈满public boolean isFull() {return top == maxSize - 1;}//栈空public boolean isEmpty() {return top == -1;}//入栈-pushpublic void push(int value) {if (isFull()) {return;}stack[++top] = value;}//出栈-pop ,将栈顶的数据返回public int pop() {//先判断栈是否空if (isEmpty()) {throw new RuntimeException("该栈为空,没有数据");}return stack[top--];}//显示栈的情况[遍历栈],遍历时,需要从栈顶开始显示数据public void list() {if (isEmpty()) {System.out.println("该栈为空,没有数据");}for (int i = top; i >= 0; i--) {System.out.println("stack[" + i + "] = " + stack[i]);}}

利用栈实现综合计算器

思路分析
  1. 通过一个index 值(索引) ,来遍历我们的表达式
  2. 如果我们发现是一个数字,就直接入数栈
  3. 如果发现扫描到的是一个符号,就分如下情况解决:
  1. 如果发现当前的符号栈为空,就直接入栈
  2. 如果发现当前的符号栈有操作符,就进行比较
  • 如果当前的操作符的优先级小于或者等于栈中的操作符,就需要从数栈中pop出两个数,再从符号栈中pop出一个符号,进行运算,将得到的结果入数栈,还需将当前的符号入符号栈。
  • 如果当前的操作符的优先级大于栈中的操作符, 就直接入符号栈
  1. 当表达式扫描完毕,就顺序的从数栈和符号栈中pop出相应的数和符号,并运行。
  2. 最后在数栈只有一个数字,就是表达式的结果。
代码实例
public class Calculator {public static void main(String[] args) {String expression = "30+2*6-2";//创建两个栈,数栈,一个符号栈ArrayStack2 numStack = new ArrayStack2(10);ArrayStack2 operStack = new ArrayStack2(10);//定义需要的相关变量int index = 0;//用于扫描int num1 = 0;int num2 = 0;int oper = 0;int res = 0;String keepNum = "";char ch = ' ';//将每次扫描得到char保存到ch//开始while循环的扫描expressionwhile (true) {//依次得到expression的每一个字符ch = expression.charAt(index);//判断ch是什么,然后做相应的处理if (operStack.isOper(ch)) { //如果是运算符//判断当前的符号栈是否为空if (!operStack.isEmpty()) {//如果符号栈中有操作符,就进行比较,如果当前的操作符的优先级小于或者等于栈中的操作符,就需要从数栈中pop出两个数//在从符号栈中pop出一个符号运算,将得到结果,入数栈,然后将当前的操作符入符号栈if (operStack.priority(ch) <= operStack.priority(operStack.peek())) {num1 = numStack.pop();num2 = numStack.pop();oper = operStack.pop();res = numStack.cal(num1, num2, oper);//把运算结果入数栈numStack.push(res);//然后把当前运算符入符号栈operStack.push(ch);} else {//如果当前的操作符的优先级大于栈中的操作符,就直接入符号栈operStack.push(ch);}} else {//如果为空直接入符号栈..operStack.push(ch);}} else {//如果是数字// numStack.push(ch - 48);//分析思路// 1. 当处理多位数时,不能发现是数字就立即入栈,它可能是多位数// 2. 在处理数时,需要向expression的表达式的index 后在看一位,如果是树就继续扫描,如果是符号则入栈// 3. 因此我们需要定义一个变量, 字符串,用于拼接多位数keepNum += ch;//如果ch已经是expression的最后一位,则直接入栈if (index == expression.length() - 1) {numStack.push(Integer.parseInt(keepNum));keepNum = "";} else {//判断下一个字符是不是数字,如果是数字则继续扫描,如果是运算符就入栈if (operStack.isOper(expression.charAt(index + 1))) {numStack.push(Integer.parseInt(keepNum));keepNum = "";}}}//index+1,并判断是否扫描到expression最后index++;if (index == expression.length()) {break;}}//当表达式扫描完毕,就顺序的从数栈和符号栈中pop出相应的数和符号,并运行。while (true) {//如果符号栈为空,则为计算的最后结果,数栈中只有一个数字if (operStack.isEmpty()) {break;}num1 = numStack.pop();num2 = numStack.pop();oper = operStack.pop();res = numStack.cal(num1, num2, oper);numStack.push(res);}res = numStack.pop();System.out.println("计算结果为:" + res);}
}
//先创建一个栈,直接使用前面创建好的//定义一个ArrayStack2 表示栈,需要扩展功能
class ArrayStack2 {private int maxSize; //栈的大小private int[] stack; //数组,数组模拟栈,数据就放在该数组中private int top = -1;//top表示栈顶,初始化为-1;//构造器public ArrayStack2(int maxSize) {this.maxSize = maxSize;stack = new int[this.maxSize];}//栈满public boolean isFull() {return top == maxSize - 1;}//栈空public boolean isEmpty() {return top == -1;}//入栈-pushpublic void push(int value) {if (isFull()) {return;}stack[++top] = value;}//出栈-pop ,将栈顶的数据返回public int pop() {//先判断栈是否空if (isEmpty()) {throw new RuntimeException("该栈为空,没有数据");}return stack[top--];}//显示栈的情况[遍历栈],遍历时,需要从栈顶开始显示数据public void list() {if (isEmpty()) {System.out.println("该栈为空,没有数据");}for (int i = top; i >= 0; i--) {System.out.println("stack[" + i + "] = " + stack[i]);}}//返回运算符的优先级,优先级是程序员来确定的,优先级使用数字表示//数字越大,优先级越高public int priority(int oper) {if (oper == '*' || oper == '/') {return 1;} else if (oper == '+' || oper == '-') {return 0;} else {return -1; //假定目前的表达式只有+,-,*,/}}//判断是不是一个运算符public boolean isOper(char val) {return val == '+' || val == '-' || val == '*' || val == '/';}//计算方法public int cal(int num1, int num2, int oper) {int res = 0;//res 用于存放计算的结果switch (oper) {case '+':res = num1 + num2;break;case '-':res = num2 - num1;break;case '*':res = num1 * num2;break;case '/':res = num2 / num1;break;default:break;}return res;}//查看栈顶的值public int peek() {return stack[top];}
}

前缀、中缀、后缀表达式(逆波兰表达式)

前缀表达式
基本介绍
  1. 前缀表达式又称为波兰式,前缀表达式的运算符位于操作数之前
  2. 举例说明:(3+4)× 5 - 6对应的前缀表达式就是- × + 3 4 5 6
思路分析

从右至左扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(栈顶元素和次顶元素),并将结果入栈;重复上述过程直到表达式最左端,最后运算得出的值即为表达式的结果。

例如:(3+4)× 5 - 6对应的前缀表达式就是- × + 3 4 5 6,针对前缀表达式求值步骤如下:

  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,由此得出最终结果
中缀表达式
基本介绍
  1. 中缀表达式就是常见的运算表达式,如(3+4)× 5 - 6
  2. 中缀表达式的求值是我们人最熟悉的,但是对计算机来说却不好操作。因此,在计算结果时,往往会将中缀表达式转成其它表达式来操作(一般转成后缀表达式)
后缀表达式
基本介绍
  1. 后缀表达式又称逆波兰表达式,与前缀表达式相似,只是运算符位于操作数之后
  2. 举例说明:(3+4)× 5 - 6对应的前缀表达式就是 3 4 + 5 × 6 -
  3. 再比如:
正常的表达式 逆波兰表达式
a+b a b +
a+(b-c) a b c - +
a+(b-c)*d a b c - d * +
a+d*(b-c) a d b c - * +
a=1+3 a 1 3 + =
思路分析

从左至右扫描表达式,遇到数字时,将数字压入栈中,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(次顶元素和栈顶元素),并将结果入栈;重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果

例如:(3+4)× 5 - 6对应的后缀表达式就是 3 4 + 5 × 6 - ,针对后缀表达式求值步骤如下:

  1. 从左至右扫描,将3和4压入栈中。
  2. 遇到 + 运算符,因此弹出4和3(4为栈顶元素,3为次栈顶元素),计算出3+4的值,得7,再将7入栈;
  3. 将5入栈;
  4. 接下来是 × 运算符,因此弹出5和7,计算出7 × 5 = 35,将35入栈;
  5. 将6入栈;
  6. 最后是 - 运算符,计算出35 - 6的值,即29,由此得到最终结果。

逆波兰计算器

基本介绍

输入一个逆波兰表达式(后缀表达式),使用栈(stack),计算其结果

代码实例
public class PolandNotation {public static void main(String[] args) {//先定义一个逆波兰表达式// (3+4)×5-6 => 3 4 + 5 × 6 -//说明: 为了方便,逆波兰表达式的数字和符号使用空格隔开String suffixExpression = "3 4 + 5 × 6 - ";//思路//1. 先将"3 4 + 5 × 6 - " => 放入到ArrayList中//2. 将ArrayList 传递给一个方法,配合栈 完成计算List<String> rpnList = getListString(suffixExpression);System.out.println("rpnList=" + rpnList);int res=calculate(rpnList);System.out.println("result="+res);}//将一个逆波兰表达式,依次将数据和运算符放入到ArrayList中public static List<String> getListString(String suffixExpression) {//将suffixExpression分割String[] split = suffixExpression.split(" ");List<String> list = new ArrayList<>();for (String s : split) {list.add(s);}return list;}//完成对逆波兰表达式的运算/**>1. 从左至右扫描,将3和4压入栈中。*>2. 遇到 + 运算符,因此弹出4和3(4为栈顶元素,3为次栈顶元素),计算出3+4的值,得7,再将7入栈;*>3. 将5入栈;*>4. 接下来是 × 运算符,因此弹出5和7,计算出7 × 5 = 35,将35入栈;*>5. 将6入栈;*>6. 最后是 - 运算符,计算出35 - 6的值,即29,由此得到最终结果。**/public static int calculate(List<String> ls) {//创建一个栈,只需要一个栈即可Stack<String> stack = new Stack<>();//遍历lsfor (String item : ls) {//这里使用正则表达式取出数if (item.matches("\\d+")) { //匹配的是多位数stack.push(item);} else {//pop出两个数,并运算,再入栈int num2 = Integer.parseInt(stack.pop());int num1 = Integer.parseInt(stack.pop());int res = 0;if (item.equals("+")) {res = num1 + num2;} else if (item.equals("-")) {res = num1 - num2;} else if (item.equals("×")) {res = num1 * num2;} else if (item.equals("/")) {res = num1 / num2;} else {throw new RuntimeException("数据异常");}//res 入栈stack.push(String.valueOf(res));}}//最后留在stack中的数据就是运算结果return Integer.parseInt(stack.pop());}
}
中缀表达式转换为后缀表达式
思路分析

后缀表达式适合计算式进行运算,但是由人输入不太容易,尤其是表达式很长的情况下,因此在开发中,我们需要将中缀表达式转化为后缀表达式。
具体步骤如下:

  1. 初始化两个栈:运算符栈s1和储存中间结果的栈s2;
  2. 从左至右扫描中缀表达式;
  3. 遇到操作数栈时,将其压入s2;
  4. 遇到运算符时,比较其与s1栈顶运算符的优先级;
  1. 如果s1为空,或栈顶运算符为左括号"(",则直接将此运算符入栈;
  2. 否则,若优先级比栈顶运算符的高,也将运算符压入s1;
  3. 否则,将s1栈顶的运算符弹出并压入到s2中,再次转到(4.1)与s1中新的栈顶运算符相比较;
  1. 遇到括号时:
  1. 如果是左括号"(",则直接压入s1
  2. 如果是右括号")",则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃
  1. 重复步骤2至5,直到表达式的最右边
  2. 将s1中剩余的运算符依次弹出并压入s2
  3. 依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式
代码实例
public class PolandNotation {public static void main(String[] args) {//完成将一个中缀表达式转成后缀表达式的功能//说明//1. 1+((2+3)×4)-5 => 转成 1 2 3 + 4 × + 5 -//2. 因为直接对str 进行操作不太方便,因此先将1+((2+3)×4)-5 => 中缀的表达式对应的List//3. 将得到的中缀表达式对应的List => 后缀表达式对应的ListString expression = "1+((2+3)×4)-5";List<String> infixExpressionList = toInfixExpressionList(expression);System.out.println("中缀表达式对应的List: " + infixExpressionList); //[1, +, (, (, 2, +, 3, ), ×, 4, ), -, 5]List<String> suffixExpreesionList = pareseSuffixExpreesionList(infixExpressionList);System.out.println("后缀表达式对应的List: " + suffixExpreesionList);System.out.println("expression对应的result="+calculate(suffixExpreesionList));}//方法:将得到的中缀表达式对应的List => 后缀表达式对应的Listpublic static List<String> pareseSuffixExpreesionList(List<String> ls) {//定义两个栈Stack<String> s1 = new Stack<>(); //符号栈//说明:因为s2这个栈,在整个转换过程中,没有pop操作,而且后面我们还需要逆序输出//因此比较麻烦,这里不使用Stack<String>,使用ArrayList<String>即可完成逆波兰表达式List<String> s2 = new ArrayList<>(); //储存中间结果的List->s2for (String item : ls) {//如果是一个数,加入s2;if (item.matches("\\d+")) {s2.add(item);} else if (item.equals("(")) {s1.push(item);} else if (item.equals(")")) {//如果是右括号")",则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃while (!s1.peek().equals("(")) {s2.add(s1.pop());}s1.pop(); //将"("弹出s1栈} else {//当item的优先级小于等于s1栈顶运算符的优先级,将s1栈顶的运算符弹出并压入到s2中//问题:缺少一个比较优先级高低的方法while (s1.size() != 0 && Operation.getValue(s1.peek()) >= Operation.getValue(item)) {s2.add(s1.pop());}//还需要将item压入栈中s1.push(item);}}//将s1中剩余的运算符依次弹出并压入s2while (!s1.isEmpty()) {s2.add(s1.pop());}return s2;}//方法:将中缀表达式转成对应的Listpublic static List<String> toInfixExpressionList(String s) {//定义一个List,存放中缀表达式对应的内容List<String> ls = new ArrayList<>();int i = 0; //这时是一个指针,用于遍历中缀表达式字符串String str;//对多位数的拼接char c; //每遍历到一个字符,就放入到cdo {//如果c是非数字,就需要加入到ls中if ((c = s.charAt(i)) < 48 || (c = s.charAt(i)) > 57) {ls.add("" + c);i++;} else {str = "";while (i < s.length() && (c = s.charAt(i)) >= 48 && (c = s.charAt(i)) <= 57) {str += c; //拼接i++;}ls.add(str);}} while (i < s.length());return ls;}//完成对逆波兰表达式的运算/**>1. 从左至右扫描,将3和4压入栈中。*>2. 遇到 + 运算符,因此弹出4和3(4为栈顶元素,3为次栈顶元素),计算出3+4的值,得7,再将7入栈;*>3. 将5入栈;*>4. 接下来是 × 运算符,因此弹出5和7,计算出7 × 5 = 35,将35入栈;*>5. 将6入栈;*>6. 最后是 - 运算符,计算出35 - 6的值,即29,由此得到最终结果。*/public static int calculate(List<String> ls) {//创建一个栈,只需要一个栈即可Stack<String> stack = new Stack<>();//遍历lsfor (String item : ls) {//这里使用正则表达式取出数if (item.matches("\\d+")) { //匹配的是多位数stack.push(item);} else {//pop出两个数,并运算,再入栈int num2 = Integer.parseInt(stack.pop());int num1 = Integer.parseInt(stack.pop());int res = 0;if (item.equals("+")) {res = num1 + num2;} else if (item.equals("-")) {res = num1 - num2;} else if (item.equals("×")) {res = num1 * num2;} else if (item.equals("/")) {res = num1 / num2;} else {throw new RuntimeException("数据异常");}//res 入栈stack.push(String.valueOf(res));}}//最后留在stack中的数据就是运算结果return Integer.parseInt(stack.pop());}
}//编写一个类 Operation 可以返回一个运算符对应的优先级
class Operation {private static int ADD = 1;private static int SUB = 1;private static int MUL = 2;private static int DIV = 2;//写一个方法,返回对应的优先级数字public static int getValue(String operation) {int result = 0;switch (operation) {case "+":result = ADD;break;case "-":result = SUB;break;case "×":result = MUL;break;case "/":result = DIV;break;default:System.out.println("不存在该数据");break;}return result;}
}

数据结构与算法学习——基础知识(一)相关推荐

  1. 数据结构与算法学习-开篇

    前言 数据结构和算法这门课一直是计算机专业最基础的一门课,大学时期掌握的不够好,毕业后长期写业务,也没有特别的花时间好好攻克一下,一直是自己的短板.这次在极客时间上订阅了两门数据结构和算法方面的专栏, ...

  2. python leetcode_leetcode 介绍和 python 数据结构与算法学习资料

    for (刚入门的编程)的高中 or 大学生 leetcode 介绍 leetcode 可以说是 cs 最核心的一门"课程"了,虽然不是大学开设的,但基本上每一个现代的高水平的程序 ...

  3. c语言将AOE网络的数据写入TXT文档中,数据结构与算法学习辅导及习题详解.张乃孝版-C/C++文档类资源...

    数据结构与算法学习辅导及习题详解.张乃孝版.04年10月 经过几年的努力,我深深体会到,编写这种辅导书要比编写一本湝通教材困难得多. 但愿我的上述理想,在本书中能够得以体现. 本书的组织 本书继承了& ...

  4. 大话数据结构与算法:基础篇

    1.数据结构的重要性 数据结构是计算机软件相关专业的基础课程,几乎可以说,要想从事编程工作,无论是否是科班出身(比如我,标准的非科班人员,我是学医的,哈哈)都不可以绕过数据结构与算法这部分知识. 数据 ...

  5. 数据结构与算法学习笔记之 从0编号的数组

    数据结构与算法学习笔记之 从0编号的数组 前言 数组看似简单,但掌握精髓的却没有多少:他既是编程语言中的数据类型,又是最基础的数据结构: 一个小问题: 为什么数据要从0开始编号,而不是 从1开始呢? ...

  6. 数据结构与算法之基础概述

    目录 数据结构和算法的重要性 数据结构概述 逻辑结构 存储结构 算法概述 如何理解"大O记法" 时间复杂度 空间复杂度 数据结构和算法的重要性 算法是程序的灵魂,优秀的程序可以在海 ...

  7. 1120_野火RT-Thread教程学习4_RTT学习基础知识梳理

    野火RT-Thread教程学习笔记4_RTT学习基础知识梳理 Grey 全部学习汇总:GitHub - GreyZhang/g_RT-Thread: learning and hacking RT-T ...

  8. 数据结构与算法学习⑤(BFS和DFS 贪心算法 二分查找)

    数据结构与算法学习⑤ 数据结构与算法学习⑤ 1.BFS和DFS 1.1.深度优先搜索算法 1.2.广度优先搜索算法 面试实战 102. 二叉树的层序遍历 104. 二叉树的最大深度 515. 在每个树 ...

  9. 深度强化学习基础知识 思维导图

    学习了王树森老师的深度强化学习入门课件,将各个算法整理在如下思维导图中. 深度强化学习基础知识 思维导图

最新文章

  1. 每日一博 - CAS(Compare-And-Swap)原理剖析
  2. 一个操作内表的函数’CTVB_COMPARE_TABLES’
  3. laravel 学习总结
  4. 学习总结——Selenium元素定位
  5. 五种世界顶级思维-20190303
  6. jsp 导入java类_JSP页面导入问题。类文件放在WEB-INF / classes中的包中
  7. SAP License:SAP应用随想
  8. JS实现返回顶部功能
  9. 数据结构丿丶树 哈夫曼树
  10. web前端面试题-1
  11. JQuery图片跟随鼠标移动
  12. [python爬虫] Selenium爬取新浪微博内容及用户信息
  13. 邢质斌退休意味着一个时代的结束
  14. 中台战略-第五章、中台建设方法论
  15. 镜播无人直播带货教程,手把手教你如何搭建直播间
  16. DNS工作原理及解析过程
  17. [计算机漫谈]网络初步:一个分组的生命历程
  18. 「 每日一练,快乐水题 」1455. 检查单词是否为句中其他单词的前缀
  19. C/C++使用ODBC连接MSSQL数据库
  20. Picture2Epub

热门文章

  1. hdu 5128 The E-pang Palace (有技巧的暴力)
  2. python3.6实现Softmax Regression测试训练模型(可视化)机器学习算法(赵志勇)学习笔记
  3. 《我如何自己做自己的导师》
  4. java mongo replica_mongo 的replica set的集群模式 实现读写分离
  5. 齐博 php7,齐博cmsv7.0后台getshell
  6. 阿里云合作伙伴返点和代理商返点的差别
  7. 如何用计算机装手机系统,教你在手机上安装Windows系统
  8. 【Java核心技术大会 PPT分享】林子熠:GraalVM的静态编译和静态分析技术
  9. 论文笔记之Estimator Varience in RL
  10. iOS1.0到iOS7,iOS七大版本特性回顾