数据结构与算法(三)链表

简介: 数据结构与算法(三)链表

定义

链表通过指针将一组零散的内存块串联在一起。其中,我们把内存块称为链表的“结点”。为了将所有的结点串起来,每个链表的结点除了存储数据之外,还需要记录链上的下一个结点的地址。

特点

  • 不需要连续的内存空间。
  • 有指针引用
  • 三种最常见的链表结构:单链表、双向链表和循环链表

图形

链表.jpeg

从单链表图中,可以发现,有两个结点是比较特殊的,它们分别是第一个结点和最后一个结点。我们一般把第一个结点叫作头结点,把最后一个结点叫作尾结点。

其中,头结点用来记录链表的基地址。有了它,我们就可以遍历得到整条链表。而尾结点特殊的地方是:指针不是指向下一个结点,而是指向一个空地址NULL,表示这是链表上最后一个结点。

  • 双向链表:双向链表有两个指针域,一个指向性上一个节点,一个指向下一个节点,头节点的上一个节点为NULL
  • 循环链表:循环链表是一种特殊的单链表。实际上,循环链表也很简单。它跟单链表唯一的区别就在尾结点。环链表的尾结点指针是指向链表的头结点。

数组和链表的区别

  1. 数组结构使用的是连续的内存空间,可以借助cpu缓存的机制,预读数组中的数据
  2. 链表在内存中并不是连续存储,所以对CPU缓存不友好,没办法有效预读。
  3. 数组的缺点是大小固定,一经声明就要占用整块连续内存空间。如果声明的数组过大,系统可能没有足够的连续内存空间分配给它,导致“内存不足(out of memory)”。如果声明的数组过小,则可能出现不够用的情况。
  4. 动态扩容:数组需再申请一个更大的内存空间,把原数组拷贝进去,非常费时。链表本身没有大小的限制,天然地支持动态扩容。
  5. 时间复杂度:数组的访问为O(1),修改为O(n);链表的访问为O(n),修改为O(1)

这里也不是绝对的,如果数组和链表都是在尾部或者头部进行操作,那数组和链表的访问和修改都是O(1),如栈和队列

手写一个LinkedList

public class MyLinkedList<E> implements MyList<E> {
    private int size;
    private Node<E> first;
    private Node<E> last;
    @Override
    public int size() {
        return size;
    }
    @Override
    public void add(E e) {
        Node<E> newNode = new Node<>(null, e, null);
        if (first == null) {
            first = newNode;
            last = newNode;
            size++;
            return;
        }
        last.next = newNode;
        newNode.pre = last;
        last = newNode;
        size++;
    }
    @Override
    public void add(E e, int index) {
        if (index < 0 || index > size) throw new IndexOutOfBoundsException();
        Node<E> newNode = new Node<>(null, e, null);
        if (index == size) {//加在末尾
            add(e);
            return;
        }
        Node<E> cur = node(index);
        newNode.next = cur;
        if (cur.pre == null) {//头
            cur.pre = newNode;
            first = newNode;
        } else {
            newNode.pre = cur.pre;
            cur.pre.next = newNode;
            cur.pre = newNode;
        }
        size++;
    }
    @Override
    public void remove(int index) {
        if (index < 0 || index > size) throw new IndexOutOfBoundsException();
        if (index == size) {
            last.pre.next = null;
            last = last.pre;
            size--;
            return;
        }
        Node<E> cur = node(index);
        if (cur.pre == null) {
            cur.next.pre = null;
            first = cur.next;
        } else {
            cur.pre.next = cur.next;
            cur.next.pre = cur.pre;
        }
        size--;
    }
    private Node<E> node(int index){
        Node<E> cur = null;
        if (index < (size >> 1)) {//插入位置小于链表的一半
            //从前遍历
            cur = first;
            for (int i = 0; i < index; i++) {
                cur = cur.next;
            }//index=3 插入在第四个位置 cur为第4个链表元素
        } else {
            //从后遍历
            cur = last;
            for (int i = size - 1; i > index; i--) {
                cur = cur.pre;
            }//index=3 插入在第四个位置 cur为第4个链表元素 size =5 last.index=4
        }
        return cur;
    }
    @Override
    public void remove(E e) {
        remove(indexOf(e));
    }
    @SuppressWarnings("unchecked")
    @Override
    public E get(int index) {
        return node(index).element;
    }
    @Override
    public Iterator<E> iterator() {
        return null;
    }
    @Override
    public int indexOf(E e) {
        Node<E> cur = first;
        for (int i = 0 ;i<size;i++){
            if(e.equals(cur.element)){
                return i;
            }
            cur = cur.next;
        }
        return -1;
    }
    private class Node<E> {
        private E element;
        private Node<E> pre;
        private Node<E> next;
        public Node(Node<E> pre, E element, Node<E> next) {
            this.pre = pre;
            this.element = element;
            this.next = next;
        }
    }
目录
相关文章
|
1月前
|
存储 算法 Perl
数据结构实验之链表
本实验旨在掌握线性表中元素的前驱、后续概念及链表的建立、插入、删除等算法,并分析时间复杂度,理解链表特点。实验内容包括循环链表应用(约瑟夫回环问题)、删除单链表中重复节点及双向循环链表的设计与实现。通过编程实践,加深对链表数据结构的理解和应用能力。
55 4
|
2月前
|
算法 索引
❤️算法笔记❤️-(每日一刷-141、环形链表)
❤️算法笔记❤️-(每日一刷-141、环形链表)
51 0
|
25天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
46 5
|
1月前
|
存储 C语言
【数据结构】手把手教你单链表(c语言)(附源码)
本文介绍了单链表的基本概念、结构定义及其实现方法。单链表是一种内存地址不连续但逻辑顺序连续的数据结构,每个节点包含数据域和指针域。文章详细讲解了单链表的常见操作,如头插、尾插、头删、尾删、查找、指定位置插入和删除等,并提供了完整的C语言代码示例。通过学习单链表,可以更好地理解数据结构的底层逻辑,提高编程能力。
89 4
|
1月前
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习之单双链表精题详解(9)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第2.3章之IKUN和I原达人之数据结构与算法系列学习x单双链表精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
1月前
|
存储 Web App开发 算法
2024重生之回溯数据结构与算法系列学习之单双链表【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构之单双链表按位、值查找;[前后]插入;删除指定节点;求表长、静态链表等代码及具体思路详解步骤;举例说明、注意点及常见报错问题所对应的解决方法
|
1月前
|
算法
数据结构之购物车系统(链表和栈)
本文介绍了基于链表和栈的购物车系统的设计与实现。该系统通过命令行界面提供商品管理、购物车查看、结算等功能,支持用户便捷地管理购物清单。核心代码定义了商品、购物车商品节点和购物车的数据结构,并实现了添加、删除商品、查看购物车内容及结算等操作。算法分析显示,系统在处理小规模购物车时表现良好,但在大规模购物车操作下可能存在性能瓶颈。
48 0
|
2月前
|
存储 缓存 算法
经典算法之链表篇(三)
经典算法之链表篇(三)
|
2月前
|
算法
经典算法之链表篇(二)
经典算法之链表篇(二)
|
2月前
|
算法 索引
经典算法之链表篇
经典算法之链表篇