Java數據結構與算法 day02 鏈表

第三章 鏈表

單鏈表介紹和內存佈局

鏈表是有序的列表,但是它在內存中是實際存儲結構如下:

在這裏插入圖片描述

小結:
1.鏈表是以節點的方式來存儲,是鏈式存儲(即各個節點之間並不一定是連續存儲的,而是相互指向的);
2.每個節點包含 data 域:存放數據的域, next 域:指向下一個節點;
3.如圖:發現鏈表的各個節點不一定是連續存儲;
4.鏈表分帶頭節點的鏈表和沒有頭節點的鏈表,根據實際的需求來確定.

單鏈表(帶頭結點) 邏輯結構示意圖如下:

在這裏插入圖片描述

單鏈表創建和遍歷的分析實現

  • 先看一個例子

使用帶head頭的單向鏈表實現 –水滸英雄排行榜管理

1)完成對英雄人物的增刪改查操作, 注: 刪除和修改,查找可以考慮學員獨立完成,也可帶學員完成

2)第一種方法在添加英雄時,直接添加到鏈表的尾部

3)第二種方式在添加英雄時,根據排名將英雄插入到指定位置
(如果有這個排名,則添加失敗,並給出提示)

  • 單鏈表的創建示意圖(添加), 顯示單向鏈表的分析
class HeroNode {
    int no;
    String name;
    String nickName;
    HeroNode next;
}

添加(創建)過程

  • 先創建一個head 頭節點, 作用就是表示單鏈表的頭;

在這裏插入圖片描述

  • 之後每添加一個節點,就直接加入到—》鏈表的最後。

在這裏插入圖片描述
在這裏插入圖片描述

遍歷過程

  • 通過一個輔助變量(臨時變量temp)遍歷,幫助遍歷整個鏈表。

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

代碼實現

/*
 * 此代碼添加數據有問題,待優化
 */
public class SingleLinkedListTest {

	public static void main(String[] args) {
		//測試一下
		//1.創建節點
		HeroNode hero = new HeroNode(1,"宋江","及時雨");
		HeroNode hero2 = new HeroNode(2,"盧俊義","玉麒麟");
		HeroNode hero3 = new HeroNode(3,"吳用","智多星");
		HeroNode hero4 = new HeroNode(4,"公孫勝","入雲龍");
		HeroNode hero5 = new HeroNode(5,"關勝","大刀");
		
		//創建一個鏈表
		SingleLinkedList singk = new SingleLinkedList();
		
		//添加數據到鏈表
		singk.add(hero);
		singk.add(hero2);
		singk.add(hero3);
		singk.add(hero4);
		singk.add(hero5); 
		
		//顯示鏈表
		singk.list();
	}

}

//定義一個SingleLinkedList類,來管理我們的英雄
class SingleLinkedList{
	//先初始化一個頭節點,頭節點不要動(防止後期找不到此鏈表),不存放具體數據
	private HeroNode head = new HeroNode(0, "", "");
	
	//添加節點到單向鏈表
	//思路,當不考慮編號的順序時
	//1.找到當前鏈表的最後節點
	//2.將最後這個節點的next域指向這個新的節點
	public void add(HeroNode heroNode){
		//由於頭節點head不能動,所以需要一個輔助變量 temp
		HeroNode temp = head;
		//遍歷鏈表,找到最後
		while(true){
			//找到鏈表的最後
			if(temp.next == null){	//判定找到了的條件
				break;
			}
			//如果沒有找到,將temp後移
			temp = temp.next;
		}
		//當退出循環時,temp就指向了鏈表的最後
		//將最後這個節點的next --指向--》 新的節點
		temp.next = heroNode;
	}
	
	//顯示鏈表[遍歷]
	public void list(){
		//判斷鏈表是否爲空
		if(head.next == null){
			System.out.println("鏈表爲空!!!");
			return;
		}
		//由於頭節點head不能動,所以需要一個輔助變量 temp
		HeroNode temp = head;
		while(true){
			//判斷是否到鏈表的最後
			if(temp == null){
				break;
			}
			//如果不爲空,輸出節點的信息
			System.out.println(temp);
			//注意!!!!將next後移(因爲不向後移動,會造成死循環)
			temp = temp.next;
		}
	}
}

//定義一個HeroNode,每個HeroNode對象就是一個節點
class HeroNode{
	public int id;
	public String name;
	public String nickName;	//別名,暱稱
	public HeroNode next;	//指向下一個節點
	
	//構造器
	public HeroNode(int id, String name, String nickName) {
		super();
		this.id = id;
		this.name = name;
		this.nickName = nickName;
	}

	//爲了顯示方便,重寫toString方法
	@Override
	public String toString() {
		return "HeroNode [id=" + id + ", name=" + name + ", nickName=" + nickName + "]";
	}
}

單鏈表按順序插入節點

  • 需要按照編號的順序添加
  • 首先找到新添加的節點的位置, 是通過輔助變量(指針), 通過遍歷來搞定

在這裏插入圖片描述

  • 新的節點.next = temp.next

在這裏插入圖片描述

  • 將temp.next = 新的節點

在這裏插入圖片描述

  • 代碼實現
/*
 * 此代碼已優化
 */
public class SingleLinkedListTest {

	public static void main(String[] args) {
		//測試一下
		//1.創建節點
		HeroNode hero = new HeroNode(1,"宋江","及時雨");
		HeroNode hero2 = new HeroNode(2,"盧俊義","玉麒麟");
		HeroNode hero3 = new HeroNode(3,"吳用","智多星");
		HeroNode hero4 = new HeroNode(4,"公孫勝","入雲龍");
		HeroNode hero5 = new HeroNode(5,"關勝","大刀");
		
		//創建一個鏈表
		SingleLinkedList singk = new SingleLinkedList();
		
		//添加數據到鏈表
//		singk.add(hero);
//		singk.add(hero2);
//		singk.add(hero4);
//		singk.add(hero3);
//		singk.add(hero5); 
		
		//添加按照編號的順序
		singk.addByOrder(hero);
		singk.addByOrder(hero4);
		singk.addByOrder(hero2);
		singk.addByOrder(hero5); 
		singk.addByOrder(hero3);
//		singk.addByOrder(hero3);
		
		//顯示鏈表
		singk.list();
	}

}

//定義一個SingleLinkedList類,來管理我們的英雄
class SingleLinkedList{
	//先初始化一個頭節點,頭節點不要動(防止後期找不到此鏈表),不存放具體數據
	private HeroNode head = new HeroNode(0, "", "");
	
	//第一種方式:第一種方法在添加英雄時,直接添加到鏈表的尾部
	//添加節點到單向鏈表,思路,當不考慮編號的順序時
	//1.找到當前鏈表的最後節點
	//2.將最後這個節點的next域指向這個新的節點
	public void add(HeroNode heroNode){
		//由於頭節點head不能動,所以需要一個輔助變量 temp
		HeroNode temp = head;
		//遍歷鏈表,找到最後
		while(true){
			//找到鏈表的最後
			if(temp.next == null){	//判定找到了的條件
				break;
			}
			//如果沒有找到,將temp後移
			temp = temp.next;
		}
		//當退出循環時,temp就指向了鏈表的最後
		//將最後這個節點的next --指向--》 新的節點
		temp.next = heroNode;
	}
	
	//顯示鏈表[遍歷]
	public void list(){
		//判斷鏈表是否爲空
		if(head.next == null){
			System.out.println("鏈表爲空!!!");
			return;
		}
		//由於頭節點head不能動,所以需要一個輔助變量 temp
		HeroNode temp = head;
		while(true){
			//判斷是否到鏈表的最後
			if(temp == null){
				break;
			}
			//如果不爲空,輸出節點的信息
			System.out.println(temp);
			//注意!!!!將next後移(因爲不向後移動,會造成死循環)
			temp = temp.next;
		}
	}
	
	//第二種方式在添加英雄時,根據排名將英雄插入到指定位置
	//(如果有這個排名,則添加失敗,並給出提示)
	public void addByOrder(HeroNode heroNode){
		//由於頭節點head不能動,所以需要一個輔助變量 temp,找到添加的位置
		//由於是單鏈表,而找到temp是位於 添加位置的前一個節點,否則插入不了
		HeroNode temp = head;
		boolean flag = false;	//標識添加的編號是否存在,默認爲false
		while(true){
			if(temp.next == null){	//說明已經在鏈表的最後
				break;	
			}
			if(temp.next.id > heroNode.id){	//位置找到了,就在temp的後面插入
				break;
			}else if(temp.next.id == heroNode.id){	//說明希望添加的heroNode的編號已然存在
				flag = true;	//說明編號存在
				break;
			}
			temp = temp.next;	//後移,遍歷當前鏈表
		}
		//判斷flag的值
		if(flag){	//不能添加,說明編號存在
			System.out.printf("準備插入的英雄編號%d已經存在了。無法加入\n",heroNode.id);
		}else{
			//插入到鏈表中,temp的後面
			heroNode.next = temp.next;
			temp.next = heroNode;
		}
	}

}

//定義一個HeroNode,每個HeroNode對象就是一個節點
class HeroNode{
	public int id;
	public String name;
	public String nickName;	//別名,暱稱
	public HeroNode next;	//指向下一個節點
	
	//構造器
	public HeroNode(int id, String name, String nickName) {
		super();
		this.id = id;
		this.name = name;
		this.nickName = nickName;
	}

	//爲了顯示方便,重寫toString方法
	@Override
	public String toString() {
		return "HeroNode [id=" + id + ", name=" + name + ", nickName=" + nickName + "]";
	}
}

單鏈表節點的修改

public class SingleLinkedListTest {

	public static void main(String[] args) {
		//測試一下
		//1.創建節點
		HeroNode hero = new HeroNode(1,"宋江","及時雨");
		HeroNode hero2 = new HeroNode(2,"盧俊義","玉麒麟");
		HeroNode hero3 = new HeroNode(3,"吳用","智多星");
		HeroNode hero4 = new HeroNode(4,"公孫勝","入雲龍");
		HeroNode hero5 = new HeroNode(5,"關勝","大刀");
		
		//創建一個鏈表
		SingleLinkedList singk = new SingleLinkedList();
		
		//添加按照編號的順序
		singk.addByOrder(hero);
		singk.addByOrder(hero4);
		singk.addByOrder(hero2);
		singk.addByOrder(hero5); 
		singk.addByOrder(hero3);
		
		//顯示鏈表
		singk.list();
		
		//測試修改節點的代碼
		HeroNode hero6 = new HeroNode(5,"渣渣輝","一刀999");
		singk.update(hero6);
		
		//顯示鏈表
		System.out.println("修改後的鏈表情況:");
		singk.list();
	}

}

//定義一個SingleLinkedList類,來管理我們的英雄
class SingleLinkedList{
	//先初始化一個頭節點,頭節點不要動(防止後期找不到此鏈表),不存放具體數據
	private HeroNode head = new HeroNode(0, "", "");
	
	//修改節點的信息,根據id編號來修改,即id編號不改
	//說明
	//1.根據newHeroNode 的 id 來修改即可
	public void update(HeroNode newHeroNode){
		//判斷是否爲空
		if(head.next == null){	//鏈表爲空
			System.out.println("鏈表爲空!!!");
			return;
		}
		//找到需要修改的節點,根據 id 編號
		//定義一個臨時變量
		HeroNode temp = head.next;
		boolean flag = false;	//表示是否找到該節點
		while(true){
			if(temp == null){
				break;	//已經遍歷完鏈表
			}
			if(temp.id == newHeroNode.id){
				//找到了
				flag = true;
				break;
			}
			temp = temp.next;
		}
		//根據flag 判斷是否找到要修改的節點
		if(flag){
			temp.name = newHeroNode.name;
			temp.nickName = newHeroNode.nickName;
		}else{
			System.out.printf("沒有找到編號爲 %d 的節點.\n",newHeroNode.id);
		}
	}
	
	//顯示鏈表[遍歷]
	public void list(){
		//判斷鏈表是否爲空
		if(head.next == null){
			System.out.println("鏈表爲空!!!");
			return;
		}
		//由於頭節點head不能動,所以需要一個輔助變量 temp
		HeroNode temp = head;
		while(true){
			//判斷是否到鏈表的最後
			if(temp == null){
				break;
			}
			//如果不爲空,輸出節點的信息
			System.out.println(temp);
			//注意!!!!將next後移(因爲不向後移動,會造成死循環)
			temp = temp.next;
		}
	}
	
	//第二種方式在添加英雄時,根據排名將英雄插入到指定位置
	//(如果有這個排名,則添加失敗,並給出提示)
	public void addByOrder(HeroNode heroNode){
		//由於頭節點head不能動,所以需要一個輔助變量 temp,找到添加的位置
		//由於是單鏈表,而找到temp是位於 添加位置的前一個節點,否則插入不了
		HeroNode temp = head;
		boolean flag = false;	//標識添加的編號是否存在,默認爲false
		while(true){
			if(temp.next == null){	//說明已經在鏈表的最後
				break;	
			}
			if(temp.next.id > heroNode.id){	//位置找到了,就在temp的後面插入
				break;
			}else if(temp.next.id == heroNode.id){	//說明希望添加的heroNode的編號已然存在
				flag = true;	//說明編號存在
				break;
			}
			temp = temp.next;	//後移,遍歷當前鏈表
		}
		//判斷flag的值
		if(flag){	//不能添加,說明編號存在
			System.out.printf("準備插入的英雄編號%d已經存在了。無法加入\n",heroNode.id);
		}else{
			//插入到鏈表中,temp的後面
			heroNode.next = temp.next;
			temp.next = heroNode;
		}
	}

}

//定義一個HeroNode,每個HeroNode對象就是一個節點
class HeroNode{
	public int id;
	public String name;
	public String nickName;	//別名,暱稱
	public HeroNode next;	//指向下一個節點
	
	//構造器
	public HeroNode(int id, String name, String nickName) {
		super();
		this.id = id;
		this.name = name;
		this.nickName = nickName;
	}

	//爲了顯示方便,重寫toString方法
	@Override
	public String toString() {
		return "HeroNode [id=" + id + ", name=" + name + ", nickName=" + nickName + "]";
	}
}

單鏈表節點的刪除和小結

從單鏈表中刪除一個節點的思路圖解

在這裏插入圖片描述

  1. 我們先找到 需要刪除的這個節點的前一個節點 temp

在這裏插入圖片描述

  1. temp.next = temp.next.next

在這裏插入圖片描述

  1. 被刪除的節點,將不會有其它引用指向,會被垃圾回收機制回收
public class SingleLinkedListTest {

	public static void main(String[] args) {
		//測試一下
		//1.創建節點
		HeroNode hero = new HeroNode(1,"宋江","及時雨");
		HeroNode hero2 = new HeroNode(2,"盧俊義","玉麒麟");
		HeroNode hero3 = new HeroNode(3,"吳用","智多星");
		HeroNode hero4 = new HeroNode(4,"公孫勝","入雲龍");
		HeroNode hero5 = new HeroNode(5,"關勝","大刀");
		
		//創建一個鏈表
		SingleLinkedList singk = new SingleLinkedList();
		
		//添加按照編號的順序
		singk.addByOrder(hero);
		singk.addByOrder(hero4);
		singk.addByOrder(hero2);
		singk.addByOrder(hero5); 
		singk.addByOrder(hero3);
		
		//顯示鏈表
		singk.list();
		
		//刪除一個節點
		singk.del(2);
		singk.del(1);
		singk.del(3);
		singk.del(4);
		singk.del(5);
		System.out.println("刪除後的情況:");
		singk.list();	//顯示鏈表
	}

}

//定義一個SingleLinkedList類,來管理我們的英雄
class SingleLinkedList{
	//先初始化一個頭節點,頭節點不要動(防止後期找不到此鏈表),不存放具體數據
	private HeroNode head = new HeroNode(0, "", "");
	
	//刪除節點
	//思路
	//1.由於頭節點head不能動,所以需要一個輔助變量 temp來找到待刪除節點的前一個節點
	//2.說明我們在比較時,是temp.next.id 和  需要刪除的節點的id比較
	public void del(int id){
		HeroNode temp = head;
		boolean flag = false;	//標誌是否找到帶刪除的節點
		while(true){
			if(temp.next == null){	//已經到鏈表的最後
				break;
			}
			if(temp.next.id == id){	//找到了帶刪除的節點的前一個節點
				flag = true;
				break;
			}
			temp = temp.next;	//temp後移,遍歷
		}
		//判斷flag
		if(flag){	//說明找到了
			//可以刪除
			temp.next = temp.next.next;
		}else{
			System.out.printf("要刪除的節點 %d 不存在。\n",id);
		}
	}
	
	
	//顯示鏈表[遍歷]
	public void list(){
		//判斷鏈表是否爲空
		if(head.next == null){
			System.out.println("鏈表爲空!!!");
			return;
		}
		//由於頭節點head不能動,所以需要一個輔助變量 temp
		HeroNode temp = head;
		while(true){
			//判斷是否到鏈表的最後
			if(temp == null){
				break;
			}
			//如果不爲空,輸出節點的信息
			System.out.println(temp);
			//注意!!!!將next後移(因爲不向後移動,會造成死循環)
			temp = temp.next;
		}
	}
	
	//第二種方式在添加英雄時,根據排名將英雄插入到指定位置
	//(如果有這個排名,則添加失敗,並給出提示)
	public void addByOrder(HeroNode heroNode){
		//由於頭節點head不能動,所以需要一個輔助變量 temp,找到添加的位置
		//由於是單鏈表,而找到temp是位於 添加位置的前一個節點,否則插入不了
		HeroNode temp = head;
		boolean flag = false;	//標識添加的編號是否存在,默認爲false
		while(true){
			if(temp.next == null){	//說明已經在鏈表的最後
				break;	
			}
			if(temp.next.id > heroNode.id){	//位置找到了,就在temp的後面插入
				break;
			}else if(temp.next.id == heroNode.id){	//說明希望添加的heroNode的編號已然存在
				flag = true;	//說明編號存在
				break;
			}
			temp = temp.next;	//後移,遍歷當前鏈表
		}
		//判斷flag的值
		if(flag){	//不能添加,說明編號存在
			System.out.printf("準備插入的英雄編號%d已經存在了。無法加入\n",heroNode.id);
		}else{
			//插入到鏈表中,temp的後面
			heroNode.next = temp.next;
			temp.next = heroNode;
		}
	}

}

//定義一個HeroNode,每個HeroNode對象就是一個節點
class HeroNode{
	public int id;
	public String name;
	public String nickName;	//別名,暱稱
	public HeroNode next;	//指向下一個節點
	
	//構造器
	public HeroNode(int id, String name, String nickName) {
		super();
		this.id = id;
		this.name = name;
		this.nickName = nickName;
	}

	//爲了顯示方便,重寫toString方法
	@Override
	public String toString() {
		return "HeroNode [id=" + id + ", name=" + name + ", nickName=" + nickName + "]";
	}
}

單鏈表面試題

單鏈表的常見面試題有如下:

1)求單鏈表中有效節點的個數

2)查找單鏈表中的倒數第k個結點 【新浪面試題】

3)單鏈表的反轉【騰訊面試題,有點難度】

4)從尾到頭打印單鏈表 【百度,要求方式1:反向遍歷 。 方式2:Stack棧】

5)合併兩個有序的單鏈表,合併之後的鏈表依然有序【課後練習.】

  • 求單鏈表中有效節點的個數
public class SingleLinkedListTest {

	public static void main(String[] args) {
		//測試一下
		//1.創建節點
		HeroNode hero = new HeroNode(1,"宋江","及時雨");
		HeroNode hero2 = new HeroNode(2,"盧俊義","玉麒麟");
		HeroNode hero3 = new HeroNode(3,"吳用","智多星");
		HeroNode hero4 = new HeroNode(4,"公孫勝","入雲龍");
		HeroNode hero5 = new HeroNode(5,"關勝","大刀");
		
		//創建一個鏈表
		SingleLinkedList singk = new SingleLinkedList();
		
		//添加按照編號的順序
		singk.addByOrder(hero);
		singk.addByOrder(hero4);
		singk.addByOrder(hero2);
		singk.addByOrder(hero5); 
		singk.addByOrder(hero3);
		
		//顯示鏈表
		singk.list();
		
		//測試一下:求單鏈表中有效節點的個數
		System.out.println("單鏈表有效的節點個數 = " + getLength(singk.getHead()));//5
	}
	
	//獲取單鏈表的節點的個數(如果是帶頭節點的鏈表,需求不統計頭結點)
	/**
	  * 
	  * @Description 
	  * @author subei
	  * @date 2020年5月22日下午5:09:46
	  * @param head 鏈表的頭節點
	  * @return 返回的是有效的節點的個數
	 */
	public static int getLength(HeroNode head){
		if(head.next == null){
			return 0;
		}
		int length = 0;
		//定義一個臨時的變量,未統計頭節點
		HeroNode str = head.next;
		while(str != null){
			length++;
			str = str.next;	//即遍歷
		}
		return length;
	}

}

//定義一個SingleLinkedList類,來管理我們的英雄
class SingleLinkedList{
	//先初始化一個頭節點,頭節點不要動(防止後期找不到此鏈表),不存放具體數據
	private HeroNode head = new HeroNode(0, "", "");
	
	//返回頭節點
	public HeroNode getHead() {
		return head;
	}

	//第一種方式:第一種方法在添加英雄時,直接添加到鏈表的尾部
	//添加節點到單向鏈表,思路,當不考慮編號的順序時
	//1.找到當前鏈表的最後節點
	//2.將最後這個節點的next域指向這個新的節點
	public void add(HeroNode heroNode){
		//由於頭節點head不能動,所以需要一個輔助變量 temp
		HeroNode temp = head;
		//遍歷鏈表,找到最後
		while(true){
			//找到鏈表的最後
			if(temp.next == null){	//判定找到了的條件
				break;
			}
			//如果沒有找到,將temp後移
			temp = temp.next;
		}
		//當退出循環時,temp就指向了鏈表的最後
		//將最後這個節點的next --指向--》 新的節點
		temp.next = heroNode;
	}
	
	//修改節點的信息,根據id編號來修改,即id編號不改
	//說明
	//1.根據newHeroNode 的 id 來修改即可
	public void update(HeroNode newHeroNode){
		//判斷是否爲空
		if(head.next == null){	//鏈表爲空
			System.out.println("鏈表爲空!!!");
			return;
		}
		//找到需要修改的節點,根據 id 編號
		//定義一個臨時變量
		HeroNode temp = head.next;
		boolean flag = false;	//表示是否找到該節點
		while(true){
			if(temp == null){
				break;	//已經遍歷完鏈表
			}
			if(temp.id == newHeroNode.id){
				//找到了
				flag = true;
				break;
			}
			temp = temp.next;
		}
		//根據flag 判斷是否找到要修改的節點
		if(flag){
			temp.name = newHeroNode.name;
			temp.nickName = newHeroNode.nickName;
		}else{
			System.out.printf("沒有找到編號爲 %d 的節點.\n",newHeroNode.id);
		}
	}
	
	//刪除節點
	//思路
	//1.由於頭節點head不能動,所以需要一個輔助變量 temp來找到待刪除節點的前一個節點
	//2.說明我們在比較時,是temp.next.id 和  需要刪除的節點的id比較
	public void del(int id){
		HeroNode temp = head;
		boolean flag = false;	//標誌是否找到帶刪除的節點
		while(true){
			if(temp.next == null){	//已經到鏈表的最後
				break;
			}
			if(temp.next.id == id){	//找到了帶刪除的節點的前一個節點
				flag = true;
				break;
			}
			temp = temp.next;	//temp後移,遍歷
		}
		//判斷flag
		if(flag){	//說明找到了
			//可以刪除
			temp.next = temp.next.next;
		}else{
			System.out.printf("要刪除的節點 %d 不存在。\n",id);
		}
	}
	
	
	//顯示鏈表[遍歷]
	public void list(){
		//判斷鏈表是否爲空
		if(head.next == null){
			System.out.println("鏈表爲空!!!");
			return;
		}
		//由於頭節點head不能動,所以需要一個輔助變量 temp
		HeroNode temp = head;
		while(true){
			//判斷是否到鏈表的最後
			if(temp == null){
				break;
			}
			//如果不爲空,輸出節點的信息
			System.out.println(temp);
			//注意!!!!將next後移(因爲不向後移動,會造成死循環)
			temp = temp.next;
		}
	}
	
	//第二種方式在添加英雄時,根據排名將英雄插入到指定位置
	//(如果有這個排名,則添加失敗,並給出提示)
	public void addByOrder(HeroNode heroNode){
		//由於頭節點head不能動,所以需要一個輔助變量 temp,找到添加的位置
		//由於是單鏈表,而找到temp是位於 添加位置的前一個節點,否則插入不了
		HeroNode temp = head;
		boolean flag = false;	//標識添加的編號是否存在,默認爲false
		while(true){
			if(temp.next == null){	//說明已經在鏈表的最後
				break;	
			}
			if(temp.next.id > heroNode.id){	//位置找到了,就在temp的後面插入
				break;
			}else if(temp.next.id == heroNode.id){	//說明希望添加的heroNode的編號已然存在
				flag = true;	//說明編號存在
				break;
			}
			temp = temp.next;	//後移,遍歷當前鏈表
		}
		//判斷flag的值
		if(flag){	//不能添加,說明編號存在
			System.out.printf("準備插入的英雄編號%d已經存在了。無法加入\n",heroNode.id);
		}else{
			//插入到鏈表中,temp的後面
			heroNode.next = temp.next;
			temp.next = heroNode;
		}
	}

}

//定義一個HeroNode,每個HeroNode對象就是一個節點
class HeroNode{
	public int id;
	public String name;
	public String nickName;	//別名,暱稱
	public HeroNode next;	//指向下一個節點
	
	//構造器
	public HeroNode(int id, String name, String nickName) {
		super();
		this.id = id;
		this.name = name;
		this.nickName = nickName;
	}

	//爲了顯示方便,重寫toString方法
	@Override
	public String toString() {
		return "HeroNode [id=" + id + ", name=" + name + ", nickName=" + nickName + "]";
	}
}

新浪面試題

在這裏插入圖片描述

public class SingleLinkedListTest {

	public static void main(String[] args) {
		//測試一下
		//1.創建節點
		HeroNode hero = new HeroNode(1,"宋江","及時雨");
		HeroNode hero2 = new HeroNode(2,"盧俊義","玉麒麟");
		HeroNode hero3 = new HeroNode(3,"吳用","智多星");
		HeroNode hero4 = new HeroNode(4,"公孫勝","入雲龍");
		HeroNode hero5 = new HeroNode(5,"關勝","大刀");
		
		//創建一個鏈表
		SingleLinkedList singk = new SingleLinkedList();
		
		//添加按照編號的順序
		singk.addByOrder(hero);
		singk.addByOrder(hero4);
		singk.addByOrder(hero2);
		singk.addByOrder(hero5); 
		singk.addByOrder(hero3);
		
		//顯示鏈表
		singk.list();
		
		//測試一下:求單鏈表中有效節點的個數
		System.out.println("單鏈表有效的節點個數 = " + getLength(singk.getHead()));//5
		
		//測試一下:查找單鏈表中的倒數第k個結點 
		HeroNode res = findLastIndexNode(singk.getHead(), 4);
		System.out.println("res = "+ res);
	}
	
	//查找單鏈表中的倒數第k個結點 
	//思路:
	//1.編寫一個方法,接收head節點,同時接收一個index 
	//2.index 表示是倒數第index個節點
	//3.先把鏈表從頭到尾遍歷,得到鏈表的總的長度 getLength
	//4.得到size 後,我們從鏈表的第一個開始遍歷 (size-index)個,就可以得到
	//5.如果找到了,則返回該節點,否則返回null
	public static HeroNode findLastIndexNode(HeroNode head, int index) {
		//判斷如果鏈表爲空,返回null
		if(head.next == null) {
			return null;//沒有找到
		}
		//第一個遍歷得到鏈表的長度(即節點個數)
		int size = getLength(head);
		//第二次遍歷  size-index 位置,即倒數的第K個節點
		//先做一個index的校驗
		if(index <= 0 || index > size) {
			return null; 
		}
		//第一一個臨時變量,for 循環定位到倒數的index
		HeroNode str = head.next; //3 // 3 - 1 = 2
		for(int i =0; i< size - index; i++) {
			str = str.next;
		}
		return str;
	}
	
	//獲取單鏈表的節點的個數(如果是帶頭節點的鏈表,需求不統計頭結點)
	/**
	  * 
	  * @Description 
	  * @author subei
	  * @date 2020年5月22日下午5:09:46
	  * @param head 鏈表的頭節點
	  * @return 返回的是有效的節點的個數
	 */
	public static int getLength(HeroNode head){
		if(head.next == null){
			return 0;
		}
		int length = 0;
		//定義一個臨時的變量,未統計頭節點
		HeroNode str = head.next;
		while(str != null){
			length++;
			str = str.next;	//即遍歷
		}
		return length;
	}

}

//定義一個SingleLinkedList類,來管理我們的英雄
class SingleLinkedList{
	//先初始化一個頭節點,頭節點不要動(防止後期找不到此鏈表),不存放具體數據
	private HeroNode head = new HeroNode(0, "", "");
	
	//返回頭節點
	public HeroNode getHead() {
		return head;
	}
	
	//顯示鏈表[遍歷]
	public void list(){
		//判斷鏈表是否爲空
		if(head.next == null){
			System.out.println("鏈表爲空!!!");
			return;
		}
		//由於頭節點head不能動,所以需要一個輔助變量 temp
		HeroNode temp = head;
		while(true){
			//判斷是否到鏈表的最後
			if(temp == null){
				break;
			}
			//如果不爲空,輸出節點的信息
			System.out.println(temp);
			//注意!!!!將next後移(因爲不向後移動,會造成死循環)
			temp = temp.next;
		}
	}
	
	//第二種方式在添加英雄時,根據排名將英雄插入到指定位置
	//(如果有這個排名,則添加失敗,並給出提示)
	public void addByOrder(HeroNode heroNode){
		//由於頭節點head不能動,所以需要一個輔助變量 temp,找到添加的位置
		//由於是單鏈表,而找到temp是位於 添加位置的前一個節點,否則插入不了
		HeroNode temp = head;
		boolean flag = false;	//標識添加的編號是否存在,默認爲false
		while(true){
			if(temp.next == null){	//說明已經在鏈表的最後
				break;	
			}
			if(temp.next.id > heroNode.id){	//位置找到了,就在temp的後面插入
				break;
			}else if(temp.next.id == heroNode.id){	//說明希望添加的heroNode的編號已然存在
				flag = true;	//說明編號存在
				break;
			}
			temp = temp.next;	//後移,遍歷當前鏈表
		}
		//判斷flag的值
		if(flag){	//不能添加,說明編號存在
			System.out.printf("準備插入的英雄編號%d已經存在了。無法加入\n",heroNode.id);
		}else{
			//插入到鏈表中,temp的後面
			heroNode.next = temp.next;
			temp.next = heroNode;
		}
	}

}

//定義一個HeroNode,每個HeroNode對象就是一個節點
class HeroNode{
	public int id;
	public String name;
	public String nickName;	//別名,暱稱
	public HeroNode next;	//指向下一個節點
	
	//構造器
	public HeroNode(int id, String name, String nickName) {
		super();
		this.id = id;
		this.name = name;
		this.nickName = nickName;
	}

	//爲了顯示方便,重寫toString方法
	@Override
	public String toString() {
		return "HeroNode [id=" + id + ", name=" + name + ", nickName=" + nickName + "]";
	}
}

騰訊面試題

單鏈表的反轉,效果圖如下:

在這裏插入圖片描述

思路:

  1. 先定義一個節點 reverseHead = new HeroNode();

  2. 從頭到尾遍歷原來的鏈表,每遍歷一個節點,就將其取出,並放在新的鏈表reverseHead 的最前端.

  3. 原來的鏈表的head.next = reverseHead.next

具體如下圖:

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

public class SingleLinkedListTest {

	public static void main(String[] args) {
		//測試一下
		//1.創建節點
		HeroNode hero = new HeroNode(1,"宋江","及時雨");
		HeroNode hero2 = new HeroNode(2,"盧俊義","玉麒麟");
		HeroNode hero3 = new HeroNode(3,"吳用","智多星");
		HeroNode hero4 = new HeroNode(4,"公孫勝","入雲龍");
		HeroNode hero5 = new HeroNode(5,"關勝","大刀");
		
		//創建一個鏈表
		SingleLinkedList singk = new SingleLinkedList();
		
		//添加按照編號的順序
		singk.add(hero);
		singk.add(hero4);
		singk.add(hero2);
		singk.add(hero5); 
		singk.add(hero3);
		
		
		//測試一下:單鏈表的反轉功能
		System.out.println("原來鏈表的情況:");
		singk.list();	//顯示鏈表
		
		System.out.println("反轉的單鏈表:");
		reversetList(singk.getHead());
		singk.list();	
	}
	
	//單鏈表反轉
	public static void reversetList(HeroNode head) {
		//如果當前鏈表爲空,或者只有一個節點,無需反轉,直接返回
		if(head.next == null || head.next.next == null) {
			return;
		}
		
		//1.定義一個輔助的指針(變量),幫助我們遍歷原來的鏈表
		HeroNode str = head.next;
		HeroNode next = null;//指向當前節點[str]的下一個節點
		HeroNode reverseHead = new HeroNode(0, "", "");
		//2.遍歷原來的鏈表,每遍歷一個節點,就將其取出,並放在新的鏈表reverseHead的最前端
		while(str != null) { 
			//先暫時保存當前節點的下一個節點,因爲後面需要使用
			next = str.next;
			//將str的下一個節點指向新的鏈表的最前端
			str.next = reverseHead.next;
			reverseHead.next = str; //將str連接到新的鏈表上
			str = next;	//讓str後移
		}
		//3.將head.next 指向 reverseHead.next, 實現單鏈表的反轉
		head.next = reverseHead.next;
	}
	
}

//定義一個SingleLinkedList類,來管理我們的英雄
class SingleLinkedList{
	//先初始化一個頭節點,頭節點不要動(防止後期找不到此鏈表),不存放具體數據
	private HeroNode head = new HeroNode(0, "", "");
	
	//返回頭節點
	public HeroNode getHead() {
		return head;
	}

	//第一種方式:第一種方法在添加英雄時,直接添加到鏈表的尾部
	//添加節點到單向鏈表,思路,當不考慮編號的順序時
	//1.找到當前鏈表的最後節點
	//2.將最後這個節點的next域指向這個新的節點
	public void add(HeroNode heroNode){
		//由於頭節點head不能動,所以需要一個輔助變量 temp
		HeroNode temp = head;
		//遍歷鏈表,找到最後
		while(true){
			//找到鏈表的最後
			if(temp.next == null){	//判定找到了的條件
				break;
			}
			//如果沒有找到,將temp後移
			temp = temp.next;
		}
		//當退出循環時,temp就指向了鏈表的最後
		//將最後這個節點的next --指向--》 新的節點
		temp.next = heroNode;
	}
	
	//顯示鏈表[遍歷]
	public void list(){
		//判斷鏈表是否爲空
		if(head.next == null){
			System.out.println("鏈表爲空!!!");
			return;
		}
		//由於頭節點head不能動,所以需要一個輔助變量 temp
		HeroNode temp = head;
		while(true){
			//判斷是否到鏈表的最後
			if(temp == null){
				break;
			}
			//如果不爲空,輸出節點的信息
			System.out.println(temp);
			//注意!!!!將next後移(因爲不向後移動,會造成死循環)
			temp = temp.next;
		}
	}
	
}

//定義一個HeroNode,每個HeroNode對象就是一個節點
class HeroNode{
	public int id;
	public String name;
	public String nickName;	//別名,暱稱
	public HeroNode next;	//指向下一個節點
	
	//構造器
	public HeroNode(int id, String name, String nickName) {
		super();
		this.id = id;
		this.name = name;
		this.nickName = nickName;
	}

	//爲了顯示方便,重寫toString方法
	@Override
	public String toString() {
		return "HeroNode [id=" + id + ", name=" + name + ", nickName=" + nickName + "]";
	}
}

百度面試題

從尾到頭打印單鏈表

思路:
1. 上面的題的要求就是逆序打印單鏈表.
2. 方式1: 先將單鏈表進行反轉操作,然後再遍歷即可,這樣的做的問題是會破壞原來的單鏈表的結構,不建議
3. 方式2:可以利用棧這個數據結構,將各個節點壓入到棧中,然後利用棧的先進後出的特點,就實現了逆序打印的效果.
    
舉例演示棧的使用 Stack 

在這裏插入圖片描述

  • 入棧出棧的代碼演示
import java.util.Stack;

//演示棧Stack的基本使用
public class TestStack {
	public static void main(String[] args) {
		Stack<String> stack = new Stack();
		//入棧
		stack.add("jack");
		stack.add("Tom");
		stack.add("smith");
		
		//出棧
		//出棧順序:smith-->Tom-->jack
		while(stack.size() > 0){
			System.out.println(stack.pop());	//pop()就是將棧頂的數據取出
		}
	}
}
  • 面試題代碼實現
import java.util.Stack;

public class SingleLinkedListTest {

	public static void main(String[] args) {
		//測試一下
		//1.創建節點
		HeroNode hero = new HeroNode(1,"宋江","及時雨");
		HeroNode hero2 = new HeroNode(2,"盧俊義","玉麒麟");
		HeroNode hero3 = new HeroNode(3,"吳用","智多星");
		HeroNode hero4 = new HeroNode(4,"公孫勝","入雲龍");
		HeroNode hero5 = new HeroNode(5,"關勝","大刀");
		
		//創建一個鏈表
		SingleLinkedList singk = new SingleLinkedList();
		
		//添加按照編號的順序
		singk.add(hero);
		singk.add(hero4);
		singk.add(hero2);
		singk.add(hero5); 
		singk.add(hero3);

		System.out.println("原來鏈表的情況:");
		singk.list();	//顯示鏈表
		
		System.out.println("測試逆序打印的單鏈表,未改變鏈表的本身結構:");
		reversePrint(singk.getHead());	
	}
	
	//方式2:
	//可以利用棧這個數據結構,將各個節點壓入到棧中,然後利用棧的先進後出的特點,就實現了逆序打印的效果
	public static void reversePrint(HeroNode head) {
		if(head.next == null){
			return;	//空鏈表,無法打印
		}
		//先創建一個棧,將各個節點壓入棧中
		Stack<HeroNode> stack = new Stack<HeroNode>();
		HeroNode str = head.next;
		//將鏈表的所有節點壓入棧
		while(str != null){
			stack.push(str);	
			str = str.next;	//str後移,這樣就可以壓入下一個節點
		}
		//將棧中的節點進行打印,pop()出棧
		while(stack.size() > 0){
			System.out.println(stack.pop());	//stack的特點是先進後出
		}
	}
	
}

//定義一個SingleLinkedList類,來管理我們的英雄
class SingleLinkedList{
	//先初始化一個頭節點,頭節點不要動(防止後期找不到此鏈表),不存放具體數據
	private HeroNode head = new HeroNode(0, "", "");
	
	//返回頭節點
	public HeroNode getHead() {
		return head;
	}

	//第一種方式:第一種方法在添加英雄時,直接添加到鏈表的尾部
	//添加節點到單向鏈表,思路,當不考慮編號的順序時
	//1.找到當前鏈表的最後節點
	//2.將最後這個節點的next域指向這個新的節點
	public void add(HeroNode heroNode){
		//由於頭節點head不能動,所以需要一個輔助變量 temp
		HeroNode temp = head;
		//遍歷鏈表,找到最後
		while(true){
			//找到鏈表的最後
			if(temp.next == null){	//判定找到了的條件
				break;
			}
			//如果沒有找到,將temp後移
			temp = temp.next;
		}
		//當退出循環時,temp就指向了鏈表的最後
		//將最後這個節點的next --指向--》 新的節點
		temp.next = heroNode;
	}
	
	//顯示鏈表[遍歷]
	public void list(){
		//判斷鏈表是否爲空
		if(head.next == null){
			System.out.println("鏈表爲空!!!");
			return;
		}
		//由於頭節點head不能動,所以需要一個輔助變量 temp
		HeroNode temp = head;
		while(true){
			//判斷是否到鏈表的最後
			if(temp == null){
				break;
			}
			//如果不爲空,輸出節點的信息
			System.out.println(temp);
			//注意!!!!將next後移(因爲不向後移動,會造成死循環)
			temp = temp.next;
		}
	}
	
}

//定義一個HeroNode,每個HeroNode對象就是一個節點
class HeroNode{
	public int id;
	public String name;
	public String nickName;	//別名,暱稱
	public HeroNode next;	//指向下一個節點
	
	//構造器
	public HeroNode(int id, String name, String nickName) {
		super();
		this.id = id;
		this.name = name;
		this.nickName = nickName;
	}

	//爲了顯示方便,重寫toString方法
	@Override
	public String toString() {
		return "HeroNode [id=" + id + ", name=" + name + ", nickName=" + nickName + "]";
	}
}

課後練習

合併兩個有序的單鏈表,合併之後的鏈表依然有序。

public class SingleLinkedListTest {

	public static void main(String[] args) {
		// 測試一下
		// 1.創建節點
		HeroNode hero = new HeroNode(1, "宋江", "及時雨");
		HeroNode hero2 = new HeroNode(2, "盧俊義", "玉麒麟");
		HeroNode hero3 = new HeroNode(3, "吳用", "智多星");
		HeroNode hero4 = new HeroNode(4, "公孫勝", "入雲龍");
		HeroNode hero5 = new HeroNode(5, "關勝", "大刀");
		HeroNode hero6 = new HeroNode(6, "林沖", "豹子頭");
		HeroNode hero7 = new HeroNode(7, "秦明", "霹靂火");

		// 創建一個鏈表
		SingleLinkedList singk = new SingleLinkedList();

		// 添加按照編號的順序
		singk.addByOrder(hero);
		singk.addByOrder(hero7);
		singk.addByOrder(hero5);

		// 創建另一個鏈表
		SingleLinkedList singk2 = new SingleLinkedList();

		// 添加按照編號的順序
		singk2.addByOrder(hero3);
		singk2.addByOrder(hero6);
		singk2.addByOrder(hero2);
		singk2.addByOrder(hero4);

		// 顯示鏈表
		singk.list();
		System.out.println("------------------");
		singk2.list();

		//合併後的
		System.out.println("合併後的:");
		SingleLinkedList singk3 = singk.merge(singk, singk2);
        singk3.list();
	}

}

// 定義一個SingleLinkedList類,來管理我們的英雄
class SingleLinkedList {
	// 先初始化一個頭節點,頭節點不要動(防止後期找不到此鏈表),不存放具體數據
	private HeroNode head = new HeroNode(0, "", "");

	public SingleLinkedList(HeroNode head) {
		this.head = head;
	}
	
	public SingleLinkedList() {
        head = new HeroNode();
    }

	// 顯示鏈表[遍歷]
	public void list() {
		// 判斷鏈表是否爲空
		if (head.next == null) {
			System.out.println("鏈表爲空!!!");
			return;
		}
		// 由於頭節點head不能動,所以需要一個輔助變量 temp
		HeroNode temp = head;
		while (true) {
			// 判斷是否到鏈表的最後
			if (temp == null) {
				break;
			}
			// 如果不爲空,輸出節點的信息
			System.out.println(temp);
			// 注意!!!!將next後移(因爲不向後移動,會造成死循環)
			temp = temp.next;
		}
	}

	// 第二種方式在添加英雄時,根據排名將英雄插入到指定位置
	// (如果有這個排名,則添加失敗,並給出提示)
	public void addByOrder(HeroNode heroNode) {
		// 由於頭節點head不能動,所以需要一個輔助變量 temp,找到添加的位置
		// 由於是單鏈表,而找到temp是位於 添加位置的前一個節點,否則插入不了
		HeroNode temp = head;
		boolean flag = false; // 標識添加的編號是否存在,默認爲false
		while (true) {
			if (temp.next == null) { // 說明已經在鏈表的最後
				break;
			}
			if (temp.next.id > heroNode.id) { // 位置找到了,就在temp的後面插入
				break;
			} else if (temp.next.id == heroNode.id) { // 說明希望添加的heroNode的編號已然存在
				flag = true; // 說明編號存在
				break;
			}
			temp = temp.next; // 後移,遍歷當前鏈表
		}
		// 判斷flag的值
		if (flag) { // 不能添加,說明編號存在
			System.out.printf("準備插入的英雄編號%d已經存在了。無法加入\n", heroNode.id);
		} else {
			// 插入到鏈表中,temp的後面
			heroNode.next = temp.next;
			temp.next = heroNode;
		}
	}
	
	//合併兩個有序的單鏈表,合併之後的鏈表依然有序
    public SingleLinkedList merge(SingleLinkedList list1, SingleLinkedList list2) {
        if (list1.head.next == null) {
            return list2;
        } else if (list2.head.next == null) {
            return list1;
        }

        HeroNode newNode = new HeroNode();
        HeroNode n1 = newNode;
        HeroNode l1 = list1.head.next;
        HeroNode l2 = list2.head.next;

        while (l1 != null && l2 != null) {
            if (l1.id < l2.id) {
                n1.next = l1;
                l1 = l1.next;
                n1 = n1.next;
            } else {
                n1.next = l2;
                l2 = l2.next;
                n1 = n1.next;
            }
        }

        if (l1 == null) {
            n1.next = l2;
        }
        if (l2 == null) {
            n1.next = l1;
        }
        return new SingleLinkedList(newNode);
    }

}

// 定義一個HeroNode,每個HeroNode對象就是一個節點
class HeroNode {
	public int id;
	public String name;
	public String nickName; // 別名,暱稱
	public HeroNode next; // 指向下一個節點

	// 構造器
	public HeroNode(int id, String name, String nickName) {
		super();
		this.id = id;
		this.name = name;
		this.nickName = nickName;
	}

	public HeroNode() {
	}

	// 爲了顯示方便,重寫toString方法
	@Override
	public String toString() {
		return "HeroNode [id=" + id + ", name=" + name + ", nickName=" + nickName + "]";
	}
}

雙向鏈表增刪改查分析圖解及實現

使用帶head頭的雙向鏈表實現 –水滸英雄排行榜管理單向鏈表的缺點分析:

1)單向鏈表,查找的方向只能是一個方向,而雙向鏈表可以向前或者向後查找。

2)單向鏈表不能自我刪除,需要靠輔助節點 ,而雙向鏈表,則可以自我刪除,所以前面我們單鏈表刪除時節點,總是找到temp,temp是待刪除節點的前一個節點.

示意圖如下:

在這裏插入圖片描述

分析 雙向鏈表的遍歷,添加,修改,刪除的操作思路:

  1. 遍歷 方式和 單鏈表一樣,只是可以向前查找,也可以向後查找

  2. 添加 (默認添加到雙向鏈表的最後)

    (1) 先找到雙向鏈表的最後這個節點

    (2) temp.next = newHeroNode

    (3) newHeroNode.pre = temp

  3. 修改 思路和 原來的單向鏈表的思路一樣.

  4. 刪除

    (1) 因爲是雙向鏈表,因此,我們可以實現自我刪除某個節點

    (2) 直接找到要刪除的這個節點,比如temp

    (3) temp.pre.next = temp.next

    (4) temp.next.pre = temp.pre;

刪除的圖示如下:

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

  • 代碼實現:
public class DoubleLinkedListTest {

	public static void main(String[] args) {
		// 測試一下
		// 1.創建節點
		HeroNode2 hero = new HeroNode2(1, "宋江", "及時雨");
		HeroNode2 hero2 = new HeroNode2(2, "盧俊義", "玉麒麟");
		HeroNode2 hero3 = new HeroNode2(3, "吳用", "智多星");
		HeroNode2 hero4 = new HeroNode2(4, "公孫勝", "入雲龍");
		HeroNode2 hero5 = new HeroNode2(5, "關勝", "大刀");

		// 創建一個雙向鏈表
		DoubleLinkedList singk = new DoubleLinkedList();

		// 添加按照編號的順序
		singk.add(hero);
		singk.add(hero4);
		singk.add(hero2);
		singk.add(hero5);
		singk.add(hero3);

		System.out.println("原來鏈表的情況:");
		singk.list(); // 顯示鏈表

		// 修改
		HeroNode2 newHeroNode = new HeroNode2(4, "公孫勝", "--");
		singk.update(newHeroNode);
		System.out.println("修改後的鏈表情況");
		singk.list();

		// 刪除
		singk.del(3);
		System.out.println("刪除後的鏈表情況~~");
		singk.list();
	}

}

// 創建一個雙向鏈表的類
class DoubleLinkedList {
	// 先初始化一個頭節點, 頭節點不要動, 不存放具體的數據
	private HeroNode2 head = new HeroNode2(0, "", "");

	// 返回頭節點
	public HeroNode2 getHead() {
		return head;
	}

	// 遍歷雙向鏈表的方法
	// 顯示鏈表[遍歷]
	public void list() {
		// 判斷鏈表是否爲空
		if (head.next == null) {
			System.out.println("鏈表爲空");
			return;
		}
		// 由於頭節點head不能動,所以需要一個輔助變量 temp,找到添加的位置
		HeroNode2 temp = head.next;
		while (true) {
			if (temp == null) { // 判斷是否到鏈表最後
				break;
			}
			// 輸出節點的信息
			System.out.println(temp);
			temp = temp.next; // 將temp後移
		}
	}

	// 添加一個節點到雙向鏈表的最後.
	public void add(HeroNode2 heroNode) {
		// 由於頭節點head不能動,所以需要一個輔助變量 temp
		HeroNode2 temp = head;
		// 遍歷鏈表,找到最後
		while (true) {
			// 找到鏈表的最後
			if (temp.next == null) { // 判定找到了的條件
				break;
			}
			// 如果沒有找到,將temp後移
			temp = temp.next;
		}
		// 當退出循環時,temp就指向了鏈表的最後
		// 將最後這個節點的next --指向--》 新的節點
		temp.next = heroNode;
	}

	// 修改節點的信息,根據id編號來修改,即id編號不改
	// 說明
	// 1.根據newHeroNode 的 id 來修改即可
	public void update(HeroNode2 newHeroNode) {
		// 判斷是否空
		if(head.next == null){	//鏈表爲空
			System.out.println("鏈表爲空!!!");
			return;
		}
		//找到需要修改的節點,根據 id 編號
		//定義一個臨時變量
		HeroNode2 temp = head.next;
		boolean flag = false;	//表示是否找到該節點
		while(true){
			if(temp == null){
				break;	//已經遍歷完鏈表
			}
			if(temp.id == newHeroNode.id){
				//找到了
				flag = true;
				break;
			}
			temp = temp.next;
		}
		// 根據flag 判斷是否找到要修改的節點
		if (flag) {
			temp.name = newHeroNode.name;
			temp.nickname = newHeroNode.nickname;
		} else { // 沒有找到
			System.out.printf("沒有找到編號爲 %d 的節點,不能修改。\n", newHeroNode.id);
		}
	}

	// 刪除節點
	// 思路
	// 1.由於頭節點head不能動,所以需要一個輔助變量 temp來找到待刪除節點的前一個節點
	// 2.說明我們在比較時,是temp.next.id 和 需要刪除的節點的id比較
	public void del(int id) {
		HeroNode2 temp = head;
		boolean flag = false; // 標誌是否找到帶刪除的節點
		while (true) {
			if (temp.next == null) { // 已經到鏈表的最後
				break;
			}
			if (temp.next.id == id) { // 找到了帶刪除的節點的前一個節點
				flag = true;
				break;
			}
			temp = temp.next; // temp後移,遍歷
		}
		// 判斷flag
		if (flag) { // 說明找到了
			// 可以刪除
			temp.next = temp.next.next;
		} else {
			System.out.printf("要刪除的節點 %d 不存在。\n", id);
		}
	}
}

// 定義HeroNode2 , 每個HeroNode 對象就是一個節點
class HeroNode2 {
	public String nickName;
	public int id;
	public String name;
	public String nickname;
	public HeroNode2 next; // 指向下一個節點, 默認爲null
	public HeroNode2 pre; // 指向前一個節點, 默認爲null

	// 構造器
	public HeroNode2(int id, String name, String nickname) {
		super();
		this.id = id;
		this.name = name;
		this.nickname = nickname;
	}

	// 爲了顯示方便,重寫toString方法
	@Override
	public String toString() {
		return "HeroNode2 [id=" + id + ", name=" + name + ", nickname=" + nickname + "]";
	}

}

環形鏈表介紹和約瑟夫問題

在這裏插入圖片描述
在這裏插入圖片描述

  • Josephu 問題

Josephu 問題爲:設編號爲1,2,… n的n個人圍坐一圈,約定編號爲k(1<=k<=n)的人從1開始報數,數到m 的那個人出列,它的下一位又從1開始報數,數到m的那個人又出列,依次類推,直到所有人出列爲止,由此產生一個出隊編號的序列。

  • 提示

用一個不帶頭結點的循環鏈表來處理Josephu 問題:先構成一個有n個結點的單循環鏈表,然後由k結點起從1開始計數,計到m時,對應結點從鏈表中刪除,然後再從被刪除結點的下一個結點又從1開始計數,直到最後一個結點從鏈表中刪除算法結束。

  • 示意圖說明如下

在這裏插入圖片描述

約瑟夫問題分析圖解和實現

構建一個單向的環形鏈表思路

  1. 先創建第一個節點, 讓 first 指向該節點,並形成環形

在這裏插入圖片描述

  1. 後面當我們每創建一個新的節點,就把該節點,加入到已有的環形鏈表中即可.

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

遍歷環形鏈表

  1. 先讓一個輔助指針(變量) curBoy,指向first節點

  2. 然後通過一個while循環遍歷 該環形鏈表即可 curBoy.next == first 結束

在這裏插入圖片描述

  • 代碼實現
public class Josepfu {

	public static void main(String[] args) {
		//測試構建環形鏈表和遍歷是否正確
		CircleLinkList cingk = new CircleLinkList();
		cingk.addBoy(5);	//加入五個節點
		cingk.shoeBoy();	//顯示一下
	}

}
//創建一個單項環形鏈表
class CircleLinkList{
	//創建一個first節點,當前沒有編號
	private Boy first = null;
	//添加新節點,構成一個環形的鏈表
	public void addBoy(int nums){
		//nums 進行數據校驗
		if(nums < 1){
			System.out.println("nums的值不正確");
			return;
		}
		Boy curBoy = null;	//輔助變量,幫助構建環形鏈表
		//使用循環創建環形鏈表
		for(int i = 1;i <= nums;i++){
			//根據編號創建節點
			Boy boy = new Boy(i);
			//如果是頭節點
			if(i == 1){
				first = boy;
				first.setNext(first); //構成環狀
				curBoy = first;	//讓curBoy指向第一個節點
			}else{
				curBoy.setNext(boy);
				boy.setNext(first);
				curBoy = boy;
			}
		}
	}
	
	//遍歷當前環形鏈表
	public void shoeBoy(){
		//判斷鏈表是否爲空
		if(first == null){
			System.out.println("鏈表爲空");
			return;
		}
		//因爲first頭節點不能動,因此創建一個輔助指針完成遍歷
		Boy curBoy = first;
		while(true){
			System.out.printf("當前節點的編號 %d\n",curBoy.getId());
			if(curBoy.getNext() == first){	//遍歷完成
				break;
			}
			curBoy = curBoy.getNext();	//讓curBoy後移 
		}
	}
}

//創建一個Boy類,表示一個節點
class Boy{
	private int id;	//編號
	private Boy next;	//指向下一個節點,默認爲null
	
	public Boy(int id){
		this.id = id;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public Boy getNext() {
		return next;
	}

	public void setNext(Boy next) {
		this.next = next;
	}
	
}

根據用戶的輸入,生成一個人物出圈的順序

n = 5, 即有5個人

k = 1, 從第一個人開始報數

m = 2, 數2下

在這裏插入圖片描述

  1. 需求創建一個輔助指針(變量) helper , 事先應該指向環形鏈表的最後這個節點.

補充: 人物報數前,先讓 first 和 helper 移動 k - 1次

在這裏插入圖片描述

  1. 當人物報數時,讓first 和 helper 指針同時 的移動 m - 1 次

在這裏插入圖片描述

  1. 這時就可以將first 指向的人物節點 出圈

first = first .next

helper.next = first

原來first 指向的節點就沒有任何引用,就會被回收

在這裏插入圖片描述
在這裏插入圖片描述

綜上,出圈的順序:2->4->1->5->3

  • 代碼實現
public class Josepfu {

	public static void main(String[] args) {
		//測試構建環形鏈表和遍歷是否正確
		CircleLinkList cingk = new CircleLinkList();
		cingk.addBoy(5);	//加入五個節點
		cingk.shoeBoy();	//顯示一下
		
		//測試一下:判斷人物出圈是否正確
		cingk.countBoy(1, 42, 5); // 2->4->1->5->3
	}

}
//創建一個單項環形鏈表
class CircleLinkList{
	//創建一個first節點,當前沒有編號
	private Boy first = null;
	//添加新節點,構成一個環形的鏈表
	public void addBoy(int nums){
		//nums 進行數據校驗
		if(nums < 1){
			System.out.println("nums的值不正確");
			return;
		}
		Boy curBoy = null;	//輔助變量,幫助構建環形鏈表
		//使用循環創建環形鏈表
		for(int i = 1;i <= nums;i++){
			//根據編號創建節點
			Boy boy = new Boy(i);
			//如果是頭節點
			if(i == 1){
				first = boy;
				first.setNext(first); //構成環狀
				curBoy = first;	//讓curBoy指向第一個節點
			}else{
				curBoy.setNext(boy);
				boy.setNext(first);
				curBoy = boy;
			}
		}
	}
	
	//遍歷當前環形鏈表
	public void shoeBoy(){
		//判斷鏈表是否爲空
		if(first == null){
			System.out.println("鏈表爲空");
			return;
		}
		//因爲first頭節點不能動,因此創建一個輔助指針完成遍歷
		Boy curBoy = first;
		while(true){
			System.out.printf("當前節點的編號 %d\n",curBoy.getId());
			if(curBoy.getNext() == first){	//遍歷完成
				break;
			}
			curBoy = curBoy.getNext();	//讓curBoy後移 
		}
	}
	
	//根據用戶的輸入,計算人物出圈的順序
	/**
	  * 
	  * @Description 
	  * @author subei
	  * @date 2020年5月23日下午4:45:48
	  * @param startId  表示從第幾個人物開始數數
	  * @param countNum 表示數幾下
	  * @param nums  表示最初有幾個人物在圈中
	 */
	public void countBoy(int startId,int countNum,int nums){
		//先對數據節點進行校驗
		if(first == null || startId < 1 || startId > nums){
			System.out.println("數據有誤,請重新輸入。");
			return;
		}
		//創建一個輔助指針,幫助人物出圈
		Boy helper = first;
		//需求創建一個輔助指針(變量) helper , 事先應該指向環形鏈表的最後這個節點
		while(true){
			if(helper.getNext() == first){	// 說明helper指向最後人物節點
				break;
			}
			helper = helper.getNext();
		}
		//人物報數前,先讓 first 和 helper 移動 k - 1次
		for(int j = 0; j < startId - 1; j++) {
			first = first.getNext();
			helper = helper.getNext();
		}		
		//當人物報數時,讓first 和 helper 指針同時 的移動 m - 1 次
		//通過循環,知道圈中只有一個節點
		while(true) {
			if(helper == first) { //說明圈中只有一個人物節點
				break;
			}
			//讓 first 和 helper 指針同時 的移動 countNum - 1
			for(int j = 0; j < countNum - 1; j++) {
				first = first.getNext();
				helper = helper.getNext();
			}
			//此處first指向的節點,就是要出圈的人物節點
			System.out.printf("人物%d出圈\n", first.getId());
			//此處將first指向的人物節點出圈
			first = first.getNext();
			helper.setNext(first);
		}
		System.out.printf("最後留在圈中的人物的編號:%d \n", first.getId());
	}
}

//創建一個Boy類,表示一個節點
class Boy{
	private int id;	//編號
	private Boy next;	//指向下一個節點,默認爲null
	
	public Boy(int id){
		this.id = id;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public Boy getNext() {
		return next;
	}

	public void setNext(Boy next) {
		this.next = next;
	}
	
}

本章導圖總結

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章