Java数据结构之链表
Posted 月上贺兰
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java数据结构之链表相关的知识,希望对你有一定的参考价值。
链表是一个有序的列表,它分为单向链表,双向链表,单向环形链表
一.单向链表
链表在内存中的存储如下:
它有如下特性:
1.链表是以节点的方式,是链式存储
2.每个节点都包含data域,next域来指向下一个节点
3.每个链表在内存中的分布不一定是连续的
4.链表分为有头结点的链表和没有头结点的链表,根据实际的情况决定
链表的逻辑结构示意图
单链表可以用来存储数据,也可以对数据进行增删改查的操作
1.创建一个单链表
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; } public HeroNode() { } @Override public String toString() { return "HeroNode{" + "no=" + no + ", name=\'" + name + \'\\\'\' + ", nickName=\'" + nickName + \'\\\'\' + \'}\'; } }
2.单链表的添加元素
public void add(HeroNode node){ /*添加操作*/ /*1.找到当前链表的最后一个节点*/ /*2.将最后这个节点的next指向这个新的节点*/ HeroNode temp = head; // 定义辅助指针来遍历链表 while (true){ if(temp.next == null){ // 找到链表的最后,退出循环 break; } temp = temp.next; // 如果没有找到最后,将链表后移 } // 当退出while循环时,temp就指向了链表的最后 // 将最后这个节点的next指向新的节点 temp.next = node; }
3.单链表按照顺序添加元素
public void addByOrder(HeroNode node){ /*按照顺序添加*/ /*因为是单链表,所以temp是要添加指针的前一个节点*/ boolean flag = false; // 表示英雄的编号是否存在,默认为false HeroNode temp = head; //辅助指针 while (true){ if (temp.next == null){ break; } if (temp.next.no > node.no){ // 位置已经找到,就在temp后面插入新节点 break; }else if (temp.no == node.no){ // 添加的新的节点已经存在 flag = true; break; } temp = temp.next; } if (flag){ System.out.printf("不能添加相同的英雄,%d",node.no); return; }else { // 将新元素插入到temp的后面 node.next = temp.next; temp.next = node; } }
4.修改元素
public void update(HeroNode newNode){ /*根据英雄的编号修改节点*/ HeroNode temp = head; boolean flag = false; // 是否找到 while (true){ if (temp.next == null){ break; } if (temp.no == newNode.no){ // 已经找到需要修改的节点 flag = true; break; } temp = temp.next; } if (flag){ temp.name = newNode.name; temp.nickName = newNode.nickName; }else { System.out.printf("没有找到编号为%d的节点",newNode.no); return; } }
5.删除元素
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.printf("没有找到编号为 %d的节点",no); return; } }
6.单链表元素显示
public void show(){ /*显示链表,直接遍历即可*/ HeroNode temp = head.next; while (true){ if (temp == null){ System.out.println("链表为空"); return; } System.out.println(temp); temp = temp.next; } }
7.单链表面试题
7.1 查看单链表中有效数据的个数
public int size(HeroNode head){ /*计算链表中有效数据的个数,不算头结点*/ if (head.next == null){ // 链表为空,返回0 return 0; } int length = 0; // 定义有效数据的个数 HeroNode temp = head.next; while (temp != null){ // 遍历,++,返回 length++; temp = temp.next; } return length; }
7.2 返回单链表中倒数第k个节点
public HeroNode printLastIndexNode(HeroNode head,int index){ /*打印倒数第k个节点*/ /*index表示倒数第几个节点*/ /*1.先获取链表的总的长度size*/ /*2.得到size后,从链表的第一个遍历到size-index个,返回该节点即可*/ if (head.next == null){ // 如果链表为空,返回null return null; } int size = size(head); if (index <= 0 || index > size){ // 数据校验,如果输入的index不合法,返回null return null; } HeroNode temp = head.next; for (int i = 0; i < size-index;i++){ // for循环定义倒数的节点个数,然后返回节点 temp = temp.next; } return temp; }
7.3 反转单链表
public void reverse(HeroNode head){ /*反转单链表*/ /*1.从头到尾遍历原先的链表,每遍历一次将其取出,放在新链表的最前端*/ /*2.原来链表的head.next 指向reserveHead.next即可*/ // 如果链表中没有元素,或者只有一个元素直接return if (head.next==null || head.next.next == null){ return; } HeroNode cur = head.next; // 定义辅助节点,遍历原链表 HeroNode next = null; // 定义当前节点的下一个节点 HeroNode reverseHead = new HeroNode(0,"",""); // 反转链表的头节点 while (cur != null){ next = cur.next; //保存当前节点的下一个节点 cur.next = reverseHead.next; // 将后面一个链表的元素挂到cur的前面 reverseHead.next = cur; // 将reserveHead的头连接到原来的链表上 cur = next; // cur下移 } head.next = reverseHead.next; }
7.4 反向打印输出单链表
public void reversePrint(HeroNode head){ /*反转打印单链表,不破坏原有的结构*/ /*使用栈这种数据结构就能简单的实现*/ if (head.next == null){ // 如果链表为空,直接return return; } HeroNode cur = head.next; // 创建一个栈,将元素压入到栈中 Stack<HeroNode> stack = new Stack<>(); while (cur!=null){ // 遍历链表,把所有的节点压进栈 stack.push(cur); cur = cur.next; } while (stack.size() > 0){ // 节点出栈 System.out.println(stack.pop()); } }
测试代码
public static void main(String[] args) { // 生成英雄的节点 HeroNode h1 = new HeroNode(1,"宋江","及时雨"); HeroNode h2 = new HeroNode(2,"卢俊义","玉麒麟"); HeroNode h3 = new HeroNode(3,"吴用","智多星"); HeroNode h4 = new HeroNode(4,"林冲","豹子头"); SingleLinkedList singleLinkedList = new SingleLinkedList(); // 添加 singleLinkedList.add(h1); singleLinkedList.add(h2); singleLinkedList.add(h3); singleLinkedList.add(h4); // 按照no添加 singleLinkedList.addByOrder(h1); singleLinkedList.addByOrder(h4); singleLinkedList.addByOrder(h2); singleLinkedList.addByOrder(h3); singleLinkedList.show(); //修改 HeroNode h5 = new HeroNode(2,"小卢卢","小麒麟"); singleLinkedList.update(h5); singleLinkedList.show(); //删除 singleLinkedList.delete(1); singleLinkedList.delete(4); singleLinkedList.show(); // 有效节点的个数 System.out.printf("一共有%d个有效节点",singleLinkedList.size(singleLinkedList.getHead())); // 倒数第k个节点 System.out.println("倒数第1个节点是: "+singleLinkedList.printLastIndexNode(singleLinkedList.getHead(),1)); // 反转单链表 singleLinkedList.reverse(singleLinkedList.getHead()); // 反转打印单链表 singleLinkedList.reversePrint(singleLinkedList.getHead()); }
二.单向环形链表(Josephu问题)
设编号为1,2,3,...n的人围坐一圈,约定编号为k的人从1开始报数,数到m的人出列,它的下一个又从1开始报数,数到m的人又出列,以此类推,直到所有的人都出列
为止.
示意图:
代码实现:
1.创建节点
class Child{ /*创建小孩节点*/ private int no; private Child next; public Child(int no) { this.no = no; } public Child() { } public int getNo() { return no; } public void setNo(int no) { this.no = no; } public Child getNext() { return next; } public void setNext(Child next) { this.next = next; } @Override public String toString() { return "Child{" + "no=" + no + \'}\'; } }
2.操作节点
public void list(){ /*遍历环形链表*/ if (first == null){ //判断链表是否为空,如果为空直接return System.out.println("链表为空"); return; } Child cur = first; // 头节点不能使用,需要创建辅助节点 while (true){ System.out.println("当前小孩的编号"+cur.getNo()); if (cur.getNext() == first){ //如果已经到老了最后节点,则退出循环 break; } cur = cur.getNext(); // 节点下移 } }
public void addChildren(int nums){ /*添加节点*/ /*1.先创建第一个节点,然后让first指向该节点,构成环*/ /*2.每创建一个新的节点,将其加入到环中即可*/ if (nums < 1){ // 数据校验,不合法直接return System.out.println("添加的数要大于1"); return; } Child cur = null; // 使用辅助节点遍历 for (int i = 1; i <= nums; i++) { Child child = new Child(i); //根据编号创建小孩节点 if (i == 1){ // 如果是第一个小孩 first = new Child(1); first.setNext(first); // 构成环 cur = first; //让cur指向first }else { cur.setNext(child); // 将新的节点添加到环中 child.setNext(first); cur = child; // 让cur指向下一个节点 } } }
/** * * @param startNo 表示从第几个小孩开始数数 * @param countNum 表示数几下 * @param nums 表示最初有多少个小孩在圈中 如果 startNo = 1,countNum = 2,num = 5,最后的顺序是2->4->1->5->3 */ public void countChildren(int startNo,int countNum,int nums){ /*计算小孩出圈的队列*/ // 校验参数是否合法 if(startNo < 1 || nums < countNum || first == null){ System.out.println("输入的参数错误"); return; } //定义辅助节点 Child helper = first; while (true){ // 该节点需要指向当前链表的最后一个节点 if (helper.getNext() == first){ break; } helper = helper.getNext(); } //报数前,需要移动k-1次 for (int i = 0; i < startNo-1;i++){ first = first.getNext(); helper = helper.getNext(); } while (true){ if (helper == first){ // 只有一个节点 break; } // 当小孩报数时,辅助节点和first节点同时移动countNum-1次,然后出圈 for (int i = 0; i < countNum-1; i++) { first = first.getNext(); helper = helper.getNext(); } // 此时first就是要出圈的节点 System.out.println("小孩出圈的编号是"+first.getNo()); first = first.getNext(); helper.setNext(first); } System.out.println("最终留下的小孩的编号是"+first.getNo()); }
测试代码:
public static void main(String[] args) { CircleLinkedList circleLinkedList = new CircleLinkedList(); circleLinkedList.addChildren(5); circleLinkedList.list(); circleLinkedList.countChildren(1,2,5); }
三.双向链表
单向链表的缺点:
1.查找的方向只能是一个方向,双向链表可以向前或向后进行查找
2.单向链表不能自我删除,需要借助辅助节点,而双向链表可以自我删除
双向链表的操作(增删改查)
创建双向链表
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; } public HeroNode2() { } @Override public String toString() { return "HeroNode2{" + "no=" + no + ", name=\'" + name + \'\\\'\' + ", nickName=\'" + nickName + \'\\\'\' + \'}\'; } }
遍历双向链表(同单向链表一样)
public void list(){ if (head.next == null){ System.out.println("链表为空"); return; } HeroNode2 temp = head; // 定义头结点的辅助节点 while (true){ if (temp.next == null){ break; } temp = temp.next; System.out.println(temp); } }
新增节点
public void add(HeroNode2 node){ HeroNode2 temp = head; // 不停的遍历链表,找到链表的最后一个节点,退出 while (true){ if (temp.next == null){ break; } temp = temp.next; } // 当退出循环时,temp就指向了链表的最后 temp.next = node; // 将新的节点加入到temp后面 node.pre = temp; }
修改节点(同单向链表)
public void update(HeroNode2 node){ boolean flag = false; if (head.next == null){ System.out.println("链表为空,不能修改"); return; } HeroNode2 temp = head.next; while (true){ if (temp == null){ break; } if (temp.no == node.no){ flag=true; break; } temp = temp.next; } if (flag){ temp.name = node.name; temp.nickName = node.nickName; }else { System.out.println("没有找到要修改的节点"); return; } }
删除节点
public void delete(int no){ /*双向链表的删除可以直接找到要删除的节点,找到后,自我删除即可*/ if (head.next == null){ System.out.println("不能删除空链表"); return; } boolean flag = false; HeroNode2 temp = head.next; 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); return; } }
以上是关于Java数据结构之链表的主要内容,如果未能解决你的问题,请参考以下文章