簡體   English   中英

LinkedList 新的反向算法

[英]LinkedList new reverse algorithm

我創建了一個用於反轉鏈接列表的算法,如下所述。 有人可以告訴我它是否有效。 它需要 O(n) 時間。

private void insertBegining(T data) {
        Node<T> newNode = new Node<T>(data);
        newNode.setNextNode(root);
        root = newNode;
}

private void reverseList(){
        Node<T> curr = root;
        while(curr.getNextNode() != null){
            insertBegining(curr.getNextNode().getData());
            curr.setNextNode(curr.getNextNode().getNextNode());
        }
    }

您不需要創建新節點,只需重用現有節點來更改下一個字段,相同的復雜度 O(n) 但堆使用量更少。

private void reverseList(){
    Node<T> reversed=null;    
    while(root != null){
        Node<T> next=root.getNextNode();
        root.setNextNode(reversed);
        reversed=root;
        root=next;
    }
    root=reversed;
}

有人可以告訴我它是否有效。

這是令人難以置信的低效。 沒有解決這個問題; 鏈表本質上就是這樣。 如果您喜歡效率,請不要在代碼中使用它們。

有兩種不同的“效率”:學術/算法(通常以大 O 表示法描述)和實用效率:在實際現實生活中現代常用硬件(例如 ARM 和 i86)上實際需要多長時間/64 架構芯片在 windows、linux 和 macos 上。

如果您想在算法上比 O(1) 更快地反轉 LinkedList,您唯一的選擇是使用原始表單。 例如,如果您有一個雙向鏈表,其中每個節點不僅知道它后面的節點,而且還知道它之前的節點,那么反轉一個列表可能是一個 O(1) 操作:創建一個從末尾開始的包裝器,並通過實際調用“getPrevious()”方法來實現“下一步”的任何嘗試。 但是,這一切都要求您從一個雙向鏈表開始。 如果你只是沒有它,那么顯然不可能在不迭代一次的情況下反轉列表,這注定了你的 O(n) 或更差的性能。

從實用的角度來看,鏈表如此糟糕(這使得情況變得更糟)的原因尤其是緩存問題

現代 CPU 設計使用片上緩存的分層層。 CPU 不再在主 memory 上運行,因為主memory太慢了; CPU 可以處理 500 個或更多周期的指令(通常,由於 CPU 有管道,因此它們中的一些或多或少是並行的,因此它可以在這 500 個周期內完成大量工作),就在從 memory 獲取一些數據所需的時間。

解決方案是,現在的 CPU 甚至根本無法訪問 memory。 相反,CPU 僅在加載到 CPU 緩存中的 memory 頁面上運行。 If the CPU needs to act on data that isn't loaded in a page that is in cache, then the CPU tells the memory controller to go fetch it, and will then go to sleep or do other work. 當 CPU 完成對它的操作時,緩存頁面最終會被 controller '保存'回實際的 memory。

每當 CPU 內核需要在 memory 上運行時,該內核未加載到稱為緩存未命中的緩存頁面中。 這些非常昂貴(我提到的那些 500 多個周期)。

鏈表的問題,至少如上所述,是碎片問題:您不僅擁有存儲在鏈表中的對象(例如,它是字符串的鏈表 - 那些字符串),您還擁有這些節點對象。

字符串和節點對象在 memory 中的位置至關重要。

最好的情況是,如果所有這些節點對象都存儲在 memory 的連續塊中(彼此相鄰,有序)。 這樣,如果您只是遍歷一個列表以例如找出它有多大,在 memory 中,您將獲得最少的未命中數(您將處理整個緩存頁面的節點對象,然后繼續下一頁)。 但是,您通常還與這些節點指向的對象進行交互,並且通常字符串位於不同的位置。

最壞的可能情況是如果節點分散在 memory 中,導致每次迭代時緩存未命中。 通常節點和它們包含的數據混合在一起,這是不好的,特別是如果包含的數據很大。

這就是為什么基於節點的鏈表本質上是低效的。 如果您自己存儲的對象包含下一個/上一個指針,效率會稍微高一些,但是 java 並沒有使這變得簡單,而且在設計方面它很煩人(它混淆了想法,意味着 object 只能存在於一個鏈表中Java 不允許您在運行中創建混合在nextprev字段中的對象的替代定義)。

ArrayList 是您通常想要的。

您不需要創建新節點,只需通過更改下一個指針的方向來重用現有節點,下面是具有相同復雜度 O(n) 但堆使用量較少的代碼。 它使用 3 個指針的概念來反轉列表。

private void reverseList(){
    if (head == null) {
        throw new EmptyListException(EMPTY_LIST);
    } else if (head.next == null) {
        return;
    }
    Node<T> nextNode;
    Node<T> node = head;
    Node<T> prevNode = null;
    while (node != null) {
        nextNode = node.next;
        node.next = prevNode;
        prevNode = node;
        node = nextNode;
    }
    head = prevNode;
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM