datastructure:单链表

本文详细介绍了链表的基本概念,包括链表的存储方式、节点结构、带头结点与不带头结点的区别。讲解了如何添加节点、遍历链表以及单链表的反转和反向打印。此外,还展示了如何修改和删除节点,并提供了相应的Java代码实现。内容涵盖了链表操作的关键步骤和常用算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

小结:
1、链表是以结点的方式来存储,是链式存储。
2、每个结点包含 data 域, next 域:指向下一个结点。
3、链表的结点不一定是连续存储。
3、链表分 带头结点的链表 和 没有头结点的链表,根据实际的需求来确定。


头指针、头结点、首结点(元结点)的区别

头指针:指向链表中第一个结点(头结点或首结点)。
头结点:链表中首结点之前附加的一个结点,其数据域一般无意义,不存放有效数据。
首结点:链表中存储第一个元素的结点,是头结点后边第一个结点。


带头结点与不带头结点的区别:
    带头结点时,不管是否为空表,头指针的值都不会发生变化,都指向头结点。
    不带头结点需要根据不同情况来修改头指针的值,操作不统一,所以绝大多是都使用带头结点。


添加(创建):
    1.先创建一个head头结点,表示单链表的头。
    2.后面每添加一个结点,直接加到链表的最后。
遍历:
    1.通过一个辅助变量,遍历整个链表。


单链表的反转思路:
    1.先定义一个结点reverseHead = new HeroNode();
    2.从头到尾遍历原来的链表,取出其中每一个结点,放在新链表的最前端;
    3.原来链表的head.next = reverseHead.next。

从尾到头打印单链表思路:
    方法1.将单链表进行反转操作,再遍历打印。问题:破坏原来的单链表的结构,不建议。
    方法2.利用栈这个数据结构,将各个结点压入栈中,利用栈的先进先出的特点,实现逆序打印的结果。
import java.util.Stack;

/*
head表示头结点,
head.next == null 表示链表为空
HeroNode temp = head;
temp.next == null 表示temp是链表的最后一个结点

添加、删除结点:要把temp结点的next指向新的结点(temp.next = heroNode),可能会导致空指针异常,
HeroNode temp = head

修改、查看结点:不存在把temp结点的next指向新的结点,不会出现空指针异常。
HeroNode temp = head.next
 */
public class SingleLinkedListTest {
    public static void main(String[] args) {
        // 创建结点
        HeroNode node1 = new HeroNode(1, "宋江", "及时雨");
        HeroNode node2 = new HeroNode(6, "林冲", "豹子头");
        HeroNode node3 = new HeroNode(3, "吴用", "智多星");
        // 创建链表
        SingleLinkedList singleLinkedList1 = new SingleLinkedList();
        // 按照编号顺序添加结点
        singleLinkedList1.addBy(node1);
        singleLinkedList1.addBy(node2);
        singleLinkedList1.addBy(node3);

        HeroNode node4 = new HeroNode(7, "秦明", "霹雳火");
        HeroNode node5 = new HeroNode(4, "公孙胜", "入云龙");
        HeroNode node6 = new HeroNode(5, "关胜", "大刀");
        HeroNode node7 = new HeroNode(2, "卢俊义", "玉麒麟");
        SingleLinkedList singleLinkedList2 = new SingleLinkedList();
        singleLinkedList2.addBy(node4);
        singleLinkedList2.addBy(node5);
        singleLinkedList2.addBy(node6);
        singleLinkedList2.addBy(node7);

        // 显示原链表
        System.out.println("单向链表1的原的链表:");
        singleLinkedList1.display();
        System.out.println("单向链表2的原的链表:");
        singleLinkedList2.display();

        // 合并两条链表
        // 方法里要用到myLinkedList、singleLinkedList2,以参数形式传进去
        mergeLinkedList(singleLinkedList1, singleLinkedList2);

        // 打印输出合并的两条链表
        System.out.println("合并后的链表:");
        int num1 = getLength(singleLinkedList1.getHead());
        int num2 = getLength(singleLinkedList2.getHead());
        if (num1 > num2) {
            singleLinkedList1.display();
        } else {
            singleLinkedList2.display();
        }

        // 以下程序默认知道单向链条2的长度长

        // 反转链表
        System.out.println("反转后的链表:");
        reverseList(singleLinkedList2.getHead());
        singleLinkedList2.display();

        // 逆序打印
        System.out.println("反向打印链表:");
        reversePrint(singleLinkedList2.getHead());

        // 修改结点
        HeroNode newNode = new HeroNode(1, "小宋", "及时雨");
        singleLinkedList2.modify(newNode);
        // 删除结点
        singleLinkedList2.delete(1);
        // 显示修改后的链表
        System.out.println("修改后的链表情况:");
        singleLinkedList2.display();

        // 求单链表中有效结点的个数
        System.out.println("单链表中有效结点的个数为:" + getLength(singleLinkedList2.getHead()));
        // 查找单链表中的倒数第k个结点
        System.out.println("倒数第3个结点是:" + findLastIndexNode(singleLinkedList2.getHead(), 3));
    }

    /**
     * 返回单链表中有效结点的个数
     * @param head 链表的头结点
     * @return 有效结点的个数
     */
    public static int getLength(HeroNode head) {
        if (head.next == null) {
            return 0;
        }
        int length = 0;
        HeroNode temp = head;
        while (temp.next != null) {
            length++;
            temp = temp.next;
        }
        return length;
    }

    /**
     * 查找单链表中的倒数第k个结点
     * @param head  链表的头结点
     * @param index 倒数第k个结点
     * @return 如果找到了,返回该结点;否则返回null
     */
    public static HeroNode findLastIndexNode(HeroNode head, int index) {
        if (head.next == null) {
            return null;
        }
        // 第一次遍历,找到链表的有效结点个数
        int size = getLength(head);
        // 第二次遍历size-index+1次,就是倒数第index个结点的位置
        if (index <= 0 || index > size) {
            return null;
        }
        HeroNode temp = head;
        for (int i = 0; i < size - index + 1; i++) {
            temp = temp.next;
        }
        return temp;
    }

    /**
     * 将单向链表反转后输出
     * @param head 原链表的头结点
     */
    public static void reverseList(HeroNode head) {
        // 如果当前链表为空,或者只有一个结点,无需反转,直接返回
        if (head.next == null || head.next.next == null) {
            // return表示直接结束方法的执行,不再执行判断if之后的操作
            return;
        }
        // 定义辅助结点,遍历原来的列表
        HeroNode temp = head.next;
        // 当前结点temp的下一个结点
        HeroNode next = null;
        // 新链表头结点
        HeroNode reverseHead = new HeroNode(0, "", "");
        // 遍历原来的链表,取出其中每一个结点,放在新链表的最前端
        while (temp != null) {
            // 保存当前结点的下一个结点为next
            next = temp.next;
            // 将reverseHead下一个结点的内存地址赋给temp的next(将reverseHead的下一个结点变成temp的下一个结点)
            temp.next = reverseHead.next;
            // 将temp连接到新的链表上
            reverseHead.next = temp;
            // temp后移
            temp = next;
        }
        // 将reverseHead下一个结点的内存地址赋给head的next
        head.next = reverseHead.next;
    }

    /**
     * 将单向链表反向打印
     * @param head 链表的头结点
     */
    public static void reversePrint(HeroNode head) {
        if (head.next == null) {
            return;
        }
        // 创建一个栈
        Stack<HeroNode> stack = new Stack<>();
        HeroNode temp = head.next;
        // 将链表的个结点压入栈中
        while (temp != null) {
            stack.push(temp);
            temp = temp.next;
        }
        // 将栈中的结点打印
        while (stack.size() > 0) {
            System.out.println(stack.pop());
        }
    }

    /**
     * 将两个单项链表按序号合并成一个单向链表
     * @param singleLinkedList1 第一个单向链表
     * @param singleLinkedList2 第二个单向链条
     */
    public static void mergeLinkedList(SingleLinkedList singleLinkedList1, SingleLinkedList singleLinkedList2) {
        int num1 = getLength(singleLinkedList1.getHead());
        int num2 = getLength(singleLinkedList2.getHead());
        if (singleLinkedList1.getHead().next == null || singleLinkedList2.getHead().next == null) {
            return;
        }
        // 当链表1比链表2长的时候,把链表2中的结点加入到链表1中
        if (num1 >= num2) {
            HeroNode temp = singleLinkedList2.getHead().next;
            HeroNode next = null;
            while (temp != null) {
                next = temp.next;
                singleLinkedList1.addBy(temp);
                temp = next;
            }
        } else {
            HeroNode temp = singleLinkedList1.getHead().next;
            HeroNode next = null;
            while (temp != null) {
                next = temp.next;
                singleLinkedList2.addBy(temp);
                temp = next;
            }
        }
    }
}

// 定义单链表
class SingleLinkedList {
    // 初始化一个头结点,不存放具体的数据
    private HeroNode head = new HeroNode(0, "", "");

    public HeroNode getHead() {
        return head;
    }

    // 添加结点到单向链表
    public void add(HeroNode heroNode) {
        // 需要一个辅助结点,头结点不能动
        HeroNode temp = head;

        // 遍历链表,当temp不是链表的最后结点
        while (temp.next != null) {
            temp = temp.next;
        }
        // 退出while循环时,temp指向了链表的最后结点
        // 将最后这个结点的next指向新的结点
        temp.next = heroNode;
    }

    // 根据编号添加新的结点,如果有这个编号,添加失败并提示
    public void addBy(HeroNode heroNode) {
        // 需要一个辅助结点,要位于添加的结点的前一个位置
        HeroNode temp = head;
        // 标识添加的结点编号是否存在
        boolean flag = false;
        // 当temp不是链表的最后结点
        while (temp.next != null) {
            if (temp.next.no > heroNode.no) {
                break;
                // 添加的结点编号已经存在
            } else if (temp.next.no == heroNode.no) {
                flag = true;
            }
            temp = temp.next;
        }
        if (flag) {
            System.out.println("结点的编号" + heroNode.no + "已经存在,不能添加!");
        } else {
            heroNode.next = temp.next;
            temp.next = heroNode;
        }
    }

    // 修改结点,根据no来修改,no不一样相当于添加
    public void modify(HeroNode heroNode) {
        // 判断链表是否为空
        if (head.next == null) {
            System.out.println("链表为空!");
            return;
        }
        // 需要一个辅助结点,头结点不能动
        HeroNode temp = head.next;

        // 遍历链表,根据no找到需要修改的结点
        boolean flag = false;
        // 当temp不是链表的最后结点
        while (temp != null) {
            if (temp.no == heroNode.no) {
                flag = true;
                break;
            }
            temp = temp.next;
        }
        if (flag) {
            temp.name = heroNode.name;
            temp.nickName = heroNode.nickName;
        } else {
            System.out.println("没有找到编号为" + heroNode.no + "的结点!");
        }
    }

    // 删除结点
    public void delete(int no) {
        // 判断链表是否为空
        if (head.next == null) {
            System.out.println("链表为空!");
            return;
        }
        //
        HeroNode temp = head;
        boolean flag = false;
        while (temp.next != null) {
            if (temp.next.no == no) {
                flag = true;
                break;
            }
            temp = temp.next;
        }
        if (flag) {
            temp.next = temp.next.next;
        } else {
            System.out.println("要删除的结点编号" + no + "不存在!");
        }
    }

    // 显示链表(遍历)
    public void display() {
        // 判断链表是否为空
        if (head.next == null) {
            System.out.println("链表为空!");
            return;
        }
        // 需要一个辅助结点,头结点不能动
        HeroNode temp = head.next;

        // 遍历链表,输出每一个结点
        // 当temp不是链表的最后结点
        while (temp != null) {
            System.out.println(temp);
            temp = temp.next;
        }
    }
}

// 定义HeroNode,每个HeroNode对象都是一个结点
class HeroNode {
    public int no;
    public String name;
    public String nickName;
    // 指向下一个结点
    public HeroNode next;

    public HeroNode(int no, String name, String nickName) {
        this.no = no;
        this.name = name;
        this.nickName = nickName;
    }

    // 为了显示结点,重写toString方法(显示结点中的信息,不需要重写)
    @Override
    public String toString() {
        return "HeroNode{" +
                "no=" + no +
                ", name=" + name +
                ", nickName=" + nickName +
                '}';
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值