数据结构-单链表
数据结构-单链表
链表是有序的列表,但是它在内存中是存储如下
1) 链表是以节点的方式来存储,是链式存储
2) 每个节点包含 data 域, next 域:指向下一个节点.
3) 如图:发现链表的各个节点不一定是连续存储.
4) 链表分带头节点的链表和没有头节点的链表,根据实际的需求来确定
单链表(带头结点) 逻辑结构示意图如下
单链表的应用实例
使用带 head 头的单向链表实现 –水浒英雄排行榜管理完成对英雄人物的增删改查操作
(1)结构
public class HeroNode { Integer id; String heroName; String nikeName; HeroNode next; }
节点信息
public class HeroNode { public int id; public String heroName; public String nikeName; public HeroNode next; public HeroNode() { } public HeroNode(int id, String heroName, String nikeName) { this.id = id; this.heroName = heroName; this.nikeName = nikeName; } @Override public String toString() { return "HeroNode{" + "id=" + id + ", heroName='" + heroName + '\'' + ", nikeName='" + nikeName + '\'' + '}'; } }
单链表
public class HeroLink { //单链表的头节点,头节点不要动,不存放具体的数据 HeroNode head = new HeroNode(0,null,null); }
一、增
①不考虑顺序,直接在链表最后插入
②考虑顺序
二、删
三、改
四、查
五、面试题
1) 求单链表中有效节点的个数
2) 查找单链表中的倒数第 k 个结点 【新浪面试题】
3) 单链表的反转【腾讯面试题,有点难度】
4) 从尾到头打印单链表 【百度,要求方式 1:反向遍历 。 方式 2:Stack 栈】
5) 合并两个有序的单链表,合并之后的链表依然有序【课后练习.】
源码
public class HeroLink { //先初始化一个头节点, 头节点不要动, 不存放具体的数据 HeroNode head = new HeroNode(0,null,null); public HeroNode getHead() { return head; } /** * 添加节点信息,第一种:不考虑编号顺序时,直接添加到链尾 * //1. 找到当前链表的最后节点 * //2. 将最后这个节点的 next 指向 新的节点 * @param node */ public void addHeroNode(HeroNode node) { //因为head不能动,所以需要一个辅助节点变量 HeroNode temp = head; //标记待添加的节点是否已经存在 boolean flag = false; //遍历链表,找到最后 while (true) { //如果节点的next为空说明该节点就是最后一个节点 if (temp.next == null) { break; } if (temp.next.id == node.id) { flag = true; break; } //否则指针下移 temp = temp.next; } if (flag) { System.out.println("添加失败,需要添加id为"+node.id+"的节点已经存在了!!!"); return; } //加入新节点 temp.next = node; } /** * 添加节点信息,第二种:根据排名将英雄插入到指定位置,按照id从小到大排序 */ public void addHeroNodeOrderById(HeroNode node) { //如果还没有任何一个节点,直接插入到head后面 if (head.next == null) { head.next = node; return; } //因为头节点不能动,因此我们仍然通过一个辅助指针(变量)来帮助找到添加的位置 HeroNode temp = head; //标记待添加的节点是否已经存在 boolean flag = false; // 因为单链表,因为我们找的 temp 是位于 添加位置的前一个节点,否则插入不了 while (true) { //说明 temp 已经在链表的最后 if (temp.next == null) { break; } //节点已经存在 if (temp.next.id == node.id) { flag = true; break; } //位置找到,就在 temp 的后面插入 if (temp.next.id > node.id) { break; } temp = temp.next; } if (flag) { System.out.println("添加失败,需要添加id为" + node.id + "的节点已经存在了!!!"); } else { node.next = temp.next; temp.next = node; } } /** * 删除节点 * //1.head 不能动,因此我们需要一个 temp 辅助节点找到待删除节点的前一个节点 * //2. 说明我们在比较时,是 temp.next.id 和 需要删除的节点的 id 比较 */ public void deleteHeroNodeById(int id) { if(head.next==null) { System.out.println("链表为空"); return; } HeroNode temp=head; // 标志是否找到待删除节点的 boolean flag=false; while (true) { //已经到了链表最后 if(temp.next==null) { break; } if(temp.next.id==id) { flag=true; break; } temp=temp.next; } if(flag) { temp.next=temp.next.next; } else { System.out.println("链表中没有id为"+id+"的节点数据"); } } /** * 修改链表节点数据,id不要动 */ public void updateHeroNode(HeroNode node) { if(head.next==null) { System.out.println("链表为空"); return; } HeroNode temp=head.next; boolean flag=false; while (true) { if(temp==null) { break; } if(temp.id==node.id) { flag=true; break; } temp=temp.next; } if (flag) { temp.heroName=node.heroName; temp.nikeName=node.nikeName; } else { System.out.println("id为"+node.id+"的节点不存在!!!"); } } /** * 查询节点信息 */ public HeroNode getHeroNodeById(int id) { if(head.next==null) { return null; } HeroNode temp=head.next; boolean flag=false; while (true) { if(temp==null) { break; } if(temp.id==id) { flag=true; break; } temp=temp.next; } if(flag) { return temp; } return null; } /** * 打印输出链表信息 */ public static void showHeroLink(HeroLink link) { if (link.head.next == null) { System.out.println("链表为空!!!"); return; } HeroNode temp = link.head.next; while (temp != null) { System.out.println(temp); temp = temp.next; } } /** * 试题1:求单链表中有效节点的个数 */ public static int getSize(HeroLink link) { HeroNode head=link.getHead(); if(head.next==null) { return 0; } int count=0; HeroNode temp=head.next; while (temp!=null) { count++; temp=temp.next; } return count; } /** * 试题2:查找单链表中的倒数第 k 个结点 【新浪面试题】 */ public static HeroNode getHeroNodeByIndex(HeroLink link,int index) { HeroNode head=link.getHead(); if(head.next==null) { return null; } int size=0; HeroNode temp=head.next; while (temp!=null) { size++; temp=temp.next; } if(index<=0||index>size) { return null; } temp=head.next; for(int i=0;i<size-index;i++) { temp=temp.next; } return temp; } /** *试题3:单链表的反转【腾讯面试题,有点难度】 */ public static void reversetList(HeroLink link) { HeroNode head=link.getHead(); //如果当前链表为空,或者只有一个节点,无需反转,直接返回 if (head.next==null || head.next.next==null) { return; } HeroNode newHead=new HeroNode(0,null,null); //定义一个辅助的指针(变量),帮助我们遍历原来的链表 HeroNode temp=head.next; // 指向当前节点[temp]的下一个节点 HeroNode next=null; //遍历原来的链表,每遍历一个节点,就将其取出,并放在新的链表 newHead 的最前端 while (temp != null) { //先暂时保存当前节点的下一个节点,因为后面需要使用 next = temp.next; //将 temp 的下一个节点指向新的链表的最前端 temp.next = newHead.next; //将 temp 连接到新的链表上 newHead.next = temp; //让 temp 后移 temp = next; } //将 head.next 指向 newHead.next, 实现单链表的反转 head.next=newHead.next; } /** * 试题4、从尾到头打印单链表 【百度,要求方式 1:反向遍历 。 方式 2:Stack 栈】 * 反向遍历 */ public static void printHeroNode2(HeroLink link) { reversetList(link); showHeroLink(link); reversetList(link); } /** * 试题4、从尾到头打印单链表 【百度,要求方式 1:反向遍历 。 方式 2:Stack 栈】 * 可以利用栈这个数据结构,将各个节点压入到栈中,然后利用栈的先进后出的特点,就实现了逆序打印的效果 */ public static void printHeroNode(HeroLink link) { HeroNode head=link.head; if(head.next==null) { System.out.println("链表为空"); return; } HeroNode temp=head.next; //创建一个栈,将各个节点压入栈 Stack<HeroNode> stack=new Stack<>(); while (temp!=null) { stack.push(temp); temp=temp.next; } //将栈中的节点进行打印,pop 出栈 while (stack.size()>0) { System.out.println(stack.pop()); } } /** * 试题5、合并两个有序的单链表,合并之后的链表依然有序 */ public static HeroLink HeroLink1AndHeroLink2(HeroLink link1,HeroLink link2) { //如果有一个为空则把另一个传出去 if(link1.head.next==null) { return link2; } if(link2.head.next==null) { return link1; } //两个都不为空 //辅助节点变量temp HeroNode temp=link2.head.next; //辅助节点next,避免插入temp后,找不到下一个节点 HeroNode next=null; while (temp!=null) { next=temp.next; //把链表2的节点依次插入到链表1中 link1.addHeroNodeOrderById(temp); temp=next; } //返回链表1 return link1; } }