Java数据结构-链表(单链表、双链表、单向循环链表)

1. 什么是链表?

  • 链表的定义:

    链表是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针

  • 链表的特点:

    1. 使用链表结构可以克服数组链表需要预先知道数据大小的缺点

    2. 链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理

    3. 链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大

2. 链表的分类

  • 单项链表:

    1. 单向链表定义:

      链表中最简单的一种是单向链表,它包含两个域,一个信息域(data)和一个指针域(next),这个指针指向列表中的下一个节点,而最后一个节点的指针则指向一个空值

    2. 单向链表的特点:

      一个单向链表的节点被分成两个部分,第一个部分保存或者显示关于节点的信息,第二个部分存储下一个节点的地址,单向链表只可向一个方向遍历

    3. 单向链表结构图:

      Java数据结构-链表(单链表、双链表、单向循环链表)_第1张图片

  • 双向链表:

    1. 双向链表定义:

      一种更复杂的链表是“双向链表”或“双面链表”。每个节点有两个指针:一个指向前一个节点,另一个指向后一个结点。一般是在需要大批量的另外储存数据在链表中的位置的时候用

    2. 双向链表的特点:

      双向链表中不仅有指向后一个节点的指针,还有指向前一个节点的指针,这样可以从任何一个节点访问前一个节点,当然也可以访问后一个节点,以至整个链表

    3. 双向链表结构图:

      Java数据结构-链表(单链表、双链表、单向循环链表)_第2张图片

  • 循环链表(单向循环链表):

    1. 循环链表定义:

      在一个 循环链表中, 首节点和末节点被连接在一起,这种方式在单向和双向链表中皆可实现,要转换一个循环链表,你开始于任意一个节点然后沿着列表的任一方向直到返回开始的节点

    2. 循环链表的特点:

      循环链表中第一个节点之前就是最后一个节点,反之亦然,循环链表的无边界使得在这样的链表上设计算法会比普通链表更加容易

    3. 循环链表结构图:

      Java数据结构-链表(单链表、双链表、单向循环链表)_第3张图片

3. 单向链表的经典面试题

  • 查找单向链表指定倒数索引处的结点:

    1. 求出单链表有效结点个数
    2. 用有效结点个数 - 指定倒数索引 = 临时变量temp往后移位次数
    3. 正向for循环移动 temp 临时遍量 n 次(n = 第二步求的次数)
    4. 返回此时的临时变量 temp 即可
  • 单向链表的反转:

    1. 首先需要新new一个空的单链表
    2. 把原链表的每个结点反向分离到新的空链表上
    3. 然后让原单链表头结点的next指针指向新单链表的第一个有效结点
  • 反向遍历单链表:

    1. 第一种方法:

      双层for循环实现,时间复杂度:O(n^2)

    2. 第二种方法:

      利用栈的先进后出特点实现,时间复杂度:O(n)

4. 单向链表的实现代码

import java.util.Stack;

class ListNode {
    Object data;
    ListNode next;

    @Override
    public String toString() {
        return "ListNode{" +
                "data=" + data +
                ", next=" + next +
                '}';
    }

    /**
     * 有参构造创建结点
     * @param value 结点的值
     */
    ListNode(Object value) { data = value; }
}
//单链表
public class SingleLinkedList{
    //先初始化一个头结点,不存放数据
    private ListNode head = new ListNode(null);

    /**
     * 在单链表的最后面插入结点
     * @param listNode 所以插入的结点
     */
    public void appendNode(ListNode listNode){
        //定义一个临时变量接收头结点
        ListNode temp = head;
        while (temp.next!=null){
            temp = temp.next;
        }
        temp.next = listNode;
    }

    /**
     * 遍历链表元素
     */
    public void show(){
        //定义一个临时变量接收头结点
        ListNode temp = head.next;
        //判断链表是否为空
        if(head.next==null){
            System.err.println("链表为空...");
            return;
        }
        while (true){
            //若遍历到最后一个结点时,则停掉死循环
            if(temp==null){
                break;
            }
            //输出结点信息
            System.out.println(temp);
            //将temp后移,准备下一次打印信息
            temp = temp.next;
        }
    }

    /**
     * 删除指定索引处的结点
     * @param index 所要删除的结点对应的索引
     */
    public void delete(int index){
        ListNode temp = head;
        if(getLength()<index+1){
            System.err.println("该索引处没有结点信息...");
            return;
        }
        for (int i = 0; i < index; i++) {
            temp = temp.next;
        }
        temp.next=temp.next.next;
    }

    /**
     * 获得单链表的有效结点个数
     * @return 有效结点个数
     */
    public int getLength(){
        //定义一个临时变量
        ListNode temp = head.next;
        //定义一个计数器
        int count = 0;
        while (true){
            if(temp==null){
                break;
            }else {
                count++;
                temp = temp.next;
            }
        }
        return count;
    }

    /**
     * 获得指定索引处的结点信息
     * @param index 索引
     * @return 该索引处的结点信息
     */
    public ListNode getNodeByIndex(int index){
        //定义一个临时变量
        ListNode temp = head;
        //index>getLength()-1,就会越界
        if(index>getLength()-1){
            throw  new RuntimeException("此索引处没有结点信息...");
        }else {
            for (int i = 0; i < index+1; i++) {
                temp = temp.next;
            }
            return temp;
        }
    }

    /**
     * 获得倒数指定索引处的结点信息
     * @param lastIndex 倒数的索引
     * @return 倒数的索引对应的结点信息
     */
    public ListNode getNodeByLastIndex(int lastIndex){
        //定义一个临时变量
        ListNode temp = head;
        if(lastIndex>getLength()-1){
            throw  new RuntimeException("此索引处没有结点信息...");
        }else {
            for (int i = 0; i < getLength() - lastIndex; i++) {
                temp = temp.next;
            }
            return temp;
        }
    }

    /**
     * 将链表反转遍历
     */
    public void reverseShow(){
        //第一种:时间复杂度:O(n^2)
//        for (int i = getLength(); i > 0; i--) {
//            ListNode temp = head;
//            for (int j = i; j > 0; j--) {
//                temp = temp.next;
//            }
//            System.out.println(temp);
//        }
        //第二种:利用栈的先进后出特点,时间复杂度:O(n)
        ListNode cur = head.next;
        Stack<ListNode> stack = new Stack<>();
        if(cur==null){
            return;
        }
        //将结点存入到栈中
        while (cur!=null){
            stack.push(cur);
            cur = cur.next;
        }
        //遍历栈元素
        while (stack.size() > 0){
            System.out.println(stack.pop());
        }
    }

    /**
     * 将链表的结点反转(****腾讯面试题****)
     */
    public void reverseNode(){
        //定义当前结点,初始值为第一个有效结点
        ListNode cur = head.next;
        //定义一个辅助头结点
        ListNode assNode = new ListNode(null);
        //如果单链表没有结点或者只有1个结点,那么反转后还是它本身
        if(cur==null||cur.next==null){
            return;
        }
        while (cur!=null){
            //定义一个临时变量,用于存放当前结点的下一个结点
            ListNode next = cur.next;
            //将 cur 结点的 next指针指向新的链表的最前端
            cur.next = assNode.next;
            //将 cur 链接到新的链表上
            assNode.next = cur;
            //让cur后移
            cur = next;
        }
        //将原单链表的指针指向新单链表的第一个结点
        head.next = assNode.next;
    }
}

5. 双向链表的实现代码

public class DoubleLinkedList {
    //先初始化一个头结点,不存放数据
    private DoubleNode head = new DoubleNode(null);

    /**
     * 添加结点到双向链表的最后
     * @param node 所要添加的结点
     */
    public void add(DoubleNode node){
        //定义一个临时变量
        DoubleNode temp = head;
        //while循环玩后,temp就会移动到双向链表尾部
        while (temp.next!=null){
            temp = temp.next;
        }
        //将最后一个结点的next指针指向所要添加的新结点
        temp.next = node;
        //将所要添加的结点的pre结点指向原来双向链表的最后一个结点
        node.pre = temp;
    }

    /**
     * 删除指定索引处的结点
     * @param index 指定索引
     */
    public void delByIndex(int index){
        if(index>getLength()-1){
            System.out.println("该索引处没有结点信息...");
        }
        //定义一个临时变量
        DoubleNode temp = head.next;
        //for循环移动temp
        for (int i = 0; i < index; i++) {
            temp = temp.next;
        }
        //所要删除结点的上一个结点
        DoubleNode preNode = temp.pre;
        //所要删除结点的下一个结点
        DoubleNode nextNode = temp.next;
        //将所要删除结点的上一个结点的next指针指向所要删除结点的下一个结点
        preNode.next = nextNode;
        if(nextNode!=null){
            //将所要删除结点的下一个结点的pre指针指向所要删除结点的上一个结点
            nextNode.pre = preNode;
        }
    }

    /**
     * 获得指定索引处的结点
     * @param index 指定索引
     */
    public DoubleNode getByIndex(int index){
        if(index>getLength()-1){
            throw new RuntimeException("该索引处没有结点信息...");
        }
        //定义一个临时变量
        DoubleNode temp = head;
        //for循环将temp向后移位 index+1 次
        for (int i = 0; i < index + 1; i++) {
            temp = temp.next;
        }
        return temp;
    }

    public void insertByIndex(int index,DoubleNode node){
        if(index>getLength()){
            System.out.println("索引超过了链表长度...");
        }

        if(index==getLength()){
            //如果index==getLength(),那么所要插入的结点就是要插入到双向链表尾部
            add(node);
            return;
        }
        //定义一个临时变量
        DoubleNode temp = head;
        //for 循环移动临时变量 temp
        for (int i = 0; i < index + 1; i++) {
            temp = temp.next;
        }
        //当前索引结点的上一个结点
        DoubleNode preNode = temp.pre;
        //将当前索引结点的上一个结点的next指针指向所要插入的结点
        preNode.next = node;
        //将所要插入结点的pre指针指向当前索引的上一个结点
        node.pre = preNode;
        //将所要插入结点的next指针指向当前结点
        node.next = temp;
    }

    /**
     * 获得双向链表的有效结点个数
     * @return 有效结点个数
     */
    public int getLength(){
        //初始一个计数器
        int count = 0;
        //定义一个临时变量
        DoubleNode temp = head;
        while (temp.next!=null){
            count++;
            temp = temp.next;
        }
        return count;
    }

    /**
     * 遍历双向链表信息
     */
    public void show(){
        //定义一个临时变量
        DoubleNode temp = head.next;
        while (temp!=null){
            System.out.println(temp);
            //不断后移temp临时变量
            temp = temp.next;
        }
    }
}
class DoubleNode{
    //用于存放结点的数据
    Object data;
    //用于指向前一个结点的指针
    DoubleNode pre;
    //用于指向下一个结点的指针
    DoubleNode next;

    public DoubleNode(Object data) {
        this.data = data;
    }

    @Override
    public String toString() {
        return "DoubleNode{" +
                "data=" + data +
                '}';
    }
}

6. 单向循环链表实现代码

public class CircularLinkedList {
    //初始化第一个结点,默认值为null,一会添加第一个结点的时候会修改它
    private CircularNode head = new CircularNode(null);

    /**
     * 在循环链表的尾部添加结点
     * @param node 所要添加的结点
     */
    public void add(CircularNode node){
        //判断循环链表是否为空
        if(head.next == null){
            head = node;
            //让它变成一个环形的
            head.next = node;
            return;
        }
        //特别注意添加第二个结点的时候
        if(getSize()==1){
            head.next = node;
            node.next = head;
        }
        //定义一个临时变量
        CircularNode temp = head;
        while (temp.next != head){
            temp = temp.next;
        }
        //让尾部的结点的 next 指针指向新结点
        temp.next = node;
        //让新结点的 next 指针指向第一个结点
        node.next = head;
    }

    /**
     * 删除指定索引处的结点信息
     * @param index 指定索引
     */
    public void delByIndex(int index){
        if (index>getSize()-1||index<0){
            System.out.println("索引越界...");
            return;
        }
        //特殊情况是链表只有一个结点,而我恰巧要删这个仅有的结点
        if(getSize()==1){
            head = new CircularNode(null);
            return;
        }
        if(index==0){
            CircularNode next = head.next;
            head = next;
            //此时原来的第一个结点被移到了最后,所以原来链表的最后一个结点会往前挪一位
            getByIndex(getSize()-2).next = next;
            return;
        }
        //定义一个临时变量
        CircularNode temp = head;
        for (int i = 0; i < index-1; i++) {
            temp = temp.next;
        }
        temp.next = temp.next.next;
    }

    /**
     * 获得循环链表的结点个数
     * @return 结点个数
     */
    public int getSize(){
        //循环链表为空时
        if(head.next == null){
            return 0;
        }
        //只有一个结点时
        if(head.next == head){
            return 1;
        }
        int count = 1;
        CircularNode temp = head;
        while (temp.next != head){
            temp = temp.next;
            count++;
        }
        return count;
    }

    /**
     * 从头到尾打印循环链表的所有结点的data值
     */
    public void show(){
        CircularNode temp = head;
        //循环链表为空
        if(temp.next == null){
            System.out.println("循环链表为空,无结点可打印...");
            return;
        }
        System.out.println(head.data);
        while (temp.next != head){
            temp = temp.next;
            System.out.println(temp.data);
        }
    }

    /**
     * 获得指定索引处的结点信息
     * @param index 指定索引
     * @return 指定索引处的结点信息
     */
    public CircularNode getByIndex(int index){
        if(index>getSize()-1||index<0){
            throw new RuntimeException("索引越界...");
        }
        //定义一个临时变量
        CircularNode temp = head;
        for (int i = 0; i < index; i++) {
            temp = temp.next;
        }
        return temp;
    }

}
class CircularNode{
    Object data;
    CircularNode next;

    public CircularNode(Object data) {
        this.data = data;
    }

    public Object getData() {
        return data;
    }

    public CircularNode getNext() {
        return next;
    }
}

你可能感兴趣的:(Java-数据结构与算法)