线性表-链表

链表是一种基础数据结构,分为单向链表、双向链表和循环链表。单向链表包含信息域和指针域,添加元素可在头部或尾部,移除元素需找到前后节点。双向链表每个节点有前驱和后继节点,查找元素更灵活。循环链表头尾节点相连。本文详细探讨了这些链表的操作,如添加、移除和查找元素的方法。
摘要由CSDN通过智能技术生成

链表

概论

wikipedia:链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而顺序表相应的时间复杂度分别是O(logn)和O(1)。

链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域(双向链表还有一个指向前驱的指针域)。

链表数据结构主要包含单向链表、双向链表循环链表

单向链表

链表中最简单的一种是单向链表,它包含两个域,一个信息域和一个指针域。这个链接指向列表中的下一个节点,而最后一个节点则指向一个空值。

image
image

/**
 * 单链表数据结构
 *
 * @param <T>
 */
public class SinglyLinkedList<T> {
    //头节点
    private Node head;

    //节点数据结构类
    private class Node {
        //数据域
        T data;
        //指针域,节点引用指向下一个节点
        Node next;
    }
}
添加元素

单链表添加元素有两种方式,第一种链表头部添加,新添加的元素成为链表的head,还有一种添加到链表尾部,新添加元素变成链表最后一个元素。

  //头部添加元素
    public void addHead(T t) {
        //新添加元素的next,指向填表之前头部,当head是null时也没问题,表示新添加的元素就是head后续无元素
        Node node = new Node(t, head);
        //新添加元素变成头部
        head = node;
        //链表长度+1
        size++;
    }
    
    //尾部添加元素
    public void addTail(T t) {
    //当链表是null时,新添加元素变成head
        if (head == null) {
            head = new Node(t, null);
        } else {
            //当链表不为null时,需遍历链表找到最后一个元素,然后next指针指向新添加元素
            Node curNode = head;
            //遍历链表
            while (curNode.next != null) {
                curNode = curNode.next;
            }
            //while循环退出表示找到最后一个元素
            curNode.next = new Node(t, null);
        }
        size++;

    }
移除元素
移除链表中指定索引位置元素
  1. 找到链表中制定位置元素节点的前驱和后继节点
  2. 将指定索引位置元素的前后节点链接起来
  3. 返回移除节点元素值
  T remove(int index) {
        //size=0 空链表
        if (index < 0 || index >= size || size == 0) {
            throw new IndexOutOfBoundsException();
        }
        Node preNode = head;
        //找到index之前的元素
        for (int i = 0; i < index - 1; i++) {
            preNode = preNode.next;
        }
        //获取当前要删除的节点
        Node curNode = preNode.next;
        //要删除元素的前一个节点指向删除元素的下一个节点
        preNode.next = curNode.next;
        size--;
        return curNode.data;
    }

移除链表中指定的元素
  1. 链表为空,直接返回false
  2. 如果删除的是头节点,新的head节点变成之前head.next
  3. 找到需要删除元素的前一个节点和后一个节点,将两者链接起来
 //移除链表中指定的元素
    boolean remove(T t) {
        //空链表直接返回false
        if (head != null) {
            if (t.equals(head.data)) {
                //删除头节点
                head = head.next;
                size--;
                return true;
            } else {
                Node preNode = head;
                Node curNode = preNode.next;
                while (curNode != null) {
                    if (t.equals(curNode.data)) {
                        preNode.next = curNode.next;
                        size--;
                        return true;
                    }
                    //指针往后移动
                    preNode = curNode;
                    curNode = curNode.next;
                }
            }
        }
        return false;
    }
返回指定元素在链表索引

返回指定元素所在链表的索引,元素不存在则返回-1,若存在多个相同元素,则返回第一次出现的索引下标

  //遍历节点判断节点值与目标值是否相等
   public int indexOf(T t) {
        int index = 0;
        for (Node curNode = head; curNode != null; curNode = curNode.next) {
            if (t.equals(curNode.data)) {
                return index;
            }
            index++;
        }
        return -1;
    }

双向链

每个节点有两个连接:一个指向前一个节点,而另一个指向下一个节点。
image
image

数据结构
public class DoublyLinkedList<T> {
    /**
     * head 头节点  tail 尾节点
     */
    private Node head;
    private Node tail;
    /**
     * 链表长度
     */
    private int size;

    public DoublyLinkedList() {
        this.head = null;
        this.tail = null;
    }
    //节点结构
    private class Node {

        /**
         * 前驱节点指针域
         */
        DoublyLinkedList.Node pre;
        /**
         * 数据域
         */
        T data;
        /**
         * 后继节点指针域
         */
        DoublyLinkedList.Node next;

        public Node(Node pre, T data, Node next) {
            this.pre = pre;
            this.data = data;
            this.next = next;
        }
    }
}
查找元素
根据索引位置获取节点
  1. 判断索引位置是否合法,索引位置从0开始
  2. 获取链表的中心点位置,如果index在前半部分,从链表head开始搜索元素,index在后半部分从链表尾部开始搜索元素
    public Node getByIndex(int index) {
        if (index < 0 || index > size - 1) {
            throw new IndexOutOfBoundsException("index is error!!!");
        }
        //根据index与size/2大小比较,如果属于前半区间的位置从head开始搜素,如果属于后半区间位置从tail开始搜索,提高效率
        if (index <= size / 2) {
            //从head开始遍历
            Node curNode = head;
            for (int i = 0; i <= size / 2 && curNode != null; i++, curNode = curNode.next) {
                if (i == index) {
                    return curNode;
                }
            }
        } else {
            //从tail开始遍历
            Node curNode = tail;
            for (int i = size - 1; i > size / 2 && curNode != null; i--, curNode = curNode.pre) {
                if (i == index) {
                    return curNode;
                }
            }
        }
        return null;
    }
查询指定元素在链表中首次出现位置索引,不存在返回-1
  public int indexOf(T t){
      // 从头结点开始搜索
      Node curNode = head;
      for (int i = 0; i < size && curNode != null; i++, curNode = curNode.next) {

          if (curNode.data.equals(t)) {
              return i;
          }

      }
      return -1;
  }

添加元素
头部添加元素
  1. 新增节点,节点的next节点为head
  2. 新节点成为新的head节点
  3. 如果是空链表,tail尾部节点等于头结点
    public void addHead(T t) {
        //新节点next指针指向原来的head
//        Node node = new Node(null, t, head);
//        this.head=node;
        head = new Node(null, t, head);
        if (head == null) {
            //空链表
            tail = head;
        }
        size++;
    }

尾部添加元素
  1. 如果是空链表,新节点成为head节点,尾结点等于head节点
  2. 如果非空链表,创建新节点,新节点的pre节点为原来的tial节点
  3. 尾节点的next指针指向新节点
  4. 新节点成为尾结点
    public void addTail(T t) {
        if (head == null) {
            //空链表
            head = new Node(null, t, null);
            tail = head;
        } else {
            //创建新节点,新节点的pre节点为原来的tial节点
            Node node = new Node(tail, t, null);
            //尾节点的next指针指向新节点
            tail.next = node;
            //新节点成为尾结点
            tail = node;
        }
        size++;
    }
指定位置插入节点
  1. 检测位置合法性
  2. 链表为空,头部插入节点
  3. 链表不为空获取index位置的前一个节点preNode
  4. 获取插入位置的后一个节点nextNode
  5. 创建插入节点,并指定pre,next
  6. 插入位置前一个节点next指向新节点
  7. 插入位置前后一个节点pre指向新节点
    public void insert(T t, int index) {
        if (index < 0 || index > size) {
            throw new IndexOutOfBoundsException("index is error!!!");
        }
        if (head == null) {
            //链表为空
            addHead(t);
        } else {
            //获取index位置节点的前一个节点
            Node preNode = getByIndex(index - 1);
            Node nextNode = preNode.next;
            Node curNode = new Node(preNode, t, nextNode);
            preNode.next = curNode;
            nextNode.pre = curNode;
            size++;
        }
    }
移除元素
移除链表中指定位置元素
  1. 检查索引的合法性,从0开始
  2. 如果删除的是头结点,头结点的下一个节点成为新的头结点
  3. 如果删除的非头结点,获取删除元素的前一个节点
  4. 被删除节点的前一个节点next 指向删除节点下一个节点
  5. 如果删除节点的下一个节点不为null,删除元素的下一个节点的pre指向删除元素的pre节点
  6. 删除节点的pre,next以及删除节点置为null,gc更高效率回收
    public T remove(int index) {
        if (index < 0 || index > size) {
            throw new IndexOutOfBoundsException("index is error!!!");
        }
        Node result = null;
        if (size == 0) {
            result = head;
            //删除head节点
            head = head.next;
            head.pre = null;
        } else {
            //获取删除节点前一个节点
            Node preNode = getByIndex(index - 1);
            Node delNode = preNode.next;
            //被删除节点的前一个节点next 指向删除节点下一个节点。
            preNode.next = delNode.next;
            if (delNode.next != null) {
                delNode.next.pre = preNode;
            }
            delNode.pre = null;
            delNode.next = null;
            result = delNode;
            delNode = null;

        }
        size--;
        return result.data;
    }

移除链表中指定元素
  1. 循环遍历找到需要移除元素的索引
  2. 根据索引移除元素
    public boolean remove(T t) {
        try {
            int i = indexOf(t);
            remove(i);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

循环链表

头节点和尾节点被连接在一起的链表称为循环链表,这种方式在单向和双向链表中皆可实现。
image

image

单向循环链表

单向循环链表,尾节点有next节点指向head。

双向循环链表

双向循环链表,head节点的pre指向尾节点,尾节点next指向head。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值