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

    零、前言

    链表是一种数据结构,用来承载数据,每个表节点装载一个数据元素
    双链表是每个节点出来数据元素外还分别持有前、后两个节点的引用
    为了统一节点的操作,一般在真实链表的首尾各加一个虚拟节点,称为头节点和尾节点
    

    一、链表的操作

    下图是一个三个节点的双链表

    9414344-e5b7ba3a7ccb3200.png
    双链表.png
    /**
     * 作者:张风捷特烈
     * 时间:2018/9/18 0018:7:35
     * 邮箱:1981462002@qq.com
     * 说明:双链表
     */
    public class DoubleLink<T> {
        /**
         * 虚拟头结点
         */
        private Node headNode;
        /**
         * 虚拟尾节点
         */
        private Node tailNode;
        /**
        * 链表长度(节点数)
        */
        private int size;
    
        /**
         * 节点类
         * @param <T>
         */
        private static class Node<T> {
            /**
             * 数据
             */
            public T data;
            /**
             * 前节点
             */
            public Node prev;
            /**
             * 后节点
             */
            public Node next;
    
            public Node(Node prev, Node next, T data) {
                this.data = data;
                this.prev = prev;
                this.next = next;
            }
        }
    }
    

    1.插入操作:addBefore()

    假如在第二个元素处插入,会发生什么:
    1---新建一个node,将前、后指向分别指向目标前节点和目标节点
    2---目标前节点next指向新节点
    3---目标节点prev指向新节点
    3---链表长度+1

    9414344-28ba687c6b8ff5df.png
    双链表前插入.png
    /**
     * 根据目标节点插入新节点
     * @param target 目标节点
     * @param data 新节点数据
     */
    private void addNodeBefore(Node<T> target, T data) {
        //新建一个node,将前、后指向分别指向目标前节点和目标节点
        Node<T> newNode = new Node<>(target.prev, target, data);
        //目标前节点next指向新节点
        target.prev.next = newNode;
        //目标节点prev指向新节点
        target.prev = newNode;
        //链表长度+1
        size++;
    }
    

    2.移除操作:removeNode()

    假如在删除第二个元素,会发生什么:
    1---目标前节点的next指向目标节点后节点
    2---目标后节点的prev指向目标节点前节点

    3---链表长度-1
    4---返回删除的数据

    9414344-9b335b6918a8955b.png
    双链表移除节点.png
    /**
     * 移除目标节点
     *
     * @param target 目标节点
     * @return 目标节点数据
     */
    private T removeNode(Node<T> target) {
        //目标前节点的next指向目标节点后节点
        target.prev.next = target.next;
        //目标后节点的prev指向目标节点前节点
        target.next.prev = target.prev;
        //链表长度-1
        size--;
        return target.data;
    }
    

    3.清空操作:clearNode()

    思路和删除一样:首尾虚拟节点互指,中间的元素就被孤立了,从而从链表上全部删除
    1---实例化头结点
    2---实例化尾节点,并将prev指向头
    3---头结点的next指向尾节点
    4---链表长度置零

    9414344-c46dce50ab3364fb.png
    双链表清空.png
    /**
     * 清空所有节点
     */
    private void clearNode() {
        //实例化头结点
        headNode = new Node<T>(null, null, null);
        //实例化尾节点,并将prev指向头
        tailNode = new Node<T>(headNode, null, null);
        headNode.next = tailNode;
        //链表长度置零
        size = 0;
    }
    

    4.获取操作:getNode

    思路:链表查找只能一个一个挨着找,就像排队报数样。
    为了尽量高效,判断一下索引在前半还是后半,来采取前报数,还是后报数。

     /**
      * 根据索引获取节点
      *
      * @param index 索引
      * @return 索引处节点
      */
     private Node<T> getNode(int index) {
         //声明目标节点
         Node<T> targetNode;
         //索引越界处理
         if (index < 0 || index > size - 1) {
             throw new IndexOutOfBoundsException();
         }
         //如果索引在前半,前序查找
         if (index < size / 2) {
             targetNode = headNode.next;
             for (int i = 0; i < index; i++) {
                 targetNode = targetNode.next;
             }
         } else {  //如果索引在后半,反序查找
             targetNode = tailNode.prev;
             for (int i = size - 1; i < index; i++) {
                 targetNode = targetNode.prev;
             }
         }
         return targetNode;
     }
    

    二、利用链表实现对数据的操作

    链表只是对节点的操作,只是一种结构,并非真正目的,在集合类中要让链表对外完全不可见,就像人的骨骼之于躯体
    躯体的任何动作是骨骼以支撑,而骨骼并不可见,从外来看只是躯体的动作而已,数据结构就是这样的骨架,数据便是躯体。
    我们需要的是按照这种结构对数据进行增删改查等操作,并暴露接口由外方调用

    1.普通集合操作:增删改查
    public class DoubleLinkedGroup<T> extends Group<T> {
    
        /**
         * 虚拟头结点
         */
        private Node headNode;
    
        /**
         * 虚拟尾节点
         */
        private Node tailNode;
    
        public DoubleLinkedGroup() {
            clear();
        }
    
        @Override
        public void add(int index, T el) {
            if (index < 0 || index > size) {
                throw new IllegalArgumentException("Add failed. Illegal index");
            }
            addNodeBefore(getNode(index), el);
        }
    
        @Override
        public T remove(int index) {
            if (index < 0 || index > size) {
                throw new IllegalArgumentException("Remove failed. Illegal index");
            }
            return removeNode(getNode(index));
        }
    
        @Override
        public void clear() {
            clearNode();
        }
    
        @Override
        public T set(int index, T el) {
            if (index < 0 || index > size) {
                throw new IllegalArgumentException("Set failed. Illegal index");
            }
            Node<T> node = getNode(index);
            T oldData = node.el;
            node.el = el;
            return oldData;
        }
    
        @Override
        public T get(int index) {
            if (index < 0 || index > size) {
                throw new IllegalArgumentException("Get failed. Illegal index");
            }
            return getNode(index).el;
        }
        
            @Override
        public void addLast(T el) {
            add(size, el);
        }
    }
    
    
    2.普通方法测试:
        private static void baseTest() {
            DoubleLinkedGroup<String> list = new DoubleLinkedGroup<>();
            //添加测试
            list.addFirst("特");
            list.addFirst("张");
            list.add(1,"风");
            list.add(2,"捷");
            list.addLast("烈");
    
            //输出测试
            System.out.println(list);
            //head: 张->风->捷->特->烈->null->
    
            //移除测试
            list.remove(3);
            System.out.println(list);
            //head: 张->风->捷->烈->null->
    
            //修改测试
            list.set(2,"神");
            System.out.println(list);
            //head: 张->风->神->烈->null->
    
            //获取测试
            for (int i = 0; i < list.size(); i++) {
                System.out.print(list.get(i));//张风神烈
            }
    
            //大小测试
            System.out.println(list.size());//4
            //是否为空测试
            System.out.println(list.isEmpty());//false
    
            //清空测试
            list.clear();
            System.out.println(list.isEmpty());//true
        }
    }
    

    二、其他方法测试

    定元素查询索引、删除
    两个双链表式集合定点连接

    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;
        }
    
        @Override
        public Group<T> contact(int index, Group<T> group) {
            if (index < 0 || index > size) {
                throw new IllegalArgumentException("Contact failed. Illegal index");
            }
    
            DoubleLinkedGroup linkedGroup = (DoubleLinkedGroup) group;
            Node targetNode = getNode(index);
            Node targetNextNode = targetNode.next;
    
            //目标节点的next指向待接链表的第一个节点
            targetNode.next = linkedGroup.getHeadNode().next;
            //向待接链表的第一个节点的prev指向目标节点
            linkedGroup.getHeadNode().next.prev = targetNode;
    
            //目标节点的下一节点指的prev向待接链表的最后一个节点
            targetNextNode.prev = linkedGroup.getTailNode().prev;
            //向待接链表的最后一个节点的next指向目标节点的下一节点的
            linkedGroup.getTailNode().prev.next = targetNextNode;
    
            return this;
        }
    
        public Node getHeadNode() {
            return headNode;
        }
    
        public Node getTailNode() {
            return tailNode;
        }
    
    2.其他方法测试
        /**
         * 其他方法测试
         */
        private static void otherTest() {
            DoubleLinkedGroup<String> linkedGroup = new DoubleLinkedGroup<>();
            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
    
            //双链表合并测试
            DoubleLinkedGroup<String> linkedGroup2 = new DoubleLinkedGroup<>();
    
            linkedGroup2.addLast("1");
            linkedGroup2.addLast("3");
            linkedGroup2.addLast("2");
    
            linkedGroup.contact(0, linkedGroup2);
            System.out.println(linkedGroup);
            //head: b->1->3->2->c->null->
    
        }
    

    后记、

    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
  • 相关阅读:
    Python中的模块与包
    Mac eclipse找不到source的解决办法
    Git常用命令
    MiniCrowler
    九度题库(所有题目整理,适合计算机考研和面试的人用)
    gtest 安装
    计算广告的相关学习资源
    使用python pylab库 画线
    3到6年的.NETer应该掌握哪些知识?
    迭代器模式的一种应用场景以及C#对于迭代器的内置支持
  • 原文地址:https://www.cnblogs.com/toly-top/p/9781868.html
Copyright © 2011-2022 走看看