xhao 2019-12-28
链表
链表是以节点的方式来存储 每个节点包含data域和next域,指向下一个节点 链表的各个节点不一定是连续存储 链表分带头节点的链表和没有头节点的链表,根据实际的需求来确定
单向列表
最大特点是可以将物理地址上不连续的数据连接起来,通过指针来对物理地址进行操作,实现增删改查等功能。
单链表分为两种:有头链表和无头链表。
有头节点的增删改查
定义一个单链表的类:
//定义一个SingleLinkedList,单链表,管理HeroNode class SingleLinkedList{ //初始化一个头节点,没有具体的值,只是表示是单链表的开头 private HeroNode head = new HeroNode(0, "", "");
无序添加图解和思路:
代码实现:
//向单链表中添加节点 //1不考虑编号顺序时!! //找到当前链表的末节点,末节点的next指向新节点 public void addNode(HeroNode heroNode) { //头节点不能动,需要一个辅助节点去进行遍历,代表的是指向哪个节点 HeroNode temp = head; //遍历链表,找到最后 while(true) { //当头节点后面没有值,说明已经到达单链表末尾 if(temp.next == null) { break; } //当没有到单链表末尾,每循环一次就把指针向后移动一位 temp = temp.next; } //当跳出循环,说明一定temp已经指向单链表末尾 //temp.next指向传入的heroNode节点 temp.next = heroNode; }
有序添加图解和思路:
代码实现:
//向单链表中添加节点 //2考虑编号顺序,根据排名把节点添加到指定位置!! public void addOrder(HeroNode heroNode) { //同样需要一个辅助节点来帮助找到要添加的位置 //因为是单链表,temp表示的是添加位置的前一个节点,然后把temp.next指向heroNode HeroNode temp = head; //flag标志要添加的编号在链表中是否已经存在 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; } //判断flag的值来向控制台输出语句 if(flag) { System.out.printf("您输入的编号为%d的英雄已存在,不能重复添加\n",heroNode.no); }else { //把辅助节点的后一个节点绑定到新节点的后面 heroNode.next = temp.next; //把新节点放到辅助节点的后一个节点上 temp.next = heroNode; } }
修改节点信息的代码实现:
//修改节点信息,根据no编号来修改 public void updateNode(HeroNode heroNode) { //判断是否为空 if(head.next == null) { System.out.println("当前链表为空"); return; } //不为空时,通过辅助节点依次遍历 HeroNode temp = head.next; //判断是否找到节点 boolean flag = false; while(true) { //如果辅助节点后面为空,说明已经到了链表末尾 if(temp.next == null) { break; } //如果辅助节点编号等于给定节点的编号,找到了 if(temp.no == heroNode.no) { flag = true; break; } temp = temp.next; } //根据flag判断是否找到 if(flag) { temp.name = heroNode.name; temp.nickname = heroNode.nickname; }else { System.out.printf("没有找到编号为%d的英雄\n",heroNode.no); } }
删除节点思路:
1.我们先找到需要删除的这个节点的前一个节点 2.temp.next = temp.next.next 3.被删除的节点没有其他引用指向,会被垃圾回收机制回收
代码实现:
//删除节点 public void delete(int no) { //头节点不能动,需要一个辅助节点找到要删除节点的前一个节点 HeroNode temp = head; //是否找到要删除节点的前一个节点 boolean falg = false; while(true) { //表示已经遍历到链表末尾 if(temp.next == null) { break; } //如果temp.next.no==no,此时temp节点就是要删除节点的前一个节点 if(temp.next.no == no) { falg = true; break; } temp = temp.next; } if(falg) { temp.next = temp.next.next; }else { System.out.printf("要删除的%d节点不存在\n",no); } }
遍历节点:
//显示单链表的数据,通过遍历 public void list() { //如果头节点后面没有值,链表为空 if(head.next == null) { System.out.println("当前链表为空"); return; } //通过辅助变量来遍历 HeroNode temp = head.next; while(true) { //当temp为空,说明已经到达单链表末尾 if(temp==null) { break; } //输出节点的信息 System.out.println(temp); //将temp后移一位 temp = temp.next; } } }
创建节点类
//定义HeroNode,每个HeroNode对象就是一个节点 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 + "]"; }
对单向链表进行测试:
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.addNode(hero1); // singleLinkedList.addNode(hero2); // singleLinkedList.addNode(hero3); // singleLinkedList.addNode(hero4); //考虑排名问题,按顺序添加 singleLinkedList.addOrder(hero4); singleLinkedList.addOrder(hero2); singleLinkedList.addOrder(hero1); singleLinkedList.addOrder(hero3); singleLinkedList.list(); //修改节点信息 HeroNode newHeroNode = new HeroNode(2, "小卢", "玉麒麟~~"); singleLinkedList.updateNode(newHeroNode); // 遍历链表 singleLinkedList.list(); //删除某个节点 singleLinkedList.delete(1); singleLinkedList.list(); }
单向链表练习题
1获取单链表中有效节点的个数
代码实现
//获取单链表中有效节点的个数 //head 链表的头节点 public int getLength(HeroNode head) { //如果头节点后面为空,则这个一个空链表 if(head.next == null) { return 0; } int length = 0; //需要一个辅助节点来帮忙遍历链表 HeroNode temp = head.next; //如果辅助节点的下一个节点不为空,有效节点个数+1; while(temp!= null) { length++; temp = temp.next; } return length; }
测试类:
//获取单链表中节点个数 //获取头节点 HeroNode head = singleLinkedList.getHead(); int length = singleLinkedList.getLength(head); System.out.println("获取到的节点个数为"+length);
2查找单链表中倒数第k个节点
思路:
1.编写一个方法,接收head及节点,同时接收index 2.index表示的是倒数第index节点 3.先把链表全部遍历一遍,得到有效的节点个数length(链表的长度) 4length-index就是这个倒数第k个节点的位置 5.找到就返回节点信息,找不到返回空
代码实现:
//查找单链表中倒数第k个节点 public HeroNode findLastIndexNode(HeroNode head,int index) { //如果头节点后为空,这是一个空链表 if(head.next == null) { return null; } //拿到链表中所有节点的个数(链表的长度) int size = getLength(head); //查找倒数第k个节点(size-index)的位置 //判断index是否是合法数据 if(index <= 0 || index > size) { return null; } //通过辅助节点帮我们遍历到倒数第k个节点的位置,temp初始值为第一个有效节点 HeroNode temp = head.next; //需要遍历的次数 for(int i = 0;i < (size-index);i++) { temp = temp.next; } return temp; }
测试类
//查找单链表中倒数第k个节点 HeroNode res = singleLinkedList.findLastIndexNode(head, 2); System.out.println("倒数的节点信息为"+res);
3反转单链表
思路:
1.先定义一个节点 HeroNode reverseHead = new HeroNode() 2.从头到尾遍历原来的链表,每遍历一个节点,就将其取出,并放在新的链表reverseHead的最前端 3.原来的链表head.next = reverseHead.next
代码实现:
//单向链表反转 public void reverseList(HeroNode head) { //如果当前列表为空,或只有一个节点,直接返回,无需遍历 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; //指针向后移动一位接着遍历 cur = next; } //当跳出while循环说明已经都反转了,再把原链表的后一个节点直接等于新链表的后一个节点,得出的数据还是在原链表下 head.next = reverseHead.next; }
测试类
HeroNode head = singleLinkedList.getHead(); singleLinkedList.reverseList(head); singleLinkedList.list();
4倒序打印单链表
思路:
方式一: 先将单链表进行反转操作,然后再进行遍历即可,但是这样会破坏原来单链表的访问,不推荐 方拾二: 可以利用栈这个数据结构,将各个节点压入到栈中,然后利用栈的先进后出的特点,实现逆序打印的效果
代码打印:
//逆序打印 //利用栈这个数据结构,将各个节点压入到栈中,然后利用栈的先进后出的特点,实现逆序打印的效果 public void reversePrint(HeroNode head) { //先判断是否为空链表 if(head.next ==null) { return; } Stack<HeroNode> stack = new Stack<HeroNode>(); //借助辅助节点,帮忙遍历 HeroNode cur = head.next; while(cur != null) { //只要辅助节点有值,就把它压在栈内 stack.push(cur); cur = cur.next; } //从栈中取值 while(stack.size() >0) { System.out.println(stack.pop()); } }
测试类
//倒序打印链表 System.out.println("倒序打印链表"); singleLinkedList.reversePrint(head);
5合并两个有序的单链表,合并之后的链表依然有序
双向列表
双向链表的遍历,添加,修改,删除的思路
添加节点(默认添加到双向链表的最后)
思路
先找到双向链表的最后节点 temp.next = newHeroNode newHeroNode.pre = temp
无序添加
//1不考虑编号顺序时!! //找到当前链表的末节点,末节点的next指向新节点 public void addNode(HeroNode2 heroNode) { //头节点不能动,需要一个辅助节点去进行遍历,代表的是指向哪个节点 HeroNode2 temp = head; //遍历链表,找到最后 while(true) { //当头节点后面没有值,说明已经到达单链表末尾 if(temp.next == null) { break; } //当没有到单链表末尾,每循环一次就把指针向后移动一位 temp = temp.next; } //当跳出循环,说明一定temp已经指向单链表末尾 //temp.next指向传入的heroNode节点,heroNode的前一个节点是temp,反指向回temp,完成双向链接 temp.next = heroNode; heroNode.pre = temp; }
有序添加
//2考虑编号顺序,根据排名把节点添加到指定位置!! public void addOrder(HeroNode2 heroNode) { //同样需要一个辅助节点来帮助找到要添加的位置 HeroNode2 temp = head; //flag标志要添加的编号在链表中是否已经存在 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; } //判断flag的值来向控制台输出语句 if(flag) { System.out.printf("您输入的编号为%d的英雄已存在,不能重复添加\n",heroNode.no); }else { // 把heroNode绑定到temp的后面 heroNode.next = temp.next; if(temp.next != null) { temp.next.pre = heroNode; } //双向链表,heroNode反向绑定 temp.next = heroNode; heroNode.pre = temp; } }
修改和遍历与单向链表思路一样
删除节点
思路:
双向链表可以实现自我删除,不用再去找它的前一个节点 直接找到要删除的节点temp temp.pre.next= temp.next temp.next.pre = temp.pre
//删除节点 public void delete(int no) { //头节点不能动,需要一个辅助节点找到要删除节点的前一个节点 HeroNode2 temp = head.next; if(temp == null) { System.out.println("此双向链表是个空链表"); return; } //是否找到要删除节点的前一个节点 boolean falg = false; while(true) { //表示已经遍历到链表末尾 if(temp == null) { break; } //如果temp.no==no,此时temp节点就是要删除的节点 if(temp.no == no) { falg = true; break; } temp = temp.next; } if(falg) { temp.pre.next = temp.next; //判断是否删除的是最后一个节点,因为最后一个节点的的temp.next为空没有pre属性,会报空指针 if(temp.next != null) { temp.next.pre = temp.pre; } }else { System.out.printf("要删除的%d节点不存在\n",no); } }
环形单向链表
构建单向环形链表
思路:
1.先创建一个节点,让first指向该节点,并形成环形 2后面每创建一个节点,就把该节点加入到已有的环形链表中
代码实现:
//创建一个小孩类 class Child{ //小孩编号 private int no; //下一个小孩 private Child next; //有参构造 public Child(int no) { this.no = no; } 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 + ", next=" + next + "]"; }
//创建一个first节点,后面会用到 private Child first = null; //添加小孩节点,参数nums表示总共有多少个小孩 public void add(int nums) { //判断nums的符合条件 if(nums < 1) { System.out.println("您输入的数字不符合环形单向链表的条件"); return; } //因为第一个节点不能动,需要一个辅助节点帮忙遍历 Child cur = null; //当输入的nums符合条件时,for循环创建环形单向链表 for(int i = 1;i <= nums;i++) { Child child = new Child(i); //如果是第一个节点 if(i == 1) { first = child;//第一个节点不动 first.setNext(first);//环形需要自己指向自己 cur = first;//让辅助节点指向第一个节点 }else { cur.setNext(child);//辅助节点就代表当前的那个节点 child.setNext(first);//每添加后都要指向第一个节点形成环形链表 cur = child;//辅助指针后移 } }
遍历环形链表
思路:
1先让辅助指针cur指向first节点 2通过while循环遍历该环形链表,当cur.next = first结束
代码实现:
//遍历环形链表 public void showChild() { //判断链表是否为空 if(first == null) { System.out.println("当前环形链表为空"); return; } //遍历时仍需借助辅助节点进行遍历 Child cur = first; while(true) { System.out.printf("小孩编号为%d\n",cur.getNo()); //判断是否是最后一个节点 if(cur.getNext() == first) { break; } cur = cur.getNext(); } }
约瑟夫问题
什么是约瑟夫问题?
设编号为1,2...n的n个人围坐一圈,约定编号k(1<= k <= n)的人从1开始报数,数到m的那个人出列,它的下一位从1开始报数,数到m的那个人再次出列,依次循环直到所有人出列为止,由此产生一个出队编号的序列
思路:
k=?代表从第几个人开始数数 m=?代表数几下 n=?代表总共有多少人 1需要创建一个辅助指针helper,事先应该让辅助指针指向环形链表的最后一个节点 2让first和helper移动k-1的位置,first表示从哪个人开始数,helper依旧在最后一个节点 3让first和helper同时移动m-1次, 4让first指向的人出圈 5需要把first向后移动一位,然后由helper指向first,这样要出圈的人没有人指向它,会被回收
代码实现:
//约瑟夫问题,小孩出圈,startNo从第几个人开始数数,countNum数几下,nums总多少人 public void outChild(int startNo,int countNum,int nums) { add(nums); //判断是否符合条件 if(startNo < 1 ||first == null || startNo > nums) { return; } //1需要创建一个辅助指针helper,事先应该让辅助指针指向环形链表的最后一个节点 Child helper = first; while(true) { if(helper.getNext() == first) { break; } helper = helper.getNext(); } //2让first和helper移动k-1的位置,first表示从哪个人开始数,helper依旧在最后一个节点 for(int j = 0;j < startNo-1;j++) { first = first.getNext(); helper = helper.getNext(); } //3让first和helper同时移动m-1次 while(true) { //如果只剩下一个人 if(first == helper) { break; } for(int j = 0;j < countNum - 1;j++) { first = first.getNext(); helper = helper.getNext(); } //4让first指向的人出圈 System.out.printf("小孩%d出圈\n",first.getNo()); //5.需要把first向后移动一位,然后由helper指向first,这样要出圈的人没有人指向它,会被回收 first = first.getNext(); helper.setNext(first); } System.out.printf("最后留在圈中的人是%d\n",first.getNo()); }
。