数据结构和算法(3)-----链表

一、链表(LinkedList)介绍

链表是有序的列表,但是它在内存中的存储如下:

  • 链表是以节点的方式来存储,是链式存储结构
  • 每个节点包含data域,next域(指向下一个节点)
  • 链表的各个节点不一定是连续存储的
  • 链表分为带头节点和不带头节点的链表,根据实际的需求来确定
  • 单链表(带头节点)的逻辑结构示意图如下所示:

二、单链表的应用实例

使用带头节点的单向链表实现水浒英雄排行榜的管理,对英雄人物进行增删改查的操作。

建立英雄人物的节点信息,代码实现如下:

package com.atguigu.linkedlist;

public class HeroNode {
	public  int no;
	public String name;
	public String nickname;
	public HeroNode next;
	
	
	public HeroNode(int no, String name, String nickname) {
		super();
		this.no = no;
		this.name = name;
		this.nickname = nickname;
	}


	@Override
	public String toString() {
		return "HeroNode [no=" + no + ", name=" + name + ", nickname=" + nickname + "]";
	}
}

1.显示链表信息

遍历整个链表,打印链表中所有节点的信息

代码实现:

//先初始化一个头节点,头节点不存放具体的数据
private HeroNode head=new HeroNode(0, "", "");

public void show(){
	if(head.next==null){
		System.out.println("链表为空");
	}else{
		HeroNode temp=head.next;
		while(temp!=null){
			System.out.println(temp);
			temp=temp.next;
		}
	}
}

2.增加操作

第一种方法:添加英雄时,直接添加到链表的尾部

思路分析示意图:

代码实现:

//先初始化一个头节点,头节点不存放具体的数据
private HeroNode head=new HeroNode(0, "", "");
	
//添加节点到单向链表
//1.找到当前链表的最后节点
//2.将最后这个节点的next指向新的节点

public void add(HeroNode heroNode){
	HeroNode temp=head;
	while(temp.next!=null){
	    temp=temp.next;
	}
	temp.next=heroNode;
}

测试代码:

package com.atguigu.linkedlist;

public class SingleLinkedListDemo {
	public static void main(String[] args) {
		SingleLinkedList singleLinkedList=new SingleLinkedList();
		HeroNode heroNode1=new HeroNode(100, "宋江", "及时雨");
		HeroNode heroNode2=new HeroNode(200, "武松", "行者");
		HeroNode heroNode3=new HeroNode(300, "时迁", "鼓上蚤");
		HeroNode heroNode4=new HeroNode(400, "孙二娘", "母夜叉");
		
		System.out.println("未添加节点的链表信息");
		singleLinkedList.show();
		
		System.out.println("---------------");
		System.out.println("在链表尾部添加节点后:");
		singleLinkedList.add(heroNode1);
		singleLinkedList.show();
		
		System.out.println("---------------");
		System.out.println("在链表尾部添加节点后:");
		singleLinkedList.add(heroNode2);
		singleLinkedList.show();
		
		System.out.println("---------------");
		System.out.println("在链表尾部添加节点后:");
		singleLinkedList.add(heroNode3);
		singleLinkedList.show();
		
		System.out.println("---------------");
		System.out.println("在链表尾部添加节点后:");
		singleLinkedList.add(heroNode4);
		singleLinkedList.show();
	}
}

效果图:

第二种方法:在添加英雄时,根据排名将英雄插入到指定的位置(如果已经存在,则添加失败,并给出提示)

思路分析示意图:

代码实现:

//先初始化一个头节点,头节点不存放具体的数据
private HeroNode head=new HeroNode(0, "", "");

public void addByOrder(HeroNode heroNode){
		HeroNode temp=head;   
		boolean flag=false;     //标志是否找到插入节点的位置
		while(true){
			if(temp.next==null){
				break;
			}
			if(temp.next.no==heroNode.no){
				flag=true;
				break;
			}
			if(temp.next.no>heroNode.no){
				
				break;
			}
			temp=temp.next;
		}
		if(flag==true){
			System.out.println("该节点已存在,添加失败");
		}else{
			heroNode.next=temp.next;
			temp.next=heroNode;
		}
	}

测试代码:

package com.atguigu.linkedlist;

public class SingleLinkedListDemo {
	public static void main(String[] args) {
		SingleLinkedList singleLinkedList=new SingleLinkedList();
		HeroNode heroNode1=new HeroNode(100, "宋江", "及时雨");
		HeroNode heroNode2=new HeroNode(200, "武松", "行者");
		HeroNode heroNode3=new HeroNode(300, "时迁", "鼓上蚤");
		HeroNode heroNode4=new HeroNode(400, "孙二娘", "母夜叉");
		
		System.out.println("未添加节点的链表信息");
		singleLinkedList.show();
		
	
		System.out.println("**********************************");
		System.out.println("按英雄排位顺序添加节点后:");
		singleLinkedList.addByOrder(heroNode3);
		singleLinkedList.show();
		
		System.out.println("**********************************");
		System.out.println("按英雄排位顺序添加节点后:");
		singleLinkedList.addByOrder(heroNode1);
		singleLinkedList.show();
		
		System.out.println("**********************************");
		System.out.println("按英雄排位顺序添加节点后:");
		singleLinkedList.addByOrder(heroNode3);
		singleLinkedList.show();
		
		System.out.println("**********************************");
		System.out.println("按英雄排位顺序添加节点后:");
		singleLinkedList.addByOrder(heroNode4);
		singleLinkedList.show();
		
		System.out.println("**********************************");
		System.out.println("按英雄排位顺序添加节点后:");
		singleLinkedList.addByOrder(heroNode2);
		singleLinkedList.show();

	}

}

效果图:

3.删除操作

思路分析示意图:

代码实现(1):

//先初始化一个头节点,头节点不存放具体的数据
private HeroNode head=new HeroNode(0, "", "");

//删除节点的信息,根据no编号来删除
	public void delete(HeroNode heroNode){
		if(head.next==null){
			System.out.println("链表为空,删除失败");
		}else{
			HeroNode temp=head.next;          //记录当前节点
			HeroNode temp_ahead=head;     //记录当前节点的前一个节点
			boolean flag=false;                       //标志是否找到了待删除节点的位置
			while(true){
				if(temp==null){   //已经到链表的最后
					break;
				}
				if(temp.no==heroNode.no){     //找到了待删除的节点
					flag=true;
					break;
				}
				temp_ahead=temp;
				temp=temp.next;
			}
			if(flag==true){
				temp_ahead.next=temp.next;
				System.out.println("删除"+heroNode.name+"节点成功");
			}else{
				System.out.println("没有找到该节点信息,删除失败");
			}
		}	
	}

代码实现(2):

//先初始化一个头节点,头节点不存放具体的数据
private HeroNode head=new HeroNode(0, "", "");

//删除节点的信息,根据no编号来删除
		public void delete(HeroNode heroNode){
			if(head.next==null){
				System.out.println("链表为空,删除失败");
			}else{
				HeroNode temp=head.next;          //记录当前节点
				boolean flag=false;                       //标志是否找到了待删除节点的位置
				while(true){
					if(temp==null){   //已经到链表的最后
						break;
					}
					if(temp.next.no==heroNode.no){     //找到了待删除的节点
						flag=true;
						break;
					}
					temp=temp.next;
				}
				if(flag==true){
					temp.next=temp.next.next;
					System.out.println("删除节点成功");
				}else{
					System.out.println("没有找到该节点信息,删除失败");
				}
			}	
		}

测试代码:

package com.atguigu.linkedlist;

public class SingleLinkedListDemo {
	public static void main(String[] args) {
		SingleLinkedList singleLinkedList=new SingleLinkedList();
		HeroNode heroNode1=new HeroNode(100, "宋江", "及时雨");
		HeroNode heroNode2=new HeroNode(200, "武松", "行者");
		HeroNode heroNode3=new HeroNode(300, "时迁", "鼓上蚤");
		HeroNode heroNode4=new HeroNode(400, "孙二娘", "母夜叉");
		
		System.out.println("未添加节点的链表信息");
		singleLinkedList.show();
		
		System.out.println("**********************************");
		System.out.println("按英雄排位顺序添加节点后:");
		singleLinkedList.addByOrder(heroNode3);
		singleLinkedList.show();
		
		System.out.println("**********************************");
		System.out.println("按英雄排位顺序添加节点后:");
		singleLinkedList.addByOrder(heroNode1);
		singleLinkedList.show();
		
		System.out.println("**********************************");
		System.out.println("按英雄排位顺序添加节点后:");
		singleLinkedList.addByOrder(heroNode3);
		singleLinkedList.show();
		
		System.out.println("**********************************");
		System.out.println("按英雄排位顺序添加节点后:");
		singleLinkedList.addByOrder(heroNode4);
		singleLinkedList.show();
		
		System.out.println("**********************************");
		System.out.println("按英雄排位顺序添加节点后:");
		singleLinkedList.addByOrder(heroNode2);
		singleLinkedList.show();

		System.out.println("------------------------------------------------");
		System.out.println("删除后的链表:");
		singleLinkedList.delete(heroNode2);
		singleLinkedList.show();
		
		System.out.println("------------------------------------------------");
		System.out.println("删除后的链表:");
		singleLinkedList.delete(heroNode4);
		singleLinkedList.show();
			
		System.out.println("------------------------------------------------");
		System.out.println("删除后的链表:");
		singleLinkedList.delete(heroNode1);
		singleLinkedList.show();
		
		System.out.println("------------------------------------------------");
		System.out.println("删除后的链表:");
		singleLinkedList.delete(heroNode3);
		singleLinkedList.show();

	}

}

效果图:

4.修改操作

遍历整个链表,根据已知的节点编号信息,找到需要修改的节点位置,并修改;如果未找到该节点,则返回修改失败的信息。

代码实现:

//先初始化一个头节点,头节点不存放具体的数据
private HeroNode head=new HeroNode(0, "", "");

//修改节点的信息,根据no编号来修改,即no编号不能改
	public void update (HeroNode heroNode){
		if(head.next==null){
			System.out.println("链表为空");
		}else{
			HeroNode temp=head.next;
			boolean flag=false;
			while(true){
				if(temp==null){
					break;
				}
				if(temp.no==heroNode.no){
					flag=true;
					break;
				}
				temp=temp.next;
			}
			if(flag==true){
				temp.name=heroNode.name;
				temp.nickname=heroNode.nickname;
			}else{
				System.out.println("没有找到该节点信息,修改未成功");
			}
		}	
	}

测试代码:

package com.atguigu.linkedlist;

public class SingleLinkedListDemo {
	public static void main(String[] args) {
		SingleLinkedList singleLinkedList=new SingleLinkedList();
		HeroNode heroNode1=new HeroNode(100, "宋江", "及时雨");
		HeroNode heroNode2=new HeroNode(200, "武松", "行者");
		HeroNode heroNode3=new HeroNode(300, "时迁", "鼓上蚤");
		HeroNode heroNode4=new HeroNode(400, "孙二娘", "母夜叉");
		
		System.out.println("未添加节点的链表信息");
		singleLinkedList.show();
		
		
		System.out.println("**********************************");
		System.out.println("按英雄排位顺序添加节点后:");
		singleLinkedList.addByOrder(heroNode3);
		singleLinkedList.show();
		
		System.out.println("**********************************");
		System.out.println("按英雄排位顺序添加节点后:");
		singleLinkedList.addByOrder(heroNode1);
		singleLinkedList.show();
		
		System.out.println("**********************************");
		System.out.println("按英雄排位顺序添加节点后:");
		singleLinkedList.addByOrder(heroNode3);
		singleLinkedList.show();
		
		System.out.println("**********************************");
		System.out.println("按英雄排位顺序添加节点后:");
		singleLinkedList.addByOrder(heroNode4);
		singleLinkedList.show();
		
		System.out.println("**********************************");
		System.out.println("按英雄排位顺序添加节点后:");
		singleLinkedList.addByOrder(heroNode2);
		singleLinkedList.show();
		
		System.out.println("------------------------------------------------");
		System.out.println("修改后的链表:");
		HeroNode heroNode5=new HeroNode(300, "卢俊义", "玉麒麟");
		singleLinkedList.update(heroNode5);
		singleLinkedList.show();
	
	}

}

效果图:

5.查找操作

遍历整个链表,根据已知的节点编号信息,查找该编号对应节点的相关信息;如果未找到该节点,则返回查找失败的信息。

代码实现:

//先初始化一个头节点,头节点不存放具体的数据
private HeroNode head=new HeroNode(0, "", "");

//查找节点的信息,根据no编号来查找
	public void get(int no){
		if(head.next==null){
			System.out.println("链表为空,查找的节点信息不存在");
		}else{
			HeroNode temp=head.next;
			boolean flag=false;
			while(true){
				if(temp==null){
					break;
				}
				if(temp.no==no){
					flag=true;
					break;
				}
				temp=temp.next;
			}
			if(flag==true){
				System.out.println("该节点信息为:"+temp);
			}else{
				System.out.println("查找的节点信息不存在");
			}
		}
	}

测试代码:

package com.atguigu.linkedlist;

public class SingleLinkedListDemo {
	public static void main(String[] args) {
		SingleLinkedList singleLinkedList=new SingleLinkedList();
		HeroNode heroNode1=new HeroNode(100, "宋江", "及时雨");
		HeroNode heroNode2=new HeroNode(200, "武松", "行者");
		HeroNode heroNode3=new HeroNode(300, "时迁", "鼓上蚤");
		HeroNode heroNode4=new HeroNode(400, "孙二娘", "母夜叉");
		
		System.out.println("未添加节点的链表信息");
		singleLinkedList.show();
		
		
		System.out.println("**********************************");
		System.out.println("按英雄排位顺序添加节点后:");
		singleLinkedList.addByOrder(heroNode3);
		singleLinkedList.show();
		
		System.out.println("**********************************");
		System.out.println("按英雄排位顺序添加节点后:");
		singleLinkedList.addByOrder(heroNode1);
		singleLinkedList.show();
		
		System.out.println("**********************************");
		System.out.println("按英雄排位顺序添加节点后:");
		singleLinkedList.addByOrder(heroNode3);
		singleLinkedList.show();
		
		System.out.println("**********************************");
		System.out.println("按英雄排位顺序添加节点后:");
		singleLinkedList.addByOrder(heroNode4);
		singleLinkedList.show();
		
		System.out.println("**********************************");
		System.out.println("按英雄排位顺序添加节点后:");
		singleLinkedList.addByOrder(heroNode2);
		singleLinkedList.show();

		
		
		System.out.println("------------------------------------------------");
		System.out.println("查找英雄排位为400的节点信息:");
		singleLinkedList.get(400);
		System.out.println("查找英雄排位为222的节点信息:");
		singleLinkedList.get(222);
		
	}

}

效果图:

三、单链表面试题(新浪,百度,腾讯)

1.求链表中的有效节点个数

思路:根据头节点依次遍历整个链表,并用全局变量记录遍历的次数,即为有效节点的个数

代码实现:

//获取单链表的节点数(如果是带头节点的链表,不需要统计头节点)
	public int getLength(HeroNode head){
		if(head.next==null){
			return 0;
		}else{
			int count=0;
			HeroNode temp=head.next;
			while(true){
				if(temp==null){
					break;
				}
				count++;
				temp=temp.next;
			}
			return count;
		}
	}

2.查找单链表中的倒数第k个节点

思路1:可以首先计算出单链表的有效节点个数,并计算出倒数第k个节点是正序的第几个节点,并以此作为遍历的次数,找到该节点。

思路2:可以将单链表反转,倒数第k个节点就变成整数第k+1个节点(因为包含头节点)

代码实现:

//查找单链表中的倒数第k个节点
	//1.先把链表从头到尾遍历,得到链表的总长度
	//2.从链表的第一个开始遍历(getLength()-k+1)个,就可以得到倒数第k个节点

	public HeroNode getLastIndexNode(int k){
		int sum=this.getLength(head);
		
		if(k>sum|| k<=0){
			throw new RuntimeException("输入值不合法");
		}else{
			
			if(head.next==null){
				return null;
			}else{
				HeroNode temp=head.next;
				int temp_count=sum-k+1;
				int count=1;
				while(true){
					if(count==temp_count){
						break;
					}
					count++;
					temp=temp.next;
				}
				return temp;
			}
		}
	}

3.单链表的反转(腾讯面试题,有点难度)

思路:首先判断链表是否为空,如果链表为空,则不进行反转;如果链表不为空,则进行反转。反转时,首先创建一个新的反转头节点,并依次遍历原来的链表,将原来链表的节点摘下,插入到到新的反转链表,直到遍历完成。最后将新的反转链表换成原来的头节点即可。

代码实现1:

将单链表反转
public void reverseList(HeroNode head){
	if(head.next==null){
		System.out.println("链表为空,反转失败");
	}else{
		HeroNode reverseHead=new HeroNode(0, "", "");
			
		HeroNode temp = head.next;
		while(temp!=null){
			head.next=temp.next;
			temp.next=reverseHead.next;
			reverseHead.next=temp;
			temp=head.next;	
		}
		head.next=reverseHead.next;
	}
}

代码实现2:

public void reverseList(HeroNode head){
		if(head.next==null || head.next.next==null){
			return;
		}else{
			//定义一个辅助指针,帮助遍历原来的链表
			HeroNode cur=head.next;
			HeroNode next=null;    //指向当前节点[cur]的下一个节点
			HeroNode reverseHead=new HeroNode(0, "", "");
			//遍历原来的链表,每遍历一个节点,就将其取出,并放在新的链表reverseHead的最前端
			while(cur!=null){
				next=cur.next;  //暂时保存当前节点的下一个节点
				cur.next=reverseHead.next;   //将cur的下一个节点指向新的链表的最前端
				reverseHead.next=cur;     //将cur连接到新的链表上
				cur=next;    //让cur后移
			}
			head.next=reverseHead.next;
		}
	}

4.从尾到头打印单链表(百度面试题,要求方式1:反向遍历;方式2:Stack栈)

思路:方式1:先将单链表进行反转操作,然后再遍历即可,这样做的问题是会破坏原来的单链表的结构,不建议使用。方式2:可以利用栈这个数据结构,将各个节点压入到栈中,然后利用栈的先进后出的特点,就实现了逆序打印的效果。

代码实现:

//使用栈结构,逆序打印单链表
public void reversePrintList(HeroNode head){
	if(head.next==null){
		System.out.println("当前链表为空,无法打印");
	}else{
		Stack<HeroNode> stack=new Stack<HeroNode>();
		HeroNode temp=head.next;
		while(temp!=null){
			stack.add(temp);
			temp=temp.next;
		}
		while(stack.size()>0){
			System.out.println(stack.pop());
		}
	}
}

效果图:

5.合并两个有序的单链表,合并之后的链表依然有序

思路:如果两个单链表均为空链表,则不进行合并;如果两个单链表中的一个链表为空,则直接返回非空的链表;如果两个单链表均为非空链表,则选择其中一个链表作为主链表,将另一个链表的节点依次取出,并有序插入主链表中。

代码实现:

//合并两个单链表
public HeroNode merge(HeroNode heroNode1,HeroNode heroNode2){
	if(heroNode1.next==null&&heroNode2.next==null){
		return null;
	}else if(heroNode1.next==null&&heroNode2!=null){
		return heroNode2;
	}else if(heroNode1.next!=null&&heroNode2==null){
		return heroNode1;
	}else{
		while(true){
			HeroNode temp=heroNode2.next;
			if(temp!=null){
				heroNode2.next=temp.next;
				addByOrder(temp, heroNode1);
			}else{
				return heroNode1;
			}
		}
	}
}

效果图:

 

四、双向链表的应用实例

使用带头节点的双向链表实现水浒英雄排行榜的管理,对英雄人物进行增删改查的操作。

建立英雄人物的节点信息,代码实现如下:

package com.atguigu.linkedlist;

public class HeroNode {
	public  int no;
	public String name;
	public String nickname;
	public HeroNode next;
	
	
	public HeroNode(int no, String name, String nickname) {
		super();
		this.no = no;
		this.name = name;
		this.nickname = nickname;
	}


	@Override
	public String toString() {
		return "HeroNode [no=" + no + ", name=" + name + ", nickname=" + nickname + "]";
	}
}

1.显示链表信息

遍历整个链表,打印链表中所有节点的信息

代码实现:

public void show(HeroNode head){
	if(head.next==null){
		System.out.println("链表为空");
		return;
	}else{
		HeroNode temp=head.next;
		while(temp!=null){
			System.out.println(temp);
			temp=temp.next;
		}
	}
}

2.增加操作

第一种方法:添加英雄时,直接添加到链表的尾部

代码实现:

public void add(HeroNode head,HeroNode heroNode){
	HeroNode temp=head;
	while(temp.next!=null){
		temp=temp.next;
	}
	temp.next=heroNode;
	heroNode.pre=temp;
}

测试代码:

package com.atguigu.doublelinkedlist;

public class DoubleLinkedListDemo {
	public static void main(String[] args) {
		DoubleLinkedList doubleLinkedList=new DoubleLinkedList();
		
		HeroNode heroNode1=new HeroNode(46, "宋江", "及时雨");
		HeroNode heroNode2=new HeroNode(89, "武松", "行者");
		HeroNode heroNode3=new HeroNode(60, "时迁", "鼓上蚤");
		HeroNode heroNode4=new HeroNode(90, "孙二娘", "母夜叉");
		HeroNode heroNode5=new HeroNode(33, "张顺", "浪里白条");
		
		System.out.println("添加节点前的双链表信息:");
		doubleLinkedList.show(doubleLinkedList.getHead());
		
		System.out.println("********************************************");
		System.out.println("添加节点后的双链表信息:");
		doubleLinkedList.add(doubleLinkedList.getHead(), heroNode1);
		doubleLinkedList.add(doubleLinkedList.getHead(), heroNode2);
		doubleLinkedList.add(doubleLinkedList.getHead(), heroNode3);
		doubleLinkedList.add(doubleLinkedList.getHead(), heroNode4);
		doubleLinkedList.add(doubleLinkedList.getHead(), heroNode5);
		doubleLinkedList.show(doubleLinkedList.getHead());
	}
}

效果图:

第二种方法:在添加英雄时,根据排名将英雄插入到指定的位置(如果已经存在,则添加失败,并给出提示)

注意中间位置插入节点的时候,需要考虑好操作的顺序。如下图所示,需要首先操作红色的部分。即先将temp.next与新节点连接起来,再将temp与新节点连接。

public void addByOrder(HeroNode head,HeroNode heroNode){
		HeroNode temp=head;
		boolean flag=false;
		while(true){
			if(temp.next==null){
				break;
			}
			if(temp.next.no==heroNode.no){
				throw new RuntimeException("链表中已存在该编号的节点,添加失败");
			}
			if(temp.next.no>heroNode.no){
				flag=true;
				break;
			}
			temp=temp.next;
		}
		if(flag==true){
			temp.next.pre=heroNode;
			heroNode.next=temp.next;
			temp.next=heroNode;
			heroNode.pre=temp;	
		}else{
			temp.next=heroNode;
			heroNode.pre=temp;
		}
	}

测试代码:

package com.atguigu.doublelinkedlist;

public class DoubleLinkedListDemo {
	public static void main(String[] args) {
		DoubleLinkedList doubleLinkedList=new DoubleLinkedList();
		
		HeroNode heroNode1=new HeroNode(46, "宋江", "及时雨");
		HeroNode heroNode2=new HeroNode(89, "武松", "行者");
		HeroNode heroNode3=new HeroNode(60, "时迁", "鼓上蚤");
		HeroNode heroNode4=new HeroNode(90, "孙二娘", "母夜叉");
		HeroNode heroNode5=new HeroNode(33, "张顺", "浪里白条");
		
		System.out.println("添加节点前的双链表信息:");
		doubleLinkedList.show(doubleLinkedList.getHead());
		
		
		System.out.println("********************************************");
		System.out.println("添加节点后的双链表信息:");
		doubleLinkedList.addByOrder(doubleLinkedList.getHead(), heroNode1);
		doubleLinkedList.addByOrder(doubleLinkedList.getHead(), heroNode2);
		doubleLinkedList.addByOrder(doubleLinkedList.getHead(), heroNode3);
		doubleLinkedList.addByOrder(doubleLinkedList.getHead(), heroNode4);
		doubleLinkedList.addByOrder(doubleLinkedList.getHead(), heroNode5);
		doubleLinkedList.show(doubleLinkedList.getHead());
		
	}
}

效果图:

3.修改操作

遍历整个链表,根据已知的节点编号信息,找到需要修改的节点位置,并修改;如果未找到该节点,则返回修改失败的信息。

代码实现:

public void update(HeroNode head,HeroNode heroNode){
		if(head.next==null){
			System.out.println("链表为空,修改失败");
		}else{
			HeroNode temp=head.next;
			boolean flag=false;
			while(true){
				if(temp==null){
					break;
				}
				if(temp.no==heroNode.no){
					flag=true;
					break;
				}
				temp=temp.next;
			}
			if(flag==true){
				temp.name=heroNode.name;
				temp.nickname=heroNode.nickname;
				System.out.println("编号为:"+heroNode.no+"的节点修改成功");
			}else{
				System.out.println("该节点不存在,修改失败");
			}
		}
	}

测试代码:

package com.atguigu.doublelinkedlist;

public class DoubleLinkedListDemo {
	public static void main(String[] args) {
		DoubleLinkedList doubleLinkedList=new DoubleLinkedList();
		
		HeroNode heroNode1=new HeroNode(46, "宋江", "及时雨");
		HeroNode heroNode2=new HeroNode(89, "武松", "行者");
		HeroNode heroNode3=new HeroNode(60, "时迁", "鼓上蚤");
		HeroNode heroNode4=new HeroNode(90, "孙二娘", "母夜叉");
		HeroNode heroNode5=new HeroNode(33, "张顺", "浪里白条");
		
		System.out.println("添加节点前的双链表信息:");
		doubleLinkedList.show(doubleLinkedList.getHead());
		
		
		System.out.println("********************************************");
		System.out.println("添加节点后的双链表信息:");
		doubleLinkedList.addByOrder(doubleLinkedList.getHead(), heroNode1);
		doubleLinkedList.addByOrder(doubleLinkedList.getHead(), heroNode2);
		doubleLinkedList.addByOrder(doubleLinkedList.getHead(), heroNode3);
		doubleLinkedList.addByOrder(doubleLinkedList.getHead(), heroNode4);
		doubleLinkedList.addByOrder(doubleLinkedList.getHead(), heroNode5);
		doubleLinkedList.show(doubleLinkedList.getHead());
		
		System.out.println("********************************************");
		System.out.println("修改操作:");
		doubleLinkedList.update(doubleLinkedList.getHead(), new HeroNode(90, "aaa", "333"));
		doubleLinkedList.show(doubleLinkedList.getHead());
		
	}
}

效果图:

4.删除操作

根据节点编号删除相应节点。

代码实现:

注意删除节点的操作需要考虑是否在链表的末尾,中间节点的删除操作和末尾节点的删除操作不一样。

	public void delete(HeroNode head,int no){
		if(head.next==null){
			System.out.println("链表为空,删除失败");
		}else{
			HeroNode temp=head.next;
			boolean flag=false;
			while(true){
				if(temp==null){
					break;
				}
				if(temp.no==no){
					flag=true;
					break;
				}
				temp=temp.next;
			}
			if(flag==true){
				temp.pre.next=temp.next;
				//如果是最后一个节点,就不需要执行下面这行代码。否则会有空指针异常。
				if(temp.next!=null){
					temp.next.pre=temp.pre;
				}
				
				System.out.println("编号为:"+no+"的节点删除成功");
			}else{
				System.out.println("该节点不存在,删除失败");
			}
		}
	}

测试代码:

package com.atguigu.doublelinkedlist;

public class DoubleLinkedListDemo {
	public static void main(String[] args) {
		DoubleLinkedList doubleLinkedList=new DoubleLinkedList();
		
		HeroNode heroNode1=new HeroNode(46, "宋江", "及时雨");
		HeroNode heroNode2=new HeroNode(89, "武松", "行者");
		HeroNode heroNode3=new HeroNode(60, "时迁", "鼓上蚤");
		HeroNode heroNode4=new HeroNode(90, "孙二娘", "母夜叉");
		HeroNode heroNode5=new HeroNode(33, "张顺", "浪里白条");
		
		System.out.println("添加节点前的双链表信息:");
		doubleLinkedList.show(doubleLinkedList.getHead());
		
		
		System.out.println("********************************************");
		System.out.println("添加节点后的双链表信息:");
		doubleLinkedList.addByOrder(doubleLinkedList.getHead(), heroNode1);
		doubleLinkedList.addByOrder(doubleLinkedList.getHead(), heroNode2);
		doubleLinkedList.addByOrder(doubleLinkedList.getHead(), heroNode3);
		doubleLinkedList.addByOrder(doubleLinkedList.getHead(), heroNode4);
		doubleLinkedList.addByOrder(doubleLinkedList.getHead(), heroNode5);
		doubleLinkedList.show(doubleLinkedList.getHead());
			
		System.out.println("********************************************");
		System.out.println("删除操作:");
		doubleLinkedList.delete(doubleLinkedList.getHead(), 46);
		doubleLinkedList.show(doubleLinkedList.getHead());

	}
}

效果图:

5.查找操作

遍历整个链表,根据已知的节点编号信息,查找该编号对应节点的相关信息;如果未找到该节点,则返回查找失败的信息。

代码实现:

	public void getNode(HeroNode head,int no){
		if(head.next==null){
			System.out.println("链表为空,该节点不存在");
		}else{
			HeroNode temp=head.next;
			boolean flag=false;
			while(true){
				if(temp==null){
					break;
				}
				if(temp.no==no){
					flag=true;
					break;
				}
				temp=temp.next;
			}
			if(flag==true){
				System.out.println(temp);
			}else{
				System.out.println("该节点不存在,查找失败");
			}
			
		}
	}

测试代码:

package com.atguigu.doublelinkedlist;

public class DoubleLinkedListDemo {
	public static void main(String[] args) {
		DoubleLinkedList doubleLinkedList=new DoubleLinkedList();
		
		HeroNode heroNode1=new HeroNode(46, "宋江", "及时雨");
		HeroNode heroNode2=new HeroNode(89, "武松", "行者");
		HeroNode heroNode3=new HeroNode(60, "时迁", "鼓上蚤");
		HeroNode heroNode4=new HeroNode(90, "孙二娘", "母夜叉");
		HeroNode heroNode5=new HeroNode(33, "张顺", "浪里白条");
		
		System.out.println("添加节点前的双链表信息:");
		doubleLinkedList.show(doubleLinkedList.getHead());
		
		
		System.out.println("********************************************");
		System.out.println("添加节点后的双链表信息:");
		doubleLinkedList.addByOrder(doubleLinkedList.getHead(), heroNode1);
		doubleLinkedList.addByOrder(doubleLinkedList.getHead(), heroNode2);
		doubleLinkedList.addByOrder(doubleLinkedList.getHead(), heroNode3);
		doubleLinkedList.addByOrder(doubleLinkedList.getHead(), heroNode4);
		doubleLinkedList.addByOrder(doubleLinkedList.getHead(), heroNode5);
		doubleLinkedList.show(doubleLinkedList.getHead());
			
		System.out.println("********************************************");
		System.out.println("查找操作:");
		doubleLinkedList.getNode(doubleLinkedList.getHead(), 90);

	}
}

效果图:

 

五、约瑟夫问题(Josephu)

Josephu 问题:设编号为12… nn个人围坐一圈,约定编号为k1<=k<=n)的人从1开始报数,数到m 的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。

:用一个不带头结点的循环链表来处理Josephu 问题:先构成一个有n个结点的单循环链表,然后由k结点起从1开始计数,计到m时,对应结点从链表中删除,然后再从被删除结点的下一个结点又从1开始计数,直到最后一个结点从链表中删除算法结束。

示意图:

 

代码实现:

创建节点:

package com.atguigu.circlesinglelinkedlist;

public class Boy {
	private int no;
	private Boy next;
	public Boy(int no) {
		super();
		this.no = no;
	}
	public int getNo() {
		return no;
	}
	public void setNo(int no) {
		this.no = no;
	}
	public Boy getNext() {
		return next;
	}
	public void setNext(Boy next) {
		this.next = next;
	}
	@Override
	public String toString() {
		return "Boy [no=" + no + "]";
	}
}

约瑟夫问题求解:

package com.atguigu.circlesinglelinkedlist;

public class CircleSingleLinkedList {
	//创建一个first节点,当前没有编号
	private Boy first=null;
	
	
	//添加节点,构建成环形链表
	public void add(int nums){
		if(nums<1){
			System.out.println("nums的值不正确");
		}else{
			Boy temp=null;
			for(int i=1;i<=nums;i++){
				Boy boy = new Boy(i);
				if(i==1){
					first=boy;
					first.setNext(first);
					temp=first;
				}else{
					temp.setNext(boy);
					boy.setNext(first);
					temp=boy;
				}
			}
		}
	}
	


    //显示链表内容
	public void show(){
		if(first==null){
			System.out.println("链表为空");
		}else{
			Boy temp=first;
			while(true){
				if(temp.getNext()==first){
					System.out.println(temp);
					break;
				}
				System.out.println(temp);
				temp=temp.getNext();
				
			}
		}
	}
	


	删除指定编号的节点
	public int delete(int no){
		if(first==null){
			System.out.println("链表为空,删除失败");
			return -1;
		}else{
			Boy temp=first;
			boolean flag=false;
			
			
			Boy first_pre=null;
			//first_pre节点的初始位置(first节点的初始位置在1号节点)
			while(true){
				if(temp.getNext()==first){
					first_pre=temp;
					temp=temp.getNext();
					break;
				}
				temp=temp.getNext();
			}
			
			//删除first指向的节点
			if(temp.getNo()==no){
				int b=temp.getNo();
				first_pre.setNext(temp.getNext());
				first=temp.getNext();
				return b;
			}
			
			//删除非first指向的节点
			while(true){
				if(temp.getNext()==first){
					break;
				}
				if(temp.getNext().getNo()==no){
					flag=true;
					break;
				}
				temp=temp.getNext();
			}
			if(flag==true){
				int a=temp.getNext().getNo();
				temp.setNext(temp.getNext().getNext());
				return a;
			}
			else{
				System.out.println("该节点不存在,删除失败");
				return -1;
			}
		}
	}
	
	
	
	
    //获取当前链表实际长度
	public int getLength(){
		if(first==null){
			return 0;
		}else{
			int count=1;
			Boy temp=first;
			while(temp.getNext()!=first){
				count++;
				temp=temp.getNext();
			}
			return count;
		}
	}
	
	
	
   //约瑟夫问题求解
	public void getJosephu(int startNo,int countNum,int nums){
		if(startNo>nums || startNo<1){
			System.out.println("输入的参数有误");
		}else{
			if(first==null){
				System.out.println("链表为空");
			}else{
				Boy temp=first;
				Boy first_pre=null;
				
				//first_pre节点的初始位置(first节点的初始位置在1号节点)
				while(true){
					if(temp.getNext()==first){
						first_pre=temp;
						temp=temp.getNext();
						break;
					}
					temp=temp.getNext();
				}
				
				//将first指针和first_pre指针指向给定位置(startNo和startNo的前一个位置)
				while(true){
					if(temp.getNo()==startNo){
						break;
					}else{
						first_pre=temp;
						temp=temp.getNext();
					}
				}
				
			
				
				//根据实际队列长度,进行约瑟夫循环,直到队列中只有一个节点
				while(this.getLength()>=2){
					System.out.println("当前队列长度:"+getLength());
					int count=1;
					for(int i=1;i<=countNum;i++){
						if(i==countNum){
							System.out.println(delete(temp.getNo()));
							temp=first_pre.getNext();
							
						}else{
							first_pre=temp;
							temp=temp.getNext();
						}	
					}
				}
				//将队列中最后一个元素去掉
				System.out.println("当前队列长度:"+getLength());
				System.out.println(delete(temp.getNo()));
			}	
		}
	}
	

}

测试代码:(总共5个节点,从第1节点开始,每2个节点产生一个出队节点)

package com.atguigu.circlesinglelinkedlist;

public class CircleSingleLinkedListDemo {
	public static void main(String[] args) {
		CircleSingleLinkedList circleSingleLinkedList=new CircleSingleLinkedList();
		circleSingleLinkedList.add(5);
		circleSingleLinkedList.show();

		System.out.println("----------------------");
		circleSingleLinkedList.getJosephu(1,2,5);
		
	}
}

效果图:

测试代码:(总共5个节点,从第2节点开始,每3个节点产生一个出队节点)

package com.atguigu.circlesinglelinkedlist;

public class CircleSingleLinkedListDemo {
	public static void main(String[] args) {
		CircleSingleLinkedList circleSingleLinkedList=new CircleSingleLinkedList();
		circleSingleLinkedList.add(5);
		circleSingleLinkedList.show();

		System.out.println("----------------------");
		circleSingleLinkedList.getJosephu(2,3,5);
	}
}

效果图:

分析:使用环形链表解决约瑟夫问题

  • 建立环形链表。首先创建一个first节点,也是后续操作的入口。如果只添加一个节点,则在first节点上修改;如果是多个节点,第一个节点在first节点上修改,其他节点在first节点后添加,并使最后一个节点的next域指向first节点。
  • 显示环形链表的节点信息。如果first节点为空,则说明该环形链表为空;否则从first节点开始,依次遍历环形链表,直到某节点的next域为first节点时结束遍历。
  • 删除节点。如果first节点为空,则说明该环形链表为空,无法删除指定节点。否则,判断是否删除的节点为第一个节点。如果删除的是第一个节点,则需该节点的前一个节点的指针,遍历得到第一个节点的前一个节点指针,进而删除第一个节点。如果删除的节点不是第一个节点,则依次遍历找到要删除节点的前一个位置,进而删除该指定节点。
  • 求解约瑟夫问题。首先需要找到开始的位置。然后根据实际链表长度进行循环删除节点的操作。如果链表的长度大于等于2,则根据指定的计数,找到要删除节点的位置,并删除。最后如果链表的长度为1,则将最后一个节点删除。进而得到约瑟夫问题的求解。

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值