数据结构03 线性表之链表

作者:nnngu
GitHub:https://github.com/nnngu
博客园:http://www.cnblogs.com/nnngu
简书:https://www.jianshu.com/users/1df20d76ea5c
知乎:https://www.zhihu.com/people/nnngu/posts


上一篇总结完了顺序表,这一篇要总结的是线性表之中的链表。我将会从以下几点进行总结:

1、为什么要使用链表?

2、链表的存储结构?

3、链表的常用操作代码实现?

1、为什么要使用链表

通过上一篇的学习,我们知道顺序表存在一些问题,主要有以下两个方面。

1、顺序表的长度是固定的,如果超出分配的长度就会造成溢出,如果存放的数据太少就会造成空间浪费。

2、在插入元素和删除元素时(尤其插入和删除的位置不在尾部时),会移动大量的元素,造成性能和效率低下。

基于以上问题,使用链表可以很好地避免顺序表中出现的问题。这也是我们要使用链表的原因。

2、链表的存储结构

1、从上图可以看出,单链表中的每个节点都包含一个“数据域”和一个“指针域”。“数据域”中包含当前节点的数据,“指针域”中包含下一节点的存储地址。

2、头指针 head 是指向开始节点的,结束节点没有后继节点,所以结束节点的指针域为空,即 null。

3、链表在物理存储结构上不连续,逻辑上连续;大小不固定。

4、根据链表的构造方式的不同可以分为:

  • 单向链表
  • 单向循环链表
  • 双向链表
  • 双向循环链表

2-1、单向链表

链表的每个节点中只包含一个指针域,叫做单向链表(即构成链表的每个节点只有一个指向后继节点的指针)

单向链表中每个节点的结构:

2-2、单向循环链表

单向循环链表和上面讲的单向链表有点相似,都是通过节点的指针指向它的下一个节点,然后这样连接起来,但不同的地方是单向链表的最后一个节点的指针为null,而单向循环链表的最后一个节点的指针指向的是头节点,这样构成了一个循环的节点的环。下面是单向循环链表的示意图:

2-3、双向链表

听名字可能就能猜到双向链表就是链表的节点包含两个指针,一个指针是指向它的下一个节点,另一个指针指向它的上一个节点。这样双向链表就可以通过第一个节点找到最后一个节点,也可以通过最后一个节点找到第一个节点,双向链表的示意图:

2-4、双向循环链表

双向循环链表相对于上面的双向链表多了“循环”两个字,我们就可以结合单向循环链表联想到,其实双向循环链表就是双向链表够成了一个环。双向循环链表的示意图:

3、链表的常用操作

链表常用的操作有:(以单向链表为例)

3-1、插入节点

思路:需要循环查找此节点应该插入的位置。所以时间复杂度为O(n)

示意图:

3-2、删除节点

思路:循环查找要删除的节点的位置,所以时间复杂度为O(n)

示意图:

3-3、查找节点

思路:与插入节点和删除节点的方法类似,需要遍历链表中的节点,所以时间复杂度为O(n)

3-4、获取链表的长度

思路:不像顺序表那样连续存储,获取表的长度非常容易;在链表中,数据不是连续存储的,因此需要循环遍历才能求得链表的长度,所以时间复杂度为O(n)

4、链表的常用操作的代码实现

4-1、插入节点

在代码里面用到了一个变量 position,它的含义如下图所示:

注意:

1、头节点不存储数据,它的数据域为null,它的地址域存储了第1个节点的地址

2、头节点不算进链表的长度,position 从头节点后面的节点开始算起

3、每个节点里面分别有数据域和地址域

下面是具体的实现代码:

先创建一个节点类: Node.java

package com.demo;

/**
 * 节点类
 */
public class Node {
    Object element; // 数据域
    Node next; // 地址域

    // 节点的构造方法
    public Node(Object element, Node next) {
        this.element = element;
        this.next = next;
    }

    // Gettet and Setter
    public Node getNext() {
        return this.next;
    }

    public void setNext(Node next) {
        this.next = next;
    }

    public Object getElement() {
        return this.element;
    }

    public void setElement(Object element) {
        this.element = element;
    }

}

注意每个节点里面分别有数据域和地址域!

定义链表类:MyLinkedList.java

package com.demo;

/**
 * 自己定义的一个链表类
 */
public class MyLinkedList {

    // 头节点
    private Node headNode;
    // 用来遍历链表的临时节点
    private Node tempNode;

    // 链表的初始化方法
    public MyLinkedList() {
        // 初始化时,链表里面只有1个节点,所以这个节点的地址域为null
        Node node = new Node("王重阳", null);
        // 头节点不存储数据,它的数据域为null,它的地址域存储了第1个节点的地址
        headNode = new Node(null, node);
    }

    /**
     * 1、插入节点:时间复杂度为O(n)
     * @param newNode 需要插入的新节点
     * @param position 这个变量的范围可以从0到链表的长度,注意不要越界。
     *                 头节点不算进链表的长度,
     *                 所以从头节点后面的节点开始算起,position为0
     */
    public void insert(Node newNode, int position) {
        // 通过position变量,让tempNode节点从头节点开始遍历,移动到要插入位置的前一个位置
        tempNode = headNode;
        int i = 0;
        while (i < position) {
            tempNode = tempNode.next;
            i++;
        }
        newNode.next = tempNode.next;
        tempNode.next = newNode;
    }

    // 遍历链表,打印出所有节点的方法
    public void showAll() {
        tempNode = headNode.next;
        while (tempNode.next != null) {
            System.out.println(tempNode.element);
            tempNode = tempNode.next;
        }
        System.out.println(tempNode.element);
    }
}

测试类:TestMyLinkedList.java

package com.demo;

public class TestMyLinkedList {
    public static void main(String[] args) {
        MyLinkedList myLinkedList = new MyLinkedList();

        Node newNode1 = new Node("欧阳锋", null);
        Node newNode2 = new Node("黄药师", null);
        Node newNode3 = new Node("洪七公", null);

        myLinkedList.insert(newNode1, 0);
        myLinkedList.insert(newNode2, 0);
        myLinkedList.insert(newNode3, 0);

        myLinkedList.showAll();
    }
}

运行结果:

王重阳是初始化的节点,之后又往链表里插入了3个节点。注意它们插入的位置和打印出来的顺序!

4-2、删除节点

在 MyLinkedList.java 中添加删除节点的方法

    /**
     * 2、删除节点:时间复杂度为O(n)
     * @param position
     */
    public void delete(int position) {
        // 这里同样需要用tempNode从头开始向后查找position
        tempNode = headNode;
        int i = 0;
        while (i < position) {
            tempNode = tempNode.next;
            i++;
        }
        tempNode.next = tempNode.next.next;
    }

测试代码:TestMyLinkedList.java

package com.demo;

public class TestMyLinkedList {
    public static void main(String[] args) {
        MyLinkedList myLinkedList = new MyLinkedList();

        Node newNode1 = new Node("欧阳锋", null);
        Node newNode2 = new Node("黄药师", null);
        Node newNode3 = new Node("洪七公", null);

        myLinkedList.insert(newNode1, 0);
        myLinkedList.insert(newNode2, 0);
        myLinkedList.insert(newNode3, 0);

        myLinkedList.showAll();

        myLinkedList.delete(0);

        System.out.println("------删除之后------");

        myLinkedList.showAll();
    }
}

运行结果:

4-3、查找节点

在 MyLinkedList.java 中添加查找节点的方法

    /**
     * 3、查找节点:时间复杂度为O(n)
     * @param position
     * @return 返回查找的节点
     */
    public Node find(int position) {
        // 这里同样需要用tempNode从头开始向后查找position
        tempNode = headNode;
        int i = 0;
        while (i < position) {
            tempNode = tempNode.next;
            i++;
        }
        return tempNode.next;
    }

测试代码:TestMyLinkedList.java

package com.demo;

public class TestMyLinkedList {
    public static void main(String[] args) {
        MyLinkedList myLinkedList = new MyLinkedList();

        Node newNode1 = new Node("欧阳锋", null);
        Node newNode2 = new Node("黄药师", null);
        Node newNode3 = new Node("洪七公", null);

        myLinkedList.insert(newNode1, 0);
        myLinkedList.insert(newNode2, 0);
        myLinkedList.insert(newNode3, 0);

        myLinkedList.showAll();

        System.out.println("----查找position为2的节点----");

        Node result = myLinkedList.find(2);

        System.out.println(result.element);

    }
}

运行结果:

4-4、获取链表的长度

在 MyLinkedList.java 中添加方法

    /**
     * 4、获取链表的长度:时间复杂度为O(n)
     * @return
     */
    public int size() {
        tempNode = headNode.next;
        int size = 0;
        while (tempNode.next != null) {
            size = size + 1;
            tempNode = tempNode.next;
        }
        size = size + 1; // tempNode的地址域为null时,size记得加上最后一个节点
        return size;
    }

测试代码:TestMyLinkedList.java

package com.demo;

public class TestMyLinkedList {
    public static void main(String[] args) {
        MyLinkedList myLinkedList = new MyLinkedList();

        Node newNode1 = new Node("欧阳锋", null);
        Node newNode2 = new Node("黄药师", null);
        Node newNode3 = new Node("洪七公", null);

        myLinkedList.insert(newNode1, 0);
        myLinkedList.insert(newNode2, 0);
        myLinkedList.insert(newNode3, 0);

        myLinkedList.showAll();

        System.out.println("----------");

        System.out.println("链表的长度为:" + myLinkedList.size());
    }
}

运行结果:

5、总结

1、链表插入和删除一个元素的时间复杂度均为O(n),另外,链表读取一个数据元素的时间复杂度也为O(n)

2、顺序表和单链表的比较:

①顺序表:

优点:主要优点是读取元素的速度较快,以及内存空间利用效率高;

缺点:主要缺点是需要预先给出顺序表的最大元素个数,而这通常很难准确做到。当实际的元素个数超过了预先给出的个数,会发生异常。另外,顺序表插入和删除操作时需要移动较多的数据元素

②单链表:

优点:主要优点是不需要预先给出最大元素个数。另外,单链表插入和删除操作时不需要移动数据元素

缺点:主要缺点是每个节点都需要分为地址域和数据域,因此单链表的空间利用率略低于顺序表。另外,单链表读取一个元素的时间复杂度为O(n) ;而顺序表读取一个元素的时间复杂度为O(1)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容

  • 本文内容取自于小甲鱼的数据结构与算法。http://www.jianshu.com/p/230e6fde9c75 ...
    阿阿阿阿毛阅读 2,870评论 0 7
  • 从数据的逻辑结构来分,数据元素之间存在的关联关系被称为数据的逻辑结构。归纳起来,应用程序中的数据大致哟如下四种基本...
    Jack921阅读 907评论 0 2
  • 一、线性表的顺序存储设计与实现(顺序表) 1.1 顺序存储结构的设计原理概要 顺序存储结构底层是利用数组来实现的,...
    千涯秋瑟阅读 1,413评论 2 4
  • 1.线性表的定义 线性表:零个或多个数据元素的有限序列序列:也就是说元素之间是有顺序的,若元素存在多个,则第一个元...
    e40c669177be阅读 2,027评论 6 15
  • 首先一定要注明:本文不是在抱怨。 以下是正文: 那一年L桑回老家去,回来的列车到站是早晨7点。冬天的早晨,为了...
    damyata阅读 1,316评论 0 12