一问读懂链表结构,及应用场景

Java中的链表结构是指,将一组数据按照指定规则连接起来的数据结构。它由多个节点组成,每个节点包含一个数据元素和一个指向下一个节点的引用。在Java中,这种数据结构被封装成了一个链表类,常见的有单向链表、双向链表和循环链表等。

一、单向链表

单向链表是最简单的一种链表结构,节点只包含一个数据元素和一个指向下一个节点的引用。它的优点是删除和插入节点非常方便,但是定位元素较为困难。

  1. 链表的节点定义

    public class Node {
        // 节点的数据元素
        private int data;
        // 指向下一个节点的引用
        private Node next;
    
        // 构造函数
        public Node(int data) {
            this.data = data;
            this.next = null;
        }
    
        // 获得数据元素
        public int getData() {
            return data;
        }
    
        // 设置数据元素
        public void setData(int data) {
            this.data = data;
        }
    
        // 获得下一个节点的引用
        public Node getNext() {
            return next;
        }
    
        // 设置下一个节点的引用
        public void setNext(Node next) {
            this.next = next;
        }
    }
    
  2. 链表的操作方法

    • 添加节点:向链表的末尾添加一个新节点

      public void addNode(int data) {
          Node newNode = new Node(data);
          if (head == null) {
              head = newNode;
          } else {
              Node currentNode = head;
              while (currentNode.getNext() != null) {
                  currentNode = currentNode.getNext();
              }
              currentNode.setNext(newNode);
          }
          size++;
      }
      
    • 删除节点:删除链表中指定元素的节点

      public boolean removeNode(int data) {
          if (head == null) {
              return false;
          }
          if (head.getData() == data) {
              head = head.getNext();
              size--;
              return true;
          }
          Node currentNode = head;
          while (currentNode.getNext() != null) {
              if (currentNode.getNext().getData() == data) {
                  currentNode.setNext(currentNode.getNext().getNext());
                  size--;
                  return true;
              }
              currentNode = currentNode.getNext();
          }
          return false;
      }
      
    • 查找节点:根据元素值查找节点

      public Node findNode(int data) {
          if (head == null) {
              return null;
          }
          Node currentNode = head;
          while (currentNode != null) {
              if (currentNode.getData() == data) {
                  return currentNode;
              }
              currentNode = currentNode.getNext();
          }
          return null;
      }
      

二、双向链表

双向链表是在单向链表基础上增加了一个指向前一个节点的引用,这样可以方便地实现反向遍历和插入、删除操作。但是相应地,它需要占用更多的存储空间。

  1. 链表的节点定义

    public class Node {
        // 节点的数据元素
        private int data;
        // 指向下一个节点的引用
        private Node next;
        // 指向前一个节点的引用
        private Node prev;
    
        // 构造函数
        public Node(int data) {
            this.data = data;
            this.next = null;
            this.prev = null;
        }
    
        // 获得数据元素
        public int getData() {
            return data;
        }
    
        // 设置数据元素
        public void setData(int data) {
            this.data = data;
        }
    
        // 获得下一个节点的引用
        public Node getNext() {
            return next;
        }
    
        // 设置下一个节点的引用
        public void setNext(Node next) {
            this.next = next;
        }
    
        // 获得前一个节点的引用
        public Node getPrev() {
            return prev;
        }
    
        // 设置前一个节点的引用
        public void setPrev(Node prev) {
            this.prev = prev;
        }
    }
    
  2. 链表的操作方法

    • 添加节点:向链表的末尾添加一个新节点

      public void addNode(int data) {
          Node newNode = new Node(data);
          if (head == null) {
              head = newNode;
          } else {
              Node currentNode = head;
              while (currentNode.getNext() != null) {
                  currentNode = currentNode.getNext();
              }
              currentNode.setNext(newNode);
              newNode.setPrev(currentNode);
          }
          size++;
      }
      
    • 删除节点:删除链表中指定元素的节点

      public boolean removeNode(int data) {
          if (head == null) {
              return false;
          }
          if (head.getData() == data) {
              head = head.getNext();
              if (head != null) {
                  head.setPrev(null);
              }
              size--;
              return true;
          }
          Node currentNode = head;
          while (currentNode != null) {
              if (currentNode.getData() == data) {
                  currentNode.getPrev().setNext(currentNode.getNext());
                  if (currentNode.getNext() != null) {
                      currentNode.getNext().setPrev(currentNode.getPrev());
                  }
                  size--;
                  return true;
              }
              currentNode = currentNode.getNext();
          }
          return false;
      }
      
    • 查找节点:根据元素值查找节点

      public Node findNode(int data) {
          if (head == null) {
              return null;
          }
          Node currentNode = head;
          while (currentNode != null) {
              if (currentNode.getData() == data) {
                  return currentNode;
              }
              currentNode = currentNode.getNext();
          }
          return null;
      }
      

三、循环链表

循环链表是一种特殊的链表结构,它的最后一个节点的指针不是指向null,而是指向第一个节点,形成一个环状结构。这样在循环遍历时可以省去边界判断,但是插入和删除操作需要特殊考虑。

  1. 链表的节点定义

    public class Node {
        // 节点的数据元素
        private int data;
        // 指向下一个节点的引用
        private Node next;
    
        // 构造函数
        public Node(int data) {
            this.data = data;
            this.next = null;
        }
    
        // 获得数据元素
        public int getData() {
            return data;
        }
    
        // 设置数据元素
        public void setData(int data) {
            this.data = data;
        }
    
        // 获得下一个节点的引用
        public Node getNext() {
            return next;
        }
    
        // 设置下一个节点的引用
        public void setNext(Node next) {
            this.next = next;
        }
    }
    
  2. 链表的操作方法

    • 添加节点:向链表的末尾添加一个新节点

      public void addNode(int data) {
          Node newNode = new Node(data);
          if (head == null) {
              head = newNode;
              head.setNext(head);
          } else {
              Node currentNode = head;
              while (currentNode.getNext() != head) {
                  currentNode = currentNode.getNext();
              }
              currentNode.setNext(newNode);
              newNode.setNext(head);
          }
          size++;
      }
      
    • 删除节点:删除链表中指定元素的节点

      public boolean removeNode(int data) {
          if (head == null) {
              return false;
          }
          if (head.getData() == data) {
              Node currentNode = head;
              while (currentNode.getNext() != head) {
                  currentNode = currentNode.getNext();
              }
              head = head.getNext();
              currentNode.setNext(head);
              size--;
              return true;
          }
          Node currentNode = head;
          while (currentNode.getNext() != head) {
              if (currentNode.getNext().getData() == data) {
                  currentNode.setNext(currentNode.getNext().getNext());
                  size--;
                  return true;
              }
              currentNode = currentNode.getNext();
          }
          return false;
      }
      
    • 查找节点:根据元素值查找节点

      public Node findNode(int data) {
          if (head == null) {
              return null;
          }
          Node currentNode = head;
          while (currentNode.getNext() != head) {
              if (currentNode.getData() == data) {
                  return currentNode;
              }
              currentNode = currentNode.getNext();
          }
          if (currentNode.getData() == data) {
              return currentNode;
          }
          return null;
      }
      

四、链表的时间复杂度分析

链表结构相比于数组具有更灵活的插入和删除操作,但是它也存在一些缺点。在访问随机元素时,需要从头开始遍历整个链表,时间复杂度为O(N);而在数组中可以通过索引直接访问元素,时间复杂度为O(1)。下面给出单向链表、双向链表和循环链表的常见操作的时间复杂度:

操作 单向链表 双向链表 循环链表
访问元素 O(N) O(N) O(N)
插入元素 O(1) O(1) O(1)
删除元素 O(N) O(N) O(N)

五、链表的应用举例

链表结构常见于计算机科学领域中的各种数据结构与算法,例如栈、队列、哈希表和图等。下面以一个简单的应用场景为例,说明链表的应用。

  1. 题目描述

    给定一个链表,判断它是否为回文链表。回文链表是指正序和倒序读取的结果相同的链表。

  2. 解题思路

    可以使用快慢指针将链表分成两半,然后将后半部分反转,最后比较前半部分和反转后的后半部分是否相同。

  3. 代码实现

    public boolean isPalindrome(Node head) {
        if (head == null || head.getNext() == null) {
            return true;
        }
        Node slow = head, fast = head;
        while (fast.getNext() != null && fast.getNext().getNext() != null) {
            slow = slow.getNext();
            fast = fast.getNext().getNext();
        }
        Node secondHalf = reverseList(slow.getNext());
        Node currentNode = head;
        while (secondHalf != null) {
            if (currentNode.getData() != secondHalf.getData()) {
                return false;
            }
            currentNode = currentNode.getNext();
            secondHalf = secondHalf.getNext();
        }
        return true;
    }
    
    private Node reverseList(Node head) {
        if (head == null || head.getNext() == null) {
            return head;
        }
        Node prev = null, current = head;
        while (current != null) {
            Node next = current.getNext();
            current.setNext(prev);
            prev = current;
            current = next;
        }
        return prev;
    }
    

六、使用场景

使用场景,常见的应用包括:

  1. 实现栈和队列:链表作为栈或队列的底层数据结构,实现入栈、出栈、入队、出队等操作时具有高效性和灵活性。
  2. 实现哈希表:在哈希表中,每个关键字会被映射到一个桶中,而这个桶则是一个链表,其中存放相同关键字的元素。
  3. 作为缓存的数据结构:在缓存中,经常需要在数据集中插入、删除、移动元素。链表结构提供了一种高效的方式来操作这些数据。
  4. 浏览器历史记录和撤销功能:浏览器历史记录和撤销功能等需要保留一系列操作或状态的应用场景都可以使用链表来实现。
  5. 实现图论算法:在图论中,使用链表结构可以方便地表示邻接表,保存每个节点的所有邻居节点。
  6. 数据库管理系统:在数据库管理系统中,可以使用链表来优化查询操作的效率。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容

  • Today :复习数据结构链表 数据结构之链表对比分析:栈和队列:申请连续空间,顺序存储数据链表:物理上非连续、非...
    司珏少年阅读 342评论 0 1
  • public class JavaTest1 { public static void main(String[]...
    乐百事52淑熙阅读 370评论 0 0
  • 原创者:文思 绪论 为何要学算法? 有个字符串”大中中国 中国我爱 中中国我爱 你中...
    文思li阅读 11,872评论 2 5
  • 链表是以节点的方式存储数据,每个节点包含data域和指向下个节点地址的next域。 相邻的节点内存地址不一定是相邻...
    zzz_0427阅读 107评论 0 1
  • 单链表是什么 【代码文件看末尾】 单链表是一种相对于线性表的一种存储方式,在线性表中我需要对我们的数据大小进行预先...
    whoishower阅读 319评论 0 1