线性结构

特点:数据元素之间存在一对一的线性关系

存在两种不同的存储结构,分别为顺序存储结构链式存储结构

  • 顺序存储的线性表:称为顺序表,顺序表中的存储元素是连续的
  • 链式存储的线性表:称为链表,链表中的存储元素不一定是连续的

线性结构常见的有:数组队列链表

1. 队列

介绍

  1. 队列是一个有序列表,可以用数组或者链表来实现
  2. 遵循先入先出的原则,也就是先存入队列的数据要先取出,后存入的要后取出
  3. 在结构图中有两个指针(也就是头指针front尾指针rear),加数据时尾指针变化,出数据时头指针变化(总结来说:头出尾入)

数组模拟队列

  1. 使用数组的结构来存储队列的数据,在自己创建的队列类ArrayQueue中需要有四个属性,分别为maxSize、front、rear和arr
    • maxSize:int类型,表示队列的最大容量
    • front:int类型,头指针,也就是下一个该出的数据的位置
    • rear:int类型,尾指针,也就是刚刚进入的数据的位置
    • arr:int[]类型,存队列数据的数组
  2. 该类中存在的方法需要实现的功能
    • 判断队列是否已满
    • 判断队列是否为空
    • 添加数据到队列中
    • 从队列中取出数据
    • 查看队列中的所有数据
    • 查看队列中的头元素(不是取出,仅仅是查看)
  3. ArrayQueue类代码的实现
  1. package unimproved;
  2. public class ArrayQueue {
  3. private int maxSize; //表示队列的最大容量
  4. private int front; //队列的头
  5. private int rear; //队列的尾
  6. private int[] arr; //该数组用于存放数据
  7. public ArrayQueue() {
  8. }
  9. public ArrayQueue(int maxSize) {
  10. this.maxSize = maxSize;
  11. arr = new int[this.maxSize];
  12. front = -1; //指向队列头的前一个位置
  13. rear = -1; //指向队列中尾部的数据
  14. }
  15. public void setMaxSize(int maxSize) {
  16. this.maxSize = maxSize;
  17. }
  18. public int getMaxSize() {
  19. return maxSize;
  20. }
  21. public int getFront() {
  22. return front;
  23. }
  24. public int getRear() {
  25. return rear;
  26. }
  27. //判断队列是否已满
  28. public boolean isFull() {
  29. return rear == maxSize - 1;
  30. }
  31. //判断队列是否为空
  32. public boolean isEmpty() {
  33. return front == rear;
  34. }
  35. //添加数据到队列
  36. public void addQueue(int n) {
  37. //判断队列是否已满
  38. if (isFull()) {
  39. System.out.println("队列已满,不能再向队列中添加数据...");
  40. return;
  41. }
  42. //加数据
  43. rear++;//让rear后移
  44. arr[rear] = n;
  45. }
  46. //获取队列的数据,出队列
  47. public int getQueue() {
  48. //判断队列是否为空
  49. if (isEmpty()) {
  50. //通过抛出异常
  51. throw new RuntimeException("队列为空,没有数据可出队列...");
  52. }
  53. //出数据
  54. front++;//让front后移
  55. return arr[front];
  56. }
  57. //显示队列的所有数据
  58. public void showQueue() {
  59. //判断队列是否为空
  60. if (isEmpty()) {
  61. System.out.println("队列为空,没有数据可查看...");
  62. return;
  63. }
  64. //遍历arr数组
  65. for (int e : arr) {
  66. System.out.printf("%d ", e);
  67. }
  68. System.out.println();
  69. }
  70. //显示队列的头数据,并不是取出,就只是查看
  71. public int headQueue() {
  72. //判断队列是否为空
  73. if (isEmpty()) {
  74. //通过抛出异常
  75. throw new RuntimeException("队列为空,没有头数据...");
  76. }
  77. return arr[front + 1];
  78. }
  79. }
  1. ArrayQueueDemo测试类代码
  1. package unimproved;
  2. import java.util.Scanner;
  3. public class ArrayQueueDemo {
  4. public static void main(String[] args) {
  5. //初始化一个队列
  6. ArrayQueue arrayQueue = new ArrayQueue(3);
  7. char key = ' '; //接受用户输入
  8. Scanner input = new Scanner(System.in);
  9. boolean loop = true;
  10. //输出一个菜单
  11. while(loop) {
  12. System.out.println("------------------------------");
  13. System.out.println("s(show):显示队列");
  14. System.out.println("a(add):添加数据到队列");
  15. System.out.println("g(get):从队列取出数据");
  16. System.out.println("h(head):查看队列的头数据");
  17. System.out.println("e(exit):退出程序");
  18. System.out.println("------------------------------");
  19. key = input.next().charAt(0);//接受一个字符
  20. switch (key) {
  21. case 's':
  22. arrayQueue.showQueue();
  23. break;
  24. case 'a':
  25. System.out.println("请输入一个整数:");
  26. int number = input.nextInt();
  27. arrayQueue.addQueue(number);
  28. break;
  29. case 'g':
  30. try {
  31. int result = arrayQueue.getQueue();
  32. System.out.println("从队列取出的数据是:" + result);
  33. } catch (Exception e) {
  34. System.out.println(e.getMessage());
  35. }
  36. break;
  37. case 'h':
  38. try {
  39. int result = arrayQueue.headQueue();
  40. System.out.println("队列的头数据时:" + result);
  41. } catch (Exception e) {
  42. System.out.println(e.getMessage());
  43. }
  44. break;
  45. case 'e':
  46. input.close();
  47. loop = false;
  48. System.out.println("程序已退出...");
  49. break;
  50. default:
  51. break;
  52. }
  53. }
  54. }
  55. }
  1. 发现该代码有缺陷,只能使用一次该数组,进行代码优化,数据模拟环形队列

数组模拟环形队列

  1. front变量的含义做一个调整,指向队列的第一个元素,之前是指向第一个元素的前一个位置,也就是arr[front]是低于个元素,不再是arr[front+1],front的初始值为0
  2. rear变量的含义做一个调整,rear指向队列的最后一个元素的后一个位置,因为希望空出一个空间作为约定,rear的初始值为0
  3. 当队列满时,(rear + 1) % maxSize == front为true,这里取模是考虑到了当rear在arr最末尾跳到arr最前方,实现循环队列,后面取模运算皆是考虑到这里
  4. 当队列为空,front与rear相等时
  5. 队列中有效的数据的个数为(rear + maxSize - front) % maxSize
  6. 思考过以上几点后,就可以在数组模拟队列的代码上进行修改
  7. CircleQueue类代码的实现
  1. package improved;
  2. public class CircleQueue {
  3. private int maxSize; //表示队列的最大容量
  4. private int front; //队列的头,指向队列头一个数据的位置,这里初始值就为0,不用重新赋值
  5. private int rear; //队列的尾,指向队列中尾部的数据的下一个位置,这里初始值就为0,不用重新赋值
  6. private int[] arr; //该数组用于存放数据
  7. public CircleQueue() {
  8. }
  9. public CircleQueue(int maxSize) {
  10. this.maxSize = maxSize;
  11. arr = new int[this.maxSize];
  12. }
  13. public void setMaxSize(int maxSize) {
  14. this.maxSize = maxSize;
  15. }
  16. public int getMaxSize() {
  17. return maxSize;
  18. }
  19. public int getFront() {
  20. return front;
  21. }
  22. public int getRear() {
  23. return rear;
  24. }
  25. //判断队列是否已满
  26. public boolean isFull() {
  27. return (rear + 1) % maxSize == front;
  28. }
  29. //判断队列是否为空
  30. public boolean isEmpty() {
  31. return front == rear;
  32. }
  33. //获取当前队列的有效数据个数
  34. public int effectiveLength() {
  35. return (rear + maxSize -front) % maxSize;
  36. }
  37. //添加数据到队列
  38. public void addQueue(int n) {
  39. //判断队列是否已满
  40. if (isFull()) {
  41. System.out.println("队列已满,不能再向队列中添加数据...");
  42. return;
  43. }
  44. //加数据
  45. arr[rear] = n;//先加入值
  46. rear = (rear + 1) % maxSize;//再将rear后移,这里考虑回到数组最前面
  47. }
  48. //获取队列的数据,出队列
  49. public int getQueue() {
  50. //判断队列是否为空
  51. if (isEmpty()) {
  52. //通过抛出异常
  53. throw new RuntimeException("队列为空,没有数据可出队列...");
  54. }
  55. //出数据
  56. int temp = arr[front];//先将要出队的值保存在一个临时变量中,因为如果先直接return,后面的代码将不会运行,使得front不能移动
  57. front = (front + 1) % maxSize;
  58. return temp;
  59. }
  60. //显示队列的所有数据
  61. public void showQueue() {
  62. //判断队列是否为空
  63. if (isEmpty()) {
  64. System.out.println("队列为空,没有数据可查看...");
  65. return;
  66. }
  67. //从front开始遍历,遍历多少个元素
  68. for (int i = front; i < front + effectiveLength(); i++ ) {
  69. System.out.printf("%d ", arr[i % maxSize]);
  70. }
  71. System.out.println();
  72. }
  73. //显示队列的头数据,并不是取出,就只是查看
  74. public int headQueue() {
  75. //判断队列是否为空
  76. if (isEmpty()) {
  77. //通过抛出异常
  78. throw new RuntimeException("队列为空,没有头数据...");
  79. }
  80. return arr[front];
  81. }
  82. }
  1. CircleQueueDemo测试类代码
  1. package improved;
  2. import java.util.Scanner;
  3. public class CircleQueueDemo {
  4. public static void main(String[] args) {
  5. //初始化一个队列
  6. CircleQueue arrayQueue = new CircleQueue(4);//这里的队列长度最大为3,留了一个空位
  7. char key = ' '; //接受用户输入
  8. Scanner input = new Scanner(System.in);
  9. boolean loop = true;
  10. //输出一个菜单
  11. while(loop) {
  12. System.out.println("---------- -环形队列-----------");
  13. System.out.println("s(show):显示队列");
  14. System.out.println("a(add):添加数据到队列");
  15. System.out.println("g(get):从队列取出数据");
  16. System.out.println("h(head):查看队列的头数据");
  17. System.out.println("e(exit):退出程序");
  18. System.out.println("------------------------------");
  19. key = input.next().charAt(0);//接受一个字符
  20. switch (key) {
  21. case 's':
  22. arrayQueue.showQueue();
  23. break;
  24. case 'a':
  25. System.out.println("请输入一个整数:");
  26. int number = input.nextInt();
  27. arrayQueue.addQueue(number);
  28. break;
  29. case 'g':
  30. try {
  31. int result = arrayQueue.getQueue();
  32. System.out.println("从队列取出的数据是:" + result);
  33. } catch (Exception e) {
  34. System.out.println(e.getMessage());
  35. }
  36. break;
  37. case 'h':
  38. try {
  39. int result = arrayQueue.headQueue();
  40. System.out.println("队列的头数据时:" + result);
  41. } catch (Exception e) {
  42. System.out.println(e.getMessage());
  43. }
  44. break;
  45. case 'e':
  46. input.close();
  47. loop = false;
  48. System.out.println("程序已退出...");
  49. break;
  50. default:
  51. break;
  52. }
  53. }
  54. }
  55. }

2.链表

介绍

  1. 链表是有序的列表,但其实在存储中别不一定是有序的,链式存储
  2. 链表是以节点的方式来存储的
  3. 每个节点包含data域,next域(指向下一个节点,也就是下一个节点的地址)
  4. 链表分为带头节点(不存放具体的数据)的链表和没有头节点的链表,根据自己的需求选择

单向链表

  1. 使用带head头的单向链表实现水浒英雄排行榜管理,完成对英雄人物的增删改查操作,在添加英雄时,一种是直接添加到链表的尾部,另一种是添加到指定位置,若要添加的英雄已存在链表,则会添加失败,并给出提示
  2. 添加(创建)单链表实现思路
    • 先创建一个head头节点,作用就是表示单链表的头
    • 后面每添加一个节点,就直接加入到链表的最后
  3. 遍历单链表实现思路
    • 通过一个辅助变量来实现整个单链表的遍历
  4. 需要按照编号顺序添加
    • 首先找到新添加节点的位置,通过辅助变量来找到
    • 新节点.next = temp.next
    • 让temp.next = 新节点
    • 通过遍历来寻找对应位置
  5. 从单链表中删除指定节点
    • 指定一个辅助变量,来找到需要删除节点的前一个节点
    • temp.next = temp.next.next
    • 被删除的节点,将不会有其他引用指向,会被垃圾回收机制回收
  6. 代码实现

    • 节点类HeroNode

      1. //定义HeroNode,每个HeroNode对象就是一个节点
      2. public class HeroNode {
      3. public int serialNumber;//序号
      4. public String name;//姓名
      5. public String nickName;//昵称
      6. public HeroNode next;//指向下一个节点
      7. public HeroNode() {}
      8. public HeroNode(int serialNumber, String name, String nickName) {
      9. this.serialNumber = serialNumber;
      10. this.name = name;
      11. this.nickName = nickName;
      12. }
      13. @Override
      14. public String toString() {
      15. return "HeroNode{" +
      16. "serialNumber=" + serialNumber +
      17. ", name='" + name + '\'' +
      18. ", nickName='" + nickName + '\'' +
      19. '}';
      20. }
      21. }
    • 链表类SingleLinkedList

      1. //定义SingleLinkedList,管理我们的水浒英雄们
      2. public class SingleLinkedList {
      3. //先初始化头节点,头节点不要改变,若是改变了整个链表都找不到了
      4. private HeroNode head = new HeroNode(0, "", "");
      5. public SingleLinkedList() {}
      6. public HeroNode getHead() {
      7. return head;
      8. }
      9. //添加节点到单向链表(链表末端)
      10. //思路:
      11. //当不考虑编号顺序时
      12. //1. 找到当前链表的最后节点
      13. //2. 将最后这个节点的next指向新的节点
      14. public void add(HeroNode heroNode) {
      15. //因为head节点不能动,我们需要一个辅助指针遍历temp
      16. HeroNode temp = head;
      17. //遍历链表,找到最后
      18. while (true) {
      19. //找到链表的最后
      20. if (temp.next == null) {
      21. break;
      22. }
      23. //如果没有找到最后,将节点后移
      24. temp = temp.next;
      25. }
      26. //当退出while循环时,temp就指向了链表的最后
      27. //将最后这个节点的next指向新的节点
      28. temp.next = heroNode;
      29. }
      30. //添加节点到链表,按照标号大小顺序插入
      31. public void addByOrder(HeroNode heroNode) {
      32. //因为头节点不能动,用辅助指针代替
      33. HeroNode temp = head;
      34. boolean flag = false;//标志新添加的英雄是否存在,这里默认不存在
      35. while(true) {
      36. if (temp.next == null) {//说明temp已经是链表的最后一个节点
      37. break;
      38. }
      39. if (temp.next.serialNumber > heroNode.serialNumber) {//位置找到了,就在temp后方
      40. break;
      41. } else if (temp.next.serialNumber == heroNode.serialNumber) {//若成立,说明该节点单链表中已存在
      42. flag = true;
      43. break;
      44. }
      45. temp = temp.next;
      46. }
      47. if (flag) {
      48. System.out.println("准备存入的英雄的编号" + heroNode.serialNumber + "已经存在,不能加入");
      49. } else {
      50. heroNode.next = temp.next;
      51. temp.next = heroNode;
      52. }
      53. }
      54. //修改节点信息,很久编号来修改,即编号不能变
      55. public void update(HeroNode newHeroNode) {
      56. //先判断链表是否为空
      57. if (head.next == null) {
      58. System.out.println("链表为空,修改失败...");
      59. return;
      60. }
      61. HeroNode temp = head.next;
      62. boolean flag = false;//表示是否找到需要修改的节点
      63. while (true) {
      64. if (temp == null) {
      65. break;//已经遍历完
      66. }
      67. if (temp.serialNumber == newHeroNode.serialNumber) {
      68. flag = true;
      69. break;
      70. }
      71. temp = temp.next;
      72. }
      73. //根据flag判断是否找到
      74. if (flag) {
      75. temp.name = newHeroNode.name;
      76. temp.nickName = newHeroNode.nickName;
      77. } else {//没有找到
      78. System.out.println("没有找到编号为" + newHeroNode.serialNumber + "的英雄...");
      79. }
      80. }
      81. //删除指定节点
      82. //head不能动,因此我们需要辅助指针来指向待删除节点的前一个节点
      83. public void del(int serialNumber) {
      84. HeroNode temp = head;
      85. boolean flag = false;//标志是否找到待删除节点
      86. while (true) {
      87. if (temp.next == null) {
      88. break;
      89. }
      90. if (temp.next.serialNumber == serialNumber) {
      91. //找到了待删除节点的前一个节点temp
      92. flag = true;
      93. break;
      94. }
      95. temp = temp.next;
      96. }
      97. //判断flag
      98. if (flag) {
      99. temp.next = temp.next.next;
      100. } else {
      101. System.out.println("要删除的编号" + serialNumber + "不存在...");
      102. }
      103. }
      104. //遍历链表
      105. public void list() {
      106. //判断链表是否为空
      107. if (head.next == null) {
      108. System.out.println("链表为空...");
      109. return;
      110. }
      111. //因为头节点不能动,我们需要一个辅助变量temp来遍历链表
      112. HeroNode temp = head.next;
      113. while (true) {
      114. //判断是否到了链表的最后
      115. if (temp == null) {
      116. break;
      117. }
      118. //不为空就输出该节点信息
      119. System.out.println(temp.toString());
      120. //将temp后移
      121. temp = temp.next;
      122. }
      123. }

}

  1. - 测试类SingleLinkedListDemo
  2. ```java
  3. public class SingleLinkedListDemo {
  4. public static void main(String[] args) {
  5. //进行测试
  6. //先创建节点
  7. HeroNode hero1 = new HeroNode(1, "宋江", "及时雨");
  8. HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟");
  9. HeroNode hero3 = new HeroNode(3, "吴用", "智多星");
  10. HeroNode hero4 = new HeroNode(4, "林冲", "豹子头");
  11. HeroNode hero5 = new HeroNode(3, "吴用", "智多星");
  12. //创建链表
  13. SingleLinkedList singleLinkedList = new SingleLinkedList();
  14. //向链表中加入英雄
  15. singleLinkedList.addByOrder(hero1);
  16. singleLinkedList.addByOrder(hero3);
  17. singleLinkedList.addByOrder(hero4);
  18. singleLinkedList.addByOrder(hero2);
  19. singleLinkedList.addByOrder(hero5);
  20. // //向链表中加入英雄
  21. // singleLinkedList.add(hero1);
  22. // singleLinkedList.add(hero2);
  23. // singleLinkedList.add(hero3);
  24. // singleLinkedList.add(hero4);
  25. //遍历
  26. singleLinkedList.list();
  27. HeroNode newHero2 = new HeroNode(2, "小卢","小麒麟");
  28. //测试修改节点的代码
  29. singleLinkedList.update(newHero2);
  30. System.out.println("修改后再次遍历...");
  31. //遍历
  32. singleLinkedList.list();
  33. singleLinkedList.del(1);
  34. singleLinkedList.del(4);
  35. singleLinkedList.del(3);
  36. singleLinkedList.del(2);
  37. System.out.println("删除后再次遍历...");
  38. //遍历
  39. singleLinkedList.list();
  40. }
  41. }

单链表面试题

  1. 求单链表中的有效节点个数

    1. //方法,获取到单链表的节点的个数(带头节点的链表,头节点不计入节点个数中)
    2. /**
    3. *
    4. * @param head 表示头节点
    5. * @return 返回的就是有效节点的个数
    6. */
    7. public static int getLength(HeroNode head) {
    8. if (head.next == null) {//表示为空
    9. return 0;
    10. }
    11. int length = 0;
    12. //定义一个辅助变量
    13. HeroNode cur = head.next;
    14. while (cur != null) {
    15. length++;
    16. cur = cur.next;
    17. }
    18. return length;
    19. }
  2. 查找单链表中的倒数第k个节点(新浪面试题)

    1. //查找单链表中的倒数第k个节点
    2. /**
    3. *
    4. * @param head 表示头节点
    5. * @param index 要找的倒数第几个节点
    6. * @return 返回的指定节点,
    7. */
    8. public static HeroNode findLastIndexNode(HeroNode head, int index) {
    9. //判断链表是否为空,为空则返回null
    10. if (head.next == null) {
    11. return null;
    12. }
    13. //先遍历单链表得到及诶单个数
    14. int size = getLength(head);
    15. //第二次遍历,size-index便是我们想要得到的节点
    16. if (index <= 0 || index > size) {
    17. return null;
    18. }
    19. //定义辅助变量
    20. HeroNode cur = head.next;
    21. for (int i = 0; i < (size - index); i++) {//这里注意循环次数
    22. cur = cur.next;
    23. }
    24. return cur;
    25. }
  3. 单链表的反转(腾讯面试题)

    1. //将单链表反转
    2. //1.先定义一个加节点,reverse = new HeroNode();
    3. //2. 从头到尾遍历原来的链表,每遍历一个节点,并放在心的链表的最前端
    4. //3. 最后将原来的head.next = reverse.next
    5. public static void reverseList(HeroNode head) {
    6. //如果当前链表为空或者只有一个节点,则不用进行任何操作
    7. if (head.next == null || head.next.next == null) {
    8. return;
    9. }
    10. //定义一个辅助变量来遍历原单链表
    11. HeroNode cur = head.next;
    12. HeroNode next = null;//指向当前节点的下一个节点,不用的话就会丢失后面的节点
    13. HeroNode reverseHead = new HeroNode(0, "", "");
    14. //遍历原来的链表,实行反转的功能
    15. while (cur != null) {
    16. next = cur.next;//暂时保存当前节点的下一个节点
    17. cur.next =reverseHead.next;
    18. reverseHead.next = cur;//这里注意要将反转列表的头节点指向插入的cur(这里用到了头插法思想)
    19. cur = next;
    20. }
    21. head.next = reverseHead.next;
    22. }
  4. 从尾到头打印单链表(百度面试题,要求方式1:方向遍历;要求方式2:Stack栈)

    1. //从尾到头打印单链表
    2. //1. 逆序打印单链表
    3. //2. 方式1:将单链表反转,再按照顺序打印,但是这样做会破坏原来单链表的结构,不建议这么做
    4. //3. 方式2:可以利用栈这个数据结构,将各个节点压入到栈中,然后利用栈的先进后出的特点,实现逆序打印
    5. //方式2
    6. public static void reversePrint(HeroNode head) {
    7. if (head.next == null) {
    8. System.out.println("链表为空,没有节点信息可打印...");
    9. return;
    10. }
    11. //创建一个栈
    12. Stack<HeroNode> stack = new Stack<>();
    13. HeroNode cur = head.next;
    14. //将每个节点压入栈中
    15. while (cur != null) {
    16. stack.push(cur);
    17. cur = cur.next;
    18. }
    19. //从栈中弹出节点,打印各个节点信息
    20. while (stack.size() > 0) {
    21. System.out.println(stack.pop());
    22. }
    23. }
  5. 合并两个有序的单链表,合并之后的链表依然有序

    1. //合并两个有序的单链表
    2. //1. 基本思想就是哪个单链表的节点序号小就先加入到新的单链表的最后面
    3. //2. 需要考虑的是两个单链表中存在重复的节点,所以在将节点添加到新的单链表的时候需要先判断该节点是否在新单链表中已经存在
    4. /**
    5. *
    6. * @param head1 : 有序单链表1
    7. * @param head2 : 有序单链表2
    8. * @return : 返回的是拼接后的有序单链表
    9. */
    10. public static HeroNode twoToOne(HeroNode head1, HeroNode head2) {
    11. //定义新单链表的头节点
    12. HeroNode newHead = new HeroNode(0,"","");
    13. //若两个单链表都为空,则直接返回一个空的单链表头节点
    14. if (head1.next == null && head2.next == null) {
    15. return newHead;
    16. }
    17. //若单链表1为空,则newHead.next直接指向单链表2,head2.next
    18. if (head1.next == null) {
    19. newHead.next = head2.next;
    20. return newHead;
    21. }
    22. //若单链表2为空,则newHead.next直接指向单链表1,head1.next
    23. if (head2.next == null) {
    24. newHead.next = head1.next;
    25. return newHead;
    26. }
    27. //定义三个辅助变量,分别代替head1、head2和newHead
    28. HeroNode temp1 = head1.next;
    29. HeroNode temp2 = head2.next;
    30. HeroNode newTemp = newHead;
    31. //再带那个一一个变量,来记录上一个存入新单链表的节点的序号,避免重复
    32. int flag = 0;
    33. while (temp1 != null && temp2 !=null) {
    34. //判断下个节点是否与上一个到新单链表的节点是否重复
    35. if (flag == temp1.serialNumber) {
    36. temp1 = temp1.next;
    37. continue;
    38. }
    39. if (flag == temp2.serialNumber) {
    40. temp2 = temp2.next;
    41. continue;
    42. }
    43. //比较两节点当前位置序号的大小,哪个节点小就加入到新链表末尾,并自己后移
    44. if (temp1.serialNumber < temp2.serialNumber) {
    45. newTemp.next = new HeroNode(temp1.serialNumber, temp1.name, temp1.nickName);
    46. flag = temp1.serialNumber;
    47. temp1 = temp1.next;
    48. newTemp = newTemp.next;
    49. } else {
    50. newTemp.next = new HeroNode(temp2.serialNumber, temp2.name, temp2.nickName);
    51. flag = temp2.serialNumber;
    52. temp2 = temp2.next;
    53. newTemp = newTemp.next;
    54. }
    55. }
    56. if (temp1 == null) {
    57. while (temp2 != null) {
    58. if (flag == temp2.serialNumber) {
    59. temp2 = temp2.next;
    60. continue;
    61. }
    62. newTemp.next = new HeroNode(temp2.serialNumber, temp2.name, temp2.nickName);
    63. flag = temp2.serialNumber;
    64. temp2 = temp2.next;
    65. newTemp = newTemp.next;
    66. }
    67. } else {
    68. while (temp1 != null) {
    69. if (flag == temp1.serialNumber) {
    70. temp1 = temp1.next;
    71. continue;
    72. }
    73. newTemp.next = new HeroNode(temp1.serialNumber, temp1.name, temp1.nickName);
    74. flag = temp1.serialNumber;
    75. temp1 = temp1.next;
    76. newTemp = newTemp.next;
    77. }
    78. }
    79. return newHead;
    80. }
  6. 测试代码如下

    1. public static void main(String[] args) {
    2. //进行测试
    3. //先创建节点
    4. HeroNode hero1 = new HeroNode(1, "宋江", "及时雨");
    5. HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟");
    6. HeroNode hero3 = new HeroNode(3, "吴用", "智多星");
    7. HeroNode hero4 = new HeroNode(4, "林冲", "豹子头");
    8. HeroNode hero5 = new HeroNode(3, "吴用", "智多星");
    9. //创建链表
    10. SingleLinkedList singleLinkedList = new SingleLinkedList();
    11. //向链表中加入英雄
    12. singleLinkedList.addByOrder(hero1);
    13. singleLinkedList.addByOrder(hero3);
    14. singleLinkedList.addByOrder(hero4);
    15. singleLinkedList.addByOrder(hero2);
    16. singleLinkedList.addByOrder(hero5);
    17. // //向链表中加入英雄
    18. // singleLinkedList.add(hero1);
    19. // singleLinkedList.add(hero2);
    20. // singleLinkedList.add(hero3);
    21. // singleLinkedList.add(hero4);
    22. //遍历
    23. singleLinkedList.list();
    24. HeroNode newHero2 = new HeroNode(2, "小卢","小麒麟");
    25. //测试修改节点的代码
    26. singleLinkedList.update(newHero2);
    27. System.out.println("修改后再次遍历...");
    28. //遍历
    29. singleLinkedList.list();
    30. singleLinkedList.del(1);
    31. // singleLinkedList.del(4);
    32. // singleLinkedList.del(3);
    33. // singleLinkedList.del(2);
    34. System.out.println("删除后再次遍历...");
    35. //遍历
    36. singleLinkedList.list();
    37. //测试一下,单链表的有效节点数
    38. System.out.println("有效的节点个数:" + getLength(singleLinkedList.getHead()));
    39. //测试一下,是否得到倒数第k个节点
    40. int index = 2;
    41. HeroNode res = findLastIndexNode(singleLinkedList.getHead(), index);
    42. System.out.println("倒数第" + index + "个节点为:" + res);
    43. System.out.println("反转后的单链表:");
    44. reverseList(singleLinkedList.getHead());
    45. singleLinkedList.list();
    46. //直接反序输出链表个节点(栈)
    47. System.out.println("反序输出链表各节点信息(没有改变链表自身的结构):");
    48. reversePrint(singleLinkedList.getHead());
    49. System.out.println("------------------------测试合并两链表------------------------");
    50. SingleLinkedList sll1 = new SingleLinkedList();
    51. SingleLinkedList sll2 = new SingleLinkedList();
    52. SingleLinkedList sll3 = new SingleLinkedList();
    53. sll1.addByOrder(hero1);
    54. sll1.addByOrder(hero3);
    55. sll2.addByOrder(hero2);
    56. sll2.addByOrder(hero4);
    57. System.out.println("链表1:");
    58. sll1.list();
    59. System.out.println("链表2:");
    60. sll2.list();
    61. HeroNode temp = twoToOne(sll1.getHead(), sll2.getHead());
    62. sll3.getHead().next = temp.next;
    63. System.out.println("链表3:");
    64. sll3.list();
    65. }

双向链表

  1. 单链表查找的方向只有一个head指向的方向;在单向链表删除节点的时候往往是找到需要删除节点的前一个节点,而不是自我删除;而双向链表便是优化了的数据结构
  2. 分析一下双向链表的遍历、添加、修改、删除的操作思想
    • 遍历方式:和单链表一样,只是可以向前查找,也可以向后查找
    • 添加方式(默认添加到双向链表的最后):首先通过遍历找到双向链表的最后一个节点,temp.next = newHeroNode;newHeroNode.pre = temp;
    • 修改方式:和单向链表一样,有稍微变动
    • 删除方式:因为是双向链表,因此我们可以实现自我删除某个节点,所以直接找到要删除的节点比如是temp,temp.pre.next = temp.next; temp.next.pre = temp.pre;

  3. 节点类HeroNode代码 ```java package doublylinkedlist;

public class HeroNode {

  1. public int serialNumber;//序号
  2. public String name;//姓名
  3. public String nickName;//昵称
  4. public HeroNode next;//指向下一个节点,默认为null
  5. public HeroNode pre;//指向上一个节点,默认为null
  6. public HeroNode() {
  7. }
  8. public HeroNode(int serialNumber, String name, String nickName) {
  9. this.serialNumber = serialNumber;
  10. this.name = name;
  11. this.nickName = nickName;
  12. }
  13. @Override
  14. public String toString() {
  15. return "HeroNode{" +
  16. "serialNumber=" + serialNumber +
  17. ", name='" + name + '\'' +
  18. ", nickName='" + nickName + '\'' +
  19. '}';
  20. }

}

  1. 4. 链表类DoublyLinkedList 代码
  2. ```java
  3. package doublylinkedlist;
  4. public class DoublyLinkedList {
  5. private HeroNode head = new HeroNode(0, "", "");
  6. public HeroNode getHead() {
  7. return head;
  8. }
  9. //遍历双向链表
  10. public void list() {
  11. //判断该链表是否为空
  12. if (head.next == null) {
  13. System.out.println("链表为空...");
  14. return;
  15. }
  16. //辅助节点
  17. HeroNode temp = head.next;
  18. //实现遍历
  19. while (true) {
  20. //判断链表是否到达了最后
  21. if (temp == null) {
  22. break;
  23. }
  24. //输出节点相关信息
  25. System.out.println(temp);
  26. //将temp指向下一个节点
  27. temp = temp.next;
  28. }
  29. }
  30. //添加,默认添加到链表末尾
  31. public void add(HeroNode heroNode) {
  32. //辅助节点
  33. HeroNode temp = head;
  34. //遍历该链表,找到链表的最后位置
  35. while (true) {
  36. if (temp.next == null) {
  37. break;
  38. }
  39. //如果没有找到,则temp向后移
  40. temp = temp.next;
  41. }
  42. //当退出while循环时,说明temp已经指向了链表最后一个节点
  43. //添加传入的节点到链表最后
  44. temp.next = heroNode;
  45. heroNode.pre = temp;
  46. }
  47. //添加,有序的,从小到大,注意这里的判断顺序!!!
  48. public void addByOrder(HeroNode heroNode) {
  49. //创建一个辅助变量
  50. HeroNode temp = head;
  51. while (true) {
  52. if (heroNode.serialNumber == temp.serialNumber) {
  53. System.out.println("编号为" + heroNode.serialNumber + "的节点已存在,添加失败...");
  54. break;
  55. }
  56. if (heroNode.serialNumber < temp.serialNumber) {
  57. temp.pre.next = heroNode;
  58. heroNode.pre = temp.pre;
  59. heroNode.next = temp;
  60. temp.pre = heroNode;
  61. break;
  62. }
  63. if (temp.next == null) {
  64. temp.next = heroNode;
  65. heroNode.pre = temp;
  66. break;
  67. }
  68. temp = temp.next;
  69. }
  70. }
  71. //修改一个节点的内容
  72. public void update(HeroNode heroNode) {
  73. //判断链表是否为空
  74. if (head.next ==null) {
  75. System.out.println("链表为空...");
  76. return;
  77. }
  78. //找到需要修改的节点,根据节点编号来找,也就是serialNumber
  79. //定义一个辅助变量
  80. HeroNode temp = head.next;
  81. boolean flag = false;//表示是否找到该节点
  82. while (true) {
  83. if (temp == null) {
  84. break;//表示已经到链表的末尾了
  85. }
  86. if (temp.serialNumber == heroNode.serialNumber) {
  87. flag = true;//找到我们要修改的节点了
  88. break;
  89. }
  90. temp = temp.next;//没找到就继续后移
  91. }
  92. //判断flag,是否找到
  93. if (flag) {
  94. temp.name = heroNode.name;
  95. temp.nickName = heroNode.nickName;
  96. } else {
  97. System.out.println("没有找到编号为" + heroNode.serialNumber + "的节点,修改失败...");
  98. }
  99. }
  100. //删除双向链表节点,双向链表是直接找到要删除的节点,不用再找到前一个节点
  101. public void del(int serialNumber) {
  102. //判断该链表是否为空
  103. if (head.next == null) {
  104. System.out.println("链表为空,无法删除...");
  105. return;
  106. }
  107. //定义一个辅助变量
  108. HeroNode temp = head.next;
  109. boolean flag = false;//是否找到了要删除的节点
  110. while(true) {
  111. if (temp == null) {
  112. break;
  113. }
  114. if (temp.serialNumber == serialNumber) {
  115. flag = true;
  116. break;
  117. }
  118. temp = temp.next;
  119. }
  120. if (flag) {
  121. temp.pre.next = temp.next;
  122. if (temp.next != null) {
  123. temp.next.pre = temp.pre;//这里要注意当删除最后一个节点时,就不需要执行这句代码,否则会出现空指针异常
  124. }
  125. } else {
  126. System.out.println("没有找到编号为" +serialNumber + "的节点,删除失败...");
  127. }
  128. }
  129. }
  1. 测试类DoublyLinkedListDemo代码 ```java package doublylinkedlist;

public class DoublyLinkedListDemo {

  1. public static void main(String[] args) {
  2. System.out.println("-------------------------双向链表的测试-------------------------");
  3. //先创建节点
  4. HeroNode hero1 = new HeroNode(1, "宋江", "及时雨");
  5. HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟");
  6. HeroNode hero3 = new HeroNode(3, "吴用", "智多星");
  7. HeroNode hero4 = new HeroNode(4, "林冲", "豹子头");
  8. DoublyLinkedList doublyLinkedList = new DoublyLinkedList();
  9. System.out.println("-------------------------双向链表的添加和遍历测试-------------------------");
  10. doublyLinkedList.add(hero1);
  11. doublyLinkedList.add(hero2);
  12. doublyLinkedList.add(hero3);
  13. doublyLinkedList.add(hero4);
  14. doublyLinkedList.list();
  15. System.out.println("-------------------------双向链表的修改测试-------------------------");
  16. HeroNode newHero4 = new HeroNode(4, "公孙胜", "入云龙");
  17. doublyLinkedList.update(newHero4);
  18. doublyLinkedList.list();
  19. HeroNode newHero5 = new HeroNode(5, "时迁", "偷儿");
  20. doublyLinkedList.update(newHero5);
  21. System.out.println("-------------------------双向链表的删除测试-------------------------");
  22. doublyLinkedList.del(3);

// doublyLinkedList.del(4); doublyLinkedList.list();

  1. doublyLinkedList.del(5);
  2. System.out.println("-------------------------双向链表的有序添加测试-------------------------");
  3. HeroNode hero1_1 = new HeroNode(1, "宋江", "及时雨");
  4. HeroNode hero2_2 = new HeroNode(2, "卢俊义", "玉麒麟");
  5. HeroNode hero3_3 = new HeroNode(3, "吴用", "智多星");
  6. HeroNode hero4_4 = new HeroNode(4, "林冲", "豹子头");
  7. DoublyLinkedList doublyLinkedList2 = new DoublyLinkedList();
  8. doublyLinkedList2.addByOrder(hero3_3);
  9. doublyLinkedList2.addByOrder(hero1_1);
  10. doublyLinkedList2.addByOrder(hero2_2);
  11. doublyLinkedList2.addByOrder(hero3_3);
  12. doublyLinkedList2.addByOrder(hero4_4);
  13. doublyLinkedList2.list();
  14. }

}

  1. <a name="4ac61592"></a>
  2. ### 单向循环链表
  3. 1. Joseph(约瑟夫、约瑟夫环)问题:约瑟夫问题,设编号为1,2,...,n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m的那个人出列,他的下一位又开始由1开始报数,数到m的那个人又出列,依次类推,直到所有人都出列,由此产生一个出队编号的序列。使用单向循环链表完成,也可以使用环形队列实现,但前者更形象
  4. 1. 构建一个单向环形链表的思路
  5. - 先创建第一个节点,让first指向该节点,并形成环状
  6. - 当每创建一个新的节点,就把该节点加入到已有的环形中即可
  7. 3. 遍历环形链表
  8. - 先让一个辅助变量curBoy,指向first节点
  9. - 然后通过一个while循环遍历该环形链表即可,即curBoy.next = first
  10. 4. 根据用户输入,生成一个小孩出圈的顺序
  11. - n:小孩人数;k:从第几个小孩开始报数;m:从1数到m
  12. - 需要创建一个辅助变量helper,事先应该指向环形链表的最后的那个节点
  13. - 小孩报数时,先让first和helper移动k-1次
  14. - 当小孩报数时,让first和helper同时移动m-1次
  15. - 这时就可以将first指向的小孩节点出圈,first = first.next; helper.next = first; 原来first指向的这个节点就没有任何引用了,就会被回收
  16. 5. 节点类Boy代码
  17. ```java
  18. package singlycircularlinkedlist;
  19. public class Boy {
  20. private int no;
  21. private Boy next;
  22. public Boy() {
  23. }
  24. public Boy(int no) {
  25. this.no = no;
  26. }
  27. public int getNo() {
  28. return no;
  29. }
  30. public void setNo(int no) {
  31. this.no = no;
  32. }
  33. public Boy getNext() {
  34. return next;
  35. }
  36. public void setNext(Boy next) {
  37. this.next = next;
  38. }
  39. @Override
  40. public String toString() {
  41. return "Boy{" +
  42. "no='" + no + '\'' +
  43. '}';
  44. }
  45. }
  1. 链表类SinglyCircularLinkedList代码 ```java package singlycircularlinkedlist;

public class SinglyCircularLinkedList {

  1. private Boy first = null;//当前没有编号
  2. public void addBoy(int nums) {
  3. //对nums做一个校验
  4. if (nums < 1) {
  5. System.out.println("nums数据有误...");
  6. return;
  7. }
  8. Boy curBoy = null;//辅助指针帮助构建环形链表
  9. for (int i = 1; i <=nums; i++) {
  10. //根据编号创建小孩节点
  11. Boy boy = new Boy(i);
  12. //如果是第一个小孩
  13. if (i == 1) {
  14. first = boy;
  15. first.setNext(first);//构成环
  16. curBoy = first;
  17. } else {
  18. curBoy.setNext(boy);
  19. boy.setNext(first);
  20. curBoy = boy;
  21. }
  22. }
  23. }
  24. //遍历当前环形链表
  25. public void showBoy() {
  26. //判断链表是否为空
  27. if (first == null) {
  28. System.out.println("没有任何小孩...");
  29. return;
  30. }
  31. //创建辅助变量
  32. Boy curBoy = first;
  33. while (true) {
  34. System.out.println("小孩的编号为" + curBoy.getNo());
  35. if (curBoy.getNext() == first) {//判断是否遍历完毕
  36. break;
  37. }
  38. curBoy = curBoy.getNext();
  39. }
  40. }
  41. //根据用户输入,计算出小孩出圈的顺序
  42. /**
  43. *
  44. * @param startNo 表示从第几个小孩开始数
  45. * @param countNum 表示数到数字几出圈
  46. * @param nums 表示最初圈中的小孩数量
  47. */
  48. public void countBoy(int startNo, int countNum, int nums) {
  49. //先进行数据的校验
  50. if (first == null || startNo < 1 || startNo >nums) {
  51. System.out.println("参数输入有误,请输入有效数据...");
  52. return;
  53. }
  54. //创建一个辅助变量
  55. Boy helper = first;
  56. //先让helper指向最后一个节点
  57. while (true) {
  58. if (helper.getNext() == first) {//判断是否到达最后一个节点
  59. break;
  60. }
  61. helper = helper.getNext();
  62. }
  63. //小孩报数前,先让first和helper移动startNo - 1 次,也就是first移动到开始报数那个小孩节点,helper移动到此时first之前一个节点
  64. for (int i = 0; i < startNo - 1; i++) {
  65. first = first.getNext();
  66. helper = helper.getNext();
  67. }
  68. //当小孩报数时,让first和helper指针同时移动countNum-1次,然后出圈
  69. //这里是一个循环操作,知道圈中只有一个节点
  70. while (true) {
  71. if (helper == first) {//说明圈中只有一个节点
  72. break;
  73. }
  74. //让first和helper指针同时移动countNum-1次,让后出圈
  75. for (int i = 0; i < countNum - 1; i++) {
  76. first = first.getNext();
  77. helper = helper.getNext();
  78. }
  79. //这时first指向的节点就是需要出圈的小孩节点
  80. System.out.println("小孩" + first.getNo() + "出圈");
  81. first = first.getNext();
  82. helper.setNext(first);
  83. }
  84. System.out.println("最后留在圈中的小孩编号为:" + first.getNo());
  85. }

}

  1. 7. 测试类Joseph代码
  2. ```java
  3. package singlycircularlinkedlist;
  4. public class Joseph {
  5. public static void main(String[] args) {
  6. //测试构建与遍历是否存在问题
  7. SinglyCircularLinkedList singlyCircularLinkedList = new SinglyCircularLinkedList();
  8. singlyCircularLinkedList.addBoy(5);//加入5个小孩节点
  9. singlyCircularLinkedList.showBoy();
  10. System.out.println("---------------------------------");
  11. //测试小孩出圈是否存在问题
  12. singlyCircularLinkedList.countBoy(1,2,5);
  13. }
  14. }

3.栈(Stack)

介绍

  1. 栈是一个先进后出(FILO First in Last out)的有序列表
  2. 栈是限制线性表中元素的插入和删除只能在线性表的同一端进行的一种特殊线性表,允许插入和删除的一端,为变化的一端,称为栈顶(top),另外一端为固定的一端,称为栈底(bottom)
  3. 根据栈的定义可知,最先放入栈中的元素在栈底,最后放入的元素在栈顶,而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除
  4. 栈的应用场景
    • 子程序的调用:在跳往子程序前,会先将下个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中
    • 处理递归调用:和子程序的调用类似,只是除了存储下一个指令的地址外,也将参数、区域变量等数据存入堆栈中
    • 表达式的转换(中缀表达式转后缀表达式)与求值
    • 二叉树遍历
    • 图形的深度优先搜索法

数组模拟栈

  1. 思路分析
    • 定义一个top来表示栈顶,初始化为-1
    • 入栈(push)操作,当有数据加入到栈时,top++;stack[top] = data
    • 出栈(pop)操作,当数据要出栈时,int value = stack[top]; top—;return value;
  2. 数组栈类ArrayStack代码 ```java package arraystack;

//定义一个ArrayStark类 表示栈 public class ArrayStack {

  1. private int maxSize;//栈的大小
  2. private int[] stack;//数组,数组模拟栈,数据放在该数组中
  3. private int top = -1;//top表示栈顶,初始化为-1
  4. public ArrayStack() {
  5. }
  6. public ArrayStack(int maxSize) {
  7. this.maxSize = maxSize;
  8. stack = new int[this.maxSize];
  9. }
  10. //栈满
  11. public boolean isFull() {
  12. return top == maxSize-1;
  13. }
  14. //栈空
  15. public boolean isEmpty() {
  16. return top == -1;
  17. }
  18. //入栈
  19. public void push(int value) {
  20. if (isFull()) {
  21. System.out.println("栈满,入栈失败...");
  22. return;
  23. }
  24. top++;
  25. stack[top] = value;
  26. }
  27. //出栈
  28. public int pop() {
  29. if (isEmpty()) {
  30. throw new RuntimeException("栈空,出栈失败...");
  31. }
  32. int value = stack[top];
  33. top--;
  34. return value;
  35. }
  36. //遍历栈,遍历时,需要从栈顶开始显示
  37. public void list() {
  38. if (isEmpty()) {
  39. System.out.println("栈空,无数据...");
  40. }
  41. for (int i = top; i >= 0; i--) {
  42. System.out.println("stack["+ i +"]=" + stack[i]);
  43. }
  44. }

}

  1. 3. 测试类ArrayStackDemo代码
  2. ```java
  3. package arraystack;
  4. import java.util.Scanner;
  5. public class ArrayStackDemo {
  6. public static void main(String[] args) {
  7. //测试ArrayStack是否正确
  8. //先创建一个ArrayStack,表示栈
  9. ArrayStack stack = new ArrayStack(4);
  10. String key = "";
  11. boolean loop = true;//控制是否退出菜单
  12. Scanner input = new Scanner(System.in);
  13. while (loop) {
  14. System.out.println("------------------");
  15. System.out.println("show:显示栈");
  16. System.out.println("push:添加数据到栈");
  17. System.out.println("pop:从栈中取出数据");
  18. System.out.println("exit:退出程序");
  19. System.out.println("------------------");
  20. System.out.println("请输入你要进行的操作:");
  21. key = input.nextLine();
  22. switch (key) {
  23. case "show":
  24. stack.list();
  25. break;
  26. case "push":
  27. System.out.println("请输入一个整数:");
  28. int value = input.nextInt();
  29. input.nextLine();
  30. stack.push(value);
  31. break;
  32. case "pop":
  33. try{
  34. int result = stack.pop();
  35. System.out.println("出栈的数据为:" + result);
  36. } catch (Exception e) {
  37. System.out.println(e.getMessage());
  38. }
  39. break;
  40. case "exit":
  41. input.close();
  42. loop = false;
  43. break;
  44. default:
  45. System.out.println("无效输入,重新输入...");
  46. break;
  47. }
  48. }
  49. System.out.println("退出程序...");
  50. }
  51. }

单链表模拟栈

  1. 思路分析
    • 用单链表来模拟栈第一个想到的是单链表的头插法,再结合单链表栈每次出栈都出头节点head的下一个节点,并将head的next指向head的next的next,实现先进后出
    • 判断栈空(isEmpty):只要head.next为空,即head.next == null为true,则栈为空
    • 判断栈满(isFull):这里我没有设置栈满,主要原因是因为单向链表无长度限制,如果要实现,则可以在SinglyLinkedListStack类中添加一个maxSize属性(通过构造器来设置)来控制,然后再设置一个size属性来获取当前栈的长度,默认为0,每进行一次入栈操作,size++,当然,每次入栈时都要判断size是否等于了maxSize,如果等于栈就已经满了,就入栈失败,然后出栈每次size都要size—
    • 入栈操作(push):使用头插法,node.next = head.next;head.next = node;可以理解为先进入栈的节点,被挤向了后面
    • 出栈操作(pop):每次出栈都出的是head节点的下一个节点,也就是删除head的下一个节点
    • 遍历栈(list):就是单链表的遍历
  2. 节点类Node代码 ```java package singlylinkedliststack;

public class Node {

  1. private int data;
  2. private Node next;
  3. public Node() {
  4. }
  5. public Node(int data) {
  6. this.data = data;
  7. }
  8. public int getData() {
  9. return data;
  10. }
  11. public void setData(int data) {
  12. this.data = data;
  13. }
  14. public Node getNext() {
  15. return next;
  16. }
  17. public void setNext(Node next) {
  18. this.next = next;
  19. }
  20. @Override
  21. public String toString() {
  22. return "Node{" +
  23. "data=" + data +
  24. '}';
  25. }

}

  1. 3. 单链表栈类SinglyLinkedListStack代码
  2. ```java
  3. package singlylinkedliststack;
  4. public class SinglyLinkedListStack {
  5. private Node head = new Node();
  6. public SinglyLinkedListStack() {
  7. }
  8. public Node getHead() {
  9. return head;
  10. }
  11. //判断栈是否为空
  12. public boolean isEmpty() {
  13. return head.getNext() == null;
  14. }
  15. //入栈
  16. public void push(Node node) {
  17. node.setNext(head.getNext());
  18. head.setNext(node);
  19. }
  20. //出栈
  21. public Node pop() {
  22. //栈空就抛出错误
  23. if (isEmpty()) {
  24. throw new RuntimeException("栈空,出栈失败...");
  25. }
  26. //栈不为空就出栈
  27. Node result = head.getNext();
  28. head.setNext(head.getNext().getNext());
  29. return result;
  30. }
  31. public void list() {
  32. //栈空就无数据可打印
  33. if (isEmpty()) {
  34. System.out.println("栈空,无数据...");
  35. return;
  36. }
  37. //栈不为空就打印信息
  38. //辅助变量,方便遍历整个栈
  39. Node temp = head.getNext();
  40. while (true) {
  41. if (temp == null) {
  42. break;
  43. }
  44. System.out.println(temp);
  45. temp = temp.getNext();
  46. }
  47. }
  48. }
  1. 测试类SinglyLinkedListStackDemo代码 ```java package singlylinkedliststack;

public class SinglyLinkedListStackDemo {

  1. public static void main(String[] args) {
  2. //新建一个栈对象
  3. SinglyLinkedListStack singlyLinkedListStack = new SinglyLinkedListStack();
  4. Node node1 = new Node(1);
  5. Node node2 = new Node(2);
  6. Node node3 = new Node(3);
  7. Node node4 = new Node(4);
  8. Node node5 = new Node(5);
  9. System.out.println("------------栈空测试遍历------------");
  10. singlyLinkedListStack.list();
  11. System.out.println("------------node1入栈------------");
  12. singlyLinkedListStack.push(node1);
  13. singlyLinkedListStack.list();
  14. System.out.println("------------node2入栈------------");
  15. singlyLinkedListStack.push(node2);
  16. singlyLinkedListStack.list();
  17. System.out.println("------------node3、node4、node5依次入栈------------");
  18. singlyLinkedListStack.push(node3);
  19. singlyLinkedListStack.push(node4);
  20. singlyLinkedListStack.push(node5);
  21. singlyLinkedListStack.list();
  22. System.out.println("------------第一次出栈------------");
  23. try {
  24. System.out.println("出栈的数据为:" + singlyLinkedListStack.pop().toString());
  25. } catch (Exception e) {
  26. System.out.println(e.getMessage());
  27. }
  28. singlyLinkedListStack.list();
  29. System.out.println("------------第二次出栈------------");
  30. try {
  31. System.out.println("出栈的数据为:" + singlyLinkedListStack.pop().toString());
  32. } catch (Exception e) {
  33. System.out.println(e.getMessage());
  34. }
  35. singlyLinkedListStack.list();
  36. System.out.println("------------将栈中数据出栈完------------");
  37. try {
  38. System.out.println("出栈的数据为:" + singlyLinkedListStack.pop().toString());
  39. System.out.println("出栈的数据为:" + singlyLinkedListStack.pop().toString());
  40. System.out.println("出栈的数据为:" + singlyLinkedListStack.pop().toString());
  41. System.out.println("出栈的数据为:" + singlyLinkedListStack.pop().toString());
  42. } catch (Exception e) {
  43. System.out.println(e.getMessage());
  44. }
  45. singlyLinkedListStack.list();
  46. }

}

  1. <a name="8da66b64"></a>
  2. ### 栈实现综合计算器
  3. 1. 要求:使用栈完成计算一个表达式的结果eg:7*2*2-5+1-5+3-4
  4. 1. 需要使用用两个栈,分别为数栈(存储数)和符号栈(存储运算符)
  5. 1. 思路
  6. - 通过一个index值(索引),来遍历我们的表达式
  7. - 如果扫描发现是一个数字,就直接入数栈
  8. - 如果扫描发现是符号,分以下情况
  9. - 如果发现当前的符号栈为空,就直接入栈
  10. - 如果符号栈有操作符,就进行比较,**如果当前的操作符的优先级小于或者等于栈中的操作符**,这时就需要从数栈中pop出两个数,然后再从符号栈中pop出一个运算符,进行运算,将得到的结果再入数栈,**这里注意!!**,不能只比一次,要清楚有可能栈中有时还有运算符,可能是优先级相等的,需要再次出栈,而在判断优先级大小时需要再次查看运算符栈是否为空,然后还需要将此时的操作符入符号栈;**如果当前的操作符的有限级大于栈中的操作符**,就直接入符号栈
  11. - 当表达式扫描完毕,就顺序地从数栈和符号栈中pop出相应的数和符号,并运行
  12. - 最后在数栈中只有一个数字,就是表达式的结果
  13. - 这里要注意的是除法中被除数不能为0,以及这是整数型的计算器,“/”可能使得计算结果不精确
  14. 4. 栈类ArrayStac代码
  15. ```java
  16. package calculator;
  17. //定义一个ArrayStark类 表示栈
  18. public class ArrayStack {
  19. private int maxSize;//栈的大小
  20. private int[] stack;//数组,数组模拟栈,数据放在该数组中
  21. private int top = -1;//top表示栈顶,初始化为-1
  22. public ArrayStack() {
  23. }
  24. public ArrayStack(int maxSize) {
  25. this.maxSize = maxSize;
  26. stack = new int[this.maxSize];
  27. }
  28. //栈满
  29. public boolean isFull() {
  30. return top == maxSize-1;
  31. }
  32. //栈空
  33. public boolean isEmpty() {
  34. return top == -1;
  35. }
  36. //入栈
  37. public void push(int value) {
  38. if (isFull()) {
  39. System.out.println("栈满,入栈失败...");
  40. return;
  41. }
  42. top++;
  43. stack[top] = value;
  44. }
  45. //出栈
  46. public int pop() {
  47. if (isEmpty()) {
  48. throw new RuntimeException("栈空,出栈失败...");
  49. }
  50. int value = stack[top];
  51. top--;
  52. return value;
  53. }
  54. //查看栈顶元素,并不是真正出栈
  55. public int peek() {
  56. return stack[top];
  57. }
  58. //遍历栈,遍历时,需要从栈顶开始显示
  59. public void list() {
  60. if (isEmpty()) {
  61. System.out.println("栈空,无数据...");
  62. }
  63. for (int i = top; i >= 0; i--) {
  64. System.out.println("stack["+ i +"]=" + stack[i]);
  65. }
  66. }
  67. //返回运算符的优先级,优先级是程序员来确定,优先级使用数字表示
  68. //数字优先级使用数字来表示
  69. public int priority(int operator) {
  70. if (operator == '*' || operator == '/') {
  71. return 1;
  72. } else if (operator == '+' || operator == '-') {
  73. return 0;
  74. } else {
  75. return -1;//假定目前表达式只有+,-,*,/
  76. }
  77. }
  78. //判断是否为一个运算符
  79. public boolean isOperator(char val) {
  80. return val == '+' || val == '-' || val == '*' || val == '/';
  81. }
  82. //计算方法
  83. /**
  84. *
  85. * @param num1 先弹出的那个数
  86. * @param num2 后弹出的那个数
  87. * @param operator 弹出的运算符
  88. * @return 返回的运算结果
  89. */
  90. public int cal(int num1, int num2, int operator) {
  91. int result = 0;
  92. switch (operator) {
  93. case '+':
  94. result = num2 + num1;
  95. break;
  96. case '-':
  97. result = num2 - num1;
  98. break;
  99. case '*':
  100. result = num2 * num1;
  101. break;
  102. case '/':
  103. if (num1 == 0) {
  104. System.out.println("表达式有误,会使被除数为0...");
  105. System.out.println("如下: " + num2 + "/" + num1);
  106. System.exit(0);
  107. }
  108. result = num2 / num1;
  109. break;
  110. }
  111. return result;
  112. }
  113. }
  1. 计算器类Calculator代码 ```java package calculator;

public class Calculator {

  1. public static void main(String[] args) {
  2. //表达式
  3. String expression = "5-3-2*3+1";
  4. //创建数栈
  5. ArrayStack numberStack = new ArrayStack(10);
  6. //创建符号栈
  7. ArrayStack operatorStack = new ArrayStack(10);
  8. //用于扫描
  9. int index = 0;
  10. int number1 = 0;
  11. int number2 = 0;
  12. int operator = 0;
  13. int result = 0;
  14. char ch = ' ';//将每次得到的char保存到ch变量中
  15. String keepNumber = "";//用于拼接多位数
  16. //开始while循环扫描
  17. while(true) {
  18. //一次得到表达式的每一个字符
  19. ch = expression.substring(index,index+1).charAt(0);
  20. //判断是数字还是运算符
  21. if (operatorStack.isOperator(ch)) {//如果是运算符
  22. //判断当前符号栈是否为空
  23. if (!operatorStack.isEmpty()) {
  24. //如果符号栈有操作符,就进行比较,如果当前的操作符的优先级小于或者等于栈中的操作符,
  25. //这时就需要从数栈中pop出两个数,然后再从符号栈中pop出一个运算符,进行运算,将得到的结果再入数栈,然后循环判定是否此时运算符号栈的栈顶优先级,直到为空或者是栈顶优先级小于此时的ch,再将此时的操作符入符号栈
  26. while (true) {
  27. if (operatorStack.isEmpty()) {
  28. operatorStack.push(ch);
  29. break;
  30. }
  31. if (operatorStack.priority(ch) <= operatorStack.priority(operatorStack.peek())) {
  32. //弹出运算需要用到的数据和运算符
  33. number1 = numberStack.pop();
  34. number2 = numberStack.pop();
  35. operator = operatorStack.pop();
  36. //得到运算结果
  37. result = numberStack.cal(number1, number2, operator);
  38. //把得到的结果入栈到数据栈中
  39. numberStack.push(result);
  40. //当前的操作符入符号栈
  41. } else {
  42. operatorStack.push(ch);
  43. break;
  44. }
  45. }
  46. } else {
  47. //如果为空,直接入栈
  48. operatorStack.push(ch);
  49. }
  50. } else { //如果是数
  51. //numberStack.push(Integer.parseInt(ch + ""));
  52. //1. 处理多位数时,不能发现一个数就直接入栈,可能是多位数,也有可能是一位的数
  53. //2. 在入栈时,需要问表达式的index后再看一位,如果是数就进行扫描,如果数运算符,就直接入栈
  54. //3. 需要定义一个字符串变量,用于拼接
  55. //处理多位数
  56. keepNumber += ch;
  57. //如果ch是表达式的最后一位了,就直接入栈
  58. if (index == expression.length() - 1) {
  59. numberStack.push(Integer.parseInt(keepNumber));
  60. //需要重置keepNumber
  61. keepNumber = "";
  62. } else {
  63. //判断下一个字符是不是数字,如果是数字,进行扫描,如不是,将数字入数据栈
  64. //注意index
  65. if (operatorStack.isOperator(expression.substring(index + 1, index + 2).charAt(0))) {
  66. numberStack.push(Integer.parseInt(keepNumber));
  67. //需要重置keepNumber
  68. keepNumber = "";
  69. }
  70. }
  71. }
  72. //index+1,判断是否扫描到表达式的最末端
  73. index++;
  74. if (index == expression.length()) {
  75. break;
  76. }
  77. }
  78. while(true) {
  79. //如果符号栈为空,则计算到了最后的结果,数栈中只有一个数字
  80. if (operatorStack.isEmpty()) {
  81. break;
  82. } else {
  83. //弹出运算需要用到的数据和运算符
  84. number1 = numberStack.pop();
  85. number2 = numberStack.pop();
  86. operator = operatorStack.pop();
  87. //得到运算结果
  88. result = numberStack.cal(number1, number2, operator);
  89. //把得到的结果入栈到数据栈中
  90. numberStack.push(result);
  91. }
  92. }
  93. //显示结果
  94. System.out.println("表达式:" + expression + " = " + numberStack.pop());
  95. }

}

  1. 6. <br />
  2. <a name="7229bf53"></a>
  3. # 非线性结构
  4. 常见的有:二维数组、多维数组、广义表、树结构和图结构
  5. 非线性结构常见的有:**二维数组**、**多维数组**、**广义表**、**树结构**和**图结构**
  6. <a name="ebbdd8cc"></a>
  7. ## 1. 稀疏数组(sparse array)
  8. <a name="61a3ec66-3"></a>
  9. ### 介绍
  10. 当一个数组中大部分元素为0,或者为同一个值的时候,可以使用稀疏数组来保存。(我的理解就是记关键的信息,这样可以节约数据的占用空间)
  11. 稀疏数组的处理方法是:
  12. 1. 记录数组一共有几行几列,有多少个不同的值;
  13. 1. 把具有不同值的元素的行列及值记录在一个小规模的数组中,从而缩小数组的规模。
  14. 例子:一个多行多列的二维数组,第一行记录的是原二维数组的行数、列数和值的个数,除去第一行外,转为稀疏数组将会是第一列为行号,第二列为列号,第三列为前面行号和列号在原二维数组所对应的值。
  15. <a name="5b0520a9"></a>
  16. ### 应用
  17. 五子棋对局保存和加载:
  18. 1. 二维数组转稀疏数组的思路
  19. - 遍历原始二维数组,得到有效数据的个数sum
  20. - 根据sum创建稀疏数组,int[][] sparseArray = new int[sum+1][3]
  21. - 将原始二维数组的数据存入到稀疏数组sparseArray
  22. 2. 稀疏数组转原始的二维数组的思路
  23. - 先读取系数数组的第一行,根据第一行的数据创建原始二维数组,int[][] originalArray = new int[sparseArray[0][0]][sparseArray[0][1]]
  24. - 读取稀疏数组后几行的数据并赋给原始数组
  25. - 最后再将空白的值赋上自己默认的值,一般为0
  26. ```java
  27. public class SparseArray {
  28. public static void main(String[] args) {
  29. //稀疏数组代码实现,五子棋应用
  30. //创建一个原始的二维数组
  31. // 0:无子 1:黑子 2:蓝子
  32. int[][] originalArray = new int[11][11];
  33. originalArray[1][2] = 1;
  34. originalArray[2][3] = 2;
  35. originalArray[5][6] = 2;
  36. //输出原始二维数组
  37. System.out.println("----------原始二维数组----------");
  38. for (int[] row : originalArray) {
  39. for (int data : row) {
  40. System.out.printf("%d ", data);
  41. }
  42. System.out.println();
  43. }
  44. //遍历原始二维数组,得到非0值得个数
  45. System.out.println("----------得到原始数组里不为0的数的个数----------");
  46. int sum = 0;
  47. for (int[] row : originalArray) {
  48. for (int data : row) {
  49. if (data != 0) {
  50. sum++;
  51. }
  52. }
  53. }
  54. System.out.println(sum);
  55. //创建对应的稀疏数组
  56. int[][] sparseArray = new int[sum+1][3];
  57. //给系数数组赋值
  58. sparseArray[0][0] = originalArray.length;
  59. sparseArray[0][1] = originalArray[0].length;
  60. sparseArray[0][2] = sum;
  61. //再次遍历原始二维数组,将非零的值存放在稀疏数组中
  62. int count = 0;//用来记录到了第几个非零值
  63. for (int i = 0; i < originalArray.length; i++) {
  64. for (int j = 0; j < originalArray[i].length; j++) {
  65. if (originalArray[i][j] != 0) {
  66. count++;
  67. sparseArray[count][0] = i;
  68. sparseArray[count][1] = j;
  69. sparseArray[count][2] = originalArray[i][j];
  70. }
  71. }
  72. }
  73. //输出生成的稀疏数组
  74. System.out.println("----------原始二维数组所对应的稀疏数组----------");
  75. for (int[] row : sparseArray) {
  76. for (int data : row) {
  77. System.out.printf("%d\t", data);
  78. }
  79. System.out.println();
  80. }
  81. //将稀疏数组恢复为原始数组
  82. int[][] originalArray2 = new int[sparseArray[0][0]][sparseArray[0][1]];
  83. //遍历稀疏数组为恢复的原始二维数组赋值
  84. for (int i = 1; i < sparseArray.length; i++) {
  85. originalArray2[sparseArray[i][0]][sparseArray[i][1]] = sparseArray[i][2];
  86. }
  87. //恢复后的原始二维数组
  88. System.out.println("----------恢复后的原始二维数组----------");
  89. for (int[] row : originalArray2) {
  90. for (int data : row) {
  91. System.out.printf("%d ", data);
  92. }
  93. System.out.println();
  94. }
  95. }
  96. }