图解Java数据结构之环形链表
本篇文章介紹數(shù)據(jù)結(jié)構(gòu)中的環(huán)形鏈表。
介紹
環(huán)形鏈表,類似于單鏈表,也是一種鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu),環(huán)形鏈表由單鏈表演化過(guò)來(lái)。單鏈表的最后一個(gè)結(jié)點(diǎn)的鏈域指向NULL,而環(huán)形鏈表的建立,不要專門(mén)的頭結(jié)點(diǎn),讓最后一個(gè)結(jié)點(diǎn)的鏈域指向鏈表結(jié)點(diǎn)。 簡(jiǎn)單點(diǎn)說(shuō)鏈表首位相連,組成環(huán)狀數(shù)據(jù)結(jié)構(gòu)。如下圖結(jié)構(gòu):
而在環(huán)形鏈表中,最為著名的即是約瑟夫環(huán)問(wèn)題。
約瑟夫環(huán)問(wèn)題
問(wèn)題介紹:
設(shè)編號(hào)為1、2、3、... 、n的n個(gè)人圍坐一圈,約定編號(hào)為k(1<=k<=n)的人從1開(kāi)始報(bào)數(shù),數(shù)到m的那個(gè)人出列,它的下一位又從1開(kāi)始報(bào)數(shù),數(shù)到m的那個(gè)人又出列。依次類推,直到所有人出列為止,由此產(chǎn)生一個(gè)出隊(duì)編號(hào)的序列。
我們可以舉個(gè)例子來(lái)分析一下:
假設(shè)一共有5個(gè)人,即n = 5;從第一個(gè)人開(kāi)始報(bào)數(shù),即k = 1;數(shù)到2的人出列,即m = 2。
示意圖如下:
出隊(duì)列的順序即為:2 -> 4 -> 1 -> 5 -> 3
那么我們首先得構(gòu)建出一個(gè)單向的環(huán)形鏈表。
實(shí)現(xiàn)分析:
先創(chuàng)建第一個(gè)節(jié)點(diǎn),讓first指向該節(jié)點(diǎn),并形成環(huán)狀
每創(chuàng)建一個(gè)新的節(jié)點(diǎn)就將該節(jié)點(diǎn)加入到已有的環(huán)形鏈表中
分析完畢,我們用代碼實(shí)現(xiàn)一下:
//創(chuàng)建一個(gè)環(huán)形的單向鏈表
class CircleSingleLinkedList {
// 創(chuàng)建一個(gè)first節(jié)點(diǎn),當(dāng)前沒(méi)有編號(hào)
private Boy first = null;
// 添加節(jié)點(diǎn),構(gòu)建成一個(gè)環(huán)形鏈表
public void addBoy(int nums) {
// 對(duì)nums做一個(gè)校驗(yàn)
if (nums < 1) {
System.out.println("數(shù)據(jù)錯(cuò)誤");
return;
}
// 定義輔助節(jié)點(diǎn)
Boy curBoy = null;
// 使用循環(huán)創(chuàng)建環(huán)形鏈表
for (int i = 1; i <= nums; i++) {
// 根據(jù)編號(hào)創(chuàng)建節(jié)點(diǎn)
Boy boy = new Boy(i);
// 如果是第一個(gè)節(jié)點(diǎn)
if (i == 1) {
first = boy;
first.setNext(first);
curBoy = first;// 讓curBoy指向第一個(gè)節(jié)點(diǎn),幫助構(gòu)建鏈表
} else {
curBoy.setNext(boy);
boy.setNext(first);// 使其指向第一個(gè)節(jié)點(diǎn),形成環(huán)狀
curBoy = boy;// curBoy后移
}
}
}
// 遍歷當(dāng)前環(huán)形鏈表
public void list() {
// 判斷鏈表是否空
if (first == null) {
System.out.println("鏈表為空");
return;
}
// 定義輔助節(jié)點(diǎn)
Boy curBoy = first;
while (true) {
System.out.println("節(jié)點(diǎn)編號(hào):" + curBoy.getNo());
if (curBoy.getNext() == first) {
// 此時(shí)說(shuō)明遍歷完畢
break;
}
curBoy = curBoy.getNext();// curBoy后移
}
}
}
//創(chuàng)建一個(gè)Boy類,表示一個(gè)節(jié)點(diǎn)
class Boy {
private int no;// 編號(hào)
private Boy next;// 指向下一個(gè)節(jié)點(diǎn)
public Boy(int no) {
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;
}
}
這樣就實(shí)現(xiàn)了一個(gè)環(huán)形鏈表,接下來(lái)測(cè)試一下:
public static void main(String[] args) {
CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
circleSingleLinkedList.addBoy(5);
circleSingleLinkedList.list();
}
運(yùn)行結(jié)果:
節(jié)點(diǎn)編號(hào):1
節(jié)點(diǎn)編號(hào):2
節(jié)點(diǎn)編號(hào):3
節(jié)點(diǎn)編號(hào):4
節(jié)點(diǎn)編號(hào):5
運(yùn)行結(jié)果也是沒(méi)有問(wèn)題的,接下來(lái)便是生成出圈序列。
問(wèn)題分析:
先創(chuàng)建一個(gè)輔助節(jié)點(diǎn)helper,事先應(yīng)該指向環(huán)形鏈表的最后一個(gè)節(jié)點(diǎn)
報(bào)數(shù)前,先讓first和helper移動(dòng)k - 1次
開(kāi)始報(bào)數(shù)時(shí),讓first和helper節(jié)點(diǎn)同時(shí)移動(dòng),移動(dòng)m - 1次
此時(shí)可以將first指向的節(jié)點(diǎn)出圈
如何出圈呢?
使first = first.next,即:將first節(jié)點(diǎn)往前移動(dòng)一下
使helper.next = first,這樣就跳過(guò)了要出圈的節(jié)點(diǎn)
接下來(lái)是代碼實(shí)現(xiàn):
/**
* 根據(jù)用戶的輸入,計(jì)算出圈序列
*
* @param startNo 表示從第幾個(gè)開(kāi)始數(shù)
* @param countNum 表示數(shù)幾下
* @param nums 表示一共有多少人
*/
public void countBoy(int startNo, int countNum, int nums) {
// 數(shù)據(jù)校驗(yàn)
if (first == null || startNo < 1 || startNo > nums) {
System.out.println("參數(shù)輸入有誤");
return;
}
// 定義輔助節(jié)點(diǎn)
Boy helper = first;
// helper事先應(yīng)該指向環(huán)形鏈表的最后一個(gè)節(jié)點(diǎn)
while (true) {
if (helper.getNext() == first) {
break;
}
helper = helper.getNext();// helper后移
}
// 報(bào)數(shù)前,先讓first和helper移動(dòng)k - 1次
for (int j = 0; j < startNo - 1; j++) {
first = first.getNext();
helper = helper.getNext();
}
// 開(kāi)始報(bào)數(shù)時(shí),讓first和helper節(jié)點(diǎn)同時(shí)移動(dòng),移動(dòng)m - 1次
// 這里是一個(gè)循環(huán)的操作,直到圈中只有一個(gè)節(jié)點(diǎn)
while (true) {
if (helper == first) {
// 此時(shí)說(shuō)明圈中只有一個(gè)人
break;
}
// 讓first和helper同時(shí)移動(dòng)countNum - 1次
for (int j = 0; j < countNum - 1; j++) {
first = first.getNext();
helper = helper.getNext();
}
// 此時(shí)first指向的節(jié)點(diǎn)就是要出圈的節(jié)點(diǎn)
System.out.println("節(jié)點(diǎn)" + first.getNo() + "出圈");
// 將該節(jié)點(diǎn)出圈
first = first.getNext();
helper.setNext(first);
}
System.out.println("最后留在圈中的節(jié)點(diǎn)編號(hào):" + first.getNo());
}
這個(gè)實(shí)現(xiàn)的邏輯相對(duì)來(lái)說(shuō)還是比較復(fù)雜和難以理解的,接下來(lái)編寫(xiě)測(cè)試代碼:
public static void main(String[] args) {
CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
circleSingleLinkedList.addBoy(5);
circleSingleLinkedList.list();
System.out.println("--------------");
// 測(cè)試出圈序列
circleSingleLinkedList.countBoy(1, 2, 5);
}
運(yùn)行結(jié)果:
節(jié)點(diǎn)編號(hào):1
節(jié)點(diǎn)編號(hào):2
節(jié)點(diǎn)編號(hào):3
節(jié)點(diǎn)編號(hào):4
節(jié)點(diǎn)編號(hào):5
--------------
節(jié)點(diǎn)2出圈
節(jié)點(diǎn)4出圈
節(jié)點(diǎn)1出圈
節(jié)點(diǎn)5出圈
最后留在圈中的節(jié)點(diǎn)編號(hào):3
和開(kāi)始計(jì)算的結(jié)果相吻合。
到此,關(guān)于約瑟夫環(huán)的問(wèn)題就成功解決了。
總結(jié)
以上是生活随笔為你收集整理的图解Java数据结构之环形链表的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 平安金管家保尊宝理财安全吗?
- 下一篇: 【雷达原理】脉冲压缩-匹配滤波的自己理解