CH4 链表2 - 实现链表

实现链表的一些注意点
注意点

一、当操作链表时,要想明白本次要找的n 前一个还是n的本身,也要根据语义起名字:如previous、currrent、delNode等等
二、注意,如果已经设定了head dummyHead tail等等,需要在new一个Node代替它,然后再操作,不要把他们整没了
三、注意插入的顺序!!! 先动newNode.next ,然后再prev.next 反了会出错的
四、当某个节点要被删除时,nnn.next = null, 而不是 n = null ????

多种写法

二、移动节点或循环节点,使其到达最后一个有效的元素的地址

		//方法一
        for(int i = 1; i < size; i ++) {
            currentNode = currentNode.next;
        }
        //方法二
        Node currentNode = dummyHead.next;
        while (currentNode.next != null) {
            currentNode = currentNode.next;
        }
        //第三种写法
        for (Node current = dummyHead.next; current != null ; current = current.next) {
            res.append(current.e + "-> ");
        }
链表操作复杂度:

添加:
     ~~~~     addFirst O(1)
     ~~~~     addLast O(n)
     ~~~~     add O(n/2) = O(n)
删除
     ~~~~     removeFirst O(1)
     ~~~~     removeLast O(n)
     ~~~~     remove O(n/2) = O(n)

     ~~~~     set O(n)

     ~~~~     get O(n)
     ~~~~     contains O(n)
链表的时间复杂度 增删改查全是O(n),
数组的优势在于如果有索引可以快速访问
但链表的优势在于,如果是针对链表头操作,增和删都是O(1)级别的

链表适合做的事情是不去修改,而且查也不查任意元素,只查链表头,增加删除时只对链表头操作。
由于链表是动态的,不会浪费大量内存,具有诸多优点

本章虽然是最基本的链表结构,但重要性在于,说明链表是基础的真正的动态结构!!!,链表是为了后续学习更重要的数据结构,比如二叉树、平衡二叉树、AVL、红黑树等

链表的改进
使用front 和 tail

实现链表(dummyHead)

关于dummyHead的说明:
     ~~~~     在很多功能操作中,需要找到被变更节点的前一个节点进行操作,这时对head需要单独操作。
为了将所有节点统一处理,可以将head前面弄一个dummyHead,这样一来所有处理都一样了,只要找到前一个节点即可。

LinkedList类中
1、首先创建一个private class Node,私有Node内部类 ,除了LinkedList类其他无法调用
     ~~~~     里边创建public的E e 和 Node node 并构造
2、LinkedList变量
     ~~~~     Node dummyHead int size
3、LinkedList构造 构造初始化一个空链表 dummyHead = new Node(null,null); size = 0;
4、LinkedList中的方法
基本方法:getSize isEmpty toString
功能方法:
增 :
     ~~~~     *add 在“索引”为n的地方添加元素
     ~~~~           ~~~~     如果要删除位置n添加元素,需要知道前面一个元素,这样可以删除
     ~~~~           ~~~~     创建 Node prev = dummyHead; 可以循环找到改n的前一个元素
     ~~~~           ~~~~     不要忘记【删除元素.next】=null 将待删除的元素指向null!!
     ~~~~     *addFirst 复用add
     ~~~~     *addLast 复用add

     ~~~~     *remove在“索引”为n的地方添加元素
     ~~~~           ~~~~     如果要位置n添加元素,需要知道前面一个元素,这样可以插入
     ~~~~           ~~~~     创建 Node prev = dummyHead; 可以循环找到改n的前一个元素
     ~~~~     *removeFirst 复用remove
     ~~~~     *removeLast 复用remove

     ~~~~     *set

     ~~~~     *get getFirst getLast contains

实现链表(head)

LinkedList类中
1、首先创建一个private class Node,私有Node内部类 ,除了LinkedList类其他无法调用
     ~~~~     里边创建public的E e 和 Node node 并构造
2、LinkedList变量
     ~~~~     Node head int size
3、LinkedList构造 构造初始化一个空链表 head = null; size = 0;
4、LinkedList中的方法
     ~~~~     基本方法:getSize isEmpty toString

public class LinkedList<E> {
    //内部类,只能LinkedList内部访问,对外部用户屏蔽底层实现细节
    private class Node{
        public E e;       //做成public,在LinkedList中可以改变和访问
        public Node next; //指向下一个Node的引用地址
        //构造
        public Node(E e, Node next) {
            this.e = e;
            this.next = next;
        }
        public Node(E e) {
            this(e, null);
        }
        public Node() {
            this(null, null);
        }
        @Override
        public String toString() {
            return e.toString();//打印每个节点的内容
        }
    }

    private Node head; //指向链表头部的引用地址
    private int size;  //链表中有效元素个数

    //LinkedList的构造函数,对于空LinkedList,里边都是空
    public LinkedList() {
        head = null;
        size = 0;
    }
    //传入数组,创建到一个链表
    public LinkedList(E[] arrs) {
        head = null;
        for (int i = arrs.length - 1; i >= 0 ; i --) {
            Node node = new Node(arrs[i]);
            node.next = head;
            head = node;
            //另一种写法
//            head = new Node(arrs[i],head);
        }
        size = arrs.length;
    }
    public int getSize() {
        return size;
    }
    public boolean isEmpty() {
        return size == 0;
    }
    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();
        res.append("LinkedList : head ");
        Node currentNode = head;
        while (currentNode != null) {
            res.append(currentNode.e + "-> ");
            currentNode = currentNode.next;
        }
        res.append("null    size:" + getSize());
        return res.toString();
    }

    //增

    //在链表头添加元素 addHead  addFirst
    public void addFirst(E e) {
        Node node = new Node(e);
        node.next = head;
        head = node;
        //另一种写法
        //①new一个新的Node节点对象 ②使其存入e,指向的第一个节点(即head指向的节点) ③移动head,使其指向新做成的节点
        //这里有一个注意点!!!! 对于一个链表,head本身就代表着第一个元素,即head指向的是第一个元素的地址,而head->next就代表了第二个元素
        //head = new Node(e,head);
        size ++;

        //可以直接复用add(e,0);
    }
    public void addLast(E e) {
        Node currentNode = head;
        //移动节点,使其到达最后一个有效的元素的地址
//        for(int i = 1; i < size; i ++) {
//            currentNode = currentNode.next;
//        }
        //另一种写法
        while (currentNode.next != null) {
            currentNode = currentNode.next;
        }
        //添加最后的节点
        Node node = new Node(e);
        currentNode.next = node;
        size ++;

        //可以直接复用add(e,size);
    }

    //在链表中间添加元素
    //在“索引”为n的地方添加元素
    //索引这一思想在链表的实际使用中并不常用,是为了练习四维和面试
    public void add(E e, int index) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("The index need to be between 0 and size.");
        }
        Node node = new Node(e);
        //如果添加位置在0 即头部节点,head是没有prev的,索引这里要特殊处理
        if (index == 0) {
            node.next = head;
            head = node;
        }else {
            Node prev = head;//创建一个指针引用,命名previous,意为将要插入的位置的前一个node,初始位置是head
            for (int i = 1; i < index; i ++) {
                prev = prev.next;
            }
            node.next = prev.next;
            prev.next = node;
        }
        size ++;
    }

    //优化:上一个方法中需要特殊处理head。(因为head没有previous,后边的都有pre,包括最后的null)
    //对于链表头添加和其他位置添加,逻辑上是有区别的
    //优化方法!!!:为head设立一个虚拟的头节点,可以将头部操作和其他操作统一起来

    public void addWithDummyHead(E e, int index) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("Add fail. Illegal index.");
        }

        Node dummyHead = new Node(null, head);//虚拟头节点里不放东西,且指向head

        Node node = new Node(e);
        Node prev = head;//prev代表要找的元素的前一个节点,prev从头节点开始寻找
        for (int i = 1; i < index; i ++) {
            prev = prev.next;
        }
        node.next = prev.next;
        prev.next = node;
        size ++;
    }

    public static void main(String[] args) {
        //测试构造函数传入数组,生成链表的功能
        Integer[] array = {1,2,3};
        LinkedList<Integer> linkedList = new LinkedList<Integer>(array);
        System.out.println(linkedList);
        linkedList.addFirst(4);
        System.out.println(linkedList);
        linkedList.addLast(5);
        System.out.println(linkedList);
        linkedList.add(666,2);
        System.out.println(linkedList);
        linkedList.add(777,0);
        System.out.println(linkedList);
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值