赣州新闻网

首页 / 科技

我理解的架构师

2020-03-28 · 赣州新闻网 · 科技

链表是什么

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(Node)(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域(data),另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间。


线性表的链式存储——算法与数据结构


链表的分类

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


  • 线性表的链式存储结构

    头节点和头指针

    1、头节点指的是链表中的第一个节点,有真实头节点和虚拟头节点。
    1.1 真实头节点:


    线性表的链式存储——算法与数据结构

    真实头节点的第一个节点存有数据
    1.2 虚拟头节点

    线性表的链式存储——算法与数据结构

    虚拟头节点的第一个节点不能存放数据
    2、头指针:存放头节点的地址

    插入元素(头插法)

    头插法就是在虚拟头节点的后一个插入新的元素,每次插入的新元素,都在最前面,最开始虚拟头节点的next指向为NULL。

    线性表的链式存储——算法与数据结构

    当有新的元素进来时,它的指针域将会给新节点的指针域指向一下个。而虚拟头节点的指针域则指向新元素的数据域地址。同时rear(尾指针)指向新插入的元素。

    线性表的链式存储——算法与数据结构

    再插入一个元素B,首先封装成Node节点,然后头节点的next给B的next指向A,然后头节点next再指向新元素。

    线性表的链式存储——算法与数据结构

    可以看出rear(尾指针)依旧指向最开始进来的元素A,所以可知,第一个头插法进来的元素绝对是尾指针。 它指向的元素是尾节点。



    插入元素(尾插法)

    尾插法就是在元素的后面插入一个新元素,然后上一节点的指针域指向新元素,同时新元素的指针域指向下一个元素。

    线性表的链式存储——算法与数据结构


    线性表的链式存储——算法与数据结构

    头插尾插结合


    当插入第一个元素A时,进行头插法,头节点的next指向元素A,A的next指向下一个,同时rear指向A。当插入B元素时,头插,B的next指向A,头节点指向B的地址。插入C是采取尾插,A的next给C的next。同时A的next指向C。rear(尾指针)指向C。头插法插入D时,D的next指向B,头节点的next指向D。

    线性表的链式存储——算法与数据结构

    一般插入

    如图:在B和A之间插入元素C

    线性表的链式存储——算法与数据结构



    删除元素(头删)

    线性表的链式存储——算法与数据结构


    如图:若果想要删除A元素,那么可以让头节点的指针域指向B,但是A的指针域此时也指向B,所以要真正删除A元素,还需要让的指针域指向NULL。

    线性表的链式存储——算法与数据结构


    线性表的链式存储——算法与数据结构

    假设此时只有A一个元素,想要删除掉他的话,头指针和尾指针会怎么变化呢?

    线性表的链式存储——算法与数据结构

    图中可以看出链表中有两个节点一个元素A。如果要删除A的话,head的next指向为空,然后将rear(尾指针重置为0的状态即谁也不指,则已删除元素A)

    线性表的链式存储——算法与数据结构

    删除元素(尾删)

    现在有三个元素A,B,C,对他们依次进行尾删。

    线性表的链式存储——算法与数据结构


    首先删除A元素(size-1),我们可以先将尾指针rear指向C,然后将C的next指向NULL,来删除A,删除C时,将rear指向B,B的next指向NULL,删除B时,rear指向虚拟头节点,然后头节点的next指向NULL。

    线性表的链式存储——算法与数据结构

    删除元素(一般删除)

    线性表的链式存储——算法与数据结构

    在上面三个元素,四个节点中删除C元素,首先要找到C元素的位置,找到之后,让B的next指向A,同时将C的next置为NULL,这样就可以删除C元素了。

    线性表的链式存储——算法与数据结构



    LinkedList实现的也是List接口,所以它得实现List中的方法。

    其中,需要有一个Node类作为内部类,来对元素进行封装,包含它的数据域(data),指针域(next)

    private class Node{
    E data; //数据域
    Node next; //指针域
    public Node(){
    this(null,null);
    }
    public Node(E data,Node next){
    this.data = data;
    this.next = next;
    }

    @Override
    public String toString() {
    return data.toString();
    }
    }

    定义三个变量,head指的是头指针,rear是尾指针,size是其中元素的个数,创建一个无参的构造方法,初始化一个节点,包括它的虚拟头节点,它的尾指针和头指针指向同一个节点对象,此时里面的元素为0

    public class LinkedList<E> implements List<E> {
    //内部类Node,结点信息
    private class Node{
    E data; //数据域
    Node next; //指针域
    public Node(){
    this(null,null);
    }
    public Node(E data,Node next){
    this.data = data;
    this.next = next;
    }

    @Override
    public String toString() {
    return data.toString();
    }
    }

    private Node head; //指向头结点的头指针
    private Node rear; //指向尾结点的尾指针
    private int size; //记录元素的个数

    public LinkedList(){
    head = new Node();
    rear = head;
    size=0;
    }

    public LinkedList(E arr){
    head = new Node();
    rear = head;
    size =0;
    }

    }

    getSize()

    获取链表中的元素个数,可以借助Arrays中的getSize()方法。因为链表也是线性表的一种。

    @Override
    public int getSize() {
    return size;
    }

    isEmpty()

    链表为空时,它的next指向为NULL,同时里面的元素个数为0,所以它的判空条件size=0,head.next=null即如图:

    线性表的链式存储——算法与数据结构

    	@Override
    public boolean isEmpty() {
    return size==0&&head.next==null;
    }

    add(int index,E e)

    首先要对index的位置进行判断是否合法,然后看是头插还是尾插或者是一般插入,当是头插法的时候,将head的next给插入节点的next然后在让head指向node。当元素个数为0的时候,让尾指针指向新的元素。

    public void add(int index, E e) {
    if(index<0||index>size){
    throw new IllegalArgumentException(\"插入角标非法!\");
    }
    //头插
    Node node = new Node(e, null);
    if(index==0){
    node.next = head.next;
    head.next = node;
    rear.next = node;
    }if(size==0){
    rear = node;
    }

    当插入元素是最后一个,则采用尾插法rear.next指向新插入的元素,然后尾指针后移

    else if(index==size){
    rear.next = node;
    rear = rear.next;
    }

    当使用一般插入的时候,首先你要找到你要插入位置的前驱的节点。找到它之后,将它的next给插入的元素然后指向下一个元素,再让它的前驱节点和它连接在一起。

    else{
    Node p = head;
    for(int i=0;i<index;i++){
    p = p.next;
    }
    node.next = p.next;
    p.next = node;
    }
    size++;
    }


    addFirst(E e)

    直接用add(int index,E e)

    public void addFirst(E e){
    add(0,e);
    }

    addLast(E e)

    public void addLast(E e){
    add(size,e);
    }

    get(int index)

    获取在index出的元素值,首先判断index的合法性,当index==0时,直接返回虚拟头节点next里面的data

    如果index= =size-1时,直接返回尾指针里的data。如果是一般情况的话,首先要找到它的前驱节点,然后再获取它里面的data。

    public E get(int index){
    if(index<0||index>=size){
    throw new IllegalArgumentExcepiton(\"查找角标非法\");
    }
    if(index==0){
    return head.next.data;
    }else if(index==size-1){
    return rear.next;
    }else{
    Node p =head;
    for(int i=0;i<=index;i++){
    p = p.next;
    }
    return p.data;
    }
    }

    getFirst()

    public E getFirst(){
    return get(0);
    }

    getLast()

    public E getLast(){
    return get(size-1);
    }

    set(int index,E e)

    将index出的元素的值更新为e,Node o = head 让他找到index的前驱的元素,使p.next指向找到的元素p.data=e就完成了值的更新。

    public void set(int index,E e){
    if(index<0||index>=size){
    throw new IllegalArgumentException(\"index参数不合法\");
    }
    if(index==0){
    head.next.data = e;
    }else if(index==size-1){
    rear.data = e;
    }else{
    Node p = head;
    for(int i=0;i<index;i++){
    p = p.next;
    }
    p.data = e;
    }
    }

    find(E e)

    寻找e的位置,这里没有给具体的位置,所以需要使用到while循环,当Node p = head,循环的条件是当p走到最后一个位置时就寻找结束,即当p.next=null;时跳出循环,p的起始位置是在虚拟头节点出,index=-1,所以它在第一个有效元素的时候,index=index+1;当找到对应的元素条件应满足p.data=e;

    public int find(E e){
    int index = -1;
    if(isEmpty()){
    return -1;
    }
    Node p = head;
    while(p.next!=null){
    p = p.next;
    index++;
    if(p.data=e){
    return index;
    }
    }
    return -1;
    }

    boolean contains(E e)

    public boolean contains(E e){
    return find(e)!=-1;
    }

    remove(int index)

    删除元素,一共有三种情况,第一种是头元素,第二种是尾元素,第三种就是一般删除

    当头删时,Node p 来存放head.next(即删除的元素),然后head.next=p.next将head和删除元素的一下个进行连接,然后断开删除元素和下一个元素的连接并将p置空,尾删:找到删除元素的前一个,然后前驱的next置为null,并将rear指针移动回来,一般删除,创建一个Node p用来移动,当找到元素的前驱时,让前驱的next指向删除元素的next,并将删除元素和下一个元素的连接断开。

    public E remove(int index){
    if(index<-||index>=size){
    throw new IllegalArgumentException(\"index参数不合法\");
    }
    E temp = null; //用来存储删除元素的data
    if(index == 0){
    Node p = head.next;
    temp = p.data;
    head.next = p.next;
    p.next = null;
    p = null;
    if(size==1){
    rear=head;
    }
    }else if(index==size-1){
    Node p = head;
    temp = rear.data;
    while(p.next!=rear){
    p=p.next;
    }
    p.next=null;
    rear = p;
    }else{
    Node p = head;
    for(int i=0;i<index;i++){
    p = p.next;
    }
    Node del = p.next;
    temp = del.data;
    p.next = del.next;
    del.next = null;
    del = null;
    }
    size--;
    return temp;
    }

    removeFirst()

    public E removeFirst(){
    return remove(0);
    }
    123

    removeLast()

    public E removeLast(){
    return removeLast(size-1);
    }

    removeElement(E e)

    通过使用find(e)来找到对应的位置,然后remove(index)

    publc void removeElement(E e){
    int index = find(e);
    if(index==-1){
    throw new IllegalArgumentException(\"元素不存在\");
    }
    remove(index);
    }

    clear()

    清空,只需将head.next置空,让rear和head相同,size=0;

    public void clear(){
    head.next = null;
    rear = head;
    size = 0;
    }

    测试

    public class Main {
    public static void main(String args) {
    LinkedList<Integer> list = new LinkedList<Integer>();
    for (int i = 1; i <= 10; i++) {
    list.addFirst(i);
    }
    System.out.println(list);
    for (int i = 11; i <=15 ; i++) {
    list.addLast(i);
    }
    System.out.println(list);
    System.out.println(list.getFirst());
    System.out.println(list.getLast());
    for (int i = 1; i <= 5 ; i++) {
    list.removeFirst();
    list.removeLast();
    }
    System.out.println(list);
    list.removeFirst();
    list.removeFirst();
    list.removeFirst();
    list.removeFirst();
    list.removeFirst();
    System.out.println(list);
    }
    }

    运行结果


    线性表的链式存储——算法与数据结构


    欢迎访问我的博客

    测试接口信息让他变化2D3D架构会昌