zoukankan      html  css  js  c++  java
  • 02--图解数据结构之单链表实现容器

    链表是一种线性的数据结构
    是一种最简单的动态数据结构

    优点:  动态创建,节省空间  
            头部添加容易  
    缺点:空间上不连续,查找困难。只能从头开始一个一个找
    
    对于单链表:
    链表类Node的成员变量有:T类型的数据和Node类型的节点,并且成员变量node指向下一个节点。
    为了统一节点的操作,通常在链表最前面添加一个虚拟头结点
    
    9414344-15790f6c1f520541.png
    链表.png
    9414344-c2fc778350576d74.png
    一个链表.png
    我想再强调一下数据结构的作用是为数据寻找一个合适的载体,让数据的操作更加快捷。
    Node只不过是一个辅助工具,并不会暴露在外。它与数据相对应,又使数据按链式排布,
    操纵节点也就等于操纵数据,就像提线木偶,我们操作的是"观众"所看不见的线,而不是木偶的各个肢体本身。
    

    一、单链表实现集合:SingleLinkedGroup

    1.节点类的设计
     /**
      * 内部私有节点类
      * @param <T>
      */
     private class Node<T>{
         /**
          * 节点数据元素
          */
         public T el;
         
         /**
          * 下一节点的引用
          */
         public Node next;
         
        public Node(T el, Node next) {
            this.el = el;
            this.next = next;
        }
        
        @Override
        public String toString() {
            return el.toString();
        }
     }
    
    2.SingleLinkedGroup继承自Group 基类见第一篇
    public class SingleLinkedGroup<T> extends Group<T> {
    
        /**
         * 虚拟头结点
         */
        private Node headNode;
    
        public SingleLinkedGroup() {
            clear();
        }
    
        @Override
        public void add(int index, T el) {
    
        }
    
        @Override
        public T remove(int index) {
            return null;
        }
    
        @Override
        public void clear() {
            headNode = new Node(null, null);
            size = 0;
        }
    
        @Override
        public T set(int index, T el) {
            return null;
        }
    
        @Override
        public T get(int index) {
            return null;
        }
    
        @Override
        public int[] getIndex(T el) {
            return new int[0];
        }
    
        @Override
        public Group<T> contact(int index, Group<T> group) {
            return null;
        }
        /////Node类见上方
    }
    
    

    二、底层节点操作

    1.定点插入

    1---新节点的next指向目标节点
    2---目标节点的上一节点(即data1)的next指向新节点
    3---增加size

    9414344-cb77d0214ebdd9b3.png
    添加节点.png
    /**
     * 在指定链表前添加节点
     *
     * @param index 索引
     * @param el    数据
     */
    private void addNode(int index, T el) {
        Node<T> target = getNode(index - 1);
        //新建节点,同时下一节点指向target的下一节点
        Node<T> tNode = new Node<>(target.next, el);
        //目标节点的next指向新节点
        target.next = tNode;
        size++;
    }
    
    2.定点获取

    思路:链表查找只能一个一个挨着找,就像排队报数样。

    /**
     * 根据索引寻找节点
     * @param index 索引
     * @return 节点
     */
    private Node<T> getNode(int index) {
        //声明目标节点
        Node<T> targetNode = headNode;
        for (int i = 0; i < index; i++) {
            //一个挨着一个找,直到index
            targetNode = targetNode.next;
        }
        return targetNode;
    }
    
    3.定点修改
    /**
     * 修改节点
     * @param index 节点位置
     * @param el 数据
     * @return 修改后的节点
     */
    private Node<T> setNode(int index, T el) {
        Node<T> node = getNode(index);
        node.el = el;
        return node;
    }
    
    4.定点移除

    思路:目标节点的前一节点的next指向目标节点的下一节点(即把元素孤立)
    把目标节点的next指向为null

    9414344-4c11cfdac14f2829.png
    链表移除节点.png
    /**
     * 移除指定索引的节点
     *
     * @param index 索引
     * @return 删除的元素
     */
    private T removeNode(int index) {
        //目标节点前一节点
        Node<T> targetPrev = getNode(index - 1);
        //目标节点
        Node<T> target = targetPrev.next;
        //目标节点前一节点的next指向目标节点下一节点
        targetPrev.next = target.next;
        target.next = null;
        size--;
        return target.el;
    }
    

    三、集合类外部访问接口实现

    1、增:添加--add
    @Override
    public void add(int index, T el) {
        // index可以取到size,在链表末尾空位置添加元素。
        if (index+1 < 0 || index > size) {
            throw new IllegalArgumentException("Add failed. Illegal index");
        }
        addNode(index + 1, el);
    }
    
    2.删:移除--remove
    @Override
    public T remove(int index) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("Remove failed. Illegal index");
        }
        return removeNode(index + 1);
    }
    
    3.该:修改--set
    @Override
    public T set(int index, T el) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("Set failed. Illegal index");
        }
        return setNode(index + 1, el).el;
    }
    
    4.查:获取--get
    @Override
    public T get(int index) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("Get failed. Illegal index");
        }
        //消除headNode影响,所以+1
        return getNode(index + 1).el;
    }
    
    5.测试
    SingleLinkedGroup<String> list = new SingleLinkedGroup<>();
    list.addFirst("特");
    list.addLast("烈");
    list.addFirst("风");
    list.addFirst("张");
    list.add(2, "杰");
    list.set(2, "捷");
    System.out.println(list);
    //head: 张->风->捷->特->烈->NULL
    for (int i = 0; i < list.size(); i++) {
        System.out.print(list.get(i));//张风捷特烈
    }
    list.remove(2);
    //head: 张->风->特->烈->NULL
    System.out.println(list);
    list.clear();
    System.out.println(list);//head: NULL
    

    四、其他操作

    1.根据指定元素获取匹配索引
    @Override
    public int[] getIndex(T el) {
        //临时数组
        int[] tempArray = new int[size];
        //重复个数
        int index = 0;
        int count = 0;
        Node node = headNode.next;
        while (node != null) {
            if (el.equals(node.el)) {
                tempArray[index] = -1;
                count++;
            }
            index++;
            node = node.next;
        }
        //将临时数组压缩
        int[] indexArray = new int[count];
        int indexCount = 0;
        for (int i = 0; i < tempArray.length; i++) {
            if (tempArray[i] == -1) {
                indexArray[indexCount] = i;
                indexCount++;
            }
        }
        return indexArray;
    }
    
    2.定点连接两个链表式集合
     @Override
     public Group<T> contact(int index, Group<T> group) {
         SingleLinkedGroup<T> linkedGroup = (SingleLinkedGroup<T>) group;
         //获取待接入链表 头结点
         Node firstNode = linkedGroup.getHeadNode().next;
         //获取待接入链表 尾结点
         Node lastNode = linkedGroup.getLastNode();
         //获取目标节点
         Node<T> target = getNode(index+1);
         //获取目标节点的下一节点
         Node targetNext = target.next;
         //获取目标节点的next连到 接入链表 头结点
         target.next = firstNode;
         //待接入链表 尾结点连到 目标节点的下一节点
         lastNode.next = targetNext;
         return this;
     }
     
    public Node getHeadNode() {
        return headNode;
    }
    public Node getLastNode() {
        return getNode(size);
    }
    
    2.其他方法测试:
            SingleLinkedGroup<String> linkedGroup = new SingleLinkedGroup<>();
            linkedGroup.addLast("a");
            linkedGroup.addLast("b");
            linkedGroup.addLast("a");
            linkedGroup.addLast("c");
            linkedGroup.addLast("a");
    
            System.out.println(linkedGroup);
            //head: a->b->a->c->a->NULL
    
            //获取a元素的所有索引位置
            int[] as = linkedGroup.getIndex("a");
            for (int a : as) {
                System.out.print(a+" ");//0 2 4
            }
    
            //删除a元素第一次出现的地方---
            linkedGroup.removeEl("a");
            System.out.println(linkedGroup);
            //head: b->a->c->a->NULL
    
            //查看a元素是否存在
            boolean b = linkedGroup.contains("a");
            System.out.println(b);//true
    
            //删除所有a元素出现的地方---
            linkedGroup.removeEls("a");
            System.out.println(linkedGroup);
            //head: b->c->NULL
            
            SingleLinkedGroup<String> linkedGroup2 = new SingleLinkedGroup<>();
            linkedGroup2.addLast("1");
            linkedGroup2.addLast("3");
            linkedGroup2.addLast("2");
            linkedGroup.contact(1, linkedGroup2);
            System.out.println(linkedGroup);
            //head: b->c->1->3->2->NULL
    

    五、时间测试:单链表式集合:SingleLinkedGroup测试

    方法数量 复杂度 1000 10000 10W 100W 1000W
    addLast O(n) 0.0029秒 0.1096秒 9.1836秒 ---- ----
    addFirst O(1) 0.0002秒 0.0009秒 0.0036秒 0.5039秒 3.1596秒
    add O(n) -- -- -- -- --
    removeLast O(n) 0.0012秒 0.1009秒 8.9750秒 ---- ----
    removeFirst O(1) 0.0001秒 0.0016秒 0.0026秒 0.0299秒 0.1993秒
    removeEl O(n) -- -- -- -- --
    removeEls O(n) -- -- -- -- --
    remove O(n) -- -- -- -- --
    clear O(1) -- -- -- -- --
    set O(n) -- -- -- -- --
    get O(n) -- -- -- -- --
    getIndex O(n) -- -- -- -- --

    后记、

    1.声明:

    [1]本文由张风捷特烈原创,各图均由本人亲自所画,转载请注明
    [2]欢迎广大编程爱好者共同交流
    [3]个人能力有限,如有不正之处欢迎大家批评指证,必定虚心改正
    [4]你的喜欢与支持将是我最大的动力

    2.连接传送门:

    更多数据结构知识欢迎访问:图解数据结构
    项目源码均在我的github/DS:欢迎star
    张风捷特烈个人网站,编程笔记请访问:http://www.toly1994.com

    3.联系我

    QQ:1981462002
    邮箱:1981462002@qq.com
    微信:zdl1994328

    4.欢迎关注我的微信公众号,最新精彩文章,及时送达:
    9414344-c474349cd3bd4b82.jpg
    公众号.jpg
  • 相关阅读:
    《牛客IOI周赛17提高组A》
    《Codeforces Round #645 (Div. 2)》
    《AtCoder Beginner Contest 168 E》思路详解
    《东华大学2020年程序设计竞赛题解》
    《牛客直播课习题1》
    《Codeforces Round #643 (Div. 2)》
    《TOJ5945》区间覆盖问题。
    解决方案、项目、程序集、命名空间
    SQL存储过程前面后面的几句代码是什么意思?
    无法将类型为“IList”的对象强制转换为类型“System.Data.DataRowView” 。的解决方法
  • 原文地址:https://www.cnblogs.com/toly-top/p/9781869.html
Copyright © 2011-2022 走看看