鏈表(二)

雙向鏈表

單雙鏈表的一些比較

  1. 單向鏈表,查找的方向只能是一個方向,而雙向鏈表能夠向前或者向後查找。
  2. 單向鏈表不能自我刪除,須要靠輔助節點,而雙向鏈表,則能夠自我刪除,因此前面咱們單鏈表刪除時節點,老是找到temp,temp是待刪除節點的前一個節點.

分析思路和代碼實現

雙向鏈表的遍歷,添加,修改,刪除的操做思路,代碼實現算法

  1. 遍歷方式和單鏈表同樣,只是能夠向前,也能夠向後查找
  2. 添加 (默認添加到雙向鏈表的最後)
  • 先找到雙向鏈表的最後這個節點
  • temp.next = newHeroNode
  • newHeroNode.pre = temp
  1. 修改 思路和 原來的單向鏈表同樣.
  2. 刪除
  • 由於是雙向鏈表,所以,咱們能夠實現自我刪除某個節點
  • 直接找到要刪除的這個節點,好比temp
  • temp.pre.next = temp.next
  • temp.next.pre = temp.pre;

public class DoubleLinkedListDemo {

	public static void main(String[] args) {
		// 測試
		System.out.println("雙向鏈表的測試");
		// 先建立節點
		HeroNode2 hero1 = new HeroNode2(1, "宋江", "及時雨");
		HeroNode2 hero2 = new HeroNode2(2, "盧俊義", "玉麒麟");
		HeroNode2 hero3 = new HeroNode2(3, "吳用", "智多星");
		HeroNode2 hero4 = new HeroNode2(4, "林沖", "豹子頭");
		// 建立一個雙向鏈表
		DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
		doubleLinkedList.add(hero1);
		doubleLinkedList.add(hero2);
		doubleLinkedList.add(hero3);
		doubleLinkedList.add(hero4);
		doubleLinkedList.list();
		// 修改
		HeroNode2 newHeroNode = new HeroNode2(4, "公孫勝", "入雲龍");
		doubleLinkedList.update(newHeroNode);
		System.out.println("修改後的鏈表狀況");
		doubleLinkedList.list();
		// 刪除
		doubleLinkedList.del(3);
		System.out.println("刪除後的鏈表狀況~~");
		doubleLinkedList.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;
		}
		// 由於頭節點,不能動,所以咱們須要一個輔助變量來遍歷
		HeroNode2 temp = head.next;
		while (true) {
			// 判斷是否到鏈表最後
			if (temp == null) {
				break;
			}
			// 輸出節點的信息
			System.out.println(temp);
			// 將temp後移, 必定當心
			temp = temp.next;
		}
	}
	// 添加一個節點到雙向鏈表的最後.
	public void add(HeroNode2 heroNode) {
		// 由於head節點不能動,所以咱們須要一個輔助遍歷 temp
		HeroNode2 temp = head;
		// 遍歷鏈表,找到最後
		while (true) {
			// 找到鏈表的最後
			if (temp.next == null) {//
				break;
			}
			// 若是沒有找到最後, 將將temp後移
			temp = temp.next;
		}
		// 當退出while循環時,temp就指向了鏈表的最後
		// 造成一個雙向鏈表
		temp.next = heroNode;
		heroNode.pre = temp;
	}
	// 修改一個節點的內容, 能夠看到雙向鏈表的節點內容修改和單向鏈表同樣
	// 只是 節點類型改爲 HeroNode2
	public void update(HeroNode2 newHeroNode) {
		// 判斷是否空
		if (head.next == null) {
			System.out.println("鏈表爲空~");
			return;
		}
		// 找到須要修改的節點, 根據no編號
		// 定義一個輔助變量
		HeroNode2 temp = head.next;
		boolean flag = false; // 表示是否找到該節點
		while (true) {
			if (temp == null) {
				break; // 已經遍歷完鏈表
			}
			if (temp.no == newHeroNode.no) {
				// 找到
				flag = true;
				break;
			}
			temp = temp.next;
		}
		// 根據flag 判斷是否找到要修改的節點
		if (flag) {
			temp.name = newHeroNode.name;
			temp.nickname = newHeroNode.nickname;
		} else { // 沒有找到
			System.out.printf("沒有找到 編號 %d 的節點,不能修改\n", newHeroNode.no);
		}
	}
	// 從雙向鏈表中刪除一個節點,
	// 說明
	// 1 對於雙向鏈表,咱們能夠直接找到要刪除的這個節點
	// 2 找到後,自我刪除便可
	public void del(int no) {
		// 判斷當前鏈表是否爲空
		if (head.next == null) {// 空鏈表
			System.out.println("鏈表爲空,沒法刪除");
			return;
		}
		HeroNode2 temp = head.next; // 輔助變量(指針)
		boolean flag = false; // 標誌是否找到待刪除節點的
		while (true) {
			if (temp == null) { // 已經到鏈表的最後
				break;
			}
			if (temp.no == no) {
				// 找到的待刪除節點的前一個節點temp
				flag = true;
				break;
			}
			temp = temp.next; // temp後移,遍歷
		}
		// 判斷flag
		if (flag) { // 找到
			// 能夠刪除
			// temp.next = temp.next.next;[單向鏈表]
			temp.pre.next = temp.next;
			// 這裏咱們的代碼有問題?
			// 若是是最後一個節點,就不須要執行下面這句話,不然出現空指針
			if (temp.next != null) {
				temp.next.pre = temp.pre;
			}
		} else {
			System.out.printf("要刪除的 %d 節點不存在\n", no);
		}
	}
}
// 定義HeroNode2 , 每一個HeroNode 對象就是一個節點
class HeroNode2 {
	public int no;
	public String name;
	public String nickname;
	public HeroNode2 next; // 指向下一個節點, 默認爲null
	public HeroNode2 pre; // 指向前一個節點, 默認爲null
	// 構造器
	public HeroNode2(int no, String name, String nickname) {
		this.no = no;
		this.name = name;
		this.nickname = nickname;
	}
	// 爲了顯示方法,咱們從新toString
	@Override
	public String toString() {
		return "HeroNode [no=" + no + ", name=" + name + ", nickname=" + nickname + "]";
	}
}
複製代碼

單向環形鏈表

Josephu(約瑟夫、約瑟夫環) 問題

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

  • 提示

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

例: n = 5 , 即有5我的 。 k = 1, 從第一我的開始報數。 m = 2, 數2下。測試

使用環形單向鏈表來解決 Josephu問題

  • 構建一個單向的環形鏈表思路
  1. 先建立第一個節點, 讓 first 指向該節點,並造成環形,當前節點爲curBoy
  2. 後面當咱們每建立一個新的節點,就把該節點,加入到已有的環形鏈表中便可.將新的節點稱爲boy,將curboy.next = boy;boy.next=first;curBoy = boy;
  • 遍歷環形鏈表
  1. 先讓一個輔助指針(變量) curBoy,指向first節點
  2. 而後經過一個while循環遍歷 該環形鏈表便可 。當 curBoy.next == first 結束遍歷
  • 根據用戶的輸入,生成一個小孩出圈的順序

n = 5 , 即有5我的ui

k = 1, 從第一我的開始報數this

m = 2, 數2下spa

  1. 需求建立一個輔助指針(變量) helper , 事先應該指向環形鏈表的最後這個節點. 補充: 小孩報數前,先讓 first 和 helper 移動 k - 1次
  2. 當小孩報數時,讓first 和 helper 指針同時 的移動 m - 1 次
  3. 這時就能夠將first 指向的小孩節點 出圈 first = first .next; helper.next = first
    原來first 指向的節點就沒有任何引用,就會被回收

出圈的順序 2->4->1->5->3

代碼實現

節點類

// 建立一個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;
	}
}
複製代碼
// 建立一個環形的單向鏈表
class CircleSingleLinkedList {
	// 建立一個first節點,當前沒有編號
	private Boy first = null;

	// 添加小孩節點,構建成一個環形的鏈表
	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;
			}
		}
	}

	// 遍歷當前的環形鏈表
	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());
	}
}
複製代碼
//測試
public class Josepfu {
	public static void main(String[] args) {
		// 測試一把看看構建環形鏈表,和遍歷是否ok
		CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
		circleSingleLinkedList.addBoy(125);// 加入5個小孩節點
		circleSingleLinkedList.showBoy();
		
		//測試一把小孩出圈是否正確
		circleSingleLinkedList.countBoy(10, 20, 125); // 2->4->1->5->3
		//String str = "7*2*2-5+1-5+3-3";
	}
}
複製代碼
相關文章
相關標籤/搜索