简体   繁体   English

LinkedList 新的反向算法

[英]LinkedList new reverse algorithm

I created an algorithm for reversing a linked list which is mentioned below.我创建了一个用于反转链接列表的算法,如下所述。 Can someone tells me if its efficient or not.有人可以告诉我它是否有效。 It is taking O(n) time tho.它需要 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());
        }
    }

You don't need to create new nodes, just reuse the existing ones changing the next field, same complexity O(n) but less heap usage.您不需要创建新节点,只需重用现有节点来更改下一个字段,相同的复杂度 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;
}

Can someone tells me if its efficient or not.有人可以告诉我它是否有效。

It is incredibly inefficient.这是令人难以置信的低效。 There's no fixing this;没有解决这个问题; linked lists just are, by nature.链表本质上就是这样。 Don't use them in your code if you like efficiency.如果您喜欢效率,请不要在代码中使用它们。

There are two different 'kinds' of efficiency: Academic/Algorithmic (described, generally, in big-O notation), and pragmatic efficiency: How long does it actually take, on actual real life modern commonly employed hardware, such as ARM and i86/64 architecture chips on windows, linux, and macos.有两种不同的“效率”:学术/算法(通常以大 O 表示法描述)和实用效率:在实际现实生活中现代常用硬件(例如 ARM 和 i86)上实际需要多长时间/64 架构芯片在 windows、linux 和 macos 上。

If you want to make reversing a LinkedList algorithmically faster than O(1), your only option is to work on the original form.如果您想在算法上比 O(1) 更快地反转 LinkedList,您唯一的选择是使用原始表单。 For example, if you have a doubly-linked list, where each node is not just aware of the node that follows it, but also aware of the node that precedes it, then reversing a list can be an O(1) operation: Just create a wrapper that starts at the end and implements any attempt to 'go next' by actually invoking the 'getPrevious()' method.例如,如果您有一个双向链表,其中每个节点不仅知道它后面的节点,而且还知道它之前的节点,那么反转一个列表可能是一个 O(1) 操作:创建一个从末尾开始的包装器,并通过实际调用“getPrevious()”方法来实现“下一步”的任何尝试。 But, this all demands that you have a doubly-linked list to start with.但是,这一切都要求您从一个双向链表开始。 If you just do not have it, then it is obviously impossible to reverse the list without iterating through it once, which dooms you to O(n) or worse performance.如果你只是没有它,那么显然不可能在不迭代一次的情况下反转列表,这注定了你的 O(n) 或更差的性能。

The reason that linked lists are so, so bad (and this makes it considerably worse) in pragmatic terms is in particular the cache issue .从实用的角度来看,链表如此糟糕(这使得情况变得更糟)的原因尤其是缓存问题

Modern CPU design uses hierarchical layers of on-chip cache.现代 CPU 设计使用片上缓存的分层层。 The CPUs no longer operate on main memory, because main memory is waaay too slow; CPU 不再在主 memory 上运行,因为主memory太慢了; the CPU can process 500 cycles worth of instructions or more (and, generally, a bunch of them more or less in parallel because CPUs have pipelines, so it could do a heck of a lot of work in those 500 cycles), just in the time it takes to fetch some data from memory. CPU 可以处理 500 个或更多周期的指令(通常,由于 CPU 有管道,因此它们中的一些或多或少是并行的,因此它可以在这 500 个周期内完成大量工作),就在从 memory 获取一些数据所需的时间。

The solution is that nowadays CPUs can't even access memory anymore, at all.解决方案是,现在的 CPU 甚至根本无法访问 memory。 Instead, the CPU only operates on a page of memory loaded in a CPU cache.相反,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. 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. A cache page is eventually 'saved' back into actual memory later by the controller when the CPU is done operating on it.当 CPU 完成对它的操作时,缓存页面最终会被 controller '保存'回实际的 memory。

Whenever a CPU core needs to operate on memory that isn't loaded in a cache page that's called a cache miss .每当 CPU 内核需要在 memory 上运行时,该内核未加载到称为缓存未命中的缓存页面中。 These are incredibly expensive (those 500+ cycles I mentioned).这些非常昂贵(我提到的那些 500 多个周期)。

The problem with linked lists, at least as implemented above, is the problem of fragmentation: You have not just the objects stored in your linked list (say, it's a linked list of strings - those strings), you also have these node objects.链表的问题,至少如上所述,是碎片问题:您不仅拥有存储在链表中的对象(例如,它是字符串的链表 - 那些字符串),您还拥有这些节点对象。

The locations in memory of both the strings and the node objects are crucial.字符串和节点对象在 memory 中的位置至关重要。

The best possible situation is if all these node objects are all stored in a contiguous block of memory (all next to each other, nicely ordered).最好的情况是,如果所有这些节点对象都存储在 memory 的连续块中(彼此相邻,有序)。 This way, if you are eg just iterating through a list to eg figure out how large it is, in memory you get the minimum amount of misses (you'd process an entire cache-page's worth of node objects and then move on to the next page).这样,如果您只是遍历一个列表以例如找出它有多大,在 memory 中,您将获得最少的未命中数(您将处理整个缓存页面的节点对象,然后继续下一页)。 However, often you also interact with the objects these nodes are pointing at, and generally the strings are in a different place.但是,您通常还与这些节点指向的对象进行交互,并且通常字符串位于不同的位置。

The worst possible situation is if the nodes are scattered throughout memory, causing a cache miss on every iteration.最坏的可能情况是如果节点分散在 memory 中,导致每次迭代时缓存未命中。 Often nodes and the data they contain are intermixed which is not good, especially if the data contained is large.通常节点和它们包含的数据混合在一起,这是不好的,特别是如果包含的数据很大。

That's why node-based linked lists are inherently inefficient.这就是为什么基于节点的链表本质上是低效的。 It'd be slightly more efficient if the objects you are storing themselves contain the next/prev pointers, but java doesn't make this easy and design-wise it's annoying (it conflates ideas and means an object can only exist in one linked list at a time. Java doesn't allow you to create on-the-fly alternate definitions of objects that have mixed in a next and prev field).如果您自己存储的对象包含下一个/上一个指针,效率会稍微高一些,但是 java 并没有使这变得简单,而且在设计方面它很烦人(它混淆了想法,意味着 object 只能存在于一个链表中Java 不允许您在运行中创建混合在nextprev字段中的对象的替代定义)。

ArrayList is what you generally want. ArrayList 是您通常想要的。

You don't need to create new nodes, just reuse the existing ones by changing the direction of next pointer, below is the code with same complexity O(n) but less heap usage.您不需要创建新节点,只需通过更改下一个指针的方向来重用现有节点,下面是具有相同复杂度 O(n) 但堆使用量较少的代码。 It uses the concept of 3 pointers to reverse a list.它使用 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