數據結構 - 雙向鏈表、單向環形鏈表、約瑟夫問題實現

雙向鏈表

單向鏈表

  • 單鏈表是一種鏈式存取的數據結構,用一組地址任意(不需要地址連續)的存儲單元存放線性表中的數據元素,可以使用零碎的空間
  • 鏈表中的數據是以節點來表示的,每個節點的構成:元素(數據元素的映象)+ 指針(指示後繼元素存儲位置),元素就是存儲數據的存儲單元value,指針就是連接每個節點的地址數據next
  • 單鏈表是單向的,當訪問一個節點後,只能接着訪問後繼節點
  • 單鏈表可以設置頭節點,頭節點爲空

單向鏈表的特點是隻能遵循鏈式的順序從鏈表頭節點到尾節點
而雙向鏈表是在單向鏈表的基礎上,新增一個前驅指針pre,可以指向前驅節點、後繼節點

在這裏插入圖片描述


Java實現雙向鏈表

雙向鏈表是在單向鏈表的基礎上實現的:

  • 節點對象HeroNode新增一個前驅指針pre
  • 更新、遍歷與單鏈表方法相同
  • 因爲有兩個指針pre、next,插入、刪除時,需要注意修改前驅結點、後繼節點的指針
package com.company.doubleLinkedList;

import org.junit.Test;

/**
 * @author zfk
 * 雙向鏈表
 */
public class DoubleLinkedListDemo {

    @Test
    public void test(){
        HeroNode hero1 = new HeroNode(1, "宋江", "及時雨");
        HeroNode hero2 = new HeroNode(2, "盧俊義", "玉麒麟");
        HeroNode hero3 = new HeroNode(3, "吳用", "智多星");
        HeroNode hero4 = new HeroNode(4, "林沖", "豹子頭");
        HeroNode hero5 = new HeroNode(5, "花榮", "小李廣");

        DoubleLinkedList doubleLinkedList = new DoubleLinkedList();

//        doubleLinkedList.add(hero1);
//        doubleLinkedList.add(hero2);
//        doubleLinkedList.add(hero3);
//        doubleLinkedList.add(hero4);
//        doubleLinkedList.add(hero5);

        doubleLinkedList.addByOrder(hero3);
        doubleLinkedList.addByOrder(hero1);
        doubleLinkedList.addByOrder(hero2);
        doubleLinkedList.addByOrder(hero5);
        doubleLinkedList.addByOrder(hero4);


        doubleLinkedList.list();

        System.out.println("=== 修改後 ===");
        doubleLinkedList.update(new HeroNode(4,"公孫勝","入雲龍"));

        doubleLinkedList.list();

        System.out.println("=== 刪除3號 ===");
        doubleLinkedList.delete(3);

        doubleLinkedList.list();

    }

}


/**
 *
 * 每個HeroNode對象代表一個鏈表節點
 *
 */
class HeroNode{

    private int no;
    public String name;
    public String nickName;
    //指向下一節點
    private HeroNode next;
    //指向上一節點
    private HeroNode pre;

    public HeroNode(int no, String name, String nickName) {
        this.no = no;
        this.name = name;
        this.nickName = nickName;
    }

    public int getNo() {
        return no;
    }

    public HeroNode getNext() {
        return next;
    }

    public void setNext(HeroNode next) {
        this.next = next;
    }

    public HeroNode getPre() {
        return pre;
    }

    public HeroNode setPre(HeroNode pre) {
        this.pre = pre;
        return this;
    }

    @Override
    public String toString() {
        return "HeroNode{" +
                "no=" + no +
                ", name='" + name + '\'' +
                ", nickName='" + nickName + '\'' +
                '}';
    }
}



/**
 *
 * 定義DoubleLinkedList雙鏈表
 *
 */
class DoubleLinkedList{
    //初始化一個頭節點
    private HeroNode head = new HeroNode(0,"","");

    public HeroNode getHead() {
        return head;
    }

    //添加節點到雙鏈表
    // 當不考慮編號順序
    public void add(HeroNode node){

        //需要一個輔助節點 遍歷 鏈表
       HeroNode temp = head;
        //遍歷鏈表,找到最後
        while (true){
            //最後的結點 next == null
            if (temp.getNext() == null){
                break;
            }
            temp = temp.getNext();
        }
        //當退出while循環時,temp指向鏈表最後節點,node指向前節點,形成雙向鏈表
        temp.setNext(node);
        node.setPre(temp);

    }

    // 有序插入
    public void addByOrder(HeroNode heroNode){
        //通過輔助指針temp找到 插入位置, temp位於添加位置前一個節點
        HeroNode temp = head;
        //標識添加的編號是否存在
        boolean flag = false;
        while (true){
            //如果temp在鏈表最後
            if (temp.getNext() == null){
                break;
            }
            //位置找到了,在temp的後面插入
            if (temp.getNext().getNo() > heroNode.getNo()){
                break;
            }
            else if (temp.getNext().getNo() == heroNode.getNo()){
                //編號存在,插入失敗
                flag = true;
                break;
            }
            //後移temp
            temp = temp.getNext();
        }
        //判斷flag
        if (flag){
            System.out.println("=== 準備插入的Hero編號"+heroNode.getNo()+"已存在 ===");
        }
        else {
            //插入到鏈表中,需要修改temp(插入節點前一節點)、temp.next(插入節點後一節點)
            heroNode.setNext(temp.getNext());
            heroNode.setPre(temp);
            
       		if (temp.getNext() != null){
                temp.getNext().setPre(heroNode);
            }
            temp.setNext(heroNode);
        }
    }

    //通過一個輔助變量 遍歷 鏈表
    public void list(){
        //判斷鏈表是否爲空
        if (head.getNext() == null){
            System.out.println("=== 鏈表爲空 ===");
            return;
        }
        //需要一個輔助變量遍歷
        HeroNode temp = head.getNext();
        while (true){
            //輸出節點信息
            System.out.println(temp);
            //判斷是否到鏈表最後
            if (temp.getNext() == null){
                break;
            }
            //將temp後移
            temp = temp.getNext();

        }
    }

    //根據no修改節點信息
    public void update(HeroNode heroNode){
        //判斷是否爲空
        if (head.getNext() == null){
            System.out.println("=== 鏈表爲空 ===");
            return;
        }
        //找到要修改的節點
        HeroNode temp = head.getNext();
        //表示是否找到該節點
        boolean flag = false;
        while (true){
            //已經遍歷完鏈表
            if (temp == null){
                break;
            }
            //找到該節點
            if (temp.getNo() == heroNode.getNo()){
                flag = true;
                break;
            }
            temp = temp.getNext();
        }
        //根據 flag 判斷要修改的節點
        if (flag){
            //因爲temp獲得的是對象的引用,可以直接修改
            temp.name = heroNode.name;
            temp.nickName = heroNode.nickName;
        }else {
            //沒有找到
            System.out.println("沒有找到編號 = "+heroNode.getNo()+"不能修改");
        }
    }


    /**
     * 刪除節點
     * 1. 先找到要刪除的節點 temp
     * 2. temp.pre.next = temp.nxet; temp.next.pre = temp.pre
     * 3. 被刪除的節點,不會其他引用指向,會被JVM垃圾回收
     * */
    public void delete(int no){
        //判斷當前鏈表是否爲空
        if (head.getNext() == null){
            System.out.println("=== 鏈表爲空 ===");
            return;
        }
        //輔助節點
        HeroNode temp = head.getNext();
        //是否找到待刪除節點
        boolean flag = false;
        while (true){
            //已經到鏈表的最後
            if (temp == null) {
                break;
            }
            //如果找到帶刪除節點的前一個節點temp
            if (temp.getNo() == no){
                flag = true;
                break;
            }
            //temp後移
            temp = temp.getNext();
        }
        //判斷flag
        if (flag){
            //找到,轉移temp前後節點的指針
            temp.getPre().setNext(temp.getNext());
            //如果是最後一個節點就不需要執行下面命令,否則會出現空指針異常
            if (temp.getNext() != null){
                temp.getNext().setPre(temp.getPre());
            }
        }
        else {
            System.out.println("=== 沒有找到該節點"+no+" ===");
        }
    }
}

測試結果:
在這裏插入圖片描述

關於單鏈表的相關問題:

  • 求單鏈表中有效節點的個數
  • 查找倒數第k個數據
  • 反轉鏈表
  • 從尾到頭打印單鏈表
  • 合併兩個有序鏈表,合併後鏈表也有序

雙向鏈表的解決方法和單鏈表類似,因爲存在前驅指針,可以更簡單的解決


單向環形鏈表

單向環形鏈表是在單向鏈表的基礎上,把鏈表尾的next指針指向了第一個節點,除去頭節點

在這裏插入圖片描述

注意:因爲不存在頭指針,需要我們自己設置一個first指針,表示第一個節點

關於單向環形鏈表,有一個經典的問題:約瑟夫問題

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


Java單向環形鏈表實現約瑟夫問題

設置一個first指針替代頭指針

在這裏插入圖片描述

  • 添加節點:因爲約瑟夫問題沒必要設置有序添加,這裏是無序添加方法
    需要注意:第一個節點next指向自己;當添加一個節點時需要修改兩個指針(前一個節點的next、當前節點的next指向first)
  • 遍歷:設置一個輔助指針curBoy在first位置,從first開始移動,curBoy.next == first表示結束
  • 出列:設置一個輔助指針helper指向鏈表尾,開始報數時同時移動first、helper指針,移動m位,first指針指向的就是要出圈的節點,修改指針helper.next = first.next ,first指針後移
package com.company.josepfu;

import org.junit.Test;

/**
 * @author zfk
 *
 * 環形鏈表完成約瑟夫問題
 */
public class MyTest{
    @Test
    public void test(){
        CircleSingleLinkedList list = new CircleSingleLinkedList();
        list.addBoy(7);

        list.show();

        list.out(2,4,7);
    }
}


/**
 * 環形單向鏈表
 */
class CircleSingleLinkedList{
    //創建一個first節點,當前沒有編號
    private Boy first = new Boy(-1);

    /**
     * @param nums 小孩數目
     *
     * 添加小孩節點,構件成環形鏈表
     */
    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輔助指針指向第一個小孩
                curBoy = first;
            }
            else {
                //當新增一個boy,需要修改兩個指針
                curBoy.setNext(boy);
                boy.setNext(first);
                //curBoy指針後移
                curBoy = boy;
            }
        }

    }


    /**
     * 遍歷當前的環形鏈表
     */
    public void show(){
        //判斷是否爲空
        if (first == null){
            System.out.println("=== 環形鏈表爲空 ===");
            return;
        }
        //first不能動,需要一個輔助指針完成遍歷
        Boy curBoy = first;
        while (true){
            System.out.println("小孩的編號:"+ curBoy.getNo());
            //判斷遍歷完畢
            if (curBoy.getNext() == first){
                break;
            }
            //curBoy後移
            curBoy = curBoy.getNext();
        }
    }


    /**
     * @param startNo 從第幾個小孩開始數
     * @param countNum 表示數幾下
     * @param nums 表示最初有多少個小孩在圈中
     *
     * 小孩出列
     */
    public void out(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();
        }
        //先移動到 startNo 處
        for (int j = 0;j < startNo - 1;j++){
            first = first.getNext();
            helper = helper.getNext();
        }
        //當報數時,讓first和helper指針同時移動countNum -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());
    }


}




/**
 * 環形鏈表節點
 */
class Boy {
    private int no;
    private Boy next;

    public Boy(int no) {
        this.no = no;
    }

    public int getNo() {
        return no;
    }

    public Boy setNo(int no) {
        this.no = no;
        return this;
    }

    public Boy getNext() {
        return next;
    }

    public Boy setNext(Boy next) {
        this.next = next;
        return this;
    }

}

測試:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章