数据结构与算法-单链表(Linked List)

    科技2025-04-02  23

    链表(Linked List)

    1、链表介绍

    链表是有序的列表,但是他在内存中的存储结构如下:

    链表是以节点的方式来存储的,是链式存储每个节点包含data域、next域:指向下一个节点链表的各个节点不一定是连续存储。链表分带头节点的链表和不带头节点的链表,根据实际情况来确定 单链表(带头节点)逻辑结构示意图

    2、单链表应用

    使用带头节点的单向链表实现-开发语言排行榜管理,完成对语言的增删改查操作。

    2.1、定义链表结构

    分析:

    使用带头节点的链表,我们需要定义头节点属性;链表包涵data域和next域,我们这里需要排序新增no排序属性、name语言名称属性 我们定义如下Node结构: /** * 单链表节点 */ public class LanguageNode { /** * 排序 */ private int no; /** * 语言名称 */ private String language; /** * 语言描述 */ private String languageDescribe; /** * 下个节点 */ private LanguageNode next; public LanguageNode(int no, String language, String languageDescribe, LanguageNode next) { this.no = no; this.language = language; this.languageDescribe = languageDescribe; this.next = next; } @Override public String toString() { return "LanguageNode{" + "no=" + no + ", language='" + language + '\'' + ", languageDescribe='" + languageDescribe + '\'' + '}'; } }

    2.2 新增节点

    我们定义个SingleLinkedList链表管理器,在链表管理器中完成链表的增删改查操作。

    下面我们实现链表的新增操作:

    第一种方式在添加语言时,直接添加在链表的尾部

    思路分析示意图:

    第二种在添加语言时,根据排名将语言插入到相应的位置(如果有这个排名,则添加失败,并给出提示)

    思路分析示意图

    代码实现:

    /** * 根据顺序添加节点 * * @param node 添加数据 * @return 当前节点 */ public LanguageNode addByOrder(LanguageNode node) { //因为头节点不能移动,定义临时变量tmp LanguageNode tmp = this.head; //定义链表添加标识符 boolean flag = false; while (true) { if (tmp.next == null) { break; } if (tmp.next.no > node.no) { break; } else if (tmp.next.no == node.no) { //说明该顺序已经存在 flag = true; break; } tmp = tmp.next; } if (flag) { System.out.printf("插入的位置%d已存在,不能插入\n", node.no); } else { node.next = tmp.next; tmp.next = node; } return node; } /** * 添加节点 * * @param node 添加数据 * @return */ public LanguageNode add(LanguageNode node) { //因为头节点不能移动,定义临时变量tmp LanguageNode tmp = this.head; //将数据添加到最后,便利链表 while (tmp.next != null) { tmp = tmp.next; } tmp.next = node; return node; }

    测试:

    首先使用add尾部添加 public static void main(String[] args) { SingleLinkedList linkedList = new SingleLinkedList(); LanguageNode java = new LanguageNode(1, "java", "超级好用"); LanguageNode python = new LanguageNode(2, "python", "python"); LanguageNode goLang = new LanguageNode(3, "goLang", "goLang"); LanguageNode js = new LanguageNode(4, "js", "js"); LanguageNode php = new LanguageNode(5, "php", "php是世界上最好的语言"); linkedList.add(python); linkedList.add(java); linkedList.add(js); linkedList.add(php); linkedList.add(goLang); linkedList.list(); }

    执行结果如下:

    从结果看出:打印的顺序和我们插入的顺序一致,没有按照no进行排序操作

    使用addByOrder测试 public static void main(String[] args) { SingleLinkedList linkedList = new SingleLinkedList(); LanguageNode java = new LanguageNode(1, "java", "超级好用"); LanguageNode python = new LanguageNode(2, "python", "python"); LanguageNode goLang = new LanguageNode(3, "goLang", "goLang"); LanguageNode js = new LanguageNode(4, "js", "js"); LanguageNode php = new LanguageNode(5, "php", "php是世界上最好的语言"); linkedList.addByOrder(python); linkedList.addByOrder(java); linkedList.addByOrder(js); linkedList.addByOrder(php); linkedList.addByOrder(goLang); linkedList.list(); }

    执行结果如下:

    从结果看出:打印的顺序和我们插入的顺序不致,按照no进行排序插入。

    2.3 修改节点信息

    思路:修改节点功能比较简单,通过遍历先找到该节点信息,在对该节点内容进行修改即可。 代码实现

    public void update(LanguageNode node) { LanguageNode tmp = this.head; if (tmp.next == null) { System.out.println("链表为空"); return; } while (true) { if (tmp.next == null) { System.out.println("被修改的数据不存在"); break; } if (tmp.next.no == node.no) { node.next = tmp.next.next; tmp.next = node; break; } tmp = tmp.next; } }

    2.4 删除节点

    思路:先找到被删除的节点的前一个节点,定义为temp,我们将temp.next=temp.next.next将节点的饮用指要删除的向下个节点。

    代码实现:

    public void delete(LanguageNode node) { LanguageNode tmp = this.head; if (tmp.next == null) { System.out.println("链表为空"); return; } while (true) { if (tmp.next == null) { System.out.println("被删除的数据不存在"); break; } if (tmp.next.no == node.no) { tmp.next = tmp.next.next; break; } tmp = tmp.next; } }

    以上完成了链表的增删改查;

    附录:完整代码

    public class SingleLinkedList { /** * 定义头节点 */ private LanguageNode head = new LanguageNode(0, "", ""); public static void main(String[] args) { SingleLinkedList linkedList = new SingleLinkedList(); LanguageNode java = new LanguageNode(1, "java", "超级好用"); LanguageNode python = new LanguageNode(2, "python", "python"); LanguageNode goLang = new LanguageNode(3, "goLang", "goLang"); LanguageNode js = new LanguageNode(4, "js", "js"); LanguageNode php = new LanguageNode(5, "php", "php是世界上最好的语言"); LanguageNode node = new LanguageNode(4, "node", "node"); linkedList.addByOrder(python); linkedList.addByOrder(java); linkedList.addByOrder(js); linkedList.addByOrder(php); linkedList.addByOrder(goLang); linkedList.update(node); linkedList.delete(node); linkedList.list(); } public void delete(LanguageNode node) { LanguageNode tmp = this.head; if (tmp.next == null) { System.out.println("链表为空"); return; } while (true) { if (tmp.next == null) { System.out.println("被删除的数据不存在"); break; } if (tmp.next.no == node.no) { tmp.next = tmp.next.next; break; } tmp = tmp.next; } } public void update(LanguageNode node) { LanguageNode tmp = this.head; if (tmp.next == null) { System.out.println("链表为空"); return; } while (true) { if (tmp.next == null) { System.out.println("被修改的数据不存在"); break; } if (tmp.next.no == node.no) { node.next = tmp.next.next; tmp.next = node; break; } tmp = tmp.next; } } /** * 根据顺序添加节点 * * @param node 添加数据 * @return 当前节点 */ public LanguageNode addByOrder(LanguageNode node) { //因为头节点不能移动,定义临时变量tmp LanguageNode tmp = this.head; //定义链表添加标识符 boolean flag = false; while (true) { if (tmp.next == null) { break; } if (tmp.next.no > node.no) { break; } else if (tmp.next.no == node.no) { //说明该顺序已经存在 flag = true; break; } tmp = tmp.next; } if (flag) { System.out.printf("插入的位置%d已存在,不能插入\n", node.no); } else { node.next = tmp.next; tmp.next = node; } return node; } /** * 添加节点 * * @param node 添加数据 * @return */ public LanguageNode add(LanguageNode node) { //因为头节点不能移动,定义临时变量tmp LanguageNode tmp = this.head; //将数据添加到最后,便利链表 while (tmp.next != null) { tmp = tmp.next; } tmp.next = node; return node; } public void list() { LanguageNode tmp = this.head; if (tmp.next == null) { System.out.println("链表为空"); } while (tmp.next != null) { System.out.println(tmp.next); tmp = tmp.next; } } /** * 单链表节点 */ public static class LanguageNode { /** * 排序 */ private int no; /** * 语言名称 */ private String language; /** * 语言描述 */ private String languageDescribe; /** * 下个节点 */ private LanguageNode next; public LanguageNode(int no, String language, String languageDescribe) { this.no = no; this.language = language; this.languageDescribe = languageDescribe; this.next = null; } public int getNo() { return no; } public String getLanguage() { return language; } public String getLanguageDescribe() { return languageDescribe; } public LanguageNode getNext() { return next; } @Override public String toString() { return "LanguageNode{" + "no=" + no + ", language='" + language + '\'' + ", languageDescribe='" + languageDescribe + '\'' + '}'; } } }

    3、关于单链表的经典面试题

    3.1 求单链表中有效节点个数

    代码实现:

    public static int getLength(LanguageNode head) { if (head.next == null) { return 0; } LanguageNode tmp = head; int length = 0; while (tmp.next != null) { length++; tmp = tmp.next; } return length; }

    3.2 查找单链表中的倒数第K个节点

    解析思路:

    编写一个方法同时接收head节点和index节点index表示倒数第index个节点先把链表从头遍历一遍,得到链表的总长度为length,这里可以使用上面的getLength函数得到size后,我们可以从链表的第一个开始遍历(size-index)个,就可以得到如果找到该节点就返回,否则返回null 代码实现: public static LanguageNode findLastIndexNode(LanguageNode head, int index) { if (head.next == null) { return null; } int size = getLength(head); if (index < 0 || index > size) { return null; } LanguageNode tmp = head.next; for (int i = 0; i < size - index; i++) { tmp = tmp.next; } return tmp; }

    3.3 单链表的反转

    实现思路:

    先定义一个节点reverseHead = new LanguageNode();从头到尾遍历原来的链表,每个遍历节点,将其取出,并放在新的链表先定义一个节点reverseHead的最前端。原来的链表的head.next = reverseHead.next

    代码实现

    /** * 单链表反转 * * @param head 原始链表 * @return */ static void reverseList(LanguageNode head) { //一个节点不需要转换 if (head.next == null || head.next.next == null) { return; } //定义辅助变量 LanguageNode reverseNode = new LanguageNode(0, "", ""); LanguageNode cur = head.next; LanguageNode next = null; while (cur != null) { //保存当前节点的下一个节点 next = cur.next; //将cut的下一个节点,指向新链表的最前端 cur.next = reverseNode.next; //将 cur 连接到新的链表上 reverseNode.next = cur; cur = next; } head.next = reverseNode.next; } public static void main(String[] args) { SingleLinkedList linkedList = new SingleLinkedList(); LanguageNode java = new LanguageNode(1, "java", "超级好用"); LanguageNode python = new LanguageNode(2, "python", "python"); LanguageNode goLang = new LanguageNode(3, "goLang", "goLang"); LanguageNode js = new LanguageNode(4, "js", "js"); LanguageNode php = new LanguageNode(5, "php", "php是世界上最好的语言"); LanguageNode node = new LanguageNode(4, "node", "node"); linkedList.addByOrder(python); linkedList.addByOrder(java); linkedList.addByOrder(js); linkedList.addByOrder(php); linkedList.addByOrder(goLang); linkedList.update(node); System.out.println("反转前链表:"); linkedList.list(); reverseList(linkedList.getHead()); System.out.println("反转后链表:"); linkedList.list(); } //结果: 反转前链表: LanguageNode{no=1, language='java', languageDescribe='超级好用'} LanguageNode{no=2, language='python', languageDescribe='python'} LanguageNode{no=3, language='goLang', languageDescribe='goLang'} LanguageNode{no=4, language='node', languageDescribe='node'} LanguageNode{no=5, language='php', languageDescribe='php是世界上最好的语言'} 反转后链表: LanguageNode{no=5, language='php', languageDescribe='php是世界上最好的语言'} LanguageNode{no=4, language='node', languageDescribe='node'} LanguageNode{no=3, language='goLang', languageDescribe='goLang'} LanguageNode{no=2, language='python', languageDescribe='python'} LanguageNode{no=1, language='java', languageDescribe='超级好用'}

    从尾到头打印单链表(方法1、反向遍历,2、Stack栈)

    实现思路:

    方式一 可以先进行链表反转,在进行便利打印,反转会破坏原始链表结构,不推荐使用 方式二 可以利用栈的数据结构,将各个节点压入打栈中,然后利用栈的先进后出的特点,实现逆序打印。

    代码实现

    static void reversePrint(LanguageNode head) { if (head.next == null) { return; } Stack<LanguageNode> stack = new Stack<>(); while (head.next != null) { stack.push(head.next); head = head.next; } while (stack.size() > 0) { System.out.println(stack.pop()); } } public static void main(String[] args) { SingleLinkedList linkedList = new SingleLinkedList(); LanguageNode java = new LanguageNode(1, "java", "超级好用"); LanguageNode python = new LanguageNode(2, "python", "python"); LanguageNode goLang = new LanguageNode(3, "goLang", "goLang"); LanguageNode js = new LanguageNode(4, "js", "js"); LanguageNode php = new LanguageNode(5, "php", "php是世界上最好的语言"); LanguageNode node = new LanguageNode(4, "node", "node"); linkedList.addByOrder(python); linkedList.addByOrder(java); linkedList.addByOrder(js); linkedList.addByOrder(php); linkedList.addByOrder(goLang); linkedList.update(node); System.out.println("正序打印:"); linkedList.list(); System.out.println("倒叙序打印:"); reversePrint(linkedList.getHead()); } //输出结果 正序打印: LanguageNode{no=1, language='java', languageDescribe='超级好用'} LanguageNode{no=2, language='python', languageDescribe='python'} LanguageNode{no=3, language='goLang', languageDescribe='goLang'} LanguageNode{no=4, language='node', languageDescribe='node'} LanguageNode{no=5, language='php', languageDescribe='php是世界上最好的语言'} 倒叙序打印: LanguageNode{no=5, language='php', languageDescribe='php是世界上最好的语言'} LanguageNode{no=4, language='node', languageDescribe='node'} LanguageNode{no=3, language='goLang', languageDescribe='goLang'} LanguageNode{no=2, language='python', languageDescribe='python'} LanguageNode{no=1, language='java', languageDescribe='超级好用'}
    Processed: 0.010, SQL: 8