圖解Java數據結構之環形鏈表


本篇文章介紹數據結構中的環形鏈表。

介紹

環形鏈表,類似於單鏈表,也是一種鏈式存儲結構,環形鏈表由單鏈表演化過來。單鏈表的最后一個結點的鏈域指向NULL,而環形鏈表的建立,不要專門的頭結點,讓最后一個結點的鏈域指向鏈表結點。 簡單點說鏈表首位相連,組成環狀數據結構。如下圖結構:
在這里插入圖片描述
而在環形鏈表中,最為著名的即是約瑟夫環問題。

約瑟夫環問題

問題介紹:
設編號為1、2、3、... 、n的n個人圍坐一圈,約定編號為k(1<=k<=n)的人從1開始報數,數到m的那個人出列,它的下一位又從1開始報數,數到m的那個人又出列。依次類推,直到所有人出列為止,由此產生一個出隊編號的序列。
我們可以舉個例子來分析一下:
假設一共有5個人,即n = 5;從第一個人開始報數,即k = 1;數到2的人出列,即m = 2。
示意圖如下:
在這里插入圖片描述
出隊列的順序即為:2 -> 4 -> 1 -> 5 -> 3

那么我們首先得構建出一個單向的環形鏈表。
在這里插入圖片描述
實現分析:

  1. 先創建第一個節點,讓first指向該節點,並形成環狀
  2. 每創建一個新的節點就將該節點加入到已有的環形鏈表中

分析完畢,我們用代碼實現一下:

//創建一個環形的單向鏈表
class CircleSingleLinkedList {
	// 創建一個first節點,當前沒有編號
	private Boy first = null;

	// 添加節點,構建成一個環形鏈表
	public void addBoy(int nums) {
		// 對nums做一個校驗
		if (nums < 1) {
			System.out.println("數據錯誤");
			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;// curBoy后移
			}
		}
	}

	// 遍歷當前環形鏈表
	public void list() {
		// 判斷鏈表是否空
		if (first == null) {
			System.out.println("鏈表為空");
			return;
		}
		// 定義輔助節點
		Boy curBoy = first;
		while (true) {
			System.out.println("節點編號:" + curBoy.getNo());
			if (curBoy.getNext() == first) {
				// 此時說明遍歷完畢
				break;
			}
			curBoy = curBoy.getNext();// curBoy后移
		}
	}
}

//創建一個Boy類,表示一個節點
class Boy {
	private int no;// 編號
	private Boy next;// 指向下一個節點

	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;
	}
}

這樣就實現了一個環形鏈表,接下來測試一下:

public static void main(String[] args) {
		CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
		circleSingleLinkedList.addBoy(5);
		circleSingleLinkedList.list();
}

運行結果:

節點編號:1
節點編號:2
節點編號:3
節點編號:4
節點編號:5

運行結果也是沒有問題的,接下來便是生成出圈序列。
問題分析:

  1. 先創建一個輔助節點helper,事先應該指向環形鏈表的最后一個節點
  2. 報數前,先讓first和helper移動k - 1次
  3. 開始報數時,讓first和helper節點同時移動,移動m - 1次
  4. 此時可以將first指向的節點出圈
  5. 如何出圈呢?
    使first = first.next,即:將first節點往前移動一下
    使helper.next = first,這樣就跳過了要出圈的節點

接下來是代碼實現:

	/**
	 * 根據用戶的輸入,計算出圈序列
	 * 
	 * @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) {
				break;
			}
			helper = helper.getNext();// helper后移
		}
		// 報數前,先讓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.println("節點" + first.getNo() + "出圈");
			// 將該節點出圈
			first = first.getNext();
			helper.setNext(first);
		}
		System.out.println("最后留在圈中的節點編號:" + first.getNo());
	}

這個實現的邏輯相對來說還是比較復雜和難以理解的,接下來編寫測試代碼:

	public static void main(String[] args) {
		CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
		circleSingleLinkedList.addBoy(5);
		circleSingleLinkedList.list();

		System.out.println("--------------");

		// 測試出圈序列
		circleSingleLinkedList.countBoy(1, 2, 5);
	}

運行結果:

節點編號:1
節點編號:2
節點編號:3
節點編號:4
節點編號:5
--------------
節點2出圈
節點4出圈
節點1出圈
節點5出圈
最后留在圈中的節點編號:3

和開始計算的結果相吻合。
到此,關於約瑟夫環的問題就成功解決了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM