資料結構學習(C++)——迴圈連結串列 (轉)

amyz發表於2007-11-07
資料結構學習(C++)——迴圈連結串列 (轉)[@more@]

原書對迴圈連結串列的介紹很簡略,實現部分也不完整(當然了,如果完整就又是重複建設)。而我也沒覺得迴圈連結串列有什麼別的用,他更應該是為了一個特殊的問題而產生的,這只是個人的看法。我從連結串列類派生出了迴圈連結串列,這需要注意幾個細節。:namespace prefix = o ns = "urn:schemas--com::office" />

1.  構造:派生類例項化時,先基類的建構函式;因此,初始化迴圈連結串列的工作就是將帶表頭的空連結串列的表頭節點的link指向表頭節點,從而構成一個圈。

2.  解構函式:釋放時,先呼叫派生類的解構函式,然後呼叫基類的解構函式。因此,釋放迴圈連結串列只需要將迴圈連結串列變成普通的單連結串列,然後這個單連結串列會被基類的解構函式釋放。這裡假定不使用這種語句Base *p = new Drived;delete p;因為我在~List()前面沒有加virtual。你可以參閱各種C++書籍搞清這類問題。

3.  判空函式:條件不是檢測頭節點的link是否為空,而是是否指向頭節點。

4.  置空函式:原來的顯然不能工作了,實際上只要從表頭位置不斷後刪直到表空就可以了。

5.  Next():遇到表頭節點要跳過去。

6.  Remove():當前節點是表頭節點時不能刪,刪了表頭節點的後果自己想吧(因為在迴圈連結串列中prior指標不一定為空——其實應該是一定不空,但是由於繼承部分List函式,所以就是不一定了,以至於原來的Remove()檢查可能無效);如果刪除的是表尾節點,刪除後當前指標將指向表頭節點,要跳過去指向下一個。總之,使用Next()和Remove()時,不能讓外界覺察到表頭節點的存在,否則,當你迴圈計數時,表頭節點就被算進去了。

7.  “<

8.  End()將不能工作,考慮到如果按照原來的功能來實現,很低,而且用處不大,所以修改End()功能定義為更正last指標。為避免混淆,將其放在private,對外不提供這個功能。

定義和實現

#ifndef CircList_H

#define CircList_H

 

include “List.h”

 

template class CircList : public List

{

public:

  CircList() { pGetFirst()->link = pGetFirst(); }

  ~CircList() { End(); pGetLast()->link = NULL; }

  BOOL IsEmpty()

  {

    Node *p = pGetFirst();

  return p->link == p;

  }

 

  Type *Next()

  {

  if (pNext() == pGetFirst()) pNext();

  return Get();

  }

 

  BOOL Remove()

  {

    if(!IsEmpty())

  {

  if (pGet() == pGetFirst()) return FALSE;

    List::Remove();

  if (pGet() == pGetFirst()) pNext();

    return TURE;

  }

  return FALSE;

  }

 

 

  void MakeEmpty()

  {

    First();

  while (!IsEmpty()) RemoveAfter();

  }

 

  void LastInsert(const Type &value)

  {

  End();

    List::LastInsert(value);

  }

private:

  void End()

  {

  if (pGetLast()->link != pGetFirst())

  {

    Node *pfirst = pGetFirst();

    for (Node *p = pGet(); p->link != pfirst; p = pNext());

    PutLast(p);

  }

  }

 

friend ostream & operator << (ostream & strm, CircList &cl)

{

    cl.First();

    Node *pfirst = cl.pGetFirst();

  while (cl.pGet()->link != pfirst) st<< *cl.Next() << " " ;

  strm << endl;

    cl.First();

  return strm;

}

 

};

 

#endif

【說明】為了後面的約瑟夫問題,我新增了LastInsert。如果使用Insert是倒插,當然可以倒輸入來解決,但這樣的做法有將就的嫌疑,而不斷的Locate顯然效率太低。顯然,Find,Loacte,Length這類繼承過來的函式,執行起來會發生意想不到的事,我沒有在這裡給出重新的實現出於以下原因:

  • Find可以把原來的程式碼拷過來修改一下迴圈判定,但也可以不改。方法是,採用後面介紹的查詢表的辦法,在表頭節點放查詢值,這樣就一定會查詢成功了。然後檢查當前節點是否為頭節點就可以判斷是否真正查詢成功,如果你自己完成這個函式,將會有很多收穫。給個例子:

BOOL Find(const Type &value)

  {

    pGetFirst()->data = value;

    List::Find(value);

  if (pGet() == pGetFirst()) return FALSE;

  return TURE;

  }

  • Locate原來的實現在這裡其實也沒什麼語義的毛病,無非是轉圈嗎,當然怎麼改隨你。建議配合Length檢查定位值的合法,這樣可以把轉圈提前扼殺。
  • Length你說迴圈連結串列有多長?就像無論多長的長跑比賽都可以在400米的跑道進行一樣。這裡建議增加一個私有資料成員length,在每次插入刪除時調整,因為改動的地方比較多,所以我就偷懶了,主要是覺得很少用。

 

 

約瑟夫問題

幾乎提到迴圈連結串列總要提到約瑟夫問題,而我當年還在學BASIC時,就告訴我解決這個問題要構造一個迴圈連結串列,當然了,在BASIC裡是靜態連結串列。真的好像迴圈連結串列就是為了這個問題而存在的。為了照顧沒聽說過的人,我簡單介紹一下這個問題:

說是一個旅行社要從n名旅客中選出一名幸運旅客,為他提供免費環球旅行服務。方法是,大家站成圈,然後選定一個m,從第1個人開始報數,報到m時,這個人OUT,然後從下一個人開始重新從1報數,重複這個過程,直到最後剩下一個人就是幸運之星。問題就是誰是幸運者呢?或者說是怎樣才能贏大獎。

我用這個問題測試了整數情況下迴圈連結串列各個成員函式的正確性,相應函式如下:

void CircListTest_int()

{

  CircList a;

  cout << endl << "整型迴圈連結串列測試:求解約瑟夫問題" << endl;

  int n, m;//n是總人數,m是報數值

  cout << "輸入總人數:";

  cin >> n;

  cout << "輸入報數值:";

  cin >> m;

  for (int i = 1; i <= n; i++) a.LastInsert(i);

  cout << a;

  a.Locate(0);

  for (i = 0; i < n - 1; i++)

  {

  for (int j = 0; j < m - 1; j++) a.Next();

  cout << "第" << *a.Get() << "位旅客被淘汰" << endl;

    a.Remove();

  }

  cout << "第" << *a.Get() << "位旅客獲勝" << endl;

  cout << a;

  a.MakeEmpty();

  cout << a;

 

}

【後記】就是做了迴圈連結串列這個派生類,我才發現了原來寫的連結串列類的許多隱患,比如原來的Find,在整數連結串列裡當你尋找0時,無一例外的會停在表頭,所以你現在看到的連結串列類已經和我當初寫的有很大的變化了,也可能有些我還沒有發現,歡迎指正。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752019/viewspace-981154/,如需轉載,請註明出處,否則將追究法律責任。

相關文章