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);
};