是什么让三端队列的Java链表实现变慢?

5hcedyr0  于 2023-09-29  发布在  Java
关注(0)|答案(3)|浏览(88)

我正在解决Kattis问题Teque,并且必须实现一个teque(三端队列),只有四个操作:推到前面、推到后面、推到中间(中间)并在索引处读取项目。这个作业的重点是时间复杂度,所以我使用了一个带尾链表来轻松地在前面和后面添加元素。
对于push-to-middle函数,我没有在列表上调用size(),而是在Teque中存储了一个类变量来跟踪列表中元素的数量。这使得能够非常快速地查找。

class Teque {
    public TailedLinkedList list;
    public Kattio io = new Kattio(System.in, System.out);
    public int numItems;

    public Teque() {
        this.list = new TailedLinkedList();
        this.numItems = 0;
    }

    public void push_back(int x) {
        numItems++;
        list.addBack(x);
    }

    public void push_front(int x) {
        numItems++;
        list.addFront(x);
    }

    public void push_middle(int x) {

        int median = (numItems + 1) / 2;
        list.addAtIndex(median, x);
        numItems++;
    }

    public int get(int i) {
        int result = list.getItemAtIndex(i);
        io.println(result);
        io.flush();
        return result;
    }
}

TailedLinkedList的实现:

import java.util.*;

class TailedLinkedList implements ListInterface {
    // attributes
    public ListNode head;
    public ListNode tail;
    public int num_nodes;

    // interface methods

    // Return true if list is empty; otherwise return false.
    public boolean isEmpty() { return (num_nodes == 0); }

    // Return number of items in list
    public int size() { return num_nodes; }

    // return index of item if item is found in the list, otherwise return -1
    public int indexOf(int item) {
        int index = 0;

        for (ListNode cur = head; cur != null; cur = cur.getNext()) {
            if (cur.getItem() == item) 
                return index;
            else 
                index++;
        }
        return -1;
    }

    // return true if item is in the list false otherwise
    public boolean contains(int item) {
        if (indexOf(item) != -1)
            return true;
        return false;
    }

    // get item at index
    public int getItemAtIndex(int index) {
        int counter = 0;
        int item = 0;

        if (index < 0 || index > size()-1) {
            System.out.println("invalid index");
            System.exit(1);
        }
        if (index == size()-1)
            item = tail.getItem();
        else {
            for (ListNode cur = head; cur != null; cur = cur.getNext()) {
                if (counter == index) {
                    item = cur.getItem();
                    break;
                }
                counter++;
            }
        }
        return item;
    }

    // Return first item
    public int getFirst() { return getItemAtIndex(0); }

    // Return last item
    public int getLast() { return getItemAtIndex(size()-1); }

    // add item at position index, shifting all current items from index onwards to the right by 1 
    // pre: 0 <= index <= size()
    public void  addAtIndex(int index, int item) {
        ListNode cur;
        ListNode newNode = new ListNode(item);

        if (index >= 0 && index <= size()) {
            if (index == 0) // insert in front
                insert(null,newNode);
            else if (index == size()) // insert at the back, don't have to move all the way to the back
                insert(tail,newNode);
            else {
                cur = getNodeAtIndex(index-1); // access node at index-1
                insert(cur,newNode);
            }
        }
        else { // index out of bounds
            System.out.println("invalid index");
            System.exit(1);
        }
    } 

    // Add item to front of list
    public void addFront(int item) { addAtIndex(0,item); }

    // Add item to back of list
    public void addBack(int item) { addAtIndex(size(),item); }

    // remove item at index and return it
    // pre: 0 <= index < size()
    public int removeAtIndex(int index) {
        ListNode cur;
        int item = 0;

        // index within bounds and list is not empty
        if (index >= 0 && index < size() && head != null) {
            if (index == 0) // remove 1st item
                item = remove(null);
            else {
                cur = getNodeAtIndex(index-1); // access node at index-1
                item = remove(cur);
            }
        }
        else { // index out of bounds
            System.out.println("invalid index or list is empty");
            System.exit(1);
        }
        return item;
    }

    // Remove first node of list
    public int removeFront() { return removeAtIndex(0); }

    // Remove last node of list
    public int removeBack() { return removeAtIndex(size()-1); }

    // Print values of nodes in list.
    public void print() {
        if (head == null)
            System.out.println("Nothing to print...");
        else {
            ListNode cur = head;
            System.out.print("List is: " + cur.getItem());
            for (int i=1; i < num_nodes; i++) {
             cur = cur.getNext();
             System.out.print(", " + cur.getItem());
            }
            System.out.println(".");
        }
    }

    // non-interface helper methods
    public ListNode getHead() { return head; }
    public ListNode getTail() { return tail; }

    /* return the ListNode at index */
    public ListNode getNodeAtIndex(int index) {
        int counter = 0;
        ListNode node = null;

        if (index < 0 || index > size()-1) {
            System.out.println("invalid index");
            System.exit(1);
        }
        if (index == size()-1) // return tail if index == size()-1
            return tail;
        for (ListNode cur = head; cur != null; cur = cur.getNext()) {
            if (counter == index) {
                node = cur;
                break;
            }
            counter++;
        }
        return node;
    }

    // insert newNode after the node referenced by cur 
    public void insert(ListNode cur, ListNode n) {
        // insert in front
        if (cur == null) {
            n.setNext(head);
            head = n; // update head
            if (tail == null) // update tail if list originally empty
                tail = head;
        }
        else { // insert anywhere else
            n.setNext(cur.getNext());
            cur.setNext(n);
            if (cur == tail) // update tail if inserted new last item
                tail = tail.getNext();
        }
        num_nodes++;
    }

    // remove the node referenced by cur.next, and return the item in the node 
    // if cur == null, remove the first node 
    public int remove(ListNode cur) {
        int value;

        if (cur == null) { // remove 1st node
            value = head.getItem();
            head = head.getNext(); // update head
            if (num_nodes == 1) // update tail to null if only item in list is removed
                tail = null;
        }
        else { // remove any other node
            value = cur.getNext().getItem();
            cur.setNext(cur.getNext().getNext());
            if (cur.getNext() == null) // update tail if last item is removed
                tail = cur;
        }
        num_nodes--;

        return value;
    }
}

然而,我的代码仍然超时(100 k行输入,限制2秒)。有没有什么地方可以立即明显地改进我的算法?时间复杂度为O(n)。

8xiog9wr

8xiog9wr1#

push_midle导致遍历列表的一半。一个解决方案是使用两个列表,前半部分和后半部分。如果列表变得不平衡,则在两个列表之间移动元素。
(It ListIterator可能也会这样做;尽管对于一种幼稚的方法,必须担心ConcurrentModificationException。)

jmo0nnb3

jmo0nnb32#

对Joop埃根的解决方案的修改是使用2个数组(前数组用于快速pushFront,后数组用于快速pushBack)。

  • frontIndex = front.length / 2和front.length - 1之间的任何值
  • frontMidIndex = frontIndex - 1
  • backIndex = 0到back.length / 2之间的任何值
  • backMidIndex = backIndex + 1

有效内容:front[frontIndex.. frontMidIndex[ + back[backMidIndex .. backIndex[
pushFront:

  • frontIndex > 0?
  • 在frontIndex处设置,递减frontIndex
  • else frontMidIndex < front.length
  • 将front数组向后移动x,将frontIndex和frontMidIndex递增x,设置同上
  • 其他
  • 要么复制一些部分到后面,要么增长数组,然后如上所述

pushBack:

  • 对于back、backIndex(< back.length)、backMidIndex(> 0)、切入/减量和移位方向也是如此

pushMid(这需要更多的思考):

  • 最佳的是,前部和后部的有效长度相等或相差一。然后,这将成为frontMidIndex或backMidIndex处的集合(与上面的逻辑类似)
  • 如果mid福尔斯在两个数组之一的有效范围的中间,则可以
  • 移动这个数组中的内容来创建元素的位置。
  • 或者在两个数组之间移动内容以调整midIndexes以进一步pushMid。
js4nwp54

js4nwp543#

也许现在回答你的问题已经太晚了,但对那些正在与这个卡提斯问题作斗争的人来说,这是一个提示;
从我对数组(列表,队列,堆栈......)之间操作的理解来看,如果我们只使用类似数组的对象,那么很难保存添加/删除元素的时间,同时仍然有O(1)的时间复杂度来访问随机索引的元素。因此,我使用hashmap来达到O(1)的添加和访问复杂度。加上Joop埃根的想法,问题就可以解决了。

相关问题