• 【算法】Java-使用数组模拟单向链表,双向链表


    目录

    试题1:实现一个单链表,并实现以下功能:

    试题2:实现一个双链表,并实现以下功能

    思路总结:

    什么情况下可能涉及到用数组实现链表呢?


          在学习时了解到了可以用数组模拟链表,使其兼顾数据查找快,链表新增和删除快的缺点,找来一些试题实现了下,如下:

    试题1:实现一个单链表,并实现以下功能:

    Java代码实现:

    1. import org.apache.commons.lang3.StringUtils;
    2. import java.util.Scanner;
    3. public class ArrayLinkedList {
    4. public static final int N = 100000;
    5. private int head; // head
    6. private int idx; // 存储新元素的索引下标
    7. private int[] e; // 存放数据的数组;
    8. private int[] ne; // 当前节点的下一个节点的地址(数组下标)。比如使用头插法,单向链表,e[5] = 5,e[5]的下一个节点的坐标是ne[5],下一个节点是e[ne[5]]
    9. public ArrayLinkedList() {
    10. e = new int[N];
    11. ne = new int[N];
    12. head = -1;
    13. idx = 0;
    14. }
    15. public void insertToHead(int val) {
    16. e[idx] = val;
    17. ne[idx] = head; // head的值是头结点指向的下一个元素的下标值
    18. head = idx++;
    19. }
    20. /**
    21. * 将val插入到索引k后面
    22. *
    23. * @param k
    24. * @param val
    25. */
    26. public void insert(int k, int val) {
    27. e[idx] = val;
    28. ne[idx] = ne[k];
    29. ne[k] = idx++;
    30. }
    31. /**
    32. * 删除k节点后面的节点(只删一个)
    33. *
    34. * @param k
    35. */
    36. public void remove(int k) {
    37. if (k + 1 == 0) {
    38. head = ne[head];
    39. } else {
    40. ne[k] = ne[ne[k]];
    41. }
    42. }
    43. public static void main(String[] args) {
    44. System.out.println("请输入:");
    45. Scanner scanner = new Scanner(System.in);
    46. ArrayLinkedList arrayLinkedList = new ArrayLinkedList();
    47. int head = arrayLinkedList.head;
    48. int[] e = arrayLinkedList.e;
    49. int[] ne = arrayLinkedList.ne;
    50. while (scanner.hasNextLine()) {
    51. String inputString = scanner.nextLine();
    52. if (StringUtils.isBlank(inputString)) {
    53. break;
    54. }
    55. String[] inputArr = inputString.split(" ");
    56. String f = inputArr[0];
    57. int s = Integer.valueOf(inputArr[1]);
    58. switch (f) {
    59. case "H":
    60. arrayLinkedList.insertToHead(s);
    61. break;
    62. case "D":
    63. arrayLinkedList.remove(s - 1); // 第k个插入的数,idx是k-1
    64. break;
    65. case "I":
    66. int val = Integer.valueOf(inputArr[2]);
    67. arrayLinkedList.insert(s - 1, val);
    68. break;
    69. }
    70. }
    71. for (int i = head; i != -1; i = ne[i]) {
    72. System.out.print(e[i] + " ");
    73. }
    74. scanner.close();
    75. }
    76. }

    试题2:实现一个双链表,并实现以下功能

    Java代码实现:

    1. import org.apache.commons.lang3.StringUtils;
    2. import java.util.Scanner;
    3. /**
    4. * 支持的操作:
    5. * 1、在最左侧插入一个数;
    6. * 2、在最右侧插入一个数;
    7. * 3、将第k个插入的数删除;
    8. * 4、在第k个插入的数左侧插入一个数;
    9. * 5、在第k个插入的数右侧插入一个数
    10. */
    11. public class TwoWayLinkedList2 {
    12. public static final int N = 100000;
    13. // 存放数组的数据
    14. private int[] e;
    15. // 存放左指针下标数组
    16. private int[] l;
    17. // 存放右指针地址数组
    18. private int[] r;
    19. // 数组待存储下标
    20. private int idx;
    21. // e[0]表示链表头;e[1]表示链表尾
    22. // 向左侧插入,就是插入到上一个节点的右侧。所以插入的逻辑都抽象成插入到具体节点的右侧
    23. // 构造方法
    24. public TwoWayLinkedList2() {
    25. e = new int[N];
    26. r = new int[N];
    27. l = new int[N];
    28. r[0] = 1;
    29. l[1] = 0;
    30. idx = 2;
    31. }
    32. /**
    33. * 将节点插入到e[k]节点右侧
    34. *
    35. * @param k
    36. * @param val
    37. */
    38. public void add(int k, int val) {
    39. e[idx] = val;
    40. l[idx] = k;
    41. r[idx] = r[k];
    42. l[r[k]] = idx;
    43. r[k] = idx;
    44. idx++;
    45. }
    46. /**
    47. * 删除e[k]
    48. *
    49. * @param k
    50. */
    51. public void remove(int k) {
    52. r[l[k]] = r[k];
    53. l[r[k]] = l[k];
    54. }
    55. public static void main(String[] args) {
    56. System.out.println("请输入:");
    57. Scanner scanner = new Scanner(System.in);
    58. TwoWayLinkedList2 linkedList = new TwoWayLinkedList2();
    59. int[] e = linkedList.e;
    60. int[] l = linkedList.l;
    61. int[] r = linkedList.r;
    62. while (scanner.hasNextLine()) {
    63. String inputString = scanner.nextLine();
    64. if (StringUtils.isBlank(inputString)) {
    65. break;
    66. }
    67. String[] inputArr = inputString.split(" ");
    68. String f = inputArr[0];
    69. Integer s = Integer.valueOf(inputArr[1]);
    70. switch (f) {
    71. case "L":
    72. linkedList.add(0, s);
    73. break;
    74. case "R":
    75. linkedList.add(l[1], s);
    76. break;
    77. case "D":
    78. linkedList.remove(s + 1); // 第k个插入的数,idx是k+1,因为我们用了0和1表示链表头和链表尾
    79. break;
    80. case "IL":
    81. linkedList.add(l[s + 1], Integer.valueOf(inputArr[2]));
    82. break;
    83. case "IR":
    84. linkedList.add(s + 1, Integer.valueOf(inputArr[2]));
    85. break;
    86. }
    87. }
    88. // 遍历输出
    89. for (int i = r[0]; i != 1; i = r[i]) {
    90. System.out.printf("" + e[i]);
    91. }
    92. scanner.close();
    93. }
    94. }

    思路总结:

           数组实现链表,一个数组用于存放数据,一个数组存放"指针",这里的指针用数组下标代替。如果是双向链表,要用两个数组存放指针。同时要注意首节点和尾结点的记录方法。在实现双链表时,我曾用两个变量表示首尾节点,对比起来,没有用e[0],e[1]表示简洁,而且非常容易搞混。占用第0位和第1位保存链表头和尾时要注意初始的idx=2,第k个插入的元素的索引下标是k+1。大家可以使用更多方法实现,过程虽然曲折,但一顿操作下来,对链表的操作会非常的熟练。

    什么情况下可能涉及到用数组实现链表呢?

            在没有操作系统和内存管理的情况下。

    1. 链表的实现需要动态内存分配和释放,这需要操作系统提供的堆内存管理。没有 OS 的动态内存管理,就无法真正实现链表节点的创建和销毁。

    2. 链表通过指针链接节点,需要操作系统提供的指针和地址引用机制。没有 OS,就无法真正用指针建立节点之间的链接关系。

    3. 数组可以预先分配一块内存,这个内存块可以视为堆内存,用下标代替指针,通过数组操作就可以模拟出指针操作。

    4. 数组是一块连续的内存,空间固定,不需要动态扩展,所以定义数组后直接就可以使用,不依赖动态内存管理。

    5. 数组中的每个元素是连续存储的,通过下标可以直接访问,不需要指针来进行寻址。可以模拟指针的移动,改变指针的指向来实现链表的操作。

            这里我们从算法分析和学习的角度来看这个问题,但不适合实际项目,只能处理规模较小的数据。要实现一个真正的可扩展链表,还需在操作系统上进行动态内存管理。

  • 相关阅读:
    7. Git 仓库创建
    ONLYOFFICE 桌面编辑器 8.1 发布:全新功能齐备的 PDF 编辑器、丰富的幻灯片版式
    近两年激光雷达运动物体分割论文阅读小结
    Java基础之《netty(5)—NIO之Selector》
    pytorch:常见的pytorch参数初始化方法总结
    IVIEW Table/Div 自适应高度
    4.开放-封闭原则
    csp-s2020儒略日
    nacos应用
    mybatis-学习笔记
  • 原文地址:https://blog.csdn.net/WKX18330698534/article/details/132814117