17_7_15:判断链表是否有环+求环的长度+求环的入口。设计不能被继承的类,只能堆/栈上创建对象的类

来源:互联网 发布:造价工程师网络教育 编辑:程序博客网 时间:2024/05/21 11:07

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);};
阅读全文
1 0
原创粉丝点击