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);
};
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值