1. 程式人生 > 其它 >連結串列:單向連結串列

連結串列:單向連結串列

簡介

單向連結串列是連結串列的一種,它由多個結點組成,每個結點都由一個數據域和一個指標域組成,資料域用來儲存資料, 指標域用來指向其後繼結點。連結串列的頭結點的資料域不儲存資料,指標域指向第一個真正儲存資料的結點。

程式碼實現:

public class SinglyLinkedList<T> implements Iterable<T>{

    //記錄頭結點
    private Node<T> head;
    //記錄連結串列的長度
    private int N;

    //結點類
    private static class Node<T> {
        //儲存資料
        T item;
        //下一個結點
        Node<T> next;

        public Node(T item, Node<T> next) {
            this.item = item;
            this.next = next;
        }
    }

    public SinglyLinkedList() {
        //初始化頭結點、
        this.head = new Node<>(null, null);
        //初始化元素個數
        this.N=0;
    }

    //清空連結串列
    public void clear() {
        head.next=null;
        this.N=0;
    }

    //獲取連結串列的長度
    public int length() {
        return N;
    }

    //判斷連結串列是否為空
    public boolean isEmpty() {
        return N==0;
    }

    //獲取指定位置i出的元素
    public T get(int i) {

        //通過迴圈,從頭結點開始往後找,依次找i次,就可以找到對應的元素
        Node<T> n = head.next;
        for(int index=0;index<i;index++){
            n=n.next;
        }

        return n.item;
    }

    //向連結串列中新增元素t
    public void add(T t) {
        //找到當前最後一個結點

        Node<T> n = head;
        while(n.next!=null){
            n=n.next;
        }


        //建立新結點,儲存元素t
        //讓當前最後一個結點指向新結點
        n.next= new Node<>(t, null);
        //元素的個數+1
        N++;
    }

    //向指定位置i出,新增元素t
    public void add(int i, T t) {
        //找到i位置前一個結點
        Node<T> pre = head;
        for(int index=0;index<=i-1;index++){
            pre=pre.next;
        }

        //找到i位置的結點
        Node<T> curr = pre.next;
        //建立新結點,並且新結點需要指向原來i位置的結點
        //原來i位置的前一個節點指向新結點即可
        pre.next= new Node<>(t, curr);
        //元素的個數+1
        N++;
    }

    //刪除指定位置i處的元素,並返回被刪除的元素
    public T remove(int i) {
        //找到i位置的前一個節點
        Node<T> pre = head;
        for(int index=0;index<=i-1;i++){
            pre=pre.next;
        }
        //要找到i位置的結點
        Node<T> curr = pre.next;
        //找到i位置的下一個結點
        Node<T> nextNode = curr.next;
        //前一個結點指向下一個結點
        pre.next=nextNode;
        //元素個數-1
        N--;
        return curr.item;
    }

    //查詢元素t在連結串列中第一次出現的位置
    public int indexOf(T t) {
        //從頭結點開始,依次找到每一個結點,取出item,和t比較,如果相同,就找到了
        Node<T> n = head;
        for(int i=0;n.next!=null;i++){
            n=n.next;
            if (n.item.equals(t)){
                return i;
            }
        }
        return -1;
    }


    @Override
    public Iterator<T> iterator() {
        return new LIterator();
    }

    private class LIterator implements Iterator<T>{
        private Node<T> n;
        public LIterator(){
            this.n=head;
        }

        @Override
        public boolean hasNext() {
            return n.next!=null;
        }

        @Override
        public T next() {
            n = n.next;
            return n.item;
        }
    }
}

單鏈表反轉

使用遞迴可以完成反轉,遞迴反轉其實就是從原連結串列的第一個存資料的結點開始,依次遞迴呼叫反轉每一個結點, 直到把最後一個結點反轉完畢,整個連結串列就反轉完畢。

程式碼實現:

    //用來反轉整個連結串列
    public void reverse(){

        //判斷當前連結串列是否為空連結串列,如果是空連結串列,則結束執行,如果不是,則呼叫過載的reverse方法完成反轉
        if (isEmpty()){
            return;
        }

        reverse(head.next);
    }

    private Node<T> reverse(Node<T> curr){
        if(curr.next == null){
            head.next = curr;
            return curr;
        }
        Node<T> pre = reverse(curr.next);
        pre.next = curr;
        curr.next = null;
        return curr;
    }

快慢指標

快慢指標指的是定義兩個指標,這兩個指標的移動速度一塊一慢,以此來製造出自己想要的差值,這個差值可以讓我們找到連結串列上相應的結點。一般情況下,快指標的移動步長為慢指標的兩倍。

中間值問題

下面有一個需求:找出連結串列的中間值

public class FastSlowTest {

    public static void main(String[] args) throws Exception {
        //建立結點
        Node<String> first = new Node<>("aa", null);
        Node<String> second = new Node<>("bb", null);
        Node<String> third = new Node<>("cc", null);
        Node<String> fourth = new Node<>("dd", null);
        Node<String> fifth = new Node<>("ee", null);
        Node<String> six = new Node<>("ff", null);
        Node<String> seven = new Node<>("gg", null);

        //完成結點之間的指向
        first.next = second;
        second.next = third;
        third.next = fourth;
        fourth.next = fifth;
        fifth.next = six;
        six.next = seven;

        //查詢中間值
        String mid = getMid(first);
        System.out.println("中間值為:"+mid);
    }

    /**
     * @param first 連結串列的首結點
     * @return 連結串列的中間結點的值
     */
    public static String getMid(Node<String> first) {
        return null;
    }

    //結點類
    private static class Node<T> {
        //儲存資料
        T item;
        //下一個結點
        Node<T> next;

        public Node(T item, Node<T> next) {
            this.item = item;
            this.next = next;
        }
    }
}

使用快慢指標即可解決:最開始,slow與fast指標都指向連結串列第一個節點,然後slow每次移動一個指標,fast每次移動兩個指標。

程式碼實現如下:

    /**
     * @param first 連結串列的首結點
     * @return 連結串列的中間結點的值
     */
    public static String getMid(Node<String> first) {
        return getMid(first, first);
    }

    private static String getMid(Node<String> slowNode, Node<String> fastNode) {
        if(fastNode.next == null) return slowNode.item;
        fastNode = fastNode.next.next;
        slowNode = slowNode.next;
        return getMid(slowNode, fastNode);
    }

單向連結串列是否有環

下面有一個需求:判斷連結串列中是否有環

使用快慢指標,如果快慢指標最後有快指標節點等於慢指標(相遇),說明有環

public class CircleListCheckTest {
    public static void main(String[] args) throws Exception {
        //建立結點
        Node<String> first = new Node<String>("aa", null);
        Node<String> second = new Node<String>("bb", null);
        Node<String> third = new Node<String>("cc", null);
        Node<String> fourth = new Node<String>("dd", null);
        Node<String> fifth = new Node<String>("ee", null);
        Node<String> six = new Node<String>("ff", null);
        Node<String> seven = new Node<String>("gg", null);

        //完成結點之間的指向
        first.next = second;
        second.next = third;
        third.next = fourth;
        fourth.next = fifth;
        fifth.next = six;
        six.next = seven;
        //產生環
        seven.next = third;
        //判斷連結串列是否有環
        boolean circle = isCircle(first);
        System.out.println("first連結串列中是否有環:"+circle);
    }

    /**
     * 判斷連結串列中是否有環
     * @param first 連結串列首結點
     * @return ture為有環,false為無環
     */
    public static boolean isCircle(Node<String> first) {
        return false;
    }

    //結點類
    private static class Node<T> {
        //儲存資料
        T item;
        //下一個結點
        Node<T> next;

        public Node(T item, Node<T> next) {
            this.item = item;
            this.next = next;
        }
    }
}

程式碼實現:

    /**
     * 判斷連結串列中是否有環
     * @param first 連結串列首結點
     * @return ture為有環,false為無環
     */
    public static boolean isCircle(Node<String> first) {
        return isCircle(first, first);
    }

    private static boolean isCircle(Node<String> slowNode, Node<String> fastNode) {
        if(fastNode.next == null) return false;
        slowNode = slowNode.next;
        fastNode = fastNode.next.next;
        if(slowNode.item.equals(fastNode.item)) return true;
        return isCircle(slowNode, fastNode);
    }

非遞迴實現:

    /**
     * 判斷連結串列中是否有環
     * @param first 連結串列首結點
     * @return ture為有環,false為無環
     */
    public static boolean isCircle(Node<String> first) {
        //定義快慢指標
        Node<String> fast = first;
        Node<String> slow = first;

        //遍歷連結串列,如果快慢指標指向了同一個結點,那麼證明有環
        while(fast!=null && fast.next!=null){
            //變換fast和slow
            fast = fast.next.next;
            slow = slow.next;

            if (fast.equals(slow)){
                return true;
            }
        }
        return false;
    }

單向連結串列環入口問題

現有一個需求:需要找打單向連結串列中環的入口

public class CircleListInTest {
    public static void main(String[] args) throws Exception {
        Node<String> first = new Node<String>("aa", null);
        Node<String> second = new Node<String>("bb", null);
        Node<String> third = new Node<String>("cc", null);
        Node<String> fourth = new Node<String>("dd", null);
        Node<String> fifth = new Node<String>("ee", null);
        Node<String> six = new Node<String>("ff", null);
        Node<String> seven = new Node<String>("gg", null);

        //完成結點之間的指向
        first.next = second;
        second.next = third;
        third.next = fourth;
        fourth.next = fifth;
        fifth.next = six;
        six.next = seven;
        //產生環
        seven.next = third;

        //查詢環的入口結點
        Node<String> entrance = getEntrance(first);
        System.out.println("first連結串列中環的入口結點元素為:"+entrance.item);
    }

    /**
     * 查詢有環連結串列中環的入口結點
     * @param first 連結串列首結點
     * @return 環的入口結點
     */
    public static Node<String> getEntrance(Node<String> first) {
        return null;
    }
    //結點類
    private static class Node<T> {
        //儲存資料
        T item;
        //下一個結點
        Node<T> next;

        public Node(T item, Node<T> next) {
            this.item = item;
            this.next = next;
        }
    }
}

思路:

當快慢指標相遇時,我們可以判斷到連結串列中有環,這時重新設定一個新指標指向連結串列的起點,且步長與慢指標一樣 為1,則慢指標與“新”指標相遇的地方就是環的入口。證明這一結論牽涉到數論的知識,這裡略,只講實現

程式碼實現如下:

    public static Node<String> getEntrance(Node<String> first) {
        //定義快慢指標
        Node<String> fast = first;
        Node<String> slow = first;
        Node<String> temp = null;

        //遍歷連結串列,如果快慢指標指向了同一個結點,那麼證明有環
        while(fast!=null && fast.next!=null){
            //變換fast和slow
            fast = fast.next.next;
            slow = slow.next;

            if (fast.equals(slow)){
                temp = first;
                continue;
            }
            if(temp != null){
                temp = temp.next;
                if(temp.item.equals(slow.item))
                    return temp;
            }
        }
        return null;
    }