链表
1. 链表(Linked List)介绍
链表是有序的列表,但是它在内存中的存储如下图所示:
1)链表是以节点的方式来存储的,是链式存储
2)每个节点包含data域,next域:指向下一个节点
3)参照上图,发现链表的各个节点不一定是连续存储的
4)链表分为带头节点的链表和没有头节点的链表,根据实际的需求来确定
单链表(带头节点)逻辑结构示意图如下:
2. 单链表的应用实列
使用带 head 头的单向链表实现 –水浒英雄排行榜管理完成对英雄人物的增删改查操作。注意:只介绍增加、删除和修改,查询后面再介绍。
1)第一种方法:在添加英雄时,直接添加在链表的尾部。
思路示意图:
2) 第二种方式在添加英雄时,根据排名将英雄插入到指定位置(如果有这个排名,则添加失败,并给出提示)
思路分析示意图:
3) 修改节点
分析思路:
(1) 先找到该节点,通过遍历,
(2) temp.name = newHeroNode.name ;
temp.nickname= newHeroNode.nickname
4) 删除节点
思路分析示意图
5)代码实现
1 package com.hut.linkedlist; 2 3 import com.hut.linkedlist.HeroNode; 4 5 /* 6 * 使用带 head 头的单向链表实现 –水浒英雄排行榜管理完成对英雄人物的增删改查操作。 7 * */ 8 9 public class SingleLinkedListDemo { 10 11 public static void main(String[] args) { 12 //进行测试 13 //先创建节点 14 HeroNode hero1 = new HeroNode(1,"宋江","及时雨"); 15 HeroNode hero2 = new HeroNode(2,"卢俊义","玉麒麟"); 16 HeroNode hero3 = new HeroNode(3,"吴用","智多星"); 17 HeroNode hero4 = new HeroNode(4,"林冲","豹子头"); 18 19 //创建要给的链表 20 SingleLinkedList singleLinkedList = new SingleLinkedList(); 21 22 /* //加入,不会按照编号大小排列 23 singleLinkedList.add(hero1); 24 singleLinkedList.add(hero2); 25 singleLinkedList.add(hero3); 26 singleLinkedList.add(hero4); 27 //显示链表 28 singleLinkedList.list(); 29 */ 30 31 //加入和插入数据节点 32 //会按照编号的大小排列 33 singleLinkedList.addByOrder(hero1); 34 singleLinkedList.addByOrder(hero4); 35 singleLinkedList.addByOrder(hero3); 36 singleLinkedList.addByOrder(hero2); 37 //显示链表 38 singleLinkedList.list(); 39 40 //根据编号删除节点 41 System.out.println("======================="); 42 System.out.println("删除前的链表情况:"); 43 singleLinkedList.list(); 44 45 singleLinkedList.del(2); 46 System.out.println("删除后的链表情况:"); 47 singleLinkedList.list(); 48 49 } 50 51 } 52 53 54 //定义单链表SingleLinkedList 管理英雄 55 class SingleLinkedList{ 56 //先初始化一个头节点, 头节点不要动, 不存放具体的数据 57 private HeroNode head = new HeroNode(0,"",""); 58 59 //添加节点到单链表尾部 60 //思路,当不考虑编号顺序时, 61 //1、找到当前链表的最后一个节点;2、将最后节点的next指向新的节点 62 public void add(HeroNode newHeroNode) { 63 //因为head节点不能动,因此需要一个辅助变量遍历 temp 64 HeroNode temp = head; 65 //遍历链表的最后 66 while(true) { 67 //找到链表的最后 68 if(temp.next == null) { 69 break; 70 } 71 //如果没有找到,temp后移 72 temp = temp.next; 73 } 74 //当while循环结束后,temp就指向了链表的最后 75 //将最后节点的next指向新的节点 76 temp.next = newHeroNode; 77 } 78 79 //添加节点到单链表中 80 //在添加英雄时,根据编号no将英雄插入到指定的位置 81 //(如果有这个排名,则添加失败,并给出提示) 82 public void addByOrder(HeroNode newHeroNode) { 83 //因为头节点不能动,因此通过一个辅助指针(变量)来帮助找到添加的位置 84 //因为单链表,因为我们找的 temp 是位于 添加位置的前一个节点,否则插入不了 85 HeroNode temp = head; 86 boolean flag = false; //flag标志添加的编号是否存在,默认为false 87 while(true) { 88 if(temp.next == null) { //说明已经在链表的最后 89 break; 90 } 91 if(temp.next.no > newHeroNode.no) { //位置找到,就在temp的后面插入 92 break; 93 }else if(temp.next.no == newHeroNode.no) { //说明要添加的编号已经存在 94 flag = true; 95 break; 96 } 97 temp = temp.next; 98 } 99 //判断flag的值 100 if(flag) { //不能添加,编号已经存在 101 System.out.printf("插入英雄的编号%d已经存在,不能再次添加了。 ",newHeroNode.no); 102 }else { 103 //插入链表中,在temp后面 104 newHeroNode.next = temp.next; 105 temp.next=newHeroNode; 106 } 107 } 108 109 //修改节点信息 110 //根据编号no来修改,即no不能改变 111 public void update(HeroNode newHeroNode) { 112 //判断是否为空 113 if(head.next == null) { 114 System.out.println("链表为空~~"); 115 return; 116 } 117 //找到需要修改的节点,根据编号no 118 //定义一个辅助变量 119 HeroNode temp = head.next; 120 boolean flag = false;//表示是否找到该节点 121 while(true) { 122 if(temp == null) { 123 break; //已经遍历完毕 124 } 125 if(temp.no == newHeroNode.no) { //找到要修改的节点 126 flag = true; 127 break; 128 } 129 temp = temp.next; 130 } 131 //根据flag判断是否找到要修改的节点 132 if(flag) { 133 temp.name = newHeroNode.name; 134 temp.nickname = newHeroNode.nickname; 135 }else { 136 System.out.printf("没有找到编号为%d的节点,无法修改~~",newHeroNode.no); 137 } 138 } 139 140 //删除节点 141 //思路:1. head 不能动,因此我们需要一个 temp 辅助节点找到待删除节点的前一个节点 142 // 2. 在比较时,是 temp.next.no 和 需要删除的节点的 no 比较 143 public void del(int no) { 144 HeroNode temp = head; 145 boolean flag = false;//标志是否找到待删除的节点 146 while(true) { 147 if(temp.next == null) { //已经到了链表的最后 148 break; 149 } 150 if(temp.next.no == no) { //找到待删除的前一个节点temp 151 flag = true; 152 break; 153 } 154 temp = temp.next; //temp后移,遍历 155 } 156 //判断flag是否找到待删除的前一个节点 157 if(flag) { 158 temp.next = temp.next.next; 159 }else { 160 System.out.printf(" 要删除的%d节点不存在~~",no); 161 } 162 } 163 164 //显示链表(遍历) 165 public void list() { 166 //判断链表是否为空 167 if(head.next == null) { 168 System.out.println("链表为空~~"); 169 return; 170 } 171 //因为头节点,不能动,因此我们需要一个辅助变量来遍历 172 HeroNode temp = head.next; 173 while(true) { 174 //判断链表是否到最后 175 if(temp == null) { 176 break; 177 } 178 //输出节点信息 179 System.out.println(temp); 180 //将temp后移,一定要小心 181 temp = temp.next; 182 } 183 } 184 } 185 186 187 //定义HeroNode节点,每个HeroNode对象就是一个节点 188 class HeroNode{ 189 public int no; //编号 190 public String name; //姓名 191 public String nickname; //绰号 192 public HeroNode next; //指向下一个节点 193 194 //构造器 195 public HeroNode(int no,String name,String nickname) { 196 this.no = no; 197 this.name = name; 198 this.nickname = nickname; 199 } 200 201 //为了显示,重写toString方法 202 @Override 203 public String toString() { 204 return "HeroNode [no=" + no + ", name=" + name + ", nickname=" + nickname + "]"; 205 } 206 207 208 }
3. 单链表面试题
单链表的常见面试题有如下:
1)求单链表中有效节点的个数
核心代码如下:
1 //求单链表中有效节点的个数 2 public static int getLength(HeroNode head) { 3 if(head.next == null) { //空链表 4 return 0; 5 } 6 int length = 0; 7 //定义一个辅助变量,这里我们没有统计头节点 8 HeroNode cur = head.next; 9 while(cur != null) { 10 length ++; 11 cur = cur.next; //遍历 12 } 13 return length; 14 }
2)查找单链表中的倒数第K个节点
核心代码如下:
1 //查找单链表中的倒数第K个节点 2 //思路: 3 //1、编写一个方法,接收head节点,同时接收一个index 4 //2、index表示倒数第index节点 5 //3、先把链表从头到尾遍历,得到链表的总长度getLength 6 //4、得到size后,我们从链表的第一个节点开始遍历(size - index)个,就可以得到 7 //5、如果找到了,则返回该节点,否则返回null 8 public static HeroNode findLastIndexNode(HeroNode 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 //定义一个辅助变量,for循环定位倒数的index位置 21 HeroNode cur = head.next; 22 for(int i =0;i< size - index;i ++) { 23 cur = cur.next; 24 } 25 return cur; 26 }
3)单链表的反转
思路分析图解:
核心代码如下:
1 //链表的反转 2 //思路: 3 //1、先定义一个节点reverseHead = new HeroNode(); 4 //2、从头到尾遍历原来的链表,每遍历一个节点,就将其取出,并放在新的链表reverseHead的最前端 5 //3、原来链表的head.next = reverseHead.next 6 public static void reversetList(HeroNode head) { 7 //如果当前链表为空,或者只有一个节点,则根本不需要反转,直接返回 8 if(head.next == null || head.next.next == null) { 9 return; 10 } 11 //定义一个辅助的变量,帮助我们遍历原来的链表 12 HeroNode cur = head.next; 13 HeroNode next = null;//指向当前节点【cur】的下一个节点 14 //定义一个新的头节点 15 HeroNode reverseHead = new HeroNode(0,"",""); 16 //遍历原来的链表,每遍历一个节点,就将其取出,并放在新的链表 reverseHead 的最前端 17 while(cur != null) { 18 next = cur.next; //被取出节点的下一个节点 19 cur.next = reverseHead.next;//将 cur 的下一个节点指向新的链表的最前端 20 reverseHead.next = cur; //将 cur 连接到新的链表上 21 cur = next;//让 cur 后移遍历链表 22 } 23 //将 head.next 指向 reverseHead.next , 实现单链表的反转 24 head.next = reverseHead.next; 25 }
4)从尾到头打印单链表 【百度,要求方式 1:反向遍历 。 方式 2:Stack 栈】
思路分析图解:
核心代码如下:
1 //单链表的逆序打印代码 2 //可以利用栈这个数据结构,将各个节点压入到栈中,然后利用栈的先进后出的特点,就实现了逆序打印的效果 3 public static void reversePrint(HeroNode head) { 4 //判断链表是否为空 5 if(head.next == null) { 6 return; 7 } 8 //创建一个栈,将各个节点压入栈中 9 Stack<HeroNode> stack = new Stack<HeroNode>(); 10 HeroNode cur = head.next; 11 //将链表的所有节点压入栈中 12 while(cur != null) { 13 stack.push(cur); 14 cur = cur.next; //cur后移,压入下一个节点 15 } 16 //将栈中的所有节点进行打印,pop出栈 17 while(stack.size() > 0) { 18 System.out.println(stack.pop()); //栈的特点时先进后出 19 } 20 }