单向链表 增删改遍历,以及部分题目练习

6. 链表(Linked List)

6.1 链表(Linked List)介绍

 

  1. 链表是以字节的方式来存储,链式存储

  2. 每个字节包含data域:存储数据,next域:指向下一个节点

  3. 如图,发现链表的各个节点不一定是连续存放的(连续存储),而且是无序的

  4. 链表分为带头结点的链表,和没有头结点的链表,根据需求决定

6.2 单链表

(逻辑结构示意图)

head(头节点):

  • 不存放具体的数据

  • 作用就是表示单链表的表头

6.2.1创建(添加)

  1. 先创建一个head头节点,作用就是单链表的头

  2. 后面我们每添加一个节点,就直接加入到链表的最后遍历

  3. 通过一个辅助遍历,帮助遍历整个链表

 1 package linkedlist;
 2  3 public class SingleLinkedListDemo {
 4     public static void main(String[] args) {
 5         // 进行测试
 6         // 先创建节点
 7         HeroNode hero1 = new HeroNode(1,"宋江", "及时雨");
 8         HeroNode hero2 = new HeroNode(2,"卢俊义", "玉麒麟");
 9         HeroNode hero3 = new HeroNode(3,"吴用", "智多星");
10         HeroNode hero4 = new HeroNode(4,"林冲", "豹子头");
11         HeroNode hero5 = new HeroNode(5,"武松", "行者");
12 13         // 创建一个链表
14         SingleLinkedList singleLinkedList = new SingleLinkedList();
15         // 加入
16         singleLinkedList.add(hero1);
17         singleLinkedList.add(hero2);
18         singleLinkedList.add(hero3);
19         singleLinkedList.add(hero4);
20         singleLinkedList.add(hero5);
21 22         // 显示
23         singleLinkedList.list();
24     }
25 }
26 // 定义SingleLinkedList管理我们的英雄
27 class SingleLinkedList{
28     // 先初始化一个头节点,头节点不要动, 不存放具体数据
29     private HeroNode head = new HeroNode(0,"","");
30 31     // 添加节点到单向链表
32     // 思路:当不考虑编号顺序时
33     // 1. 找到当前链表的最后节点
34     // 2. 将最后这个节点的next 指向 新的节点
35     public void add(HeroNode heroNode){
36         // 因为头节点不能东,因此我们需要一个辅助指针完成遍历
37         HeroNode temp = head;
38         // 遍历链表,找到最后
39         while (true){
40             // 找到链表的最后
41             if (temp.next == null){
42                 break;
43             }
44 45             // 如果没有找到,将temp后移
46             temp = temp.next;
47         }
48         // 当退出while循环时,temp就指向了链表的最后
49         // 将最后这个节点的next 指向新的节点
50         temp.next = heroNode;
51     }
52     // 显示链表[遍历]
53     public void list(){
54         // 判断链表是否为空
55         if (head.next == null){
56             System.out.println("链表为空");
57             return;
58         }
59         // 因为头节点不能东,因此我们需要一个辅助变量来遍历
60         HeroNode temp = head.next;
61         while (true){
62             // 判断是否到链表最后
63             if (temp == null){
64                 break;
65             }
66             // 输出节点信息
67             System.out.println(temp);
68             // 将temp后移,一定要后移,不然是个死循环
69             temp = temp.next;
70         }
71     }
72 }
73 // 定义HeroNode
74 class HeroNode{
75     public int no;
76     public String name;
77     public String nickname;
78     public HeroNode next; // 指向下一个节点
79 80     public HeroNode(int no, String name, String nickname) {
81         this.no = no;
82         this.name = name;
83         this.nickname = nickname;
84 85     }
86     // 为了显示方法,我们重写toString
87     @Override
88     public String toString() {
89         return "HeroNode{" +
90                 "no=" + no +
91                 ", name='" + name + '\'' +
92                 ", nickname='" + nickname +
93                 '}';
94     }
95 }

 


需要按照编号添加

  1. 首先需要找到要添加节点的位置,是通过辅助变量(指针),通过遍历来实现

  2. 新的节点.next = temp.next

  3. temp.next = 新的节点

 1 // 第二种方式在添加英雄时,根据排名将英雄插入到指定位置
 2     // (如果有这个排名,则添加失败,并给出提示)
 3     public void addByOrder(HeroNode heroNode){
 4         // 因为头节点不能动,因此我们仍然通过一个辅助指针完成遍历
 5         // 因为单链表,因为我们找的temp,是位于添加位置的前一个节点,否则插入不了
 6         HeroNode tenp = head;
 7         boolean flag = false; // 标识添加的编号是否存在,默认为false
 8         while (true){
 9             if (tenp.next == null){
10                 // 说明temp已经在链表的最后
11                 break;
12             }
13             if (tenp.next.no > heroNode.no){
14                 // 位置找到了,就在temp的后面插入
15                 break;
16             } else if (tenp.next.no == heroNode.no){
17                 // 说明希望添加的heroNode的编号已存在
18                 flag = true;
19                 break;
20             }
21             // 后移,遍历当前链表
22             tenp = tenp.next;
23         }
24         // 判单flag 的值
25         if (flag){
26             // 不能添加,说明编号存在
27             System.out.println("准备插入的英雄的编号"+heroNode.no+"已经存在");
28         }else {
29             // 插入到链表中,temp的后面
30             heroNode.next = tenp.next;
31             tenp.next = heroNode;
32         }
33     }

 

6.2.2修改

 1 // 完成修改节点的信息,根据no编号来修改,即no不能修改
 2     // 根据newHeroNode的no进行修改
 3     public void update(HeroNode newHeroNode){
 4         if (head.next == null){
 5             System.out.println("链表为空");
 6             return;
 7         }
 8         // 找到需要修改的节点,根据no编号
 9         // 定义一个辅助变量
10         HeroNode temp = head;
11         boolean flag = false; //  表示找到该节点
12         while (true){
13             if (temp == null){
14                 break; // 链表的遍历结束了
15             }
16             if (temp.no == newHeroNode.no){
17                 // 找到
18                 flag = true;
19                 break;
20             }
21             temp = temp.next;
22         }
23         // 判断是否找到要修改的节点
24         if (flag){
25             temp.name = newHeroNode.name;
26             temp.nickname = newHeroNode.nickname;
27         }else{
28             // 没有找到
29             System.out.println("没有找到编号为 "+newHeroNode.no+" 的节点,不能修改");
30         }
31     }

 

6.2.3删除

  1. 我们先找到需要删除的这个节点的前一个节点 temp,temp指向待删除节点的前一个节点

  2. temp.next = temp.next.next

  3. 被删除的节点将不会有其他引用指向,会被垃圾回收机制回收

 1 // 删除节点
 2     // head 不能动,依次我们需要一个temp辅助节点找到待删除节点的前一个节点
 3     // 我们在比较时。是temp.next.no 和 需要删除的节点的no比较
 4     public void deleteNode(int no){
 5         HeroNode temp = head;
 6         boolean flag = false; //  是否找到待删除节点的
 7         while (true){
 8             if (temp.next == null){
 9                 // 已经到链表的最后
10                 break;
11             }
12             if (temp.next.no == no){
13                 flag = true; // 找到待删除节点的前一个节点
14                 break;
15             }
16             // temp后移,完成遍历
17             temp = temp.next;
18         }
19         if (flag){
20             temp.next = temp.next.next;
21         } else {
22             System.out.println("没有找到 "+no+" 的信息");
23         }
24     }

 

6.2.4 小结

  1 package LinckedList;
  2   3 public class SingleLinkedListDemo {
  4     public static void main(String[] args) {
  5         SingleLinkedList s1 = new SingleLinkedList();
  6         Node n1 = new Node(1,"xiao");
  7         Node n2 = new Node(2,"biao");
  8         Node n3 = new Node(3,"diao");
  9  10         s1.addByOrder(n1);
 11         s1.addByOrder(n3);
 12         s1.addByOrder(n2);
 13         System.out.println("添加");
 14         s1.list();
 15  16         System.out.println("修改");
 17         Node n4 = new Node(3,"Xdiao");
 18         s1.updata(n4);
 19         s1.list();
 20  21         System.out.println("删除");
 22         s1.del(3);
 23         s1.list();
 24     }
 25 }
 26 // 定义一个SingleLinkedList
 27 class SingleLinkedList{
 28     // 先初始化一个头节点
 29     private Node head = new Node(0,"");
 30     
 31     // 获取头节点
 32     public Node getNode(){
 33         return head;
 34     }
 35     
 36     // 添加节点
 37     public void addByOrder(Node node){
 38         Node temp = head;
 39         boolean flag = false; // 添加的元素是否存在
 40         while(true){
 41             if (temp.next == null){
 42                 break;
 43             }
 44             if (temp.next.no > node.no){
 45                 break;
 46             } else if (temp.next.no == node.no){
 47                 flag = true;
 48                 break;
 49             }
 50             // 如果没找到最后
 51             temp = temp.next;
 52         }
 53         if (flag == false){
 54             node.next = temp.next;
 55             temp.next = node;
 56  57         }else{
 58             System.out.println("编号存在");
 59         }
 60     }
 61     // 修改链表
 62     public void updata(Node node){
 63         Node temp = head.next;
 64         boolean flag = false; // 判断是否找到当前值
 65         while (true){
 66             if (temp == null){
 67                 break;
 68             }
 69             if (temp.no == node.no){
 70                 flag = true;
 71                 break;
 72             }
 73             temp = temp.next;
 74         }
 75         if (flag){
 76             temp.no = node.no;
 77             temp.name = node.name;
 78         }else {
 79             System.out.println("不存在");
 80         }
 81     }
 82  83     // 删除链表
 84     public void del(int no){
 85         Node node = head.next;
 86         boolean flag = false;
 87         while (true){
 88             if (node == null){
 89                 break;
 90             }
 91             if (node.next.no == no){
 92                 flag = true;
 93                 break;
 94             }
 95             node = node.next;
 96         }
 97         if (flag){
 98             node.next = node.next.next;
 99         }else{
100             System.out.println("编号不存在");
101         }
102     }
103     // 显示链表
104     public void list(){
105         if (head.next == null){
106             System.out.println("链表为空");
107             return;
108         }
109         Node temp = head.next;
110         while (true){
111             if (temp == null){
112                 break;
113             }
114             System.out.println(temp.toString());
115             temp = temp.next;
116         }
117     }
118 119 120 }
121 // 定义一个Node 对象
122 class Node{
123     public String name;
124     public int no;
125     // 指向下一个
126     public Node next;
127 128     public Node(int no, String name) {
129         this.name = name;
130         this.no = no;
131     }
132 133     @Override
134     public String toString() {
135         return "Node{" +
136                 "name='" + name + '\'' +
137                 ", no=" + no +
138                 '}';
139     }
140 }

 

6.2.5 单链表练习(方便起见,使用上述链表)

6.2.5.1获取单链表的节点个数(如果是带头节点的链表,不需要统计头节点
    
 1 // 获取单链表的节点个数(如果是带头节点的链表,不需要统计头节点)
 2     public static int getLength(Node head){
 3         if (head.next == null){
 4             return 0;
 5         }
 6         int length = 0;
 7         // 辅助节点
 8         Node temp = head.next;
 9         while (temp != null){
10             length++;
11             temp = temp.next;// 遍历
12         }
13         return length;
14     }

 

6.2.5.2获取单链表倒数第k个元素
 1 // 2.查找单链表中倒数第k个节点
 2     // 思路:
 3     // 1. 编写一个方法,用来接受head节点,同时接受一个index
 4     // 2. index 表示是倒数第index个节点
 5     // 3. 先把链表从头到位遍历,得到链表的总的长度
 6     // 4. 得到size后,我们从链表的第一个开始遍历(size - index)个,就可以得到
 7     // 5. 如果找到就返回节点,否则返回空
 8     public static Node findeLastIndexNode(Node head, int index){
 9         // 判断为空,返回null
10         if (head.next == null){
11             return null;
12         }
13         // 第一个遍历得到链表的长度(节点个数)
14         int size = getLength(head);
15         // 第二遍遍历,size-index 位置, 就是我们倒数的第K个节点
16         // 先做一个index校验
17         if (index <= 0 || index > size){
18             return null;
19         }
20         // 定义一个辅助变量
21         Node cur = head.next;
22         for (int i = 0; i < size - index; i++){
23             cur = cur.next;
24         }
25         return cur;
26     }

 

6.2.5.3 链表的反转(腾讯面试题)

思路:

  1. 先去创建定义一个节点,reverseHead = new Node()

  2. 从头到尾遍历原来的链表,每遍历一个节点,就将其取出,并放进新的链表

  3. 原来的链表的head.next = reverseHead.next

 1 // 将单链表进行反转
 2     public static void reversetList(Node head){
 3         // 如果当前列表为空,或者只有一个节点,无需反转,直接返回
 4         if (head.next == null || head.next.next == null){
 5             return;
 6         }
 7         // 定义一个而辅助的指针,帮助我们遍历原来的链表
 8         Node cur = head.next;
 9         // 指向当前节点的下一个
10         Node next = null;
11         Node reverseHead = new Node(0,"");
12         // 遍历原来的链表
13         while (cur != null){
14             next = cur.next; // 暂时保存当前节点的下一个
15             cur.next = reverseHead.next; // 将cur的下一个节点指向新的链表的头补
16             reverseHead.next = cur; // 将节点连接上新的链表上
17             cur = next; // 让cur后移
18         }
19         // 将head.next 指向 reverseHead,实现反转
20         head.next = reverseHead.next;
21     }

 

6.2.5.4 从尾到头打印链表(百度面试)

思路:

  1. 上面的题是逆序打印单链表

  2. 方式一:先将单链表进行反转操作,然后遍历打印即可,这样的问题是会破坏原来的单链表的结构,不建议

  3. 方式2:可以利用这个数据结构,将各个节点压入栈中,然后利用栈的先进后出的特点,就实现了逆序打印

// 先简单了解下什么叫做栈
import java.util.Stack;
​
// 演示栈Stack的基本使用
public class TeskStack {
    public static void main(String[] args) {
        Stack<String> stack = new Stack<>();
        // 入栈
        stack.add("jack");
        stack.add("tom");
        stack.add("smith");
        // 出栈
        while (stack.size() > 0){
            // pop就是Stack的将栈顶部数据取出的方法
            System.out.println(stack.pop());
        }
    }
}
 1 // 4.逆序打印链表,使用栈
 2     public static void reversePrint(Node head){
 3         // 判断空链表,直接返回
 4         if (head.next == null){
 5             return;
 6         }
 7         // 创建一个栈,将各个节点压入栈
 8         Stack<Node> stack = new Stack<>();
 9         Node cur = head.next;
10         // 将链表的所有节点压入栈
11         while (cur != null){
12             stack.push(cur);
13             cur = cur.next;
14         }
15         // 将栈中的元素出栈使用pop
16         while (stack.size() > 0){
17             System.out.println(stack.pop()); // stack的特点是先进后出
18         }
19     }

 

 

posted @ 2021-07-31 10:23  笔锋  阅读(112)  评论(0)    收藏  举报