17_7_15:判斷鏈表是否有環+求環的長度+求環的入口。設計不能被繼承的類,只能堆/棧上創建對象的類

1.【基礎題】–判斷鏈表是否帶環?若帶環求環的長度?若帶環求環的入口點?並計算以上每個問題的時間複雜度?

2.【附加題】–1.設計一個類不能被繼承 2.設計一個類只能在堆上創建對象。 3.設計一個類只能在棧上創建對象。 ps:以上三個問題是類似的。

**

1,基礎題

**
這個三個問題,都是處理單鏈表中存在環的情況。難度依次遞增。

(1)判斷鏈表是否帶環。
思路:定義兩個指針:快指針與滿指針
快指針fast,每次走走兩步。
慢指針slow,每次走一步。
如果鏈表有環,則,快慢指針終有碰撞重合的機會。
如果沒環,則快指針,終會指向NULL。
注意:返回碰撞點。

PNode Is_Has_Ring(PNode pHead)
{
    //此時,pHead是否爲空,都在邏輯判斷之內處理
    PNode pFast = pHead;
    PNode pSlot = pHead;

    while (pFast && pFast->_pNext)
    {
        pFast = pFast->_pNext->_pNext;
        pSlot = pSlot->_pNext;

        if (pFast == pSlot) //兩個節點相等時退出
            break;
    }

    //判斷鏈表是是否環,有環,則pFast及pFast->_pNext不會爲空
    if (pFast != pSlow)
        return NULL;

    return pFast;
}

(2)求單鏈表中環的長度
思路:首先獲取碰撞點的位置。然後,快慢指針再次走,直到再次重合時慢指針所走的步長即使環長度。

int Get_Length_Of_Ring(PNode pHead)
{
    PNode pFast = Is_Has_Ring(pHead);
    PNode pSlot = pFast;
    int length = 0;

    if (NULL == pFast) //鏈表不帶環
        return 0;

    //快慢指針再次相遇時,慢指針的步長,表示環的長度。
    do 
    {
        pFast = pFast->_pNext->_pNext;
        pSlot = pSlot->_pNext;
        ++length;
    } while (pFast != pSlot);

    return length;
}

(3)求單鏈表中環的入口點
思路:
//頭結點到入口點的長度設爲:X
//環的長度設爲R
//碰撞點到入口的長度設爲:R1
//通過計算,可以推到出存在某一個n(n爲非負整數),使得:X = nR + R1.
//所以,頭結點和碰撞點同時出發, 最終會在入口點相遇。

PNode Get_Entrance_Of_Ring(PNode pHead)
{
    PNode pColl = Is_Has_Ring(pHead); //獲取碰撞點

    if (NULL == pColl) //鏈表不帶環
        return NULL;

    while (pColl != pHead)
    {
        pColl = pColl->_pNext;
        pHead = pHead->_pNext;
    }

    return pColl;
}

關於上述算法的時間複雜度,在獲取碰撞點算法的時間複雜度上,留下疑問,希望有人能夠回答我,謝謝了。

**

2,附加題

**
(1)該類不能被繼承:
思路:不能被繼承,可以看作是,在繼承的類中,無法使用子類的東西。
相當於他的子類無法調用父類的構造函數,或者無法調用父類的析構函數。
所以,可以將父類的構造函數或析構函數,聲明爲私有的即可。

class Test1_Base
{
private:
    Test1_Base() //這裏是將構造函數設置爲私有的
    {
        cout << "Test1_Base constructor function" << endl;
    }
public: 
    ~Test1_Base()  //還可以將析構函數聲明爲私有的
    {
        cout << "Test1_Base destructor function" << endl;
    }
};

此時,如果有類來繼承該類,則在編譯器期間就會報錯,因爲在子類沒有權限去調用父類的構造函數或析構函數。
(2)該類只能在堆上創建對象

C++有兩種方式創建一個類對象:
一種是靜態創建,在棧上創建。如 A a;
一種是動態創建,在堆上創建,如 A* pa = new A;
兩者的區別在於:
a,創建對象時,靜態創建通過棧頂指針的偏移,留出一片空間,調用類的構造函數直接初始化這篇空間;而動態創建,則是分兩步,先調用operator new函數在堆上尋找一塊空間,然後調用類的構造函數初始化這快空間。
b,在銷燬對象時,棧上的對象,由編譯器自動調用析構函數,來釋放資源。
而堆上的對象,必須有用戶自已通過delete來釋放資源。這一過程也分爲兩步。先調用類的析構函數,然後調用operator delete函數來釋放資源。

通過以上分析。
要使得一個類只能在堆上創建對象,就必須使得編譯器無法在棧上創建空間,或者無法釋放資源,如果將構造函數設置爲私有的,則也無法在堆上創建對象。如果將析構函數設置爲私有的,則也無法直接在堆上釋放資源。但是,我們可以通過新添一個資源釋放函數Destroy函數封裝delete操作,來避免delete權限不夠的情況。(不鞥通過新添一個init函數封裝new,因爲調用init函數前提是類對象已經存在)

class Test2
{
public:
    void Destroy()
    {
        delete this;
    }
private:
    ~Test2()
    {}
};

(3)該類只能在棧上創建對象
能在棧上分配空間:可將 T:: operator new 全部私有,因爲要在堆上分配空間,需要用到new來實現,當把new私有化,就不能調用new T()這樣的語句,這樣可以達到只能在棧上來分配空間了。

class Test3
{
private:
    void* operator new(size_t size);
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章