数据结构.链表

1.链表的概念

链表是真正的动态数据结构,栈和队列底层只是依托静态数组,靠resize解决固定容量问题

链表是由一个个结点Node构成,数据存储在Node里面,可以把结点看成一个个对象
在这里插入图片描述

如上图,Node结点(类-----对象)里面有两个属性 e存储元素内容1、2、3 。 next 是下一个Node结点

2.链表的特点

特点,比起前面的数组,数组是连续空间,链表就不一定是了,没有这个要求,不会浪费空间

3.链表的操作

1.添加

1.从头添加:

在这里插入图片描述

2.从中间添加:

在这里插入图片描述在这里插入图片描述

1.删除

在这里插入图片描述

注意:添加删除的重点是找到对应点的前置结点,想想,要删除头结点呢,解决办法就是给头结点加一个虚拟头结点 置为null

3.具体实现

package com.ffyc.datastructure;

import java.util.Random;

//列表

/*删除的时候加一个虚拟头结点  浪费一个空间  删除操作时间复杂度由O(n)变成O(1)*/
public class LinkedList<T extends Comparable<T>>  {


    //1.定义节点相关属性
    //内部类定义节点
    /*节点定义好了  以后向列表中添加一个元素 就是添加一个节点*/
    private  class Node{
        T ele;//节点中的内容
        Node next;//索引  作用是指向下一个节点

        //构造函数初始化  下次添加元素就是添加一个节点
        public Node(T ele){
            this.ele=ele;
        }

        //重写toString  方便后面打印列表而已   重写Ctrl+O
        @Override
        public String toString() {
            return ele.toString();//打印元素内容
        }
    }

    //查看列表中的元素 循环  但不能for循环 不知道多少个节点 定义一个当前节点 用一个索引遍历列表 不断指向下一个  用while循环
    public String showLinkedList(){
        Node cur=head;//从头开始遍历
        StringBuilder sb=new StringBuilder();//把字符串遇到一个加进去遇到一个加进去  最后显示出来 用到缓冲流
        while (cur!=null){//当当前结点遍历到null时遍历完  不为空继续遍历
            sb.append(cur.ele+"--->");//追加
            cur=cur.next;//更新cur  前移
        }
        return sb.toString();
    }


    //2.定义链表相关属性

    Node head;//链表的头
    int size;//链表的长度  即链表中节点的个数

public LinkedList(){//初始化链表
    this.head=null;//头为空
    this.size=0;
}

    //3.添加操作
    //3.1在链表头部添加节点
    public void addHead(T ele){
    /*
    //1.先创建节点

        Node node=new Node(ele);//传入数据 创建节点
   //2.这个节点的next与头结点连接

        node.next=head;
  //3.更新head头结点
        head=node;
    //4.更新列表长度
        this.size++;
*/
        add(0,ele);
    }
    //在列表的尾部添加
    public void addTail(T ele){
    add(this.size,ele);
    }

    //在任意位置添加  需要知道待插入结点的前一个结点pre
    public void add(int index,T ele){



        // 1.边界处理
        //空间是连续的 index不合法就抛异常
        if (index<0||index>this.size){
            throw new IllegalArgumentException("index is invalid!");
        }

        //2.创建节点
        Node node=new Node(ele);
        this.size++;//长度++

        Node dummyHead=new Node(null);//虚拟头结点,即头结点的头结点
        dummyHead.next=head;//把虚拟头结点放到头结点的前面

/*
        //判空  如果链表为空  直接返回
        if(head==null){
            head=node;
            return;
        }*/

        //如果列表中只有头结点  头结点是没有前一个结点的  单独处理 相当于在头结点添加
        /*改进:如果头结点有个虚拟头结点  这样就不用特殊处理了*/
       /*if (index==0){
           node.next=head;
           head=node;//更新头结点
           return;
       }*/


        //3.找到待插入结点的前一个结点
        Node pre=dummyHead;//从头找
        for (int i=0;i<index;i++){//从1  因为0已经是head了
            pre=pre.next;//一个一个往前找  前移
        }

        //4.插入操作
        node.next=pre.next;
        pre.next=node;//不能交换位置  不然后半数据丢失
        head=dummyHead.next;//就不用判断链表为空  //更新head
    }


    /*删除操作*/
    //头删  删除链表头结点
    public T removeHead(){
        if (head==null){
            return null;//直接返回null 下面操作不能再走
        }
        T result= head.ele;//保存要删除的元素  以便等会返回
        head=head.next;
        this.size--;
        return result;
    }


    //尾删  从链表尾结点删除元素  得先遍历 找到尾 需要定义一个当前结点  然后遍历
 public T removeTail(){
        //判空
     if (head==null){
         return null;
     }
   return remove(this.size-1);

   
 }


 //删除任意位置的结点
    public T remove(int index){

        //空间是连续的 index不合法就抛异常
        if (index<0||index>=this.size){
            throw new IllegalArgumentException("index is invalid!");
        }
        //定义一个空结点放在头结点的前面 弥补了头结点没有前置结点的缺点 这样所有位置都可以删
        Node dummyHead = new Node(null);
        dummyHead.next = head;//从头结点的前面开始

        // 找到删除结点的前置结点
        Node pre = dummyHead;//从最头头开始找
        for (int i = 0; i < index; i++) {
            pre = pre.next;
        }
        // 1、拿到删除结点
        Node removeNode = pre.next;
        T result = (T) removeNode.ele;//删除结点的内容存起来


        // 2、删除
        pre.next = removeNode.next;
        removeNode.next = null;
        this.size--;
        // 3、重置头结点  为什么重置  因为你删除的是头结点的话头结点就变了
        head = dummyHead.next;
        return result;
    }




    //获取链表头结点
    public T getHead(){
        if (this.size==0){
            return null;
        }
        return this.head.ele;
        /*相当于  return this.head==null?null:this.head.ele;   */
    }

    //获取链表中最后一个元素  用一个临时指针遍历
    public T getTail(){
        if (head==null){
            return null;
        }
        Node cur=head;
        for (int i=0;i<this.size-1;i++){
            cur=cur.next;
        }
return cur.ele;
    }

    //获取链表中结点的个数
    public int getSize(){
        return this.size;
    }

//判断列表是否为空
    public boolean isEmpty(){
        return this.size==0;//如果size=0 返回true
    }

    //判断指定元素是否存在
    public boolean contains(T ele){
        //说起遍历  就应该想到一个临时的当前结点
        Node cur=head;
        while (cur!=null){//cur遍历到null就结束了 不等于null就继续遍历
            if (cur.ele.compareTo(ele)==0){//让T继承Comparable 就可以用compareTo方法了
                return true;
            }
cur=cur.next;//不相等相互移  跟下一个比
        }
return false;//都比完了还不相等的话  返回false
    }



    @Override
    public String toString() {
    return showLinkedList();
    }

    //根据索引获取指定位置的内容
    public T get(int index){

       if (index <0||index >= this.size){
           throw new IllegalArgumentException("index is invalid!!");
       }
        if (head==null){
            return null;
        }
       //要遍历就要想到添加临时索引  指向当前结点
        Node cur=head;
       for (int i=0;i<index;i++){
           cur=cur.next;
       }
       return cur.ele;
    }

    //修改指定位置的结点内容
    public void set(int index,T ele){
        if (index<0||index>=this.size){
            throw new IllegalArgumentException("index is invalid!!");
        }
        //要遍历就要想到添加临时索引  指向当前结点
        Node cur=head;
        for (int i=0;i<index;i++){
            cur=cur.next;
        }
cur.ele=ele;
    }


    public static void main(String[] args) {
        LinkedList<Integer> linkedList=new LinkedList<>();//创建一个列表  空列表  一个节点都没有

        int count=10;
        Random random=new Random();
        for (int i=0;i<count;i++){
           int ele=random.nextInt(100);
           linkedList.addHead(ele);
        }
        System.out.println(linkedList.showLinkedList());
        linkedList.add(7,666);
        System.out.println(linkedList.showLinkedList());
        linkedList.add(0,888);
        System.out.println(linkedList.showLinkedList());
        //删除索引为0,3,最后一个
        linkedList.remove(0);
        System.out.println(linkedList.showLinkedList());

        linkedList.remove(3);
        System.out.println(linkedList.showLinkedList());

        linkedList.remove(linkedList.size-1);
        System.out.println(linkedList.showLinkedList());
        linkedList.removeTail();
        System.out.println(linkedList.showLinkedList());
        System.out.println("长度"+linkedList.getSize());
        System.out.println("头节点"+linkedList.getHead());
        System.out.println("尾节点"+linkedList.getTail());
        System.out.println("索引为2的结点"+linkedList.get(2));
        System.out.println("链表里有没有30"+linkedList.contains(30));
        System.out.println("链表是不是为空"+linkedList.isEmpty());
        linkedList.set(1,24);
        System.out.println(linkedList.showLinkedList());
    }

}

测试结果:

在这里插入图片描述

4.时间复杂度分析

插入:头插O(1),尾插要遍历到最后面O(n) ,从任意位置就O(n/2)=O(n)
删除:同插入一样
修改:要遍历O(n)
查找:要遍历O(n)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

团团kobebryant

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值