Java链表基础

一.链表概念

链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的.

一个链表如下图所示:

Java链表基础_第1张图片

二.链表的分类

  • 单向链表 双向链表

  • 带头链表 不带头链表

  • 循环的 非循环的

排列组合后我们可以的到八种链表,但是在链表的面试中我们常考的经常是不带头单向非循环链表和Java的集合框架中LinkedList底层实现的不带头双向非循环链表.

三.链表的节点

链表的组成是由一个个节点组成的,节点又是由一个数据域(val)和一个指针域(next)组成的.数据域用来存放你想要存放的数据(根据数据类型),指针域用来存放下一个节点的地址.这样一个个的节点就连接起来了,构成我们所说的链表.

节点的图示如下:

Java链表基础_第2张图片

四.链表的操作(增删改查)

首先我们来看一个五个节点的链表:

Java链表基础_第3张图片

第一个节点,地址是0x673 数据域是1,指针域是0x996 指向了第二个节点的地址

第二个节点,地址是0x996 数据域是4,指针域是0x532 指向了第三个节点的地址

第三个节点,地址是0x532 数据域是5,指针域是0x885 指向了第四个节点的地址

第四个节点,地址是0x885 数据域是7,指针域是0x345 指向了第五个节点的地址

第五个节点,地址是0x345 数据域是6,指针域是null 说明它是最后一个节点,不指向其他的节点

最后还需要定义一个head头节点 地址为0x673

1.创建节点(理解为链表的单位)

节点由数据域(val)和指针域(next)组成,数据域可以根据所需要的的类型定义,但是指针域存放的是下一个节点的地址,故指针域的类型是引用类型(public ListNode next).然后需要提供一个构造方法,对val值进行初始化.

 class ListNode {
        public int val;//数据域
        public ListNode next;//指针域:存放下一个节点的域

        public ListNode(int val){
            this.val=val;
        }
2.创建链表

方法一:使用枚举法(内容简单,但过于繁琐不建议使用)

这种方法首先是创建五个节点出来(以五个为例),然后让第一个节点next指向第二个节点,第二个节点next域指向第三个节点,以此类推把一个个节点串起来构成一个链表.

最后一个节点可以不赋值,因为next域是引用类型,默认值是null.

 public void create(){
        //先创建节点
        ListNode listNode=new ListNode(0);
        ListNode listNode1=new ListNode(1);
        ListNode listNode2=new ListNode(2);
        ListNode listNode3=new ListNode(3);
        ListNode listNode4=new ListNode(4);
        //把节点的next域连接起来
        listNode.next=listNode1;
        listNode1.next=listNode2;
        listNode2.next=listNode3;
        listNode3.next=listNode4;
    }

方法二:头插法

头插法是在链表的头节点之间插入一个节点,首先我们要定义一个要插入的节点(node),然后对node的next域赋值,如果要让node与链表产生联系,它的next域就应该等于头结点(head),最后更新head的位置.

 //头插法
    public void addFirst(int data){
        //首先要创建一个节点,要头插的节点
        ListNode listNode =new ListNode(data);
        //让节点的next等于head,然后把head的位置改变一下
        listNode.next=head;
        head= listNode;
    }

方法三:尾插法

尾插法是在链表的尾节点插入一个节点,首先还是定义一个要插入的节点(node),在尾巴节点插入,这时我们需要找尾巴节点,为了不改变头结点的位置,我们要定义一个代跑的变量(cur)来遍历链表,cur赋值为head头结点,然后如果cur的next不为空我们就一直遍历,如果cur.next域为空了那么我们就找到了最后一个节点,让cur.next=node;

我们还要注意一点就是,head如果为空,那么我们直接让head=node,以免造成空指针异常;

 //尾插法
    public void addLast(int data){
        //尾插也是先要创建一个节点
        ListNode listNode =new ListNode(data);
        ListNode cur=head;
        //判断head为空的情况
        if(head==null){
            head= listNode;
            return;
        }
        while(cur.next!=null){
            cur=cur.next;
        }
        cur.next= listNode;
    }
3.打印链表

通过链表的结构我们可以知道每个节点之间通过next产生联系的,而且我们也知道头结点,所以我们可以根据头结点的移动来遍历这个链表并打印.但是为了保证head头结点的位置不发生改变,我们需要定义一个cur代跑的变量ListNode cur=head;通过cur=cur.next来移动

//打印链表
    public void display(){
        //代跑变量cur
        ListNode cur=head;
        while(cur!=null){
            System.out.print(cur.val+" ");
            cur=cur.next;
        }
        System.out.println();
    }
4.查找一个链表中是否包含key

还是一样我们需要遍历链表,同样定义cur代跑,对比cur.val与key是否相同,如果相同返回true,否则让cur往后走.但是当cur走完还没有返回true,那就说明没有这个key.

如果head为空,就说明是空链表没有任何节点,直接返回true.

 //查找是否包含关键字key是否在单链表当中
    public boolean contains(int key){
        ListNode cur=head;
        if(head==null){
            return false;
        }
        while(cur!=null){
            if(cur.val==key){
                return true;
            }
            cur=cur.next;
        }
        return false;
    }
5.求链表的长度

首先我们可以定义一个计数器count,然后还是遍历链表,代跑cur继续上线.当cur不为空我们就count++,cur往后走一步.

 //得到单链表的长度
    public int size(){
        ListNode cur=head;
        int count=0;
        while(cur!=null){
            count++;
            cur=cur.next;
        }
        return count;
    }
6.在任意位置插入,第一个节点为0号下标

比如我们需要把一个节点插入2号位置.

Java链表基础_第4张图片

根据图解我们可以分析得出插入到2号位置,我们需要把head移动到2号位置的前一个位置.滴滴滴,代跑上线,这时我们还是需要借助cur来移动.找到一个节点之后,定义要插入的节点node.让node.next=cur.next,cur.next=node.

//找到前一个节点的位置
    private ListNode findIndex(int index){
        ListNode cur=head;
        if(head==null){
            return null;
        }
        for (int i = 0; i 
7.删除第一次出现关键字key的节点

首先我们还是要找到要删除的节点的前一个节点,然后使前一个节点的next指向要删除的节点的next域,简而言之,就是使指向跳过要删除的节点.这里还要注意head如果为空的情况,还有head.val正好四是key的情况

如图所示:

Java链表基础_第5张图片
//找到前一个节点
 private ListNode searchPrev(int key) {
        ListNode cur = head;
        while (cur.next != null) {
            if(cur.next.val == key) {
                return cur;
            }
            cur = cur.next;
        }
        return null;//没有你要删除的节点
    }

//删除第一次出现关键字为key的节点 O(N)
    public void remove(int key){
        if(head == null) {
            return ;//一个节点都没有
        }
        if(head.val == key) {
            head = head.next;
            return;
        }
        ListNode cur = searchPrev(key);
        if(cur == null) {
            return;
        }
        ListNode del = cur.next;//要删除的节点
        cur.next = del.next;
    }
8.删除所有关键字为key的节点

很多同学可能会想到把删除第一个出现key节点的函数多次调用就可以实现,但是如果上升到面试的高度,只能遍历一次链表,那该如何解决呢?

我们可以首先定义一个前驱节点(prev)让它等于head,然后定义一个cur等于head的下一个节点(ListNode cur=head.next),当cur!=null的时候我们进入循环 判断cur.val是否等于key,如果等于的话,就让前驱节点的next等于cur.next;如果不等于的话就让prev移动到cur.不管相不相等cur都要往后面遍历.

注意:head节点没有判断,最后还要判断一下head.val是否等于key,等于的话还要把head往后走一步(head=head.next).

Java链表基础_第6张图片
Java链表基础_第7张图片
//删除所有值为key的节点
    public void removeAllKey(int key){
        if(head==null){
            System.out.println("链表为空");
            return;
        }
        ListNode prev=head;
        ListNode cur=head.next;
        while(cur!=null){
            if(cur.val==key){
                prev.next=cur.next;
            }else{
                prev=cur;
            }
            cur=cur.next;
        }
        //判断head节点的val是否等于key
        if(head.val==key){
            head=head.next;
        }
    }
9.清空链表

暴力求解:直接让head为null.

温柔求解:一个节点一个节点的赋值为null

//清空链表
 public void clear(){
        while(head!=null){
            ListNode curNext=head.next;
            head.next=null;
            head=curNext;
        }
    }
10.总结

以上是对链表的基础操作,大部分都是通过遍历链表实现的,学习链表一定要搞清楚next域的指向,不要乱就可以写出来,学习数据结构要多画图,很多不会的题一画图就非常明了.

以上是我对链表基础操作的了解,如有错误,请大家指出.

你可能感兴趣的:(链表,java,数据结构)