從新認識鏈表(下)

寫鏈表代碼是最考驗 邏輯思惟能力的,指針來回指,指着指着就指迷糊,在寫代碼以前先注意這麼幾個問題。

日誌

2019年3月20日 查漏補缺,新增循環鏈表的實現
2019年3月18日  查漏補缺,新增雙向鏈表的實現

問題1:什麼是指針?

定義:

有些語言有「指針」的概念,好比 C 語言;有些語言沒有指針,取而代之的是「引用」,好比 Java、Python。無論是「指針」仍是「引用」,實際上,它們的意思都是同樣的,都是存儲所指對象的內存地址。
node

將某個變量賦值給指針,實際上就是將這個變量的地址賦值給指針,或者反過來講,指針中存儲了這個變量的內存地址,指向了這個變量,經過指針就能找到這個變量。
git

舉例:

最多見的指針代碼應該是這個了,p->next=q。它的意思是說p 結點中的 next 指針存儲了 q 結點的內存地址。github

問題2:什麼是指針丟失和內存泄漏?

在插入結點時,必定要注意操做的順序。

由於操做不當的時候,很容易形成指針丟失。好比a ->b之間插入一個c,同時不注意的時候很容易這麼寫緩存

//聲明一個p;bash

p->next = c; // 將 p 的 next 指針指向 c 結點; 測試

c->next = p->next; // 將 c 的結點的 next 指針指向 b 結點;ui

這就很容易讓鏈表斷成兩半,形成指針丟失。正確的寫法是將二三兩行代碼順序換過來便可。this

刪除鏈表結點時,也必定要記得手動釋放內存空間。

和插入同樣,若是不及時釋放內存空間,聚沙成塔,很容易形成內存泄漏。spa

技巧:利用哨兵節點簡化實現難度

哨兵節點主要是處理邊界問題的,並不直接參與業務邏輯,當引入哨兵節點的時候,無論鏈表是否是空,head 指針都會一直指向這個哨兵結點。咱們也把這種有哨兵結點的鏈表叫帶頭鏈表。相反,沒有哨兵結點的鏈表就叫做不帶頭鏈表3d

練習:

以單鏈表爲例,全部源碼均已上傳至github:連接

聲明一個單鏈表類

public class Node {
	public int data;
	public Node next;

	public Node(int data, Node next) {
		this.data = data;
		this.next = next;
	}
}複製代碼

1.實現單鏈表,支持增刪查操做

順序插入

public void insertTail(int value) {
		Node node = new Node(value, null);
		if (head == null) {
			head = node;
		} else {
			Node q = head;
			// 找到最後next一個爲不空節點並賦值
			while (q.next != null) {
				q = q.next;
			}
			// 注意,順序不可反,不然鏈表就斷開了
			// 很精髓
			node.next = q.next;
			q.next = node;
		}
	}複製代碼

刪除,難點就一行q.next = q.next.next;

public void deleteByNode(int value) {
		if (head == null)
			return;
		Node p = head;
		Node q = null;
		// 從鏈表中找到要刪除的value
		while (p != null && p.data != value) {
			q = p;
			p = p.next;
		}
		if (p == null)
			return;
		if (q == null) {
			head = head.next;
		} else {
			// 刪除節點,其實就是把要刪除的值prev節點指向他的next節點
			// 很精髓
			q.next = q.next.next;
		}
	}複製代碼

查詢,分兩個,一個是根據下標查詢,一個是根據value查詢

public Node findByIndex(int index) {
		Node p = head;
		int pos = 0;
		while (p != null && pos != index) {
			p = p.next;
			++pos;
		}
		return p;
	}複製代碼

public Node findByValue(int value) {
		Node p = head;
		while (p != null && p.data != value) {
			p = p.next;
		}
		return p;
	}複製代碼

測試結果:


2.單鏈表反轉 

該實現的思想是:從前日後反轉各個結點的指針域的指向

將當前節點cur的next節點 緩存到nextNode,而後更改當前節點指針指向上一結點prevNode。也就是說在反轉當前結點指針指向前,先把當前結點的指針域用nextNode臨時保存,以便下一次使用

public Node reverse(Node node) {
		Node resNode = null;
		Node prevNode = null;
		Node curNode = node;
		while (curNode != null) {
			Node nextNode = curNode.next;
			if (nextNode == null) {
				resNode = curNode;
			}
			curNode.next = prevNode;
			prevNode = curNode;
			curNode = nextNode;
		}
		return resNode;
	}複製代碼

測試結果:



3.鏈表中環的檢測 

該檢測的核心思想是:快慢指針法。在這裏,滿指針每走一步,快指針走兩步,能夠用數學概括法來考慮,首先,若是鏈表是個環,因此相遇的過程能夠看做是快指針從後邊追趕慢指針的過程。那麼

1:快指針與慢指針之間差一步。此時繼續日後走,慢指針前進一步,快指針前進兩步,二者相遇。
2:快指針與慢指針之間差兩步。此時繼續日後走,慢指針前進一步,快指針前進兩步,二者之間相差一步,轉化爲第一種狀況。

...
N:快指針與慢指針之間差N步。此時繼續日後走,慢指針前進一步,快指針前進兩步,二者之間相差(N+1-2)-> N-1步。

代碼以下:

public boolean checkCircle(Node node) {
		if (node == null)
			return false;
		// 快慢指針法
		Node slow = node;
		Node fast = node.next;

		while (fast != null && fast.next != null) {
			fast = fast.next.next;
			slow = slow.next;

			if (slow == fast)
				return true;
		}
		return false;
	}複製代碼

測試結果:


4.求鏈表的中間結點

該實現也是用到了快慢指針法

代碼以下:

public Node findMiddleByNode(Node node) {
		if (node == null)
			return null;
		// 快慢指針法
		Node fast = node.next;
		Node slow = node;
		// 快指針走完一圈,慢指針半圈
		while (fast != null && fast.next != null) {
			fast = fast.next.next;
			slow = slow.next;
		}
		return slow;
	}複製代碼

測試結果以下:


5.刪除鏈表倒數第 n 個結點

該實現依舊用到了快慢指針法,不過和以前的有一點區別。以node長度爲n,刪倒數第k(k<n)個爲例。首先,第一個while先計算若是正着刪,須要讓慢指針走幾步,計算爲n-k。第二個while循環則經過遍歷快指針計算定位須要走n-k步,獲得preNode。而後判斷是否爲空,不爲空則常規方法刪除。

public Node deleteLastKByNode(Node node, int k) {
		// 快慢指針法
		Node fast = node;
		// 計數
		int i = 1;
		while (fast != null && i < k) {
			fast = fast.next;
			++i;
		}
		if (fast == null)
			return node;
		Node slow = node;
		Node prevNode = null;
		while (fast.next != null) {
			fast = fast.next;
			prevNode = slow;
			slow = slow.next;
		}
		if (prevNode == null) {
			node = node.next;
		} else {
			// 刪除
			prevNode.next = prevNode.next.next;
		}
		return node;
	}複製代碼

測試結果:


6.兩個有序的鏈表合併

該實現的思路是,首先比較兩個有序鏈表,若是pNode爲空,直接返回qNode,反之qNode爲空,則返回pNode,而後比較p和q,將最小的節點賦值給resNode,同時將最小的節點向後移動一位。設置一個node指向resNode節點,用於方便鏈接其它節點,在繼續比較p和q,一樣選出小的那個節點,將該節點設爲合併後的鏈表的第二個節點,用node.next表示該節點,一直重複上述過程,直到p和q有一個爲null,而後再將不爲null的節點放入新鏈表後便可。

public Node mergeNode(Node pNode, Node qNode) {
		if (pNode == null)
			return qNode;
		if (qNode == null)
			return pNode;
		Node p = pNode;
		Node q = qNode;
		Node resNode = null;
		if (p.data < q.data) {
			resNode = p;
			p = p.next;
		} else {
			resNode = q;
			q = q.next;
		}
		Node node = resNode;
		while (p != null && q != null) {
			if (p.data < q.data) {
				node.next = p;
				p = p.next;
			} else {
				node.next = q;
				q = q.next;
			}
			node = node.next;
		}
		if (p != null) {
			node.next = p;
		} else {
			node.next = q;
		}
		return resNode;
	}複製代碼

測試結果:


雙向鏈表

雙向鏈表類

package juejin.lc.linkedList;

/**
 * 雙向鏈表類
 */
public class DulNode {
    /**
     * 數據
     */
    public int data;
    /**
     * 前驅節點
     */
    public DulNode prev;
    /**
     * 後繼節點
     */
    public DulNode next;

    private DulNode(){}
    /**
     * 有參構造方法
     *
     * @param data 數據
     */
    public DulNode(int data) {
        this.data = data;
    }
}複製代碼

初始化

private DulNode head;
    private DulNode tail;
	private int count;
    private DulLinkList() {
        head = null;
        tail = null;
        count = 0;
    }複製代碼

添加到鏈表頭部

private void insertToHead(int data) {
        DulNode dulNode = new DulNode(data);
        if (count == 0) {
            head = dulNode;
            tail = dulNode;
        } else {
            DulNode prev = head;
            prev.prev = dulNode;
            dulNode.next = head;
            head = dulNode;
        }
        ++count;
    }複製代碼

添加到鏈表尾部

private void insertToTail(int data) {
        DulNode dulNode = new DulNode(data);
        if (count == 0) {
            head = dulNode;
            tail = dulNode;
        } else {
            DulNode next = tail;
            dulNode.prev = next;
            next.next = dulNode;
            tail = dulNode;
        }
        ++count;
    }複製代碼

添加到鏈表指定位置

private void insertByIndex(int index, int data) {
        if (index > count) {//放入鏈表尾部
            insertToTail(data);
        } else {
            DulNode resNode = selectByIndex(index);
            if (null != resNode) {
                System.out.println("根據index索引所得返回值爲:" + resNode.data);
                DulNode dulNode = new DulNode(data);
                DulNode prev = resNode.prev;
                prev.next = dulNode;
                dulNode.prev = prev;
                dulNode.next = resNode;
                resNode.prev = dulNode;
            }
        }
        ++count;
    }複製代碼

刪除鏈表頭部

private DulNode deleteHead() {
        DulNode p = head;
        if (count > 0) {
            head = head.next;
            head.prev = null;
            --count;
        }
        return p;
    }複製代碼

刪除鏈表尾部

private DulNode deleteTail() {
        DulNode q = tail;
        if (count > 0) {
            tail = tail.prev;
            tail.next = null;
            --count;
        }
        return q;
    }複製代碼

刪除鏈表指定位置

private void deleteByIndex(int index) {
        if (index > count)
            return;
        //刪除這邊加了個小技巧,若是是鏈表頭部或者尾部,直接刪除,中間值開始查找
        if (index == 0) {
            head = head.next;
            head.prev = null;
        } else if (index == count - 1) {
            tail = tail.prev;
            tail.next = null;
        } else {
            DulNode resNode = selectByIndex(index);
            if (null != resNode) {
                System.out.println("要刪除的元素爲:" + resNode.data);
                DulNode prev = resNode.prev;
                DulNode next = resNode.next;
                prev.next = next;
                next.prev = prev;
                resNode.prev = null;
                resNode.next = null;
            }
        }
        --count;
    }複製代碼

根據指定索引獲取節點

private DulNode selectByIndex(int index) {
        if (index > count) return null;
        int num = 0;
        DulNode dulNode = head;
        while (num < index) {
            dulNode = dulNode.next;
            ++num;
        }
        return dulNode;
    }複製代碼

根據指定節點獲取下標

private int indexOf(int data) {
        int num = 0;
        DulNode dulNode = head;
        while (num < count && null != dulNode) {
            if (data == dulNode.data) {
                return num;
            }
            dulNode = dulNode.next;
            ++num;
        }
        return -1;
    }複製代碼

測試代碼:

DulLinkList dulLinkList = new DulLinkList();
        dulLinkList.insertToHead(3);
        dulLinkList.insertToHead(2);
        dulLinkList.insertToHead(1);
        dulLinkList.insertToTail(5);
        dulLinkList.insertToTail(6);
        dulLinkList.insertToTail(7);
        dulLinkList.insertToTail(8);
        dulLinkList.insertToTail(9);
        dulLinkList.printAll();
        System.out.println("刪除頭部,並返回");
        DulNode resHead = dulLinkList.deleteHead();
        System.out.println("返回值:" + resHead.data);
        dulLinkList.insertByIndex(3, 4);
        dulLinkList.printAll();
        System.out.println("刪除尾部,並返回");
        DulNode resTail = dulLinkList.deleteTail();
        System.out.println("返回值:" + resTail.data);
        dulLinkList.printAll();
        int data = 5;
        int res = dulLinkList.indexOf(data);
        System.out.println("數據" + data + "的下標 " + (res != -1 ? res : "不存在"));
        int delData = 5;
        dulLinkList.deleteByIndex(delData);
        dulLinkList.printAll();複製代碼

測試結果:


循環鏈表

初始化

/**
     * 頭結點
     */
    private Node head;
    /**
     * 尾結點
     */
    private Node tail;
    /**
     * 數據數量
     */
    private int count;

    /**
     * 無參構造方法
     */
    private CircleLinkList() {
        head = null;
        tail = null;
        count = 0;
    }複製代碼

插入頭部

private void insertHead(int data) {
        Node node = new Node(data, null);
        if (count == 0) {
            head = node;
            tail = node;
        } else {
            node.next = head;
            head = node;
            tail.next = head;
        }
        ++count;
    }複製代碼

插入尾部

private void insertTail(int data) {
        Node node = new Node(data, null);
        if (count == 0) {
            head = node;
            tail = node;
        } else {
            tail.next = node;
            node.next = head;
            head = node;
        }
        ++count;
    }複製代碼

插入指定位置

private void insertByIndex(int index, int data) {
        if (index > count) {
            insertTail(data);
        } else {
            Node resNode = findByIndex(index);
            if (null != resNode) {
                System.out.println("返回節點的值:" + resNode.data);
                resNode.next = new Node(data, resNode.next);
                ++count;
            }
        }
    }複製代碼

刪除

private void delete(int index) {
        if (index > count) return;
        if (index == 0) {
            Node node = head;
            head = head.next;
            node.next = null;
            tail.next = head;
        } else if (index == count - 1) {
            Node resNode = findByIndex(index);
            if (null != resNode) {
                Node node = tail;
                tail = resNode;
                node.next = null;
                tail.next = head;
            }
        } else {
            Node resNode = findByIndex(index - 1);//查找該節點的前一個節點
            if (null != resNode) {
                System.out.println("返回節點的值:" + resNode.data);
                resNode.next = resNode.next.next;
            }
        }
        --count;
    }複製代碼

按索引查找

private Node findByIndex(int index) {
        if (index > count) return null;
        Node node = head;
        int num = 0;
        while (num < index) {
            node = node.next;
            ++num;
        }
        return node;
    }複製代碼

測試代碼

public static void main(String[] args) {
        CircleLinkList circleLinkList = new CircleLinkList();
        circleLinkList.insertHead(3);
        circleLinkList.insertHead(2);
        circleLinkList.insertHead(1);
        circleLinkList.insertTail(5);
        circleLinkList.insertTail(6);
        circleLinkList.insertTail(7);
        circleLinkList.insertTail(8);
        circleLinkList.insertByIndex(3,4);
        circleLinkList.printAll();
        LinkedListAlgo linkedListAlgo = new LinkedListAlgo();
        boolean result = linkedListAlgo.checkCircle(circleLinkList.head);
        System.out.println("檢測環返回值:" + result);
        circleLinkList.delete(1);
        circleLinkList.printAll();
    }複製代碼

測試結果


end


您的點贊和關注是對我最大的支持,謝謝!
相關文章
相關標籤/搜索