1. 程式人生 > >十四、循環鏈表的實現

十四、循環鏈表的實現

但是 this 內部 his 多少 next find函數 ins code

1、循環鏈表簡介

概念上:

  • 任意數據元素都有一個前驅和一個後繼
  • 所有的數據元素的關系構成一個邏輯上的環

實現上:

  • 循環鏈表是一種特殊的單鏈表
  • 尾結點的指針域保存了首結點的地址

技術分享圖片

循環鏈表的繼承層次結構

技術分享圖片

2、循環鏈表的實現思路

  • 通過模板定義CircleList類,繼承自LinkList
  • 定義內部函數last_to_first(),用於將單鏈表首尾相連
  • 特殊處理:首元素的插入操作和刪除操作
  • 重新實現:清空操作和遍歷操作

循環鏈表的實現要點:

  • 插入位置為0時:插入的結點成為首結點
    • 頭結點和尾結點均指向新結點
    • 新節點成為首結點插入鏈表
  • 刪除位置為0時:
    • 頭結點和尾結點指向位置為1的結點
    • 安全銷毀首結點

3、循環鏈表的具體實現

#ifndef CIRCLELIST_H
#define CIRCLELIST_H

#include "LinkList.h"


namespace DTLib
{
template <typename T>
class CircleList : public LinkList<T>
{
protected:
    typedef typename LinkList<T>::Node Node;

    int mod(int i) const
    {
        return (this->m_length == 0)? 0 : (i % this->m_length);
    }

    Node* last() const    // 獲得指向最後一個結點的指針
    {
        return this->position(this->m_length-1)->next;
    }
    void last_to_first()
    {
        last()->next = this->m_header.next;
    }
public:
    bool insert(const T& e)
    {
        return insert(this->m_length, e);
    }
    bool insert(int i, const T& e)
    {
        bool ret = true;

        i = i % (this->m_length + 1);   // 對i進行歸一化處理
        ret = LinkList<T>::insert(i, e);   // 用父類的insert函數來實現

        // 註意首尾相連
        if(ret && ( i == 0))
        {
            last_to_first();
        }


        return ret;
    }

    bool remove(int i)
    {
        bool ret = true;

        i = mod(i);
        if(i == 0)
        {
            Node* toDel = this->m_header.next;
            if(toDel != NULL)
            {
                this->m_header.next = toDel->next;
                this->m_length--;
                if(this->m_length > 0)  // 在還有元素的時候挪動
                {
                    last_to_first();
                    if(this->m_current == toDel)
                    {
                        this->m_current = toDel->next;
                    }
                }
                else
                {
                    this->m_header.next = NULL;
                    this->m_current = NULL;
                }
                this->destory(toDel);
            }
            else
            {
                ret = false;
            }
        }
        else
        {// 刪除非首結點
            ret = LinkList<T>::remove(i);
        }

        return ret;
    }

    bool set(int i, const T& e)
    {
        return LinkList<T>::set(mod(i), e);
    }

    T get(int i) const
    {
        return LinkList<T>::get(mod(i));
    }

    bool get(int i, const T& e) const
    {
        return LinkList<T>::get(mod(i), e);
    }

    int find(const T& e) const
    {
        int ret = -1;
//        last()->next = NULL;    // 將尾結點的next指針置空,循環鏈表變成了單鏈表
//        ret = LinkList<T>::find(e);
//        last_to_first();
        // 但是這樣就改變了循環鏈表的狀態,不能這樣幹,因為find裏面可能發生異常,循環鏈表就變成單鏈表了
        // 不是異常安全的
        // 需要重新實現find函數

        Node* slider = this->m_header.next;
        for(int i = 0; i < this->m_length; i++)
        {
            if(slider->value == e)
            {
                ret = i;
                break;
            }
            slider = slider->next;
        }
        // 異常安全,比較的時候就算發生異常,也不會造成循環鏈表的狀態改變

        return ret;
    }

    void clear()
    {
        if(this->m_length > 0)
        {
//            last()->next = NULL;
//            LinkList<T>::clear();
            // 同樣的問題,clear裏面如果發生異常,不能保證異常安全性
            while( this->m_length > 1)
            {
                remove(1);  // 只要當前結點的長度大於1,就將結點1刪除,直到所有元素刪除
                // 不用remove(0)的原因是:考慮到效率問題
                // 每次remove(0)都會調用last_to_first等一大批操作
                // 刪除非首結點就快很多
                // 所以選擇刪除結點1,這樣最後就只會剩下結點0和首結點,再單獨處理
            }
            if(this->m_length == 1)
            {
                Node* toDel = this->m_header.next;
                this->m_header.next = NULL;
                this->m_length = 0;
                this->m_current = NULL;
                this->destory(toDel);
            }
        }
    }

    // 重新實現遍歷操作
    bool move(int i, int step)
    {
        return LinkList<T>::move(mod(i), step);
    }

    bool end()
    {
        return (this->m_length == 0) || (this->m_current == NULL);
    }

    ~CircleList()
    {
        clear();
    }

};

}

#endif // CIRCLELIST_H

4、循環鏈表的應用

約瑟夫環問題

已知n個人(以編號1,2,3...n分別表示)圍坐在一張圓桌周圍。從編號為k的人開始報數,數到m的那個人出列;他的下一個人又從1開始報數,數到m的那個人又出列;依此規律重復下去,直到圓桌周圍的人全部出列。

// n 人數, s 第幾號開始報數, m 報多少
void josephus(int n, int s, int m)
{
    CircleList<int> cl;
    for(int i = 1; i <= n; i++)
    {
        cl.insert(i);
    }

    cl.move(s-1, m-1);
    while(cl.length() > 0)
    {
        cl.next();
        cout << cl.current() << endl;
        cl.remove(cl.find(cl.current()));
    }
}

int main()
{
    josephus(41, 1, 3);


    return 0;
}

5、小結

循環鏈表是一種特殊的單鏈表

尾結點的指針域保存了首結點的地址

特殊處理首元素的插入操作和刪除操作

重新實現清空操作和遍歷操作

十四、循環鏈表的實現