單向迴圈連結串列與約瑟夫問題求解
阿新 • • 發佈:2021-01-24
單向環形連結串列
將單鏈表中中斷結點的指標端有空指標改為指向頭結點,就使整個單鏈表形成一個環,這種頭尾詳解的單鏈表稱為單迴圈連結串列,簡稱迴圈連結串列;
示意圖:
注意:
①迴圈連結串列中沒有NULL指標。涉及遍歷操作時,其終止條件就不再是像非迴圈連結串列那樣判別p或p->next是否為空,而是判別它們是否等於某一指定指標,如頭指標或尾指標等。
②在單鏈表中,從一已知結點出發,只能訪問到該結點及其後續結點,無法找到該結點之前的其它結點。而在單迴圈連結串列中,從任一結點出發都可訪問到表中所有結點,這一優點使某些運算在單迴圈連結串列上易於實現。
Josephu 問題
Josephu 問題為:設編號為1,2,… n的n個人圍坐一圈,約定編號為k(1<=k<=n)的人從1開始報數,數到m 的那個人出列,它的下一位又從1開始報數,數到m的那個人又出列,依次類推,直到所有人出列為止,由此產生一個出隊編號的序列。
提示:
用一個不帶頭結點的迴圈連結串列來處理Josephu 問題:先構成一個有n個結點的單迴圈連結串列,然後由k結點起從1開始計數,計到m時,對應結點從連結串列中刪除,然後再從被刪除結點的下一個結點又從1開始計數,直到最後一個結點從連結串列中刪除演算法結束。
程式碼實現:
定義連結串列節點的結構如下:
// 建立一個Boy類,表示一個節點
class Boy {
private int no;// 編號
private Boy next; // 指向下一個節點,預設null
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; }
}
構建成一個環形的連結串列
輸入nums,構成一個nums個節點的環形連結串列
// 新增小孩節點,構建成一個環形的連結串列
public void addBoy(int nums) {
// nums 做一個數據校驗
if (nums < 1) {
System.out.println("nums的值不正確");
return;
}
Boy curBoy = null; // 輔助指標,幫助構建環形連結串列
// 使用for來建立我們的環形連結串列
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;
}
}
}
根據使用者的輸入,計算出小孩出圈的順序
/**
*
* @param startNo
* 表示從第幾個小孩開始數數
* @param countNum
* 表示數幾下
* @param nums
* 表示最初有多少小孩在圈中
*/
public void countBoy(int startNo, int countNum, int nums) {
// 先對資料進行校驗
if (first == null || startNo < 1 || startNo > 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 < startNo - 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.getNo());
//這時將first指向的小孩節點出圈
first = first.getNext();
helper.setNext(first); //
}
System.out.printf("最後留在圈中的小孩編號%d \n", first.getNo());
}
完整程式碼
public class Josephu {
public static void main(String[] args) {
// 測試一把看看構建環形連結串列,和遍歷是否ok
CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
//circleSingleLinkedList.addBoys(5);// 加入5個小孩節點
Boy b1 = new Boy(1);
Boy b2 = new Boy(2);
Boy b3 = new Boy(3);
Boy b4 = new Boy(4);
Boy b5 = new Boy(5);
circleSingleLinkedList.addBoy(b2);
circleSingleLinkedList.addBoy(b1);
circleSingleLinkedList.addBoy(b5);
circleSingleLinkedList.addBoy(b4);
circleSingleLinkedList.addBoy(b3);
circleSingleLinkedList.showBoy();
circleSingleLinkedList.delete(1);
System.out.println("刪除節點1");
circleSingleLinkedList.showBoy();
circleSingleLinkedList.delete(3);
System.out.println("刪除節點3");
circleSingleLinkedList.showBoy();
circleSingleLinkedList.delete(5);
System.out.println("刪除節點5");
circleSingleLinkedList.showBoy();
//測試一把小孩出圈是否正確
// circleSingleLinkedList.countBoy(1, 2, 5); // 2->4->1->5->3
//String str = "7*2*2-5+1-5+3-3";
}
}
// 建立一個環形的單向連結串列
class CircleSingleLinkedList {
// 建立一個first節點,當前沒有編號
private Boy first = null;
// 新增小孩節點,構建成一個環形的連結串列
public void addBoys(int nums) {
// nums 做一個數據校驗
if (nums < 1) {
System.out.println("nums的值不正確");
return;
}
Boy curBoy = null; // 輔助指標,幫助構建環形連結串列
// 使用for來建立我們的環形連結串列
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 addBoy( Boy boy){
if(first ==null){//如果第一個節點為空,即連結串列為空
first = boy;
first.setNext(first);
return;
}
else{
boolean flag = false; // flag標誌新增的編號是否存在,預設為false
Boy curBoy = first; // 輔助指標,
while (true){
if (first.getNo() > boy.getNo()){//插入到左端,即插入到first的前面
boy.setNext(curBoy);
curBoy.setNext(boy);
first = boy;
break;//插入完成直接返回
}
if (curBoy !=first && curBoy.getNext() == first){//說明連結串列已經遍歷完,則新節點插入到原理連結串列的最後
curBoy.setNext(boy);
boy.setNext(first);
break;
}
if(curBoy.getNext().getNo() > boy.getNo()){//找到待插入的位置
boy.setNext(curBoy.getNext());//插入方式與普通單鏈表的方式相同
curBoy.setNext(boy);
break;
}
if (curBoy.getNext().getNo() == boy.getNo()){
System.out.println("待插入結點已經存在,不能重複插入");
break;
}
curBoy = curBoy.getNext();//後移
}
}
}
//移除環形連結串列中的某個節點
public void delete(int no){
Boy curBoy = first;
if (first.getNo() == no){//刪除的是第一個節點
//如果連結串列中只有一個節點
if(first.getNext() ==first){
first =null;//將first置為空即可
}
else {
//找到連結串列的最後一個節點
curBoy = curBoy.getNext();
while (curBoy.getNext() != first){
curBoy = curBoy.getNext();
}
curBoy.setNext(first.getNext());//將最後一個節點指向first的下一個節點
first = first.getNext();//將first後移一位
}
return;
}
//遍歷連結串列,找到待刪除的點
boolean flag = false;//判斷是否找到了待刪除節點
while (true){
if (curBoy.getNext().getNo() == no){//找到
//判斷待刪除節點是不是連結串列的最後一個節點
if (curBoy.getNext().getNext() ==first){//是
curBoy.setNext(first);
}else {//否
curBoy.setNext(curBoy.getNext().getNext());
}
flag=true;
break;
}
if (curBoy.getNext()==first){//遍歷完
break;
}
curBoy = curBoy.getNext();
}
if (flag ==false){
System.out.printf("未找到待刪除節點 %d",no);
}
}
// 遍歷當前的環形連結串列
public void showBoy() {
// 判斷連結串列是否為空
if (first == null) {
System.out.println("沒有任何小孩~~");
return;
}
// 因為first不能動,因此我們仍然使用一個輔助指標完成遍歷
Boy curBoy = first;
while (true) {
System.out.printf("小孩的編號 %d \n", curBoy.getNo());
if (curBoy.getNext() == first) {// 說明已經遍歷完畢
break;
}
curBoy = curBoy.getNext(); // curBoy後移
}
}
// 根據使用者的輸入,計算出小孩出圈的順序
/**
*
* @param startNo
* 表示從第幾個小孩開始數數
* @param countNum
* 表示數幾下
* @param nums
* 表示最初有多少小孩在圈中
*/
public void countBoy(int startNo, int countNum, int nums) {
// 先對資料進行校驗
if (first == null || startNo < 1 || startNo > 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 < startNo - 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.getNo());
//這時將first指向的小孩節點出圈
first = first.getNext();
helper.setNext(first); //
}
System.out.printf("最後留在圈中的小孩編號%d \n", first.getNo());
}
}
// 建立一個Boy類,表示一個節點
class Boy {
private int no;// 編號
private Boy next; // 指向下一個節點,預設null
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; }
}