算法 中的 链表

链表是一种在物理上非连续、非顺序的数据结构,由若干个节点组成。

链表既然是非连续,非顺序的,它的每一个节点都分布在内存的不同位置,并且依靠着next指针关联起来的,像这样:

 

分类

链表根据结构又被分成了单向链表和双向链表(但下面的举例全部使用单链表)。

单向链表的每一个节点又包含两部分,一部分是存放数据的变量data,另一部分是指向下一个节点的指针next。

    private static class Node{
        int data;            //存放数据的变量data
        Node next;           //下一个节点的指针
        Node(int data){
            this.data = data;
        }
    }

 

双向链表每一个节点除了拥有data和next指针,还拥有指向前置节点的prev指针。

    private static class Node{
        int data;            //存放数据的变量data
        Node previous;       //上一个节点的指针
        Node next;           //下一个节点的指针
        Node(int data){
            this.data = data;
        }
    }

 

内存中的分配方式

箭头代表next指针:


这样的好处是可以灵活的利用零散空间。

 

查找节点

链表的查找节点每次只能从头节点开始,然后根据它的next指针一个个节点开始查找,知道找到想要的那个节点。

比如说我现在要找到画框中的第三个节点:

首先,先找到它的头节点;

然后再根据next指针,找到了第二个节点;

第二个节点还不是想要的,同理再去寻找下一个节点;

到三个节点时候,发现是自己想要的节点。

    /**
     * @param size  链表的实际长度
     * @param index  需要查找的位置(0代表链表中第一个位置,取第一个数)
     * @return temp  节点指针
     * @throws Exception  查找的节点位置超出范围
     */
    public Node get(int index)throws Exception{
        if(index <0 || index>=size) {
            throw new IndexOutOfBoundsException("Out of linked list");
        }
        //找到head节点,将值赋给temp,用于操作temp去向下查找
        Node temp = head;
        for(int i=0; i<index; i++) {
            //把下一个链表赋值给temp变量,如果index=2,那temp就取到了第三个节点,
            //此循环会执行两遍,也就是向后查找了两次(指针向后移动了两次)。
            temp = temp.next;
        }
        return temp;
    }

 

更新结点

更新结点不用考虑很多,找到结点直接替换即可。

现在找到第二个节点,data中是6,现在将它直接覆盖成8就行。

    /**
     *  修改链表的第index(0-based)个位置的元素为e
     * @param index  要更改的位置
     * @param newData  更改为的数据
     */
    public void set(int index, int newData) {
        if (index < 0 || index >= size) {
            throw new IllegalArgumentException("Set failed. Illegal index.");
        }
        Node cur = head.next;
        for (int i = 0; i < index; i++) {
            cur = cur.next;
        }
        //找到位置并替换
        cur.data = newData;
    }

 

插入节点

像数组一样,链表插入元素同样有三种情况。

尾部插入

只需要从尾部直接插入,然后把最后一个节点的next指针指向新插入的节点。

 

头部插入

插入头部节点只需要比尾部插入多一个重新确认头节点的过程。

这就将新节点插入了,并把新插入的节点的next指针指向原来的头节点。

最后再重新确认头节点。

 

中间插入

中间插入的步骤没有数组那么麻烦,依旧是两个步骤。

首先将新节点的next指针指向插入位置的节点。

再把上一个节点的next指针指向新插入的节点。

 

只要想插入,根本不用考虑最多可以插入多少的问题。

    /**
     * 在链表中插入元素
     * @param data 要插入的元素
     * @param index 要插入的位置
     * @param size 链表总长度
     * @throws Exception 超出链表节点范围
     */
    public void insert(int data,int index) throws Exception{
        if(index < 0 || index >= size) {
            throw new IndexOutOfBoundsException("Out of linked list");
        }
        Node insertedNode = new Node(data);
        if(size == 0) {
            //链表实际长度为0,空链表
            head = insertedNode;
            last = insertedNode;
        }else if(index == 0) {
            //插入在链表头部
            insertedNode.next = head;
            head = insertedNode;
        }else if(size == index){
            //插入在链表的尾部
            last.next = insertedNode;
            last = insertedNode;
        }else {
            //插入在链表的中间
            Node previousNode = get(index - 1);//拿到上一个节点
            insertedNode.next = previousNode.next;
            previousNode.next = insertedNode;
        }
        size ++;
    }

 

删除节点

尾部删除

要删除掉最后一个节点,然后把最后的节点的next指针指向null。

 

头部删除

先将头部删除,然后再将头节点重置为next指针指向的下一个节点。

 

中间删除

中间删除要删除节点后,将前置节点的next指针指向要删除节点的下一个节点。

    /**
     * 链表删除元素
     * @param index 删除的位置
     * @param size 链表总长度
     * @return removedNode 删除的元素
     * @throws Exception 超出链表节点范围
     */
    public Node remove(int index)throws Exception{
        if(index < 0 || index >= size) {
            throw new IndexOutOfBoundsException("Out of linked list");
        }
        Node removedNode = null;
        if(index == 0) {
            //删除头节点
            removedNode = head;
            head = head.next;
        }else if(index == size-1) {
            //删除尾节点
            Node previousNode = get(index-1);
            removedNode = previousNode.next;
            previousNode.next = null;
            last = previousNode;
        }else {
            //删除中间节点
            Node previousNode = get(index-1);
            Node nextNode = previousNode.next.next;
            removedNode = previousNode.next;
            previousNode.next = nextNode;
        }
        size--;
        return removedNode;
    }

 

总结

链表的优势在于能灵活地进行插入和删除操作,所以在插入和删除操作较多的场景下,用链表会更合适。

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值