文章目錄
第三章 鏈表
單鏈表介紹和內存佈局
鏈表是有序的列表,但是它在內存中是實際存儲結構如下:
小結:
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 + "]";
}
}
單鏈表節點的刪除和小結
從單鏈表中刪除一個節點的思路圖解
- 我們先找到 需要刪除的這個節點的前一個節點 temp
- temp.next = temp.next.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.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 + "]";
}
}
騰訊面試題
單鏈表的反轉,效果圖如下:
思路:
先定義一個節點 reverseHead = new HeroNode();
從頭到尾遍歷原來的鏈表,每遍歷一個節點,就將其取出,並放在新的鏈表reverseHead 的最前端.
原來的鏈表的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) temp.next = newHeroNode
(3) newHeroNode.pre = temp
修改 思路和 原來的單向鏈表的思路一樣.
刪除
(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開始計數,直到最後一個結點從鏈表中刪除算法結束。
- 示意圖說明如下
約瑟夫問題分析圖解和實現
構建一個單向的環形鏈表思路
- 先創建第一個節點, 讓 first 指向該節點,並形成環形
- 後面當我們每創建一個新的節點,就把該節點,加入到已有的環形鏈表中即可.
遍歷環形鏈表
先讓一個輔助指針(變量) curBoy,指向first節點
然後通過一個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下
- 需求創建一個輔助指針(變量) helper , 事先應該指向環形鏈表的最後這個節點.
補充: 人物報數前,先讓 first 和 helper 移動 k - 1次
- 當人物報數時,讓first 和 helper 指針同時 的移動 m - 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;
}
}