数据结构系列之链表的数据结构

Posted smileNicky

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构系列之链表的数据结构相关的知识,希望对你有一定的参考价值。

数据结构系列之链表的数据结构

上一章的学习中,我们知道了数组的基本概念和相关特性,接着本博客继续学习数据结构中一个比较常用的数据结构,链表。ps:本博客基于java中的数据结构。

1、什么是链表?

链表是由一系列节点组成的很常见的数据结构,每一个节点都包含一个值和指向下一个节点的指针。“头”节点指向序列的第一个节点,序列的最后一个节点指向NULL(对于单链表)。链表也是线性的顺序存储数据,不过在内存地址上是不连续的。

画图表示单链表:

2、链表和数组的对比

在上一章节,学习了数组,所以这一章和数组做对比:

  • 数组在内存地址是连续的,与数组不同,链接的节点(链接)不需要连续出现在内存中
  • 链表的大小不需要提前声明,只有内存允许就可以
  • 链表是动态的,所以新增移除节点都是控制指针就可以,所以链表的新增移除操作都是比较快的,这个和数组相反
  • 数组是通过下标指针就可以直接访问节点数据,而链表需要全局遍历,所以数组的访问速度是比较快,链接相反
  • 链表的数据结构需要更多的内存,因为链表不止要保存数据,还要保存下一个节点的指针

3、链表的类型分类

数据结构中支持如下几种类型的链表:

  • 单链表:在单链表中,节点的遍历只能往前遍历,指针从Head节点开始
  • 双向链表:双向链表的遍历导航可以和单链表一样往前遍历,也可以后腿,往后遍历
  • 循环链表:循环链表是一种特别的链表,其最后一个节点的next指针指向第一个节点,也即第一个节点的previous节点为最后一个节点

4、单链表的简介


由图看出链表的特性:

  • 1、 Head节点指向第1个节点
  • 2、 最后一个节点指针指向null
  • 3、 每一个节点都存储数据和指向下一个节点的指针

5、双向链表简介

画图表示双向链表:

由图可以归纳双向链表的特性:

  • 1、Head节点指向链表的第一个节点
  • 2、每一个节点都有一个存储数据的节点,和指向下一个节点的next指针、指向上一个节点的prev指针
  • 3、第一个节点的prev指针指向null
  • 4、最好一个节点的next指针指向null

6、循环单向链表

画图表示循环单向链表:

由图归纳循环单向链表的特性:

  • 1、 Head节点指向第1个节点
  • 2、Tail (Last) 指针指向列表的最后一个节点。
  • 3、每个节点都有存储数据的区域和一个指向下一个节点的指针
  • 4、最后一个节点的next指针指向第一个节点

7、链表的基本操作

本章节以最简单的单链表,介绍链表的基本操作,开发语言基于Java

需要构建一个链表的节点模型,单链表只需要保存数据,加上一个next指针就行:

 private static class Node<T> {
    public T data;
    public Node next;
    Node(T data) {
        this.data = data;
        next = null;
    }
}
  • 新增节点
    这种情况,直接将新增节点附加到链表的后面,这里分情况:
    • 1、如果是empty的链表,直接将新节点作为head节点
    • 2、其它节点,直接附加到链表最后面既可
 /**
 * 链表新增节点,直接附加到链表最后面
 * @Date 2021/08/04 13:54
 * @Param [list, data]
 * @return void
 */
public static <T> void add(MySinglyLinkedList<T> list , T data) {
    Node<T> newNode = new Node<T>(data);

    // 单链表是empty的,将新增的节点直接作为head节点
    if (list.head == null) {
        list.head = newNode;
    }
    else { // 其它情况直接在链表后面附加上新节点
        Node<T> currentNode = list.head;
        while (currentNode.next != null) {
            currentNode = currentNode.next;
        }
        // 将新增的节点作为最后一个节点
        currentNode.next = newNode;
    }
}

如上的方法,都是固定在链表最后面加上的,然后可以实现在指定节点后面加上节点,这里分三步进行:

  • 1、创建新的节点,赋值数据
  • 2、将新节点的指针指向前驱节点原来的next节点
  • 3、将前驱节点的指针指向新建的节点
/**
 * 在指定前驱节点后面附加新节点 <br>
 * @Date 2021/08/04 14:26
 * @Param prevNode 前驱节点
 * @Param data 新增节点数据
 * @return void
 */
public static <T> void addAfter(Node<T> prevNode , T data) {
    if (prevNode == null) {
        System.out.println("前驱节点不能为null");
    }
    // 原本链表0->2,创建新节点1
    Node<T> newNode = new Node<T>(data);
    // 将新节点的指针指向前驱节点原本的next节点 1->2
    newNode.next = prevNode.next;
    // 将前驱节点的next指针指向新节点 0->1
    prevNode.next = newNode;
}
  • 移除节点
    移除节点,可以在指定位置移除节点,这里可以分为几种情况:
    • 1、移除第一个节点。
    • 2、移除任何中间元素。
    • 3、移除最后一个元素。
/**
 * 指定下标移除链表节点 <br>
 * @Date 2021/08/04 14:34
 * @Param [list, pos]
 * @return void
 */
public static <T> void removeAtPosition(MySinglyLinkedList<T> list , int pos) {
    // 当前节点
    Node<T> currentNode = list.head;
    // 记录前一个节点
    Node<T> prevNode = null;
    // 下标计数器
    int counter = 0;
    if (currentNode != null) {
        // 移除第一个节点
        if (pos == 0) {
            // 指针移动,节点往后移,再将下一个节点置为head节点
            list.head = currentNode.next;
        }
        else {
            while (currentNode!= null) {
                // 找到移除节点要节点位置
                if (counter == pos) {
                    prevNode.next = currentNode.next;
                    break;
                }
                else {
                    // 记录前驱节点
                    prevNode = currentNode;
                    // 往后遍历
                    currentNode = currentNode.next;
                    // 记录指针位置
                    counter++;
                }
            }

        }
    }

}
/**
 * 通过数据找到节点,移除节点 <br>
 * @Date 2021/08/04 15:12
 * @return void
 */
public static <T> void removeByValue(MySinglyLinkedList<T> list , T data) {
    Node<T> currentNode = list.head;
    Node<T> prevNode = null;
    if (currentNode != null) {
        // 第一个节点
        if (currentNode.data == data) {
            // 节点移动,改变head节点
            list.head = currentNode.next;
        }
        else {
            while (currentNode != null && currentNode.data != data) {
                prevNode = currentNode;
                currentNode = currentNode.next;
            }
            // 找到了节点
            if (currentNode != null) {
                // 节点移动
                prevNode.next = currentNode.next;
                System.out.println(String.format("移除节点:%s" , data));
            }
            else { // 找不到对应节点
                System.out.println("遍历不到对应data的节点");
            }
        }
    }
}
  • 链表size
    这个逻辑就比较容易理解了,直接遍历,统计就行
/**
 * 统计单遍历节点数量 <br>
 * @Date 2021/08/04 16:25
 * @Param [list]
 * @return int
 */
public static <T> int size(MySinglyLinkedList<T> list) {
    int counter = 0;
    Node<T> currentNode = list.head;
    while (currentNode.next != null) {
        currentNode = currentNode.next;
        counter ++;
    }
    return counter;
}
  • 链表查询
    变量查找的,可以通过递归,也可以用while循环
/**
 * 查询单链表<br>
 * @Date 2021/08/04 16:24
 * @Param [head, data]
 * @return boolean
 */
public static <T> boolean search(Node<T> head, T data) {
    if (head == null) {
        return false;
    }
    if (head.data == data) {
        return true;
    }
    // 递归查找
    return search(head.next , data);
}

8、Java实现链表实例


/**
 * 单链表java代码实现
 * @date 2021/08/04
 */
public class MySinglyLinkedList<T> {

    public Node<T> head;

    private static class Node<T> {
        public T data;
        public Node next;
        Node(T data) {
            this.data = data;
            next = null;
        }
    }

    /**
     * 链表新增节点,直接附加到链表最后面
     * @Date 2021/08/04 13:54
     * @Param [list, data]
     * @return void
     */
    public static <T> void add(MySinglyLinkedList<T> list , T data) {
        Node<T> newNode = new Node<T>(data);

        // 单链表是empty的,将新增的节点直接作为head节点
        if (list.head == null) {
            list.head = newNode;
        }
        else { // 其它情况直接在链接后面附加上新节点
            Node<T> currentNode = list.head;
            while (currentNode.next != null) {
                currentNode = currentNode.next;
            }
            // 将新增的节点作为最后一个节点
            currentNode.next = newNode;
        }
    }

    /**
     * 在指定前驱节点后面附加新节点 <br>
     * @Date 2021/08/04 14:26
     * @Param prevNode 前驱节点
     * @Param data 新增节点数据
     * @return void
     */
    public static <T> void addAfter(Node<T> prevNode , T data) {
        if (prevNode == null) {
            System.out.println("前驱节点不能为null");
        }
        // 原本链表0->2,创建新节点1
        Node<T> newNode = new Node<T>(data);
        // 将新节点的指针指向前驱节点原本的next节点 1->2
        newNode.next = prevNode.next;
        // 将前驱节点的next指针指向新节点 0->1
        prevNode.next = newNode;
    }

    /**
     * 指定下标移除链表节点 <br>
     * @Date 2021/08/04 14:34
     * @Param [list, pos]
     * @return void
     */
    public static <T> void removeAtPosition(MySinglyLinkedList<T> list , int pos) {
        // 当前节点
        Node<T> currentNode = list.head;
        // 记录前一个节点
        Node<T> prevNode = null;
        // 下标计数器
        int counter = 0;
        if (currentNode != null) {
            // 移除第一个节点
            if (pos == 0) {
                // 指针移动,节点往后移,再将下一个节点置为head节点
                list.head = currentNode.next;
            }
            else {
                while (currentNode!= null) {
                    // 找到移除节点要节点位置
                    if (counter == pos) {
                        prevNode.next = currentNode.next;
                        break;
                    }
                    else {
                        // 记录前驱节点
                        prevNode = currentNode;
                        // 往后遍历
                        currentNode = currentNode.next;
                        // 记录指针位置
                        counter++;
                    }
                }

            }
        }

    }

    /**
     * 通过数据找到节点,移除节点 <br>
     * @Date 2021/08/04 15:12
     * @return void
     */
    public static <T> void removeByValue(MySinglyLinkedList<T> list , T data) {
        Node<T> currentNode = list.head;
        Node<T> prevNode = null;
        if (currentNode != null) {
            // 第一个节点
            if (currentNode.data == data) {
                // 节点移动,改变head节点
                list.head = currentNode.next;
            }
            else {
                while (currentNode != null && currentNode.data != data) {
                    prevNode = currentNode;
                    currentNode = currentNode.next;
                }
                // 找到了节点
                if (currentNode != null) {
                    // 节点移动
                    prevNode.next = currentNode.next;
                    System.out.println(String.format("移除节点:%s" , data));
                }
                else { // 找不到对应节点
                    System.out.println("遍历不到对应data的节点");
                }
            }
        }
    }

    /**
     * 打印单链表数据 <br>
     * @Date 2021/08/04 16:24
     * @Param [list]
     * @return void
     */
    public static <T> void printLinkedList(MySinglyLinkedList<T> list) {
        Node<T> currentNode = list.head;
        while (currentNode != null) {
            if (currentNode.next != null) {
                System.out.print(String.format("%s -> ", currentNode.data));
            }
            else {
                System.out.print(String.format("%s", currentNode.data));
            }
            currentNode = currentNode.next;
        }
        System.out.println();
    }

    /**
     * 查询单链表<br>
     * @Date 2021/08/04 16:24
     * @Param [head, data]
     * @return boolean
     */
    public static <T> boolean search(Node<T> head, T data) {
        if (head == null) {
            return false;
        }
        if (head.data == data) {
            return true;
        }
        // 递归查找
        return search(head.next , data);
    }

    /**
     * 统计单遍历节点数量 <br>
     * @Date 2021/08/04 16:25
     * @Param [list]
     * @return int
     */
    public static <T> int size(MySinglyLinkedList<T> list) {
        int counter = 0;
        Node<T> currentNode = list.head;
        while (currentNode.next != null) {
            currentNode = currentNode.next;
            counter ++;
        }
        return counter;
    }

    public static MySinglyLinkedList<Integer> buildLinedList() {
        MySinglyLinkedList<Integer> list = new MySinglyLinkedList<Integer>();
        MySinglyLinkedList.add(list , 0);
        MySinglyLinkedList.add(list , 1);
        MySinglyLinkedList.add(list , 2);
        MySinglyLinkedList.add(list , 3);
        MySinglyLinkedList.add(list , 4);
        MySinglyLinkedList.add(list , 5);
        MySinglyLinkedList.add(list , 6);
        MySinglyLinkedList.add(list , 7);
        MySinglyLinkedList.add(list , 8);
        MySinglyLinkedList.add(list , 9);
        MySinglyLinkedList.add(list , 10);
        return list;
    }


    public static void main(String[] args) {
        MySinglyLinkedList<Integer> list = buildLinedList();

        System.out.println(String.format("linked list size : %s" , size(list)));
        // 打印验证,0 -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10
        printLinkedList(list);

        // 在head节点的下一个节点后面加上新节点
        addAfter(list.head.next , 11);
        // 打印验证,0 -> 1 -> 11 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10
        printLinkedList(list);

        // 重置链表
        list = buildLinedList();
        // 在对应位置移除链表节点
        removeAtPosition(list , 1);
        // 打印验证,0 -

以上是关于数据结构系列之链表的数据结构的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript数据结构之链表

数据结构实验之链表三:链表的逆置

数据结构之链表篇(单链表的常见操作)

数据结构实验之链表五:单链表的拆分

数据结构之链表

数据结构之链表