提起链表,很多人可能都会知道它的优势就是能够快速插入、删除数据。但是往链表中插入数据的时间复杂度真的是O(1)吗?相信看完这篇文章,读者会有自己的答案了。

为什么用一节来讲解链表代码实现 ?

1. 链表的代码量虽然不多,但是其中充满了大量的指针操作,所以很考验面试者对指针引用操作的熟练程度。

2. 链表的操作往往需要考虑到对边界问题的考虑,比如在链表是空链表的情况下,代码是否还能正确执行。

这同时考察了一个程序员的编程基本功,也能体现一个程序员在逻辑上是否是足够严谨的,思维是否缜密。所以很面试官在面试的时候都特别青睐询问链表相关的题目。

需要掌握到何种程度

上一节发布之后,很多人私信跟我说: 我已经了解了什么是链表,以及它的特点。难道这就已经足够了吗??代码方面还是写不出来没有关系吗??

链表并不像 红树、图算法那样,复杂到几乎不可能在短短的1、2个小时内写出完整并正确的实现代码。相反,链表的创建、插入和删除结点等操作都只需要20行左右的代码就能实现,所以面试官一般都是直接让应聘者手写链表代码的。

基于这个原因,对于链表的基本操作代码,应聘者必须熟练到如同写 Hello World 的程度。不要仅仅满足于能够实现功能即可,还需要保证代码的准确性以及高运行效

正式进入代码阶段

链表的基本操作

链表的基本操作,基本可以按照下图中从上到下的流程依次来掌握。

基本可以概括为增、删、查找。其中,添加和删除头结点、尾节点的时间复杂度为O(1),查找操作以及向链表中间位置插入节点的时间复杂度则为O(n)。

工欲善其事必先利其器

正所谓巧妇难为无米之炊在开始实现以上几个操作之前,我们必须先实现一个链表的节点Node。有了节点之后,才能展开后续的一些列操作。

实现代码:

class LinkedList
{ Node head;  // 链表中的头节点Node tail;  // 链表中的尾节点/* * 节点类,包含数据域data* 和指向下一个节点的指针next*/class Node { int data; Node next; Node(int d) {data = d;next = null;} }
}

为了操作方便,我们一般会在一个链表中保存一个head引用和一个tail引用,分别用来保存链表的头节点尾节点。这种机制通常被叫做哨兵机制。当初始化链表LinkList时,头节点和尾节点都为null,也就是一个空链表

有了节点之后,就可以来看一下链表基本操作的具体实现了。

先从查找节点开始

   searchNode、nodeAtPosition

因为后续的插入和删除操作会用到这里的代码,所以将查找操作放到前面来讲。查找操作分为两种:searchNodenodeAtPosition,见名知意一个是根据value查找,另一个是根据下标index查找。

searchNode:  查找指定value的节点

比如要在下列链表中,查找是否含有value等于9的节点,如果找到就返回,否则返回null。

我们只要从head节点3开始,依次判断所遍历到的节点value是否等于9,如果不是就继续遍历节点next所指向的下一个节点。

也就是说,查找操作需要遍历链表中的每一个节点进行判断,直到找到相应节点或者遍历到尾结点tail为止。因此时间复杂度为O(n)。

实现代码如下:

/** 查找指定元素,找到了返回节点Node,找不到返回null*/
public Node searchNode(int value){Node current = head;  // 从头结点head开始遍历while(current != null){  // 只有尾节点tail的next指向nullif(value == current.data){return current;  // 找到节点并返回}else{current = current.next;  // 继续遍历下一个节点}}return null;  // 遍历整个链表也没有找到,返回null
}

nodeAtPosition: 查找指定位置index的节点

根据下标查找相对简单一些,只要定义一个循环次数就是index的 while语句 即可,直接贴出代码

1  private Node nodeAtPosition(int index) {
2    verify (index >= 0);  // 伪代码,检验输入index是否有效
3
4    Node result = head;   // 从头节点head开始,取index次next引用的节点
5
6    while (index > 0) {
7      result = result.next;
8      index--;
9    }
10
11    return result;
12  }

接下来插入节点

addLastaddHeadaddNode

addLast

我们调用  addLast(int value) 方法,向链表尾部插入一个元素5。向链表中插入元素时,我们需要判断链表是否为空链表。

1. 如果此时的链表是一个空链表,只需要将被插入元素同时赋值给head和tail节点即可。此时被插入的元素即是头结点,也是尾结点。

2. 如果链表不为空,这种情况头节点head不需要做任何修改,所有的操作都发生在链表的尾部:

2.1 先将尾节点5的next指针指向被插入元素

2.2 然后重新将被插入元素赋值给尾结点tail即可。此时的尾节点变为被插入节点
同样的操作,依次插入9、13之后的链表如下: 

将元素9、13插入到链表中

具体实现代码如下:

void addLast(int value) {Node newNode = new Node(value);if (head == null) { // 空链表head = tail = newNode;} else {  // 非空链表  // 将当前尾节点的next指针指向被插入元素tail.next = newNode;  }tail = newNode;  // 重新将被插入元素赋值给tail节点size++;
}

addHead

我们调用  addHead(int value) 方法,向链表头部插入一个元素。这个操作甚至比  addLast 更加简单,为什么这么说呢,先看下代码:

1void addHead(String value) {
2    Node newNode = new Node(value);
3
4    newNode.next = head;  // 将head赋值给新插入节点的next指针
5    head = newNode;       // 重新将被插入节点赋值给head引用
6    size++;
7}

可以看出,在addHead方法中,甚至都不需要判断链表是否为空链表。因为即使head是null,第4行代码无非就是将被插入节点newNode的next指针指向一个空值null罢了,然后在第5行代码中会重新给head赋值。因此addHead方法不需要判断操作,因为代码很简单,并且同addLast类似,这里就不再贴图占用篇幅了。

addNode(int index, int value)

这是一个比较重量级的操作,意思是向链表中index位置上,插入指定vaule的节点。比如

输入:

 原始链表 3->5->8->10, index = 2, value = 9

则输出

 修改后链表 3->5->9->8->10

基本可以按照以下思路去实现这一操作:

  1. 从0下标开始遍历链表到 position - 1 的位置

  2. 当遍历到 position - 1 的节点(可叫做currentNode)时,根据传入的value创建节点newNode

  3. 将被插入节点newNode的next指针指向当前currentNode的next指针指向的节点

  4. 重新将当前currentNode的next指针指向newNode

 1  /*2   * linked list3   */4  public void addNode(int index, int value) {5    if ((index < 0) || (index > size)) {6      String badIndex =7        new String ("index " + index + " must be between 0 and " + size);8      throw new IndexOutOfBoundsException(badIndex);9    }
10    if (index == 0) {
11      addHead (value);
12    } else {
13      addAfter (nodeAtPosition (index - 1), value);
14    }
15    size++;
16  }

注意:新手可能经常会将步骤3和步骤4的执行顺序给颠倒!导致的后果就是指针丢失

【插图--正常执行顺序和错误执行顺序】

最后删除节点

removeLast、removeHead、removeNode

链表的删除操作同添加操作基本大同小异,遵循的规律也是一样的。所以直接po出代码,就不配图了。

removeNode: 删除指定下标的节点

1/**
2 * 删除指定位置的节点,时间复杂度O(n)
3 */
4public void removeNode(int index){
5    if(index < 0 || index > size){
6        throw new IllegleStateException();
7    }
8    //删除链表中的第一个元素
9    if(index == 0){
10        removeHead();
11    }
12
13    int i=1;
14    Node preNode = head;
15    Node curNode = preNode.next;
16
17    while(curNode != null){
18        if(i == index){
19            preNode.next = curNode.next;
20        }
21        preNode = curNode;
22        curNode = curNode.next;
23        i++;
24    }
25}

removeHead: 删除头节点

1void removeHead() {
2    if (head == null) {
3        String exceptionMsg = new String ("LinkList is empty");
4        throw new IndexOutOfBoundsException(badIndex);
5    }
6    Node temp = head;
7    head = head.next;
8    tmp.next = null;
9    size--;
10}

removeLast: 删除尾节点

因为我们已经实现了删除指定下标的节点,而尾节点的下标就是size - 1,所以直接调用 removeNode(size - 1) 即可

/** 直接调用removeNode,所以时间复杂度也为O(n)*/void removeFirst() {removeNode(size - 1);
}

链表

内容小结

链表的操作无非就是添加、删除、查找操作

01

添加节点

addLast 添加尾节点,时间复杂度O(1)

addHead 添加头节点,时间复杂度O(1)

addNode 添加节点到链表指定位置,时间复杂度O(n)

02

删除结点

removeLast 删除尾节点,时间复杂度O(1)

removeHead 删除头节点,时间复杂度O(1)

removeNode 删除指定位置的节点,时间复杂度O(n)

03

查找节点

searchNode    搜索指定value的节点,时间复杂度O(n)

nodeAtPosition    搜索指定位置的节点,时间复杂度O(n)

链表操作极容易发生指针丢失

应聘者在面试时,如果遇到稍微复杂的链表操作,比如一个经典的链表题--单链表反转(我司必考题),可以尝试列举一个具体的例子,并通过画图来李理清思路,看图写代码就简单多了。

链表的操作需要考虑边界情况

针对这种情况,给应聘者的建议就是对你写出的实现代码提出以下几个问题,如果对于每个问题的回答都是肯定的,那就说明你给出的代码基本考虑到了边界情况。

  1. 如果链表为空时,代码是否能正常工作?

  2. 如果链表只包含一个结点时,代码是否能正常工作?

  3. 如果链表只包含两个结点时,代码是否能正常工作

链表的中级操作

此文主要是列举了链表的几个基本操作,但是在面试中经常会碰到一些更加复杂的链表操作,这里列举几个比较典型的操作,后续会在APP端给出实现思路以及具体实现代码。

1. 单链表反转

2. 链表的倒序打印

3. 找到倒数第k个元素

4. 检测链表是否有环

5. 两个有序链表的合并

扫描二维码, 下载App了解更多

链表插入操作的时间复杂度真的是O(1)吗?相关推荐

  1. 单链表 插入操作 和 删除操作 的易错点

    单链表中的插入操作: Status ListInsert(LinkList &L,int i,Elemtype e){cout<<"在第"<<i&l ...

  2. 从链表中删除数据的时间复杂度真的是O(1)吗?

    本文经授权转载自微信公众号:小争哥(xiaozhengge0822),作者:小争哥 数组和链表作为最基础的数据结构,在面试的时候,经常会被问到.最常被问到的一个问题,那就是,对比一下数组和链表.如果你 ...

  3. namenode的元数据会被删除吗_从链表中删除数据的时间复杂度真的是O(1)吗?

    本文转载自微信:小争哥(xiaozhengge0822),作者:小争哥 数组和链表作为最基础的数据结构,在面试的时候,经常会被问到.最常被问到的一个问题,那就是,对比一下数组和链表.如果你是Java工 ...

  4. 在单链表写入一组数据代码_链表常见操作和15道常见面试题

    什么是单链表 链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer),简单来说链表并不像数组那样 ...

  5. [数据结构]顺序单链表插入

    一,单链表插入操作 [cpp] view plaincopy typedef struct NODE { struct NODE *link; int value; }Node; #include & ...

  6. 单链表的插入操作(全)

    1 在指定位序插入数据 第一步   主要执行操作:查找  先查找所要插入位置的前一个元素  具体方法:根据链表的特点-每一个节点都需要一个数据域和指针域  所以只需从头节点遍历到所要插入数据的的前一个 ...

  7. 单链表中一个插入操作的分析

    首先看链表的结构:p- >节点->节点....(单向),结点包括两部分,值value和指向下一节点的指针link,p为根节点,只有指针域,其类型为指向节点的指针,如要对其进行修改,调用函数 ...

  8. 双链表插入、删除操作单步解析(十四)

    1.双链表定义 单链表只能向后操作,不能向前操作.双链表可以向前和向后操作. 双链表特点:以下图解释 一个前驱指针:ai的前驱指针,指向ai-1结点,即存放ai-1的地址. 数据域:存放数据 一个后驱 ...

  9. 单链表插入、删除操作单步解析(十三)

    1.单链表定义 每个结点包含数据域和指针域,指针域存储下一个结点的地址.  2.插入操作 在第i个结点前面,插入一个e结点. 分析: <1>.s->next = p->next ...

最新文章

  1. python中如何定义一个数组_Python数组定义方法
  2. Python基础之 Django视图和 URL 配置
  3. Android工程中javax annotation Nullable找不到的替代方案
  4. 六、Analysis of quicksort
  5. 用Notepad++来编写第一个HTML网页程序,你也可以!!!
  6. DOM克隆操作(深克隆/浅克隆)
  7. 理想汽车2021年Q4盈利2.955亿元 CTO王凯离职
  8. visual studio支持python吗_微软 Visual Studio Online 更新,更好地支持 Python 语言和 Docker...
  9. Java中几种高性能的队列
  10. java代码表示非空链表整数_Leetcode: Topic 2 给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的.......
  11. 44个基于SaaS的商业智能解决方案
  12. Java实战项目,附带源码+视频教程,收藏!
  13. JQuery-表单验证
  14. 软著申请合作开发协议模板
  15. SREng日志全分析(一)
  16. 【毕业设计】基于单片机的心率血氧健康监测手表 - 物联网 嵌入式
  17. 【实用工具】让文件资源管理器像浏览器一样实现多标签化——QTTabBar
  18. 计算机组成原理、操作系统、数据结构和计算机网络融会贯通
  19. sonix c语言 pdf,松翰c语言(项目)例程(Sonix C language routines (project)).doc
  20. PTA L1-020 帅到没朋友 (20 分)(C++)

热门文章

  1. 流量治理神器 Sentinel的限流模式
  2. (附源码)springboot幼儿园管理系统 毕业设计 160901
  3. 胡伟武老师在讲座上提到的几句警言
  4. OneMO模组说:蜂窝通信模组为何物?
  5. 在Anaconda下安装jieba库
  6. Yocto 编译libsdl2-native 报错
  7. Sublime Text 4 4126 可用
  8. 【MySQL】建立、写入、修改数据实例操作
  9. Visum中导入GIS地图
  10. mybatis中的${} 与 #{}实际使用-模糊查询的几种实现方式