(转)问题:假设一个没有头指针的单链表。一个指针指向此单链表中间的一个节点(既不是第一个,也不是最后一个节点),请将该节点从单链表中删除。

问题:假设一个没有头指针的单链表。一个指针指向此单链表中间的一个节点(既不是第一个,也不是最后一个节点),请将该节点从单链表中删除。

p->data = p->next->data;

p->next = p->next->next;

firee(p->next); 

 

链表结点定义如下:

struct ListNode

{

         int       m_nKey;

         ListNode* m_pNext;

};

解答:假设给定的指针为pCurrent,ListNode* pNext = pCurrent->m_pNext;

由题意知,pCurrent指向链表的某一个中间节点,因此pCurrent->m_pNext != NULL。

要删除pCurrent指向的节点B很简单,但必须将节点B前后两个节点A和C连接起来,但是单链表节点没有头指针,因此无法追溯到A,也就无法将A和C相连了。

无法删除节点B,但我们可以删除B的后继节点C,并通过pCurrent->m_pNext = pCurrent->m_pNext->m_pNext重新将链表连接起来,而唯一丢失的是节点C的数据项m_nKey。因此,我们只需要将节点C的数据项取代节点B的数据项,然后将真正指向节点C的指针删除即可实现将节点B“删除”!!关键代码如下:

pCurrent->m_pNext = pNext->m_pNext;

pCurrent->m_nKey = pNext->m_nKey;

delete pNext;

完整代码如下:

#include <iostream>

#include <assert.h>

struct ListNode

{

    int m_nKey;

    ListNode* m_pNext;      

};

//参数必须是指向指针的指针,因为要修改指针

//所以要考虑“参数本地拷贝”问题

void InitList(ListNode** pList)

{

     *pList = (ListNode*)malloc(sizeof(ListNode));

     (*pList)->m_pNext = NULL;

}

//第一个参数不必是指向指针的指针,因为函数内只修改指针指向的数据

//所以不用担心“参数本地拷贝”问题

void InsertList(ListNode* pList, int data)

{

    ListNode* pNewNode = (ListNode*)malloc(sizeof(ListNode));

    pNewNode->m_nKey = data;

    pNewNode->m_pNext = pList->m_pNext;

    pList->m_pNext = pNewNode;

}

//核心算法

void DeleteRandomNode(ListNode* pCurrent)

{

    assert(pCurrent != NULL);

    ListNode* pNext = pCurrent->m_pNext;

    if(pNext != NULL)

    {

        pCurrent->m_pNext = pNext->m_pNext;

        pCurrent->m_nKey = pNext->m_nKey;

        delete pNext;      

                   pNext = NULL;

    }    

}

//打印链表元素

void PrintListNormally(ListNode* pListHead)

{

    ListNode* pTempNode = pListHead->m_pNext;

    while(pTempNode != NULL)

    {

        std::cout<<pTempNode->m_nKey<<std::endl;

        pTempNode = pTempNode->m_pNext;            

    }    

}

int main()

{

    ListNode* pHead = NULL;

    InitList(&pHead);

    for(int i=9; i>=0; i--)

    {

        InsertList(pHead,i);       

    }

    PrintListNormally(pHead);

    ListNode* pNext = pHead->m_pNext->m_pNext; //删除第个元素

    DeleteRandomNode(pNext);

    PrintListNormally(pHead);

    system("pause");

    return 0;   

}

=======================================================

扩展题目:给定链表的头指针和一个结点指针,在O(1)时间删除该结点。链表结点的定义如下:

struct ListNode

{

         int        m_nKey;

         ListNode*  m_pNext;

};

函数的声明如下:

void DeleteNode(ListNode* pListHead, ListNode* pToBeDeleted);

分析:这是一道广为流传的Google面试题,能有效考察我们的编程基本功,还能考察我们的反应速度,更重要的是,还能考察我们对时间复杂度的理解。

在链表中删除一个结点,最常规的做法是从链表的头结点开始,顺序查找要删除的结点,找到之后再删除。由于需要顺序查找,时间复杂度自然就是O(n) 了。

我们之所以需要从头结点开始查找要删除的结点,是因为我们需要得到要删除的结点的前面一个结点。我们试着换一种思路。我们可以从给定的结点得到它的下一个结点。这个时候我们实际删除的是它的下一个结点,由于我们已经得到实际删除的结点的前面一个结点,因此完全是可以实现的。当然,在删除之前,我们需要需要把给定的结点的下一个结点的数据拷贝到给定的结点中。此时,时间复杂度为O(1)。

上面的思路还有一个问题:如果删除的结点位于链表的尾部,没有下一个结点,怎么办?我们仍然从链表的头结点开始,顺便遍历得到给定结点的前序结点,并完成删除操作。这个时候时间复杂度是O(n)。

那题目要求我们需要在O(1)时间完成删除操作,我们的算法是不是不符合要求?实际上,假设链表总共有n个结点,我们的算法在n-1总情况下时间复杂度是O(1),只有当给定的结点处于链表末尾的时候,时间复杂度为O(n)。那么平均时间复杂度[(n-1)*O(1)+O(n)]/n,仍然为O(1)。

基于前面的分析,我们不难写出下面的代码。

核心参考代码:

//核心算法

//pListHead是链表头指针,pCurrent是要删除的节点指针

void DeleteRandomNode(ListNode* pListHead, ListNode* pCurrent)

{

    assert(pCurrent != NULL || pListHead != NULL);

    if(pCurrent->m_pNext != NULL) //要删除的节点不是最后一个节点

    {

        ListNode* pNext = pCurrent->m_pNext;

        pCurrent->m_nKey = pNext->m_nKey;

        pCurrent->m_pNext = pNext->m_pNext;

        delete pNext;

        pNext = NULL;                   

    }

    else //要删除的节点是链表中最后一个节点

    {

        ListNode* pNode = pListHead;

        while(pNode->m_pNext != pCurrent) //得到要删除节点的前继节点

        {

            pNode = pNode->m_pNext;                    

        }

        pNode->m_pNext = NULL;

        delete pCurrent;

        pCurrent = NULL;

    }

}

完整的代码只需替换第一题的相应函数,并在main函数调用中做相应修改即可。

值得注意的是,为了让代码看起来简洁一些,上面的代码基于两个假设:(1)给定的结点的确在链表中;(2)给定的要删除的结点不是链表的头结点。不考虑第一个假设对代码的鲁棒性是有影响的。至于第二个假设,当整个列表只有一个结点时,代码会有问题。但这个假设不算很过分,因为在有些链表的实现中,会创建一个虚拟的链表头,并不是一个实际的链表结点。这样要删除的结点就不可能是链表的头结点了。当然,在面试中,我们可以把这些假设和面试官交流。这样,面试官还是会觉得我们考虑问题很周到的。

本文来自CSDN博客,转载请标明出处:http://blog.csdn.NET/ACE1985/archive/2010/08/06/5793287.aspx


  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 以下是C++代码实现: ```c++ int countX(Node* head, int x) { int count = 0; Node* cur = head; while (cur != NULL) { if (cur->data == x) { count++; } cur = cur->next; } return count; } ``` 其,Node是单链表节点的结构体,包含一个数据域和一个指向一个节点指针域。head为头指针指向单链表第一个节点。x为要查找的元素。函数遍历单链表,每当遇到一个值为x的节点,计数器count加1。最后返回count即可。 ### 回答2: 题目要求编写一个函数,计算给定单链表域为x的节点的个数。 首先要理解题目的一些概念: - 单链表:即链表的每个节点只包含一个指针域,指向链表的下一个节点。 - 头指针:链表的头节点指针。 - 域:链表的每个节点的数据部分。 根据以上概念,可以编写一个函数来计算域为x的节点的个数。 首先,需要定义链表的节点的结构体: ```C++ struct ListNode { int val; ListNode* next; ListNode(int x) : val(x), next(nullptr) {} }; ``` 接下来,我们可以定义一个函数来计算域为x的节点的个数: ```C++ int countX(ListNode* head, int x) { int count = 0; ListNode* curr = head; while (curr != nullptr) { if (curr->val == x) { count++; } curr = curr->next; } return count; } ``` 在该函数,使用一个变量`count`来记录域为x的节点个数,初始值为0。然后,使用一个指针变量`curr`来遍历链表。在遍历过程,如果当前节点的值等于x,则将`count`加1。最后返回`count`的值,即为域为x的节点的个数。 这是一个简单的链表遍历问题,时间复杂度是O(n),其n是链表的长度。 ### 回答3: 假设单链表的每个节点的数据域为value,指针域为next。 解决这个问题的一种方法是,使用一个计数器count,遍历链表的每个节点,如果节点的数据域等于给定的域x,则计数器count加1。代码如下所示: ```python def calculate_count(head, x): count = 0 current = head # current用于指向当前节点,从头节点开始 while current is not None: if current.value == x: count += 1 current = current.next # 指向一个节点 return count ``` 函数的参数有两个,一个头指针head,另一个是给定的域x。我们使用一个current指针来遍历链表的每个节点,初始时指向节点。然后,我们进入一个循环,只要current指针不为空,就执行循环体。 在循环体,我们首先判断current节点的数据域是否等于给定的域x,如果是,则计数器count加1。然后,我们将current指针向后移动,指向一个节点,以便继续遍历链表。 最后,我们返回计数器count的值,即为链表域为x的个数。 注意,上述代码仅为示例,具体实现可能需要根据链表的具体实现方式进行适当修改。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值