面向对象来理解链表

目录

一、链表知识
    1.1 链表定义
    1.2 链表结构
    1.3 说明
二、面向对象分析链表
    2.1 节点封装类Node.java
    2.2 链表封装类ChainTable.java
    2.3 关于环的补充
    2.4 链表测试类TestChainTable.java

一、链表知识

1.1 链表定义

百度百科:链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

1.2 链表结构

链表的元素是节点,由一个或多个结点组成。节点包括两个元素:数据域(存储数据的)和指针域(指向下一个结点地址)。

简单理解的话,类似链条,一节一节相连,每个链节都有自己的零件(数据)并且链接下一个链接(指针)。

自行车的车链是一个环状的链子,或者说是首位相连的,这是链表中环结构下面会有说明。

图1-1、链条图

我们可以从图1-1 链表中抽象出链表如图1-2 所示,从首节点headNode 依此指向下一个node,直至尾节点tailNode

图1-2、抽象链表图

进而具体到单向链表

  • 初始化指针指向首节点headNode
  • 每一个节点node 都由数据域和指针域组成,数据域存放当前节点的数据,指针域存放当前指针的下一个指向节点
  • 尾节点tailNode 的指针为空

?图1-3、单向链表图

1.3 说明

常见链表有单向链表和双向链表,单向链表是只有一个方向,从链表的首节点headNode 一直指向尾节点tailNode。双向链表是链表即可从headNode 指向tailNode 又可反向从tailNode 指向heaNode。

此处以单向链表为例,面向对象分析链表结构,将节点对象化,链表对象化,此处使用Java 语言演示。

本文涉及到链表的初始化,添加节点,删除节点,查询节点,链表的逆置,模拟链表的环结构,判断链表是否存在环结构,寻找环的入口。关于链表的相交问题暂不涉及。

二、面向对象分析链表

2.1 节点封装类Node.java

有两个属性,当前节点存储的数据Object data,指向下一节点的指针Node next。

/**
 * @Description :节点类
 * @Author: niaonao
 * @Date: 2018/8/11 13:07
 */
public class Node {

    //当前节点的数据
    Object data;
    //当前节点的下一个节点
    Node next;

    public Object getData() { return data; }
    public void setData(Object data) { this.data = data; }
    public Node getNext() { return next; }
    public void setNext(Node next) { this.next = next; }

    /**
     * 无参构造函数
     */
    public Node() { next = null; }

    /**
     * 带参构造函数
     * @param data
     */
    public Node(Object data) {
        this.data = data;
        next = null;
    }
}

2.2 链表封装类ChainTable.java

封装单链表的常用方法包括插入首节点,插入尾节点,指定位置插入节点,删除指定位置节点等。

方法不一一分开介绍了,其中的方法都有注释说明。

基本方法:

  • void insert(Object val)     插入链表元素的方法(插入链表首位, 插入链表尾部, 插入链表指定位置)
  • int getLength()     获取链表长度的方法
  • Node getPosition(int position)      获取链表指定位置元素(正序获取, 逆序获取)
  • boolean judgeLoop()     判断链表是否有环的方法
  • int getLoopLength()     获取环的长度
  • Node entryLoop()     查找环入口的方法
  • Node reverse()      逆置链表的方法
  • void clear()     清除链表
/**
 * 链表
 * 由一个或多个节点组成
 * 每一个节点存放下一个元素, 通过首节点即可获取链表全部节点元素, 因此可以将首节点作为链表对象
 * 节点:
 * 每个节点包含两个元素: 数据对象data 和下一个节点对象next
 * 通过data 存储当前节点的数据
 * 通过next 存储下一个节点
 * 基本方法:
 * void insert(Object val)     插入链表元素的方法(插入链表首位, 插入链表尾部, 插入链表指定位置)
 * int getLength()     获取链表长度的方法
 * Node getPosition(int position)      获取链表指定位置元素(正序获取, 逆序获取)
 * boolean judgeLoop()     判断链表是否有环的方法
 * int getLoopLength()     获取环的长度
 * Node entryLoop()     查找环入口的方法
 * Node reverse()      逆置链表的方法
 * void clear()     清除链表
 *
 * @Description :链表类
 * @Author: niaonao
 * @Date: 2018/8/11 13:07
 */
public class ChainTable {

    //声明链表
    private Node chain;

    /**
     * 构造函数
     */
    public ChainTable() {
        chain = new Node();
    }

    /**
     * 获取链表的长度
     *
     * @return
     */
    public int getLength() {
        //遍历计算链表长度,当前节点的next 下一个节点不为null, 节点数自增一即链表长度自增一
        int count = 0;
        Node cursor = chain;
        while (cursor.next != null) {
            cursor = cursor.next;
            count++;
        }
        return count;
    }

    /**
     * 在链表首部添加节点
     */
    public void insertHead(Object val) {
        //新建链表节点,下一节点指向链表的首位,然后更新链表首位为此节点。
        Node head = new Node(val);
        head.next = chain.next;
        chain.next = head;
    }

    /**
     * 在链表尾部添加节点
     *
     * @param val
     */
    public void insertTail(Object val) {
        //新建链表节点node,当cursor.next为null即cursor已经在链表尾部
        Node tail = new Node(val);
        Node cursor = chain;
        while (cursor.next != null) {
            cursor = cursor.next;
        }
        //获取到最后一个节点,使其next 指向tail 节点
        cursor.next = tail;

    }

    /**
     * 链表下表从1 开始,在指定位置插入节点
     *
     * @param val
     * @param position
     */
    public void insert(Object val, int position) {
        // 新建节点, 遍历获取链表的第port 个节点
        Node Node = new Node(val);
        Node cursor = chain;
        // 找到插入的位置前的的那一个节点
        if (position >= 0 && position <= this.getLength()) {
            for (int i = 1; i < position; i++) {
                cursor = cursor.next;
            }
        }
        // 插入
        Node.next = cursor.next;
        cursor.next = Node;
    }

    /**
     * 删除指定位置的节点
     * @param position
     * @return
     */
    public void delete(int position) {

        if (chain == null)
            return ;

        if (position > 0 && position < this.getLength()) {
            for (int i = 1; i < this.getLength()+1; i++)
                //当前位置的上一个节点的Next 指向当前节点的Next 节点, 此时当前节点不存在于该链表中
                if (i == position) {
                    this.getPosition(i-1).next = this.getPosition(i+1);
                }
        }
    }

    /**
     * 链表逆置方法
     * 将指向逆置, 更新原链表的下一个节点Next 为上一个节点,
     * 原链表的第一个节点逆置后作为新链表的最后一个节点, 其Next 指向null 即可
     *
     * @return
     */
    public Node reverse() {
        Node pre = null;
        Node cursor = chain;
        // 当cursor.next==null时即cursor到尾部时再循环一次把cursor变成头指针。
        while (cursor != null) {
            Node cursorNext = cursor.next;
            if (cursorNext == null) {
                // 逆序后链表
                chain = new Node();
                chain.next = cursor;
            }
            if (pre != null && pre.getNext() == null && pre.getData() == null)
                pre = null;
            cursor.next = pre;
            pre = cursor;
            cursor = cursorNext;
        }
        return chain;
    }

    /**
     * 链表下标从1开始,获取第position 个节点
     *
     * @param position
     * @return
     */
    public Node getPosition(int position) {
        Node cursor = chain;
        //节点插入的位置超出链表范围
        if (position < 0 || position > this.getLength()) {
            return null;
        }
        for (int i = 0; i < position; i++) {
            cursor = cursor.next;
        }
        return cursor;
    }

    /**
     * 获取倒数第position 个节点
     *
     * @param position
     * @return
     */
    public Node getBackPosition(int position) {
        Node cursor = chain;
        //节点插入的位置超出链表范围
        if (position < 0 || position > this.getLength())
            return null;

        //找到倒数第position 个位置
        for (int i = 0; i < this.getLength() - position + 1; i++)
            cursor = cursor.next;

        return cursor;

    }

    /**
     * 链表存在环
     * 追逐方法解决该问题
     * 环模型:
     * 存在环即链表中存在某个节点的Next 指向它前面的某个节点, 此时遍历链表是一个死循环
     *
     * @return
     */
    public boolean judgeLoop() {
        if (chain == null)
            return false;

        //快节点和慢节点从首位节点一起出发
        Node fast = chain;
        Node slow = chain;
        //快节点每次走两步, 慢节点每次走一步, 如果是环, 则快节点会追上慢节点
        while (fast.next != null && fast.next.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if (fast == slow)
                return true;
        }
        return false;
    }

    /**
     * 计算环的长度
     * @return
     */
    public int getLoopLength() {
        if (chain == null)
            return 0;
        Node fast = chain;
        Node slow = chain;
        // 标识是否相遇
        boolean  tag = false;
        // 初始化环长
        int length = 0;
        // fast走两步, slow走一步, 快指针与慢指针第二次相遇时慢指针走的长度就是环的大小
        while(fast.next != null && fast.next.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            // 初始化tag 为false, 第一次相遇标记tag 为true
            if(fast == slow && tag == false) {
                tag =true;
            }
            // 第一次相遇后开始计算环的长度
            if(tag == true ) {
                length++;
            }
            // tag为true 是已经相遇过了,当fast == slow 此时是第二次相遇
            if(fast == slow && tag == true) {
                break;
            }
        }
        return length;
    }

    /**
     * 环的入口
     *
     * 环后续单独拉出来写一个博客, 此处应该画个图更好理解
     * 暂时先给个不错的链接: https://www.cnblogs.com/fankongkong/p/7007869.html
     * @return
     */
    public Node entryLoop(){
        // 指向首节点
        Node fast = chain;
        Node slow = chain;
        // 找环中相汇点, 快节点走两步慢节点走一步
        while(slow.next != null && fast.next.next != null){
            fast = fast.next.next;
            slow = slow.next;
            // 相遇, 此时慢节点走了x 个节点, 快节点走了2x 个节点, 设环有m 个节点, fast 多走了n 圈
            // 有x + m*n = 2*x 即m*n = x, slow 走到环入口后就一直在环里, 所以相遇点距离环入口的长度和慢节点从首节点走到环入口的距离相等
            if(fast == slow) {
                break;
            }
        }
        // fast 从相遇点一步一步走, slow 从首节点开始一步一步走, 走相同的距离相遇的点就是环的入口
        slow = chain;
        while(fast != slow){
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }

    /**
     * 清除链表元素
     */
    public void clear() {
        chain = new Node();
    }
}

2.3 关于环的补充

关于环,在下面2-3 中测试类中模拟环,让尾节点指向首节点,实现一个简单的环结构。存在环结构的链表遍历会进入死循环。

测试类中在模拟环结构之前,链表结构如下:

图2-1、链表逆置的数据图

模拟环结构之后,链表数据结构如下:

图2-2、链表模拟环结构的数据图

2-4 链表测试类TestChainTable.java

测试链表封装类的常用方法,以供参考。

/**
 * @Description :链表测试类
 * @Author: niaonao
 * @Date: 2018/8/11 16:11
 */
public class TestChainTable {

    //声明链表
    private static ChainTable chainLink;

    public static void main(String a[]) {
        testChainMethod();
    }

    /**
     * 测试链表方法
     */
    private static void testChainMethod() {
        System.out.println("\n1.1 初始化链表数据");
        init();

        if (chainLink == null || chainLink.getLength() < 1) {
            return;
        }
        showChain();

        System.out.println("\n2.1 链表第三个节点之后插入数据为Three 的节点");
        chainLink.insert("Three", 3);

        showChain();

        System.out.println("\n3.1 获取链表中第5 个节点的数据: " + chainLink.getPosition(5).getData());
        showChain();

        System.out.println("\n4.1 获取链表中倒数第5 个节点的数据: " + chainLink.getBackPosition(5).getData());
        showChain();

        System.out.println("\n5.1 链表删除第2 个节点 ");
        chainLink.delete(2);
        showChain();

        System.out.println("\n6.1 链表逆序");
        chainLink.reverse();
        showChain();

        System.out.println("\n7.1 链表是否相交: " + chainLink.judgeLoop());
        showChain();

        System.out.println("\n7.2 模拟环模型, 使当前链表尾节点的Next 指向首节点");
        Node firstNode = chainLink.getPosition(1);
        Node lastNode = chainLink.getBackPosition(1);
        lastNode.setNext(firstNode);
        //出现环时, 链表遍历是个死循环
        //showChain();

        if (chainLink.judgeLoop()){
            System.out.print("\n7.3 链表是否有环: " + Boolean.TRUE + "\n\t出现环时, 链表遍历是个死循环");

            System.out.print("\n\t环的长度: " + chainLink.getLoopLength());
            System.out.print("\n\t环的入口: " + chainLink.entryLoop()
                            + "\n\t\t环入口节点的数据data: " + chainLink.entryLoop().getData()
                            + "\n\t\t下一个节点对象node: " + chainLink.entryLoop().getNext());
        }
        else {
            System.out.println("\n7.3 链表是否有环: " + Boolean.TRUE + "\n\t" + chainLink);
        }

    }

    /**
     * 初始化数据
     */
    private static void init() {

        chainLink = new ChainTable();

        System.out.println("\t在链表首位添加A,链表尾部依此添加B、C、D、E");
        chainLink.insertHead("A");
        chainLink.insertTail("B");
        chainLink.insertTail("C");
        chainLink.insertTail("D");
        chainLink.insertTail("E");

    }

    /**
     * 链表数据显示
     */
    private static void showChain() {
        System.out.println("\t链表长度: " + chainLink.getLength());
        System.out.print("\t链表数据: ");
        for (int i = 0; i < chainLink.getLength(); i++) {
            Node node = chainLink.getPosition(i + 1);
            System.out.print("\t " + node.getData());
        }
    }
}

测试结果:

1.1 初始化链表数据
    在链表首位添加A,链表尾部依此添加B、C、D、E
    链表长度: 5
    链表数据:      A     B     C     D     E
2.1 链表第三个节点之后插入数据为Three 的节点
    链表长度: 6
    链表数据:      A     B     Three     C     D     E
3.1 获取链表中第5 个节点的数据: D
    链表长度: 6
    链表数据:      A     B     Three     C     D     E
4.1 获取链表中倒数第5 个节点的数据: B
    链表长度: 6
    链表数据:      A     B     Three     C     D     E
5.1 链表删除第2 个节点 
    链表长度: 5
    链表数据:      A     Three     C     D     E
6.1 链表逆序
    链表长度: 5
    链表数据:      E     D     C     Three     A
7.1 链表是否相交: false
    链表长度: 5
    链表数据:      E     D     C     Three     A
7.2 模拟环模型, 使当前链表尾节点的Next 指向首节点

7.3 链表是否有环: true
    出现环时, 链表遍历是个死循环
    环的长度: 1
    环的入口: [email protected]
        环入口节点的数据data: E
        下一个节点对象node: [email protected]

原文地址:https://www.cnblogs.com/niaonao/p/9460832.html

时间: 2024-10-30 05:22:07

面向对象来理解链表的相关文章

js面向对象深入理解

js面向对象深入理解 ECMAScript 有两种开发模式:1.函数式(过程化),2.面向对象(OOP).面向对象的语言有一个标志,那就是类的概念,而通过类可以创建任意多个具有相同属性和方法的对象.但是,ECMAScript 没有类的概念,因此它的对象也与基于类的语言中的对象有所不同. 一.创建对象 创建一个对象,然后给这个对象新建属性和方法. var box = new Object(); //创建一个Object 对象 box.name = 'Lee'; //创建一个name 属性并赋值 b

javascript面向对象的理解(一)

第一次在园子发文: 关于js面向对象的理解: 工厂方式是什么?构造函数是什么?原形链?对象的引用? 1.对象是什么? 在js接触的比较多的就是对象了,比如: 1 var arr = []; 2 3 arr.number = 10; //对象下面的变量:叫做对象的属性 4 5 //alert( arr.number ); 6 //alert( arr.length ); 7 8 arr.a= function(){ //对象下面的函数 : 叫做对象的方法 9 alert(123); 10 }; 1

黑马程序员——Java基础---面向对象之理解

------<a href="http://www.itheima.com" target="blank">Java培训.Android培训.iOS培训..Net培训</a>.期待与您交流! ------- 面向对象之理解 一:理解面向对象           1,什么是对象 对象就是实际生活中的事物,可以说一切事物都是对象.   如:桌子,椅子,电脑,电视机等. 对象的3个主要特征: a)对象行为:这个对象能做什么,既可以让这个对象完成什么

对java面向对象的理解

前言: 在写博客前我们应该有个好的定位,可以是对知识的梳理和理解,可以是一种新技术,可以是对难点易错的解析和理解. 正文: java面向对象的理解.面向说的通俗点就是针对,对象就是有实际意义的事物(万物皆对象)其实也就是java中的一个参数,但这个参数实际代表了某个东西(比如你有个类Tourism,这个类对 属性姓名.年龄.当Tourism tou = new Tourism(),这个tou参数就实际代表了一个类Tourism的有意义对象,而某一个方法tourism(Tourism tou)那么

Java面向对象的理解和实现代码

理解Java面向对象的重要知识点: 一. 类,对象 类?首先举一个例子:小李设计了一张汽车设计图,然后交给生产车间来生产汽车,有黑色的.红色的.白色的... 这里,汽车设计图就是我们说的类(class),生产车间就是new构造器(大部分对象都是new出来的),生产出来的汽车就是我们要说的对象.可以说java编程实质就是构建类的过程. 对象?万物皆对象,宇宙中,如植物,动物,人类,每个个体都是各司其职.各尽所能的.这就要求对象高内聚.低耦合(简单理解就是人的大脑,它负责思考,想象,记忆,而不能呼吸

彻底理解链表中为何使用二级指针或者一级指针的引用

 彻底理解链表中为何使用二级指针或者一级指针的引用 http://blog.csdn.net/u012434102/article/details/44886339 struct _node  {  void*data;  struct_node *prior;  struct_node *next;  } typedef_node Node;   //给这个_node结构体定义一个别名,任何使用_node的地方都可以用Node来替换 typedef_node* PNode;   //给这个指向

JS javascript面向对象的理解及简单的示例

javascript面向对象的理解及简单的示例 一. javascript面向对象概念: 为了说明 JavaScript 是一门彻底的面向对象的语言,首先有必要从面向对象的概念着手 , 探讨一下面向对象中的几个概念: 1.一切事物皆对象 2.对象具有封装和继承特性 3.对象与对象之间使用消息通信,各自存在信息隐藏 以这三点做为依据,C++ 是半面向对象半面向过程语言,因为,虽然他实现了类的封装.继承和多态,但存在非对象性质的全局函数和变量.Java.C# 是完全的面向对象语言,它们通过类的形式组

数据结构---在分内分彩平台出租存上理解链表

首先,在学习分内分彩平台出租haozbbs.comQ1446595067 数据结构中,对链表在内存上的理解非常重要,上代码public class LinkNode<M> {public M data;public LinkNode nextNode;br/>@Overridepublic String toString() {return "LinkNode [data=" + data + ", nextNode=" + nextNode +

面试总结之谈谈你对面向对象的理解

对面向对象的理解 在我理解,面向对象是向现实世界模型的自然延伸,这是一种“万物皆对象”的编程思想.在现实生活中的任何物体都可以归为一类事物,而每一个个体都是一类事物的实例.面向对象的编程是以对象为中心,以消息为驱动,所以程序=对象+消息. 面向对象有三大特性,封装.继承和多态. 封装就是将一类事物的属性和行为抽象成一个类,使其属性私有化,行为公开化,提高了数据的隐秘性的同时,使代码模块化.这样做使得代码的复用性更高. 继承则是进一步将一类事物共有的属性和行为抽象成一个父类,而每一个子类是一个特殊