链表

1、什么是链表

链表是一种用于存储数据集合的数据结构。链表有一下的属性:

  • 相连的元素之间通过指针进行连接
  • 最后一个元素的后继指针为NULL
  • 在执行的过程中链表的长度可以增加或者是减少
  • 链表的空间可以按需进行分配
  • 内有内存空间的浪费,但是链表中的指针需要额外的内存开销
    mark

2、链表的抽象数据类型

链表的主要操作:

  • 插入:插入一个元素到链表中
  • 删除:移除并返回链表中指定位置的元素

链表的辅助操作

  • 删除链表:移除链表中的所有的元素
  • 计数:返回链表中元素的个数
  • 查找:寻找从链表表尾开始的第n个结点

3、为什么需要使用链表

有许多数据结构可以像链表一样做同样的事情。在讨论链表前,首先要了解链表和数组的区别,链表和数组都是可以用于存储数据集合的。由于两者的用途相同,所以需要对他们的的用法进去区别,需要了解什么情况下使用数组,什么情况下使用链表;

4、数组的概述

整个数组在内存中分配一块连续的存储空间。通过使用特定的元素的索引作为数组下标,可以在常数时间内访问数组元素。
mark
数组的特点是:
- 访问速度快(常数时间)
- 简单易用
数组访问的缺点:
- 大小固定:数组的大小是静态的(使用前指定数组的大小)
- 分配一个连续的空间:数组初始化分配空间时,有时无法分配能存储整个数组的内存空间(数组的规模太大)
- 基于位置的插入操作复杂:如果在数组中指定的位置插入元素,则需要移动数组中其他的元素的位置

5、链表的优点

链表的优点是:他们可以在常数时间内扩展。当创建数组的时候,必须分配能存储一定数量的内存,如果向数组中添加跟多的元素,那么必须创建一个新的数组,然后把原数组中的元素复制到新数组中,这将花费大量的时间;
当然,可以通过为数组预先分配一个很大的空间来防止上述的情况的发生。但是这个方法会因为分配超过用户需要的空间而造成内存的浪费。而对于链表,初始时仅需要分配一个元素的存储空间,并且添加新的元素也很容易,不需要做任何内存复制和重新分配的操作

6、链表的缺点

主要体现在访问单个元素的时间开销问题。数组是随机存取的,即存取数组中任意元素的时间开销为O(1)。而链表在最差情况下访问一个元素的开销为O(n)。数组在存取时间方面的另外一个优点是内存的空间局部性。由于数组被定义为连续的内存块,所以任何数组元素与邻居是物理相连的。这极大的得益于CPU的缓存模式。
尽管链表的动态存储分配存储空间有很大的优势,但在存储和检索数据的开销方面却有很大的不足 。有时很难对链表进行操作。如果要删除最后一项,倒数第二项必须更改后继指针值为NULL,这时需要从头开始遍历,找到倒数第二个结点的链接,并设置其后继指针为NULL

7、单链表

链表通常是指向单链表,它包含多个结点,每个结点有一个指向后继元素的next(下一个)指针。表中的最后一个结点的next指针为NULL,表示该链表的结束。
mark
下面是一个链表的声明:

**
 - @Author: River
 - @Date:Created in  16:42 2018/5/28
 - @Description:
 */
public class ListNode {
    private int data;
    private ListNode next;

    public ListNode(int data) {
        this.data=data;
    }

    public int getData() {
        return data;
    }

    public void setData(int data) {
        this.data = data;
    }

    public ListNode getNext() {
        return next;
    }

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

7.1、链表的基本操作

  • 遍历链表
  • 插入元素
  • 删除元素

7.2、链表的遍历

假设表头结点的指针指向链表中的第一个结点。遍历链表需要完成以下步骤。

  • 沿指针遍历
  • 遍历是显示结点的内容(或计数)
  • 当next指针的值为NULL时结束遍历。
    mark
    ListLength()函数的输入为链表,其功能是统计链表中结点的个数。下面是函数的代码,还可以添加额外的输出函数来输出链表中的数据
//统计结点的个数
    int ListLength(ListNode headNode) {
        int length=0;
        ListNode currentNode=headNode;
        while (currentNode!=null) {
            length++;
            currentNode=currentNode.getNext();

        }
        return length;
    }

时间复杂度为O(n),用于扫描长度为n的链表。空间复杂度为O(1),仅仅用于创建临时变量

7.3、单链表的插入操作

  • 在表头插入一个新结点
  • 在表尾插入一个新结点
  • 在链表的中间插入一个结点
7.3.1、在单链表的开头插入结点

若需要在表头插入结点,值需要修改一个next指针(新结点的next指针),可以通过以下的两个步骤:

  • 更新新结点的next指针使其指向头结点
    mark
  • 更新表头指针的值,使其指向新结点
    mark
7.3.2、在单链表的结尾插入结点
  • 新节点的next指针指向null
    mark
  • 最后一个结点的next指针指向新结点
    mark
7.3.3、在单链表的中间插入结点

如果要在位置3增加一个元素,则需要将指针定位于链表的位置2处。即需要从表头开始经过两个结点,然后插入新的结点
mark
位置结点的next指针指向新结点
mark

7.3.4、插入代码如下
 ListNode insertLinkedList(ListNode headNode,ListNode nodeToInsert,int position) {
        if (headNode == null) {
            return nodeToInsert;
        }
        int size = ListLength(headNode);

        if (position > size + 1 || position < 1) {
            System.out.println("position of Node to insert is invalid,The valid inputs are 1 to " + (size + 1));
            return headNode;
        }
        if (position == 1){//在表头插入
            nodeToInsert.setNext(null);
            headNode=nodeToInsert;
            return headNode;
        }else {
            //在表的中间或者末尾
            ListNode previousNode = headNode;
            int count=1;
            while (count < position - 1) {
                previousNode = previousNode.getNext();//找到需要插入位置的前一个结点,也就是位置结点
                count++;
            }
            ListNode currentNode = previousNode.getNext();//这个结点是插入位置的结点,如果position=size+1,则currentNode=null
            nodeToInsert.setNext(currentNode);
            previousNode.setNext(nodeToInsert);
        }
        return headNode;
    }

7.4、单链表的删除

  • 在表头删除一个结点
  • 在表尾删除一个结点
  • 在链表的中间删除一个结点
7.4.1、删除单链表的第一个结点
  • 创建一个临时结点,它指向表头指针指向的结点
    mark
  • 修改表头指针的值,使其指向下一个结点,并移除临时结点
    mark
7.4.2、删除单链表的最后一个结点

-遍历链表,在遍历时还要保存前驱结点的地址。当遍历到链表的表尾时,将有两个指针,分别保存表尾结点的指针tail(表尾)及指向表尾结点的前驱结点的指针
mark
- 将表尾的前驱结点的next指针更新为NULL
mark
- 移除表尾结点
mark

7.4.3、删除单链表的中间一个结点
  • 与上一种情况类似,在遍历时保存前驱(前一次经过的)结点的地址,一旦找到要被删除的结点,将前驱结点的next指针的值更新为被删除的结点的next值
    mark
  • 移除需要删除的结点
    mark
7.4.4、删除代码如下
ListNode DeleteNodeFromLinkedList(ListNode headNode,int position){
        int size=ListLength(headNode);
        if (position > size || position < 1) {
            System.out.println("position of Node to delete is invalid,the valid inputs are 1 to " + (size));
        }
        if (position == 1) {
            ListNode currentNode = headNode.getNext();
            headNode = currentNode;
            return headNode;
        } else {//删除中间或者是表尾结点
            ListNode previousNode=headNode;
            int count=1;
            while (count < position-1) {
                previousNode = previousNode.getNext();
                count++;
            }
            ListNode currentNode = previousNode.getNext();
            previousNode.setNext(currentNode.getNext());
            currentNode=null;
        } 
        return headNode;


    }

7.5、清空单向链表

void DeleteLinkedList(ListNode head) {
        ListNode auxilaryNode,iterator = head;
        while (iterator != null) {
            auxilaryNode = iterator.getNext();
            iterator = null;  //在java中垃圾回收器将自动处理
            iterator =auxilaryNode;
        }
    }

7.6、单向链表总结

单向链表是最常用也是基本的链表结构,需要熟练的掌握单向链表的增删改查等基本的操作

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值