1. 程式人生 > >Java資料結構和演算法(一)線性結構之單鏈表

Java資料結構和演算法(一)線性結構之單鏈表

Java資料結構和演算法(一)線性結構之單鏈表

     prev                 current              next
  --------------      --------------     --------------
 | value | next | -> | value | next | -> | value | next |
  --------------      --------------     --------------

單鏈表的結構如上:最後一個節點的 next=null。下面看一下程式碼。

(1) 連結串列的基本操作

public class Node<E> {

    private E value;
    private Node next;

    public Node(E value) {
        this.value = value;
    }

    // 追加到最後一個元素
    public Node append(Node node) {
        Node tail = tail();
        tail.next(node);
        return this;
    }

    // 刪除指定的節點
    public void remove(Node node) {
        Node prev = prev(node);
        if (prev != null) {
            prev.next = node.next;
        }
    }

    // 節點總數
    public int size() {
        int size = 1;
        Node current = this;
        while (current.next != null) {
            size++;
            current = current.next;
        }
        return size;
    }

    // 查詢指定節點的上一個節點
    public Node prev(Node node) {
        Node prev = this;
        while (prev != null) {
            if (prev.next == node) {
                return prev;
            }
            prev = prev.next;
        }
        return null;
    }

    // 查詢尾節點,單鏈表 tail.next=null
    public Node tail() {
        Node tail = this;
        while (tail.next != null) {
            tail = tail.next;
        }
        return tail;
    }

    // 設定當前節點的下一個節點
    public void next(Node next) {
        // 設定該節點的後繼節點
        next.next = this.next;
        // 將該節點設定為當前節點的前驅節點
        this.next = next;
    }

    public Node next() {
        return next;
    }

    public E getValue() {
        return value;
    }

    public void setValue(E value) {
        this.value = value;
    }
}

(2) 取出中間節點

偶數節點取中間兩個節點的前一個節點,奇數節點取正中間的節點

public Node mid() {
    Node stepOneNode = this;
    Node stepTwoNode = this;
    while (stepTwoNode != null) {
        stepTwoNode = stepTwoNode.next;
        if (stepTwoNode != null) {
            stepTwoNode = stepTwoNode.next;
            if (stepTwoNode != null) {
                stepOneNode = stepOneNode.next;
            }
        }
    }
    return stepOneNode;
}

(3) 連結串列反轉

public Node reverse() {
    Node prev = null;
    Node next = null;
    Node current = this;
    while (current != null) {
        next = current.next;
        current.next = prev;
        prev = current;
        current = next;
    }
    return prev;
}

測試一把:

public void test1() {
    Node n1 = new Node(1);
    Node n2 = new Node(2);
    Node n3 = new Node(3);

    n1.append(n2).append(n3);
    Assert.assertEquals(3, n1.next().next().getValue());
    Assert.assertEquals(3, n1.tail().getValue());
    Assert.assertEquals(2, n1.prev(n3).getValue());
    Assert.assertEquals(3, n1.size());

    n1.remove(n2);
    Assert.assertEquals(3, n1.next().getValue());
    Assert.assertEquals(1, n1.mid().getValue());

    n1.next(n2);
    Assert.assertEquals(3, n1.next().next().getValue());
    Assert.assertEquals(2, n1.mid().getValue());

    Node reverse = n1.reverse();
    Assert.assertEquals(3, reverse.getValue());
    Assert.assertEquals(2, reverse.next().getValue());
    Assert.assertEquals(1, reverse.next().next().getValue());
}

(4) 有序連結串列的合併

兩個有序連結串列合併後還是有序的,程式碼如下:

// 有序連結串列合併,兩個連結串列均升序排列,最終的結果也升序排列
public static Node merge(Node<Integer> node1, Node<Integer> node2) {
    if (node1 == null || node2 == null) {
        return node1 == null ? node2 : node1;
    }

    Node<Integer> head = node1.value < node2.value ? node1 : node2;
    Node<Integer> cur1 = head == node1 ? node1 : node2; // 小
    Node<Integer> cur2 = head == node1 ? node2 : node1; // 大

    Node prev = null; // curl1 的前驅節點,小
    while (cur1 != null && cur2 != null) {
        if (cur1.value < cur2.value) {
            prev = cur1;
            cur1 = cur1.next;
        } else {
            // 將 curl2 插入到 prev 和 curl1 之間
            Node tmp = cur2.next;
            cur2.next = cur1;
            prev.next = cur2;
            prev = cur2;
            cur2 = tmp;
        }
    }
    prev.next = cur1 == null ? cur2 : cur1;
    return head;
}

// 有序連結串列合併,兩個連結串列均升序排列,最終的結果也升序排列
public static Node mergeRecurse(Node<Integer> node1, Node<Integer> node2) {
    if (node1 == null || node2 == null) {
        return node1 != null ? node1 : node2;
    }
    Node head = null;
    if (node1.value > node2.value) {
        head = node2;
        head.next = mergeRecurse(node1, node2.next);
    } else {
        head = node1;
        head.next = mergeRecurse(node1.next, node2);
    }
    return head;
}

測試:

public void mergeTest() {
    Node n1 = new Node(1);
    // 省略...
    Node n6 = new Node(6);

    n1.append(n3).append(n5);
    n2.append(n4).append(n6);

    Node merge1 = Node.merge(n1, n2);
    //Node merge1 = Node.mergeRecurse(n1, n2);
    Assert.assertEquals(6, merge1.size());
    Assert.assertEquals(2, merge1.next().getValue());
}

每天用心記錄一點點。內容也許不重要,但習慣很重要!