链表 一、链表介绍
链表是以节点的方式来存储,是链式存储
每个节点包含 data 域, next 域:指向下一个节点.
如图:发现链表的各个节点不一定是连续存储.
链表分带头节点的链表和没有头节点的链表,根据实际的需求来确定
单链表
链表通过指针将一组零散的内存块串联在一起,内存块称为链表的“结点”。每个链表的结点除了存储数据之外,还需要记录链上的下一个结点的地址,叫作后继指针 next。链表有两个特殊的结点,分别是第一个结点(头结点)和最后一个结点(尾结点)。头结点用来记录链表的基地址,用它可以遍历得到整条链表。尾结点指向一个空地址 NULL,表示这是链表上最后一个结点。
双向链表
双向链表支持两个方向,每个结点同时有后继指针 next 指向后面的结点,还有一个前驱指针 pre 指向前面的结点。双向链表需要额外的两个空间来存储后继结点和前驱结点的地址,存储同样的数据,双向链表要比单链表占用更多的内存空间。
循环链表
循环链表跟单链表的区在尾结点指针是指向链表的头结点。和单链表相比,循环链表的优点是从链尾到链头比较方便。当要处理的数据具有环型结构特点时,采用循环链表实现代码会简洁很多。
内存结构图
单链表(带头结点) 逻辑结构图
二、单链表应用实例
使用head头的单链表实现对水浒传英雄的增删改查等操作
测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class SingleLinkedListDemo { public static void main (String[] args) { HeroNode hero1 = new HeroNode(1 , "宋江" , "及时雨" ); HeroNode hero2 = new HeroNode(2 , "卢俊义" , "玉麒麟" ); HeroNode hero3 = new HeroNode(3 , "吴用" , "智多星" ); HeroNode hero4 = new HeroNode(4 , "林冲" , "豹子头" ); SingleLinkedList singleLinkedList = new SingleLinkedList(); singleLinkedList.addByOrder(hero1); singleLinkedList.addByOrder(hero4); singleLinkedList.addByOrder(hero3); singleLinkedList.addByOrder(hero2); singleLinkedList.list(); System.out.println("修改2号:" ); HeroNode newHeroNode = new HeroNode(2 , "小卢" , "玉麒麟。。。" ); singleLinkedList.update(newHeroNode); System.out.println("修改后情况!" ); singleLinkedList.list(); System.out.println("删除1号:" ); singleLinkedList.delete(1 ); System.out.println("删除后的链表情况" ); singleLinkedList.list(); } }
(1)添加节点
第一种方式,直接添加到链表的尾部
第二种方式,插入节点,可以根据排名插入到指定位置
思路:
需要按照编号的顺序添加
首先找到新添加的节点的位置, 是通过辅助变量(指针), 通过遍历来搞定
newNode.next = temp.next
将temp.next = newNode
(2)删除节点 思路:
我们先找到 需要删除的这个节点的前一个节点 temp
temp.next = temp.next.next
被删除的节点,将不会有其它引用指向,会被垃圾回收机制回收
(3)修改节点 思路:
通过遍历找到该节点,然后直接重新赋值
temp.data= newNode.data
(4)查询节点 链表的随机访问第 k 个元素,必须根据指针一个结点一个结点地依次遍历,直到找到相应的结点。
详细代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 class SingleLinkedList { private HeroNode head = new HeroNode(0 , "" , "" ); public void add (HeroNode heroNode) { HeroNode temp = head; while (true ) { if (temp.next == null ) { break ; } temp = temp.next; } temp.next = heroNode; } public void addByOrder (HeroNode heroNode) { HeroNode temp = head; boolean flag = false ; while (true ) { if (temp.next == null ) { break ; } if (temp.next.no > heroNode.no) { break ; } else if (temp.next.no == heroNode.no) { flag = true ; break ; } temp = temp.next; } if (flag) { System.out.println("准备插入的编号" + heroNode.no +"已经存在了" ); } else { heroNode.next = temp.next; temp.next = heroNode; } } public void update (HeroNode newHeroNode) { if (head.next == null ) { System.out.println("链表为空" ); return ; } HeroNode temp = head.next; boolean flag = false ; while (true ) { if (temp == null ) { break ; } if (temp.no == newHeroNode.no) { flag = true ; break ; } temp = temp.next; } if (flag) { temp.name = newHeroNode.name; temp.nickname = newHeroNode.nickname; } else { System.out.println("没有找到编号为" +newHeroNode.no + "的节点" ); } } public void delete (int no) { HeroNode temp = head; boolean flag = false ; while (true ) { if (temp.next == null ) { break ; } 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 list () { if (head.next == null ) { System.out.println("链表为空" ); return ; } HeroNode temp = head.next; while (true ) { if (temp == null ) { break ; } System.out.println(temp); temp = temp.next; } } } 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; } @Override public String toString () { return "HeroNode [no = " + no +", name = " + name +", nickname = " + nickname +"]" ; } }
效果展示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 HeroNode [no = 1 , name = 宋江, nickname = 及时雨] HeroNode [no = 2 , name = 卢俊义, nickname = 玉麒麟] HeroNode [no = 3 , name = 吴用, nickname = 智多星] HeroNode [no = 4 , name = 林冲, nickname = 豹子头] 修改2 号: 修改后情况! HeroNode [no = 1 , name = 宋江, nickname = 及时雨] HeroNode [no = 2 , name = 卢, nickname = 玉麒麟] HeroNode [no = 3 , name = 吴用, nickname = 智多星] HeroNode [no = 4 , name = 林冲, nickname = 豹子头] 删除1 号 删除后的链表情况 HeroNode [no = 2 , name = 卢, nickname = 玉麒麟] HeroNode [no = 3 , name = 吴用, nickname = 智多星] HeroNode [no = 4 , name = 林冲, nickname = 豹子头]
三、双链表应用实例 (1)添加节点
第一种方式,添加到尾部
思路:
先找到双向链表的最后这个节点temp
temp.next = newNode
newNode.pre = temp;
第二种方式,插入节点,可以根据排名插入到指定位置
思路:
需要按照编号的顺序添加
首先找到新添加的节点的位置, 是通过辅助变量(指针), 通过遍历来搞定
👉需要注意下面代码的先后顺序
1 2 3 4 5 6 newNode.next = temp.next; if (temp.next != null ) { temp.next.pre = newNode; } temp.next = newNode; newNode.pre = temp;
(2)删除节点 思路:
因为是双向链表,因此,我们可以实现自我删除某个节点
直接找到要删除的这个节点,比如temp
temp.pre.next = temp.next
temp.next.pre = temp.pre;
(3)修改节点 👉双链表修改节点思路和单链表思路一样
(4)遍历节点 👉遍历方式和单链表一样,可以向前遍历也可以向后遍历
代码实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 public class DoubleLinkedListDemo { public static void main (String[] args) { System.out.println("双向链表" ); HeroNode2 hero1 = new HeroNode2(1 , "宋江" , "及时雨" ); HeroNode2 hero2 = new HeroNode2(2 , "卢俊义" , "玉麒麟" ); HeroNode2 hero3 = new HeroNode2(3 , "吴用" , "智多星" ); HeroNode2 hero4 = new HeroNode2(4 , "林冲" , "豹子头" ); DoubleLinkedList doubleLinkedList = new DoubleLinkedList(); doubleLinkedList.addByOrder(hero1); doubleLinkedList.addByOrder(hero4); doubleLinkedList.addByOrder(hero3); doubleLinkedList.addByOrder(hero2); doubleLinkedList.list(); System.out.println("修改4号" ); HeroNode2 newHeroNode = new HeroNode2(4 , "林冲..." , "豹子..." ); doubleLinkedList.update(newHeroNode); System.out.println("修改后:" ); doubleLinkedList.list(); System.out.println("删除吴用" ); doubleLinkedList.delete(3 ); System.out.println("删除后:" ); doubleLinkedList.list(); } } class DoubleLinkedList { private HeroNode2 head = new HeroNode2(0 , "" , "" ); public HeroNode2 getHead () { return head; } public void add (HeroNode2 heroNode) { HeroNode2 temp = head; while (true ) { if (temp.next == null ) { break ; } temp = temp.next; } temp.next = heroNode; heroNode.pre = temp; } public void addByOrder (HeroNode2 heroNode) { HeroNode2 temp = head; boolean flag = false ; while (true ) { if (temp.next == null ) { break ; } if (temp.next.no > heroNode.no) { break ; } else if (temp.next.no == heroNode.no) { flag = true ; break ; } temp = temp.next; } if (flag) { System.out.println("准备插入的编号" + heroNode.no +"已经存在了" ); } else { heroNode.next = temp.next; if (temp.next != null ) { temp.next.pre = heroNode; } temp.next = heroNode; heroNode.pre = temp; } } public void update (HeroNode2 newHeroNode) { if (head.next == null ) { System.out.println("链表为空" ); return ; } HeroNode2 temp = head.next; boolean flag = false ; while (true ) { if (temp == null ) { break ; } if (temp.no == newHeroNode.no) { flag = true ; break ; } temp = temp.next; } if (flag) { temp.name = newHeroNode.name; temp.nickname = newHeroNode.nickname; } else { System.out.println("没有找到编号为" +newHeroNode.no + "的节点" ); } } public void delete (int no) { if (head.next == null ) { System.out.println("链表为空,无法删除" ); return ; } HeroNode2 temp = head.next; boolean flag = false ; while (true ) { if (temp == null ) { break ; } if (temp.no == no) { flag = true ; break ; } temp = temp.next; } if (flag) { temp.pre.next = temp.next; if (temp.next != null ) { temp.next.pre = temp.pre; } }else { System.out.println("要删除的" + no +"这个节点不存在" ); } } public void list () { if (head.next == null ) { System.out.println("链表为空" ); return ; } HeroNode2 temp = head.next; while (true ) { if (temp == null ) { break ; } System.out.println(temp); temp = temp.next; } } } class HeroNode2 { public int no; public String name; public String nickname; public HeroNode2 next; public HeroNode2 pre; public HeroNode2 (int no, String name, String nickname) { this .no = no; this .name = name; this .nickname = nickname; } @Override public String toString () { return "HeroNode [no = " + no +", name = " + name +", nickname = " + nickname +"]" ; } }
实现效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 双向链表 HeroNode [no = 1 , name = 宋江, nickname = 及时雨] HeroNode [no = 2 , name = 卢俊义, nickname = 玉麒麟] HeroNode [no = 3 , name = 吴用, nickname = 智多星] HeroNode [no = 4 , name = 林冲, nickname = 豹子头] 修改4 号 修改后: HeroNode [no = 1 , name = 宋江, nickname = 及时雨] HeroNode [no = 2 , name = 卢俊义, nickname = 玉麒麟] HeroNode [no = 3 , name = 吴用, nickname = 智多星] HeroNode [no = 4 , name = 林冲..., nickname = 豹子...] 删除吴用 删除后: HeroNode [no = 1 , name = 宋江, nickname = 及时雨] HeroNode [no = 2 , name = 卢俊义, nickname = 玉麒麟] HeroNode [no = 4 , name = 林冲..., nickname = 豹子...]
四、单向环形链表应用场景
Josephu(约瑟夫、约瑟夫环)问题
详细可以看这篇
单向环形链表和Josephu
加油!!!😘😘