java數據結構之鏈表

轉:http://www.cnblogs.com/wing011203/archive/2013/04/09/3010985.html
在面試過程中,數據結構和算法基本上算是研發類崗位必考的部分,而鏈表基本上又是數據結構中相對容易掌握、而且容易出題的部分,因此我們先整理一下鏈表部分的經典題目。

(聲明:以下所有程序都是用java編寫)

首先,我們來定義一個鏈表的數據結構,如下:

public class Link {
    private int value;
    private Link next;
    public void set_Value(int m_Value) {
        this.value = m_Value;
    }
    public int get_Value() {
        return value;
    }
    public void set_Next(Link m_Next) {
        this.next = m_Next;
    }
    public Link get_Next() {
        return next;
    }
}

有了這個數據結構後,我們需要一個方法來生成和輸出鏈表,其中鏈表中每個元素的值採用的是隨機數。

生成鏈表的代碼如下:

public static Link init(int count, int maxValue)
    {
        Link list = new Link();
        Link temp = list;
        Random r = new Random();
        temp.set_Value(Integer.MIN_VALUE);
        for (int i = 0; i < count; i++)
        {
            Link node = new Link();
            node.set_Value(r.nextInt(maxValue));
            temp.set_Next(node);
            temp=node;
        }
        temp.set_Next(null);
        return list;
    }

    public static Link init(int count)
    {
        return init(count, Integer.MAX_VALUE);
    }

對於鏈表的頭結點,我們是不存儲任何信息的,因此將其值設置爲Integer.MIN_VALUE。我們重載了生成鏈表的方法。

下面是打印鏈表信息的方法:

public static void printList(Link list)
    {
        if (list == null || list.get_Next() == null)
        {
            System.out.println("The list is null or empty.");
            return;
        }
        Link temp = list.get_Next();
        StringBuffer sb = new StringBuffer();
        while(temp != null)
        {
            sb.append(temp.get_Value() + "->");
            temp=temp.get_Next();
        }
        System.out.println(sb.substring(0, sb.length() - 2));
    }

好了,有了以上這些基礎的方法, 我們就可以深入探討鏈表相關的面試題了。

  • 鏈表反轉

思路:有兩種方法可以實現鏈表反轉,第一種是直接循環每個元素,修改它的Next屬性;另一種是採取遞歸的方式。
首先來看直接循環的方式:

public static Link Reverve(Link list)
    {
        if (list == null || list.get_Next() == null || list.get_Next().get_Next() == null)
        {
            System.out.println("list is null or just contains 1 element, so do not need to reverve.");
            return list;
        }        
        Link current = list.get_Next();
        Link next = current.get_Next();
        current.set_Next(null);
        while(next != null)
        {
            Link temp = next.get_Next();
            next.set_Next(current);
            current = next;
            next = temp;
        }
        list.set_Next(current);

        return list;
    }

然後是遞歸方式:

public static Link RecursiveReverse(Link list)
    {
        if (list == null || list.get_Next() == null || list.get_Next().get_Next() == null)
        {
            System.out.println("list is null or just contains 1 element, so do not need to reverve.");
            return list;
        }

        list.set_Next(Recursive(list.get_Next()));

        return list;
    }


    private static Link Recursive(Link list)
    {
        if (list.get_Next() == null)
        {
            return list;
        }
        Link temp = Recursive(list.get_Next());
        list.get_Next().set_Next(list);
        list.set_Next(null);

        return temp;
  • 輸出指定位置的元素(倒數第N個元素)

思路:採用兩個遊標來遍歷鏈表,第1個遊標先走N步,然後兩個遊標同時前進,當第一個遊標到最後時,第二個遊標就是想要的元素。

public static Link find(Link list, int rPos)
    {
        if (list == null || list.get_Next() == null)
        {
            return null;
        }
        int i = 1;
        Link first = list.get_Next();
        Link second = list.get_Next();
        while(true)
        {
            if (i==rPos || first == null) break;
            first = first.get_Next();
            i++;
        }
        if (first == null)
        {
            System.out.println("The length of list is less than " + rPos + ".");
            return null;
        }
        while(first.get_Next() != null)
        {
            first = first.get_Next();
            second = second.get_Next();
        }

        return second;
    }
  • 刪除指定節點

思路:可以分情況討論,如果指定節點不是尾節點,那麼可以採用取巧的方式,將指定節點的值修改爲下一個節點的值,將指定節點的Next屬性設置爲Next.Next;但如果指定節點爲尾節點,那麼只能是從頭開始遍歷。

public static void delete(Link list, Link element)
    {
        if (element.get_Next() != null)
        {
            element.set_Value(element.get_Next().get_Value());
            element.set_Next(element.get_Next().get_Next());
        }
        else
        {
            Link current = list.get_Next();
            while(current.get_Next() != element)
            {
                current = current.get_Next();
            }
            current.set_Next(null);
        }
    }
  • 刪除重複節點

思路:採用hashtable來存取鏈表中的元素,遍歷鏈表,當指定節點的元素在hashtable中已經存在,那麼刪除該節點。

public static void removeDuplicate(Link list)
    {
        if (list == null || list.get_Next() == null || list.get_Next().get_Next() == null) return;
        Hashtable table = new Hashtable();
        Link cur = list.get_Next();
        Link next = cur.get_Next();
        table.put(cur.get_Value(), 1);
        while(next != null)
        {
            if (table.containsKey(next.get_Value()))
            {
                cur.set_Next(next.get_Next());
                next = next.get_Next();                 
            }
            else
            {
                table.put(next.get_Value(), 1);
                cur= next;
                next = next.get_Next();
            }

        }        
    }
  • 尋找鏈表中間節點

思路:採用兩個遊標的方式,第一個遊標每次前進兩步,第二個遊標每次前進一步,當第一個遊標到最後時,第二個遊標就是中間位置。需要注意的是,如果鏈表元素的個數是偶數,那麼中間元素應該是兩個。

public static void findMiddleElement(Link list)
    {
        if (list == null || list.get_Next() == null) return;
        System.out.println("The Middle element is:");
        if (list.get_Next().get_Next() == null)
        {
            System.out.println(list.get_Next().get_Value());
        }
        Link fast = list.get_Next();
        Link slow = list.get_Next();
        while(fast.get_Next() != null && fast.get_Next().get_Next() != null)
        {
            fast = fast.get_Next().get_Next();
            slow = slow.get_Next();
        }

        if (fast != null && fast.get_Next() == null)
        {
            System.out.println(slow.get_Value());
        }
        else
        {
            System.out.println(slow.get_Value());
            System.out.println(slow.get_Next().get_Value());
        }
    }
  • 鏈表元素排序

思路:鏈表元素排序,有兩種方式,一種是鏈表元素本身的排序,一種是鏈表元素值得排序。第二種方式更簡單、靈活一些。

public static void Sort(Link list)
    {
        if (list == null || list.get_Next() == null || list.get_Next().get_Next() == null)
        {
            return;
        }
        Link current = list.get_Next();
        Link next = current.get_Next();
        while(current.get_Next() != null)
        {
            while(next != null)
            {
                if (current.get_Value() > next.get_Value())
                {
                    int temp = current.get_Value();
                    current.set_Value(next.get_Value());
                    next.set_Value(temp);
                }
                next = next.get_Next();
            }
            current = current.get_Next();
            next = current.get_Next();
        }
    }
  • 判斷鏈表是否有環,如果有,找出環上的第一個節點

思路:可以採用兩個遊標的方式判斷鏈表是否有環,一個遊標跑得快,一個遊標跑得慢。當跑得快的遊標追上跑得慢的遊標時,說明有環;當跑得快的遊標跑到尾節點時,說明無環。
至於如何找出換上第一個節點,可以分兩步,首先確定環上的某個節點,計算頭結點到該節點的距離以及該節點在環上循環一次的距離,然後建立兩個遊標,分別指向頭結點和環上的節點,並將距離平攤(哪個距離大,先移動哪個遊標,直至兩個距離相等),最後同時移動兩個遊標,碰到的第一個相同元素,就是環中的第一個節點。

public static Link getLoopStartNode(Link list)
    {
        if (list == null || list.get_Next() == null || list.get_Next().get_Next() == null)
        {
            return null;
        }
        int m = 1, n = 1;
        Link fast = list.get_Next();
        Link slow = list.get_Next();
        while(fast != null && fast.get_Next() != null)
        {
            fast = fast.get_Next().get_Next();
            slow = slow.get_Next();
            if (fast == slow) break;
            m++;
        }
        if (fast != slow)
        {
            return null;
        }
        Link temp = fast;
        while(temp.get_Next() != fast)
        {
            temp = temp.get_Next();
            n++;
        }
        Link node1 = list.get_Next();
        Link node2 = fast;
        if (m < n)
        {
            for (int i = 0; i < n - m; i++)
            {
                node2 = node2.get_Next();
            }
        }
        if (m > n)
        {
            for (int i = 0; i < m - n; i++)
            {
                node1 = node1.get_Next();
            }
        }
        while(true)
        {
            if (node1 == node2)
            {
                break;
            }
            node1 = node1.get_Next();
            node2 = node2.get_Next();
        }

        return node1;

    }
  • 判斷兩個鏈表是否相交

思路:判斷兩個鏈表的尾節點是否相同,如果相同,一定相交

public static boolean isJoint(Link list1, Link list2)
    {
        if (list1 == null || list2 == null || list1.get_Next() == null || list2.get_Next() == null)
        {
            return false;
        }
        Link node1 = list1;
        Link node2 = list2;
        while(node1.get_Next() != null)
        {
            node1 = node1.get_Next();
        }
        while(node2.get_Next() != null)
        {
            node2 = node2.get_Next();
        }

        return node1 == node2;
    }
  • 合併兩個有序鏈表

思路:新建一個鏈表,然後同時遍歷兩個有序鏈表,比較其大小,將元素較小的鏈表向前移動,直至某一個鏈表元素爲空。然後將非空鏈表上的所有元素追加到新建鏈表中。

public static Link merge(Link list1, Link list2)
    {
        Link list = new Link();
        list.set_Value(Integer.MIN_VALUE);
        Link current1 = list1.get_Next();
        Link current2 = list2.get_Next();
        Link current = list;
        while(current1 != null && current2 != null)
        {
            Link temp = new Link();
            if (current1.get_Value() > current2.get_Value())
            {
                temp.set_Value(current2.get_Value());
                current2 = current2.get_Next();
            }
            else
            {
                temp.set_Value(current1.get_Value());
                current1 = current1.get_Next();
            }
            current.set_Next(temp);
            current = temp;
        }
        if (current1 != null)
        {
            while(current1 != null)
            {
                Link temp = new Link();
                temp.set_Value(current1.get_Value());
                current.set_Next(temp);
                current = temp;
                current1 = current1.get_Next();
            }
        }

        if (current2 != null)
        {
            while(current2 != null)
            {
                Link temp = new Link();
                temp.set_Value(current2.get_Value());
                current.set_Next(temp);
                current = temp;
                current2 = current2.get_Next();
            }
        }

        current.set_Next(null);

        return list;
    }
  • 交換鏈表中任意兩個元素(非頭結點)

思路:首先需要保存兩個元素的pre節點和next節點,然後分別對pre節點和next節點的Next屬性重新賦值。需要注意的是,當兩個元素師相鄰元素時,需要特殊處理,否則會將鏈表陷入死循環。

public static void swap(Link list, Link element1, Link element2)
    {
        if (list == null || list.get_Next() == null || list.get_Next().get_Next() == null || 
                element1 == null || element2 == null || element1 == element2) 
            return;

        Link pre1 = null, pre2 = null, next1 = null, next2 = null;
        Link cur1=element1, cur2=element2;
        Link temp = list.get_Next();
        boolean bFound1 = false;
        boolean bFound2 = false;
        while(temp != null)
        {
            if(temp.get_Next() == cur1)
            {
                pre1=temp;
                next1 = temp.get_Next().get_Next();
                bFound1 = true;
            }
            if (temp.get_Next() == cur2)
            {
                pre2 = temp;
                next2 = temp.get_Next().get_Next();
                bFound2=true;
            }
            if (bFound1 && bFound2) break;
            temp = temp.get_Next();
        }

        if (cur1.get_Next() == cur2)
        {
            temp = cur2.get_Next();
            pre1.set_Next(cur2);
            cur2.set_Next(cur1);
            cur1.set_Next(temp);
        }
        else if (cur2.get_Next() == cur1)
        {
            temp = cur1.get_Next();
            pre2.set_Next(cur1);
            cur1.set_Next(cur2);
            cur2.set_Next(temp);
        }
        else
        {
            pre1.set_Next(cur2);
            cur1.set_Next(next2);
            pre2.set_Next(cur1);
            cur2.set_Next(next1);
        }
    }

這裏,還有另外一種取巧的方法,就是直接交換兩個元素的值,而不需要修改引用。

public static void swapValue(Link list, Link element1, Link element2)
    {
        if (element1 == null || element2 == null)
        {
            return;
        }
        int temp = element1.get_Value();
        element1.set_Value(element2.get_Value());
        element2.set_Value(temp);
    }

不過,這種方式,應該不是面試官所希望看到的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章