1. 程式人生 > 其它 >資料結構學習3-單向連結串列、雙向連結串列以及使用連結串列解決約瑟夫環

資料結構學習3-單向連結串列、雙向連結串列以及使用連結串列解決約瑟夫環

技術標籤:基礎知識資料結構

連結串列學習筆記

連結串列是一種使用十分頻繁的資料結構,它的優點在於可以將大量的資料儲存在分散的空間內,當需要插入或修改節點的時候,只需要修改節點之間的指標即可。
與之相反,陣列的儲存則需要連續的空間,當需要向陣列中插入資料或者修改順序的時候,需要對整個空間進行處理。
所以:我們常說 如果查詢的比較頻繁就使用陣列,如果修改比較頻繁那麼建議使用連結串列

連結串列是以節點的方式來進行資料儲存的,它分為帶頭節點的連結串列和不帶頭節點的連結串列

單向連結串列

每一個節點中都分為data域和next域 ,data域存放資料 ,next域指向下一個節點

next next next head node1 node2 node3

程式碼實現

public class SingleLinkedList {

    /**
     * 頭節點中沒有任何資料 它只是一個輔助節點
     */
    private SingleLinkedNode head;

    public SingleLinkedList() {
        head =
new SingleLinkedNode(-1, null); } /** * 新增節點 * * @param node */ public void add(SingleLinkedNode node) { SingleLinkedNode temp = head; while (true) { if (temp.next == null) { break; } temp = temp.next;
} temp.next = node; } /** * 按排序新增 * * @param node */ public void addByRank(SingleLinkedNode node) { SingleLinkedNode temp = head; while (true) { if (temp.next != null) { if (temp.next.rank > node.rank) { node.next = temp.next; temp.next = node; break; } else { temp = temp.next; } } else { temp.next = node; break; } } } /** * 移除節點 * * @param rank */ public SingleLinkedNode remove(int rank) { SingleLinkedNode temp = head; SingleLinkedNode removed = null; while (true) { if (temp.next != null) { if (temp.next.rank == rank) { removed = temp.next; temp.next = temp.next.next; break; } // 往後面移動一位 temp = temp.next; } else { // 不存在當前移除的key break; } } return removed; } public void print() { // 列印 SingleLinkedNode lastNode = head.next; System.out.println("head"); while (lastNode != null) { System.out.println(" -> " + lastNode); lastNode = lastNode.next; } } /** * 從尾部開始列印 * 利用棧 先進後出的原則 */ public void printFromEnd() { Stack<String> stack = new Stack<>(); SingleLinkedNode lastNode = head.next; while (lastNode != null) { stack.add(" -> " + lastNode); lastNode = lastNode.next; } System.out.println("printFromEnd"); while (!stack.isEmpty()) { System.out.println(stack.pop()); } } /** * 連結串列的有效長度 * * @return */ public int size() { int len = 0; SingleLinkedNode temp = head; while (temp.next != null) { len++; temp = temp.next; } return len; } /** * 從尾部開始查詢到第n個節點 * * @param index 下標都是從0開始計算 * @return */ public SingleLinkedNode lastIndexOf(int index) { // 先獲取到真正的長度 int size = size(); if (index >= size) { throw new RuntimeException("out of bound:" + index + "[" + size + "]"); } // 計算正向的下標 int realIndex = size - 1 - index; SingleLinkedNode temp = head; int curIndex = -1; while (temp.next != null) { curIndex++; temp = temp.next; if (realIndex == curIndex) { return temp; } } return null; } /** * 反轉 * 原連結串列是 head - 1 -> 2 -> 5 * 反轉之後 head - 5 -> 2 -> 1 * <p> * 思路: * tempHead -> 1 * tempHead -> 2 -> 1 * tempHead -> 5 -> 2 -> 1 */ public void reverse() { if (head.next == null) { return; } // 當前迴圈遍歷的節點 SingleLinkedNode cur = head.next; // 當前迴圈的節點的下一個節點 SingleLinkedNode next; // 反轉的臨時頭節點 SingleLinkedNode reverseHead = new SingleLinkedNode(0, ""); while (cur != null) { // 先獲取到下一個節點 next = cur.next; // 斷開原本當前節點和下一個節點之間的關係 並構建新的關係 // 這一句很巧妙 實現了將1掛在2的後面 cur.next = reverseHead.next; // 將當前節點設定為反轉連結串列的第一個節點 reverseHead.next = cur; // 連結串列向後移動 cur = next; } head.next = reverseHead.next; } /** * 合併2個有序的連結串列 * @param another * @return */ public SingleLinkedList merge( SingleLinkedList another){ SingleLinkedNode aTemp = this.head.next; SingleLinkedNode bTemp = another.head.next; SingleLinkedList mergeList = new SingleLinkedList(); SingleLinkedNode mergeNode = mergeList.head ; while(aTemp != null && bTemp != null){ // 比較 if(aTemp.rank > bTemp.rank){ mergeNode.next = aTemp; aTemp = aTemp.next; }else{ mergeNode.next = bTemp; bTemp = bTemp.next; } mergeNode = mergeNode.next; } if(aTemp == null){ mergeNode.next = bTemp; }else { mergeNode.next = aTemp; } return mergeList; } static class SingleLinkedNode { private int rank; private String name; private SingleLinkedNode next; public SingleLinkedNode(int rank, String name) { this.rank = rank; this.name = name; } @Override public String toString() { return "SingleLinkedNode{" + "rank=" + rank + ", name='" + name + '\'' + '}'; } } public static void main(String[] args) { SingleLinkedList singleLinkedList = new SingleLinkedList(); singleLinkedList.add(new SingleLinkedNode(1, "zhangsan")); singleLinkedList.add(new SingleLinkedNode(3, "lisi")); singleLinkedList.add(new SingleLinkedNode(5, "wangwu")); singleLinkedList.print(); singleLinkedList.remove(4); singleLinkedList.print(); singleLinkedList.addByRank(new SingleLinkedNode(4, "haha")); singleLinkedList.print(); singleLinkedList.remove(4); singleLinkedList.print(); System.out.println("size:" + singleLinkedList.size()); System.out.println("\tlastIndexOf(2):" + singleLinkedList.lastIndexOf(2)); System.out.println("\tlastIndexOf(1):" + singleLinkedList.lastIndexOf(1)); System.out.println("\tlastIndexOf(0):" + singleLinkedList.lastIndexOf(0)); System.out.println("reverse:"); singleLinkedList.reverse(); singleLinkedList.print(); singleLinkedList.printFromEnd(); SingleLinkedList singleLinkedList2 = new SingleLinkedList(); singleLinkedList2.add(new SingleLinkedNode(8, "zhaoliu")); singleLinkedList2.add(new SingleLinkedNode(3, "songqi")); singleLinkedList2.add(new SingleLinkedNode(2, "songqi2")); System.out.println("merge:"); singleLinkedList.merge(singleLinkedList2).print(); } }

雙向連結串列

和單向連結串列不同的點在於雙向連結串列除了data域和next域以外還有一個pre域,它指向當前節點的上一個節點
所以:雙向連結串列可以自刪除,可以反向查詢

next next next pre pre pre head node1 node2 node3

程式碼實現

public class DoubleLinkedList {


    private DoubleLinkedNode head;

    public DoubleLinkedList() {
        head = new DoubleLinkedNode(-1, "頭節點");
    }

    public void add(DoubleLinkedNode node) {
        // 找到最後一個節點
        DoubleLinkedNode temp = head;
        while (temp.next != null) {
            temp = temp.next;
        }
        // 雙向繫結
        temp.next = node;
        node.pre = temp;
    }

    public void addByRank(DoubleLinkedNode node) {
        DoubleLinkedNode temp = head;
        while (temp.next != null) {
            if (temp.next.rank > node.rank) {
                break;
            }
            temp = temp.next;
        }
        if (temp.next != null) {
            temp.next.pre = node;
            node.next = temp.next;
        }
        // 雙向繫結
        temp.next = node;
        node.pre = temp;
    }

    /**
     * 移除節點
     *
     * @param rank
     */
    public DoubleLinkedNode remove(int rank) {
        DoubleLinkedNode temp = head;
        DoubleLinkedNode removed = null;
        while (true) {
            if (temp.next != null) {
                if (temp.next.rank == rank) {
                    removed = temp.next;
                    DoubleLinkedNode preNode = removed.pre;
                    DoubleLinkedNode nextNode = removed.next;
                    preNode.next = nextNode;
                    if (nextNode != null) {
                        nextNode.pre = preNode;
                    }
                    break;
                }
                // 往後面移動一位
                temp = temp.next;
            } else {
                // 不存在當前移除的key
                break;
            }
        }
        return removed;
    }


    /**
     * 列印資料
     */
    public void print() {
        System.out.println("head:");
        DoubleLinkedNode temp = head;
        while (temp.next != null) {
            System.out.println("\t -> " + temp.next);
            temp = temp.next;
        }
        System.out.println();
    }


    /**
     * 從尾部開始列印
     */
    public void printFromEnd() {
        // 首先找到最後一個節點
        DoubleLinkedNode temp = head;
        // 向後查詢
        while (temp.next != null) {
            temp = temp.next;
        }
        System.out.println("printFromEnd");
        // 向前查詢
        while (temp.pre != null) {
            System.out.println("\t -> " + temp);
            temp = temp.pre;
        }

    }

    /**
     * 連結串列的有效長度
     * @return
     */
    public int size() {
        int len = 0;
        DoubleLinkedNode temp = head;
        while (temp.next != null) {
            len++;
            temp = temp.next;
        }
        return len;
    }

    /**
     * 從尾部開始查詢到第n個節點
     *
     * @param index 下標都是從0開始計算
     * @return
     */
    public DoubleLinkedNode lastIndexOf(int index) {
        // 先獲取到最後一個節點
        DoubleLinkedNode temp = head;
        int len = 0;
        while (temp.next != null) {
            temp = temp.next;
            len++;
        }
        if (index >= len) {
            throw new RuntimeException("out of bound:" + index + "[" + len + "]");
        }
        // 向前找
        while (index > 0) {
            temp = temp.pre;
            index--;
        }
        return temp;
    }

    /**
     * 反轉
     */
    public void reverse() {
        if(head.next == null){
            return;
        }
        // 首先需要一個臨時的反轉節點
        DoubleLinkedNode reverseHead = new DoubleLinkedNode(-1, "");
        // 迴圈原連結串列
        DoubleLinkedNode cur = head.next;
        while(cur!= null){
            // 先獲取到下一個節點
            DoubleLinkedNode next = cur.next;
            // 反轉pre和當前節點的關係
            cur.pre = cur;
            // 處理next的關係
            cur.next = reverseHead.next;
            // 將當前節點綁到新的頭節點上
            reverseHead.next = cur;
            // 向後移動
            cur = next;
        }
        // 重新繫結頭節點
        head.next = reverseHead.next;
        reverseHead.next.pre = head;
    }

    static class DoubleLinkedNode {

        private int rank;
        private String name;

        private DoubleLinkedNode pre;
        private DoubleLinkedNode next;

        public DoubleLinkedNode(int rank, String name) {
            this.rank = rank;
            this.name = name;
        }

        @Override
        public String toString() {
            return "DoubleLinkedNode{" +
                    "rank=" + rank +
                    ", name='" + name + '\'' +
                    '}';
        }
    }

    public static void main(String[] args) {
        DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
        doubleLinkedList.add(new DoubleLinkedNode(1, "zhangsan"));
        doubleLinkedList.add(new DoubleLinkedNode(3, "lisi"));
        doubleLinkedList.add(new DoubleLinkedNode(5, "wangwu"));
        doubleLinkedList.print();

        doubleLinkedList.remove(3);
        doubleLinkedList.print();

        doubleLinkedList.addByRank(new DoubleLinkedNode(4, "zhaoliu"));
        doubleLinkedList.print();

        doubleLinkedList.printFromEnd();

        System.out.println("size:" + doubleLinkedList.size());

        System.out.println("\tlastIndexOf(2):" + doubleLinkedList.lastIndexOf(2));
        System.out.println("\tlastIndexOf(1):" + doubleLinkedList.lastIndexOf(1));
        System.out.println("\tlastIndexOf(0):" + doubleLinkedList.lastIndexOf(0));

        System.out.println("reverse:");
        doubleLinkedList.reverse();
        doubleLinkedList.print();

    }
}

使用不帶頭節點的單向環形連結串列實現約瑟夫環

單向迴圈連結串列

如圖所示,不帶頭節點的單線環形連結串列中 最後一個節點的next域會指向第一個節點,所以我們可以通過這個資料結構實現約瑟夫環

next next next 張三 李四 王五

約瑟夫環

在這裡插入圖片描述

程式碼實現

public class Josephu {

    static class Children {

        private Child first;

        public Children(int nums) {
            addChild(nums);
        }

        public void addChild(int nums) {
            first = new Child(1);
            Child temp = first;
            for (int i = 2; i <= nums; i++) {
                Child child = new Child(i);
                temp.next = child;
                temp = child;
            }
            temp.next = first;
        }

        public void print() {
            System.out.println("head:");
            Child temp = first;
            if (temp != null) {
                System.out.println("\t -> " + temp);
                while (temp.next != first) {
                    temp = temp.next;
                    System.out.println("\t -> " + temp);
                }
            }
        }

        public void calculate(int num) {
            System.out.println("calculate split:" + num);
            int size = size();
            int splitSpace = (num - 1) % size;
            if(splitSpace == 0){
                splitSpace = size;
            }
            Child cur = first;
            Child next;
            int curIndex = 0;
            while (size > 1) {
                curIndex++;
                if (curIndex == splitSpace) {
                    // 需要移除下一個
                    next = cur.next;
                    System.out.println("\t -> "+next);
                    cur.next = next.next;
                    // 長度-1
                    size--;
                    // 重新計算間隔位置
                    splitSpace = (num - 1) % size;
                    if(splitSpace == 0){
                        splitSpace = size;
                    }
                    // 給當前節點當作最後一個
                    curIndex = 0;
                }
                cur = cur.next;
            }
            System.out.println("\t -> "+cur);
        }


        public int size() {
            if (first == null) {
                return 0;
            }
            int len = 1;
            Child temp = first;
            while (temp.next != first) {
                temp = temp.next;
                len++;
            }
            return len;
        }
    }


    static class Child {

        int no;
        Child next;
        public Child(int no) {
            this.no = no;
        }

        @Override
        public String toString() {
            return "Child{" +
                    "no=" + no +
                    '}';
        }
    }

    public static void main(String[] args) {
        Children children = new Children(5);
        children.print();
        // 1 2 3 4 5 的結果應該是 2 -> 4 -> 1 -> 5 -> 3
        children.calculate(7);

    }
}