链表

作者: 王一航 | 来源:发表于2016-06-28 22:39 被阅读718次

    阅读原文


    • 什么是链表
      • 概念

      • 链表

      • 是一种物理存储单元上非连续/非顺序的存储结构.

      • 节点

        • 概念
        • 所谓链表即是由多个节点组成,像一条铁链,铁链的每一个环就相当于链表的节点.
        • 组成
          • 数据域
          • 一个节点用于储存数据的内存控件
          • 指针域
          • 一个节点用于储存和它连接的下一个节点地址的内存空间
      • 优点

        • 创建节点
        • 不需要指定内存空间的大小,非常具有灵活性
        • 删除节点
        • 删除一个节点不需要像数组那样将其余的数据整体向前移动,只需要修改节点的指针域即可完成删除节点的操作
      • 缺点

        • 访问节点
        • 可以通过循环或者递归访问到链表的任何数据,但是访问效率低于数组(线性数据结构)
        • 储存方面
        • 由于不是线性结构,并且相对于线性的储存结构来说,需要保存每个节点的指针域,这又是一笔内存开销,占用了内存空间,对内存空间的利用效率低

    • 链表的种类

      • 方向

        • 单向链表
        • 是连接方向单一的链表,从头结点开始可以一直指向尾节点,方向不会改变(即从头结点走到尾节点只有一条路径).其中每一个节点的指针域只有一个指向,也就是一个节点有一个指针,可以保存下一个节点
      • 双向链表

        • 每一个节点的指针域中保存有两个指针(引用),可以同时指向该节点的前驱节点和后继节点
    • 形状

    • 单链表

      • 单链表:是一种逻辑上的线性结构,只有一条链,首尾不相接
    • 环形链表

      • 环形链表:首尾相接的链表(类比丢手帕)(约瑟夫问题)

    • 如何创建链表
      • 思路分析

        • 图形化结构
          • 链表 signal-linked-list
            signal-circular-linked-list
            double-signal-linked-list
            double-signal-circular-linked-list
          • 节点
            • 节点类图 Node-class
            • 节点属性
              • 数据域 : 节点中储存数据内存控件
              • 指针域 : 节点中储存其他节点(可能是下一个,也可能是上一个)的地址信息的内存空间
      • 类图

        • [Java]单项链表类图//TODO 修改类图(无bug的类图) LinkedList_Class_Map
          • 文本思路
            • 单向链表
              * //TODO 书写思路
            • 双向链表
              * //TODO 书写思路
            • 单向环形链表
              * //TODO 书写思路
            • 双向环形链表
              * //TODO 书写思路
      • 代码实现

      • [Java]单向链表

      • [Java]双向链表//TODO 添加超链接

      • [Java]单向环形链表//TODO 添加超链接

      • [Java]双向环形链表//TODO 添加超链接


    • 如何使用链表

      • 节点

        • 属性

          • 数据域 : Object data
          • 指针域 :
          • Node successorNode//前驱节点(双向链表节点特有属性)
          • Node precursorNode//后继节点
        • 方法

          • 构造方法 : (根据传入的Object对象创建一个后继节点指向null的Node对象)
          • 查询节点数据 : private Object getData()
          • 修改节点数据 : private void setData(Object newData)//当Node类的成员属性不是私有的时候不需要这些方法,并且就算有这些方法,它们的修饰符也不能是private
      • 链表

      • 属性

      • 长度 : int length = 0

      • 头结点引用 : Node headNode = null

      • 尾节点引用 : Node tailNode = null ? ?(双向链表特有属性)

      • 方法[单向链表]//TODO完成四种链表的分类

      • 初始化链表(利用构造器) : public LinkedList()

      • 增加尾节点 : public void add()

      • 插入指定节点: public void insert(int index)

      • 删除尾节点 : public void del()

      • 删除指定节点 : public void del(int index)

      • 修改尾节点 : public void update(Object newData)

      • 修改指定节点 : public void update(int index, Object newData)

      • 获取尾节点 : public Node getNode()

      • 获取指定节点 : public Node getNode(int index)

      • 链表长度 : public int length()

      • 判断空链表 : public boolean isEmpty()

      • 排序 : public void sort()

      • 截取链表 : public LinkedList subLinkedList(int start, int end)


    • 如何进行测试

      • 当我们构建好了我们自己的链表的时候,就必须得对其正确性和健壮性进行测试,这个测试的过程一般来说是随着编码一起进行的,我们来看一下应该如何进行测试.

        • 正确性//TODO
        • 健壮性//TODO

    • 如何优化链表

      • 算法方面
        • 排序算法
        • 冒泡排序
        • 选择排序
        • 插入排序
        • 检索算法
      • 操作方法
      • 健壮性
      • 待续

    • 链表的应用场景

      • 首先我们知道链表的特点就是可以动态地增加和删除元素,就类似与一个可以自动扩大和缩小容量的数组,因此,我们可以想到它的应用场景应该有:

        • 1.求解约瑟夫问题(利用环形链表)

    代码实现

    [Java]单向链表代码 :

    package linkedList;
    /** 
       * 节点类(单向链表) 
       * @author 王一航 
       */
    class Node {
        //数据域   Object data;
        //指针域   Node nextNode = null;//单向链表因此只有一个指向自身类型的引用
            //构造函数
        public Node(Object data){
            this.data = data;
        }
    }
    
    package linkedList;
    /** * 单向链表类 * @author 王一航 */
    public class LinkedList {
        /**  * 成员变量  */ int length = 0;
        Node headNode = null;
        /**  * 成员方法  */
        //初始化链表(利用构造器)
        public LinkedList(){
            int length = 0;//记录链表长度
            Node headNode = null;//头结点
        }
        //增加尾节点
        public void add(Object data){
            insert(length, data);
        }
        //插入指定节点
        public void insert(int index, Object data){
            Node temp = getNode(index);//使用一个临时的引用记录需要插入的位置的前驱结点的地址
            Node node = new Node(data);//创建新节点准备插入
            if(index > 0){
                getNode(index - 1).nextNode = node;     
                }else{
                headNode = node;
            }
            node.nextNode = temp;
            length++;
        }
        //删除尾节点
        public void del(){
            del(length - 1);
        }
        //删除指定节点
        public void del(int index){
            //TODO 检查入口参数
            if(index > 0){
                getNode(index - 1).nextNode = getNode(index + 1);
                length--;
            }
            if(index == 0){
                headNode = getNode(1);
                length--;
            } //TODO 释放内存
        }
        //修改尾节点
        public void update(Object newData){
            update(length - 1, newData);
        }
        //修改指定节点
        public void update(int index, Object newData){  
                getNode(index).data = newData;
        }   
            //获取尾节点
        public Node getNode(){
            return getNode(length - 1);
        }
        //获取指定节点
        public Node getNode(int index){
            try{
                point = headNode;
                int count = 0;
                while(count < index){
                    point = point.nextNode;
                    count++;
                }
                return point;
            }catch(NullPointerException e){
                System.out.println("请检查链表是否越界! 链表长度 : " + length);
                return null;
            }
        }
        //链表长度
        public int length(){
            return length;
        }
        //判断空链表
        public boolean isEmpty(){
            return length == 0 ? true : false;
        }
        //排序
        public void sort(){
            //TODO 完成排序功能
            System.out.println("请完成排序功能!");
        }
        //截取链表
        public LinkedList subLinkedList(int start, int end){
            if(end >= start){
                headNode = getNode(start);
                getNode(end - start).nextNode = null;
                length = end - start;
            }
            return this;
            //TODO 释放不需要的内存
        }
        //打印链表所有元素
        public void show(){
            for(int i = 0; i < length; i++){
                System.out.println("索引 : " + i + " 第" + (i+1) + "个" + getNode(i).data);
            }
        }
    }
    

    [Java]双向链表代码 :

    package doubleLinkedList;
    /** * 双向链表节点类 * @author 王一航 */
    class Node {
        //数据域   Object data;
        //指针域   Node lastNode = null;//前驱节点引用
        Node nextNode = null;//后继节点引用
        //构造函数  public Node(Object data){
            this.data = data;
        }
    }
    //TODO 添加双向链表的代码
    

    [Java]单向环形链表代码 :

    //TODO 添加代码
    package circularLinkedList;
    /** * 单向环形链表节点类 * @author 王一航 */
    public class Node {
        /**  * 成员属性  */
        //数据域   Object data;
        //指针域   Node successorNode;//后继节点
        /**  * 成员方法  */
        public Node(Object data){
            this.data = data;
        }
    }
    

    [Java]双向环形链表代码 :

    //TODO 添加代码
    package circularLinkedList;
    /** * 双向环形链表节点类 * @author 王一航 */
    public class Node {
        /**  * 成员属性  */
        //数据域   Object data;
        //指针域   Node successorNode;//后继节点
        Node precursorNode;//前驱节点
        /**  * 成员方法  */
        public Node(Object data){
            this.data = data;
        }
    }
    

    总结(个人理解):


    • 关于健壮性 :
      当某一个方法需要入口参数的时候,必须对入口参数的合法性进行检查(以后要将这种意识变成一种意识)
    • 关于代码结构 :
    • 关于方法的组织 :
      良好的代码中方法的组织结构应该具有"高内聚低耦合"的特点
      高内聚 :
      低耦合 :
    • 关于属性和方法的命名 :
    • 关于类图的使用 :

    注意 :


    1 . 杜绝魔术数字的出现,程序中一旦需要使用到某些常量,则必须首先声明常量为一个有意义的名字,然后根据这个名字去访问该常量

    相关文章

      网友评论

        本文标题:链表

        本文链接:https://www.haomeiwen.com/subject/yzwtjttx.html