zoukankan      html  css  js  c++  java
  • <剑指OFFER18> 18_01_DeleteNodeInList在O(1)时间删除链表结点

    https://blog.csdn.net/shen_jz2012/article/details/50631317

    // 面试题18(一):在O(1)时间删除链表结点
    // 题目:给定单向链表的头指针和一个结点指针,定义一个函数在O(1)时间删除该
    // 结点。

    #include<cstdio>
    
    struct ListNode
    {
        int m_nValue;
        ListNode* m_pNext;
    };
    
    void DeleteNode(ListNode** pListHead, ListNode* pToBeDeleted);
    
    void DeleteNode(ListNode** pListHead, ListNode* pToBeDeleted)
    {
        //头指针和要删除节点不为空
        if (!pListHead || !pToBeDeleted)
        {
            return;
        }
        else
        {
            // 要删除的节点不是尾节点,可以是头节点,链表有多个元素
            if (pToBeDeleted->m_pNext != nullptr)
            {
                ListNode* pNew = pToBeDeleted->m_pNext;
                pToBeDeleted->m_nValue = pNew->m_nValue;
                pToBeDeleted->m_pNext = pNew->m_pNext;
    
                delete pNew;
                pNew = nullptr;
            }
            // 链表只有一个节点,删除头节点(也是尾节点)
            // 进入这个条件说明 pToBeDeleted->m_pNext == nullptr,是尾节点
            // 如果 *pListHead == pToBeDeleted  说明链表只有一个节点
            else if (*pListHead == pToBeDeleted)
            {
                delete pToBeDeleted;
                pToBeDeleted = nullptr;
                *pListHead = nullptr;
            }
            //链表有多个节点,删除尾节点
            else
            {
                ListNode *pNode = *pListHead;
                while (pNode->m_pNext != pToBeDeleted)
                {
                    pNode = pNode->m_pNext;
                }
                pNode->m_pNext == nullptr;
                delete pToBeDeleted;
                pToBeDeleted = nullptr;
    
            }
    
            // 要删除的节点是尾节点
        }
    }
    /*******************************************************************
    Copyright(c) 2016, Harry He
    All rights reserved.
    
    Distributed under the BSD license.
    (See accompanying file LICENSE.txt at
    https://github.com/zhedahht/CodingInterviewChinese2/blob/master/LICENSE.txt)
    *******************************************************************/
    
    //==================================================================
    // 《剑指Offer——名企面试官精讲典型编程题》代码
    // 作者:何海涛
    //==================================================================
    
    // 面试题18(一):在O(1)时间删除链表结点
    // 题目:给定单向链表的头指针和一个结点指针,定义一个函数在O(1)时间删除该
    // 结点。
    
    #include <cstdio>
    #include "..UtilitiesList.h"
    
    void DeleteNode(ListNode** pListHead, ListNode* pToBeDeleted)
    {
        if(!pListHead || !pToBeDeleted)
            return;
    
        // 要删除的结点不是尾结点
        if(pToBeDeleted->m_pNext != nullptr)
        {
            ListNode* pNext = pToBeDeleted->m_pNext;
            pToBeDeleted->m_nValue = pNext->m_nValue;
            pToBeDeleted->m_pNext = pNext->m_pNext;
     
            delete pNext;
            pNext = nullptr;
        }
        // 链表只有一个结点,删除头结点(也是尾结点)
        else if(*pListHead == pToBeDeleted)
        {
            delete pToBeDeleted;
            pToBeDeleted = nullptr;
            *pListHead = nullptr;
        }
        // 链表中有多个结点,删除尾结点
        else
        {
            ListNode* pNode = *pListHead;
            while(pNode->m_pNext != pToBeDeleted)
            {
                pNode = pNode->m_pNext;            
            }
     
            pNode->m_pNext = nullptr;
            delete pToBeDeleted;
            pToBeDeleted = nullptr;
        }
    }
    
    // ====================测试代码====================
    void Test(ListNode* pListHead, ListNode* pNode)
    {
        printf("The original list is: 
    ");
        PrintList(pListHead);
    
        printf("The node to be deleted is: 
    ");
        PrintListNode(pNode);
    
        DeleteNode(&pListHead, pNode);
        
        printf("The result list is: 
    ");
        PrintList(pListHead);
    }
    
    // 链表中有多个结点,删除中间的结点
    void Test1()
    {
        ListNode* pNode1 = CreateListNode(1);
        ListNode* pNode2 = CreateListNode(2);
        ListNode* pNode3 = CreateListNode(3);
        ListNode* pNode4 = CreateListNode(4);
        ListNode* pNode5 = CreateListNode(5);
    
        ConnectListNodes(pNode1, pNode2);
        ConnectListNodes(pNode2, pNode3);
        ConnectListNodes(pNode3, pNode4);
        ConnectListNodes(pNode4, pNode5);
    
        Test(pNode1, pNode3);
    
        DestroyList(pNode1);
    }
    
    // 链表中有多个结点,删除尾结点
    void Test2()
    {
        ListNode* pNode1 = CreateListNode(1);
        ListNode* pNode2 = CreateListNode(2);
        ListNode* pNode3 = CreateListNode(3);
        ListNode* pNode4 = CreateListNode(4);
        ListNode* pNode5 = CreateListNode(5);
    
        ConnectListNodes(pNode1, pNode2);
        ConnectListNodes(pNode2, pNode3);
        ConnectListNodes(pNode3, pNode4);
        ConnectListNodes(pNode4, pNode5);
    
        Test(pNode1, pNode5);
    
        DestroyList(pNode1);
    }
    
    // 链表中有多个结点,删除头结点
    void Test3()
    {
        ListNode* pNode1 = CreateListNode(1);
        ListNode* pNode2 = CreateListNode(2);
        ListNode* pNode3 = CreateListNode(3);
        ListNode* pNode4 = CreateListNode(4);
        ListNode* pNode5 = CreateListNode(5);
    
        ConnectListNodes(pNode1, pNode2);
        ConnectListNodes(pNode2, pNode3);
        ConnectListNodes(pNode3, pNode4);
        ConnectListNodes(pNode4, pNode5);
    
        Test(pNode1, pNode1);
    
        DestroyList(pNode1);
    }
    
    // 链表中只有一个结点,删除头结点
    void Test4()
    {
        ListNode* pNode1 = CreateListNode(1);
    
        Test(pNode1, pNode1);
    }
    
    // 链表为空
    void Test5()
    {
        Test(nullptr, nullptr);
    }
    
    int main(int argc, char* argv[])
    {
        Test1();
        //Test2();
        //Test3();
        //Test4();
        //Test5();
    
        return 0;
    }
    #if 0
    /*正确写法,指向指针的指针*/
    /*
    其实真的很好理解,既然你懂得函数中的值传参,假设int a,作为参数传入的时候没被修改,
    所以需要用指向a的指针,那么应该也可以理解,指针变量pHead作为参数传入的时候被修改无效,
    因此需要用指向pHead的指针,只不过pHead本身就是一个指针了,
    所以才存在有指针的指针看起来稍微复杂一点的说法。
    因为,指向a的指针作为参数传入进去时,如果你对它进行修改,其实也是无效的,
    但是修改指针指向的内容的修改是有效的,也即,(&a)对a取地址得到的指针传入进去之后,
    此时你修改这个指针也是没有什么实际作用的,原因我等下会说。
    但是,你修改指针指向的内容这就有效了,因此通常我们在函数体内是修改对指针取内容后的内存,
    即*(&a)。所以,你对指针pHead的修改时无效的,只有对指向pHead的指针指向的内容(很绕吧,其实就是pHead),
    这时候才是有效的,因此AddToTail的第一个参数必须用指针的指针。
    
    现在来说说为什么对值传参在函数体内的修改无效。因为a传进去的时候会被复制了一份copy,
    此后的修改都是在临时变量copy上,出了函数体copy被销毁,a还是原来的a,
    根本就没被修改过,所以才会值传参对变量的修改无效。要使得对a的修改有效,
    一方面是传入a的地址,也就是对指向a的指针作为值传参(反正修改的不是a的指针,
    修改了也无所谓,反正只是修改a的指针的copy),此时a的指针的copy指向的内容也是a,
    因此对copy指向的内容修改会导致a的内容也被修改,check!另外一种方式就是引用传参,
    引用传参往往要比值传参高效,因为它是直接将a作为参数传入进去,而少了对a进行复制这部分的开销,
    既然传入进去的是a,那么对a的修改肯定也生效。
    
    */
    #include<iostream>
    #include<string>
    using namespace std;
    
    struct ListNode
    {
        int val;
        ListNode* next;
    };
    /*pHead必须定义成指向指针的指针,形参传进去的时候会被复制一份copy,此后的修改
    都是在临时变量的copy上,出了函数体copy被销毁,传进去的形参还是原来的数,根本就
    没被修改过,所以值传参对变量修改无效。要使得对传进去参数修改有效,有两种方法
    1。传入值得地址
    2. 引用传参,没有copy这部分的开销
    */
    void AddToTail(ListNode** pHead, int value);
    
    int main()
    {
        ListNode* head = NULL;
        AddToTail(&head, 10);
        if (head != NULL)
        {
            cout << head->val << endl;
        }
        else
        {
            cout << "head is NULL.." << endl;
        }
    
    }
    
    void AddToTail(ListNode** pHead, int value)
    {
        ListNode* pNew = new ListNode();
        pNew->val = value;
        pNew->next = NULL;
    
        if (*pHead == NULL)
        {
            *pHead = pNew;
        }
        else
        {
            ListNode* p = *pHead;
            while (p->next != NULL)
            {
                p = p->next;
            }
            p->next = pNew;
        }
    }
    
    #endif
    
    
    #if 0
    /*错误写法
    如果pHead定义成ListNode* ,则指针pHead的值不会被修改
    就像swap(a,b),如果直接传值进去,a,b值交换无效
    必须得交换swap(&a,&b),地址是真实的,可以修改地址里的内容,达到交换a,b的目的
    但是如果函数里改变,&a,&b则没有意义,形参出了函数就失效了,什么都没变
    */
    #include<iostream>
    #include<string>
    using namespace std;
    
    struct ListNode
    {
        int val;
        ListNode* next;
    };
    void AddToTail(ListNode* pHead, int value);
    
    int main()
    {
        ListNode* head = NULL;
        AddToTail(head, 10);
        if (head != NULL)
        {
            cout << head->val << endl;
        }
        else
        {
            cout << "head is NULL.." << endl;
        }
    
    }
    
    void AddToTail(ListNode* pHead, int value)
    {
        ListNode* pNew = new ListNode();
        pNew->val = value;
        pNew->next = NULL;
    
        if (pHead == NULL)
        {
            pHead = pNew;
        }
        else
        {
            ListNode* p = pHead;
            while (p->next != NULL)
            {
                p = p->next;
            }
            p->next = pNew;
        }
    }
    
    #endif
    
    #if 1
    /*
    正确写法:引用传参
    */
    #include<iostream>
    #include<string>
    using namespace std;
    
    struct ListNode
    {
        int val;
        ListNode* next;
    };
    void AddToTail(ListNode* &pHead, int value);
    
    int main()
    {
        ListNode* head = NULL;
        AddToTail(head, 10);
        if (head != NULL)
        {
            cout << head->val << endl;
        }
        else
        {
            cout << "head is NULL.." << endl;
        }
    
    }
    
    void AddToTail(ListNode* &pHead, int value)
    {
        ListNode* pNew = new ListNode();
        pNew->val = value;
        pNew->next = NULL;
    
        if (pHead == NULL)
        {
            pHead = pNew;
        }
        else
        {
            ListNode* p = pHead;
            while (p->next != NULL)
            {
                p = p->next;
            }
            p->next = pNew;
        }
    }
    
    #endif

    在看书的时候有个往链表里添加节点的函数,代码中考虑到可能给出的头指针为空,并做另外一些处理。具体代码如下:

    #include <iostream>
    #include <string>
    using namespace std;

    struct ListNode
    {
    int val;
    ListNode* next;
    };

    void AddToTail(ListNode** pHead, int value);

    int main() {
    // TODO
    }

    void AddToTail(ListNode** pHead, int value) {
    ListNode* pNew = new ListNode();
    pNew->val = value;
    pNew->next = NULL;

    if (*pHead == NULL) {
    *pHead = pNew;
    }
    else {
    ListNode* p = *pHead;
    while (p->next != NULL) {
    p = p->next;
    }
    p->next = pNew;
    }
    }


            网上其他人的博客中对函数AddToTail的参数的描述跟书中如出一辙:第一个参数pHead是一个指向指针的指针,当向一个空链表插入一个节点时,新插入的节点是链表的头指针,此时会改动头指针,因此必须把pHead参数设置为指向指针的指针。

            为什么呢?在以前学习C++的时候,我们只知道在参数中,以传值的形式作为参数的变量在函数体内被修改之后,出了函数体就会失效,准确的说这个变量没有被修改过,因此需要传入该变量的指针或者使用引用传参的方式。可是上述AddToTail中已经是一个指针了啊?于是我测试了一下,不使用指针的指针会怎样:

    #include <iostream>
    #include <string>
    using namespace std;

    struct ListNode
    {
    int val;
    ListNode* next;
    };

    void AddToTail(ListNode* pHead, int value);

    int main() {
    // TODO
    ListNode* head = NULL;
    AddToTail(head, 10);
    if (head != NULL) {
    cout << head->val << endl;
    }
    else {
    cout << "head is NULL.." << endl;
    }

    }

    void AddToTail(ListNode* pHead, int value) {
    ListNode* pNew = new ListNode();
    pNew->val = value;
    pNew->next = NULL;

    if (pHead == NULL) {
    pHead = pNew;
    }
    else {
    ListNode* p = pHead;
    while (p->next != NULL) {
    p = p->next;
    }
    p->next = pNew;
    }
    }


            运行结果如下

            作为指针pHead竟然真的没被修改过!

            其实真的很好理解,既然你懂得函数中的值传参,假设int a,作为参数传入的时候没被修改,所以需要用指向a的指针,那么应该也可以理解,指针变量pHead作为参数传入的时候被修改无效,因此需要用指向pHead的指针,只不过pHead本身就是一个指针了,所以才存在有指针的指针看起来稍微复杂一点的说法。因为,指向a的指针作为参数传入进去时,如果你对它进行修改,其实也是无效的,但是修改指针指向的内容的修改是有效的,也即,(&a)对a取地址得到的指针传入进去之后,此时你修改这个指针也是没有什么实际作用的,原因我等下会说。但是,你修改指针指向的内容这就有效了,因此通常我们在函数体内是修改对指针取内容后的内存,即*(&a)。所以,你对指针pHead的修改时无效的,只有对指向pHead的指针指向的内容(很绕吧,其实就是pHead),这时候才是有效的,因此AddToTail的第一个参数必须用指针的指针。

            现在来说说为什么对值传参在函数体内的修改无效。因为a传进去的时候会被复制了一份copy,此后的修改都是在临时变量copy上,出了函数体copy被销毁,a还是原来的a,根本就没被修改过,所以才会值传参对变量的修改无效。要使得对a的修改有效,一方面是传入a的地址,也就是对指向a的指针作为值传参(反正修改的不是a的指针,修改了也无所谓,反正只是修改a的指针的copy),此时a的指针的copy指向的内容也是a,因此对copy指向的内容修改会导致a的内容也被修改,check!另外一种方式就是引用传参,引用传参往往要比值传参高效,因为它是直接将a作为参数传入进去,而少了对a进行复制这部分的开销,既然传入进去的是a,那么对a的修改肯定也生效。

            为了证明上述废话,我将代码2中的AddToTail函数的第一个参数也作为引用参数传入(指向指针的指针肯定正确啦,就不测试了),此时预测的结果是修改有效。代码如下:

    #include <iostream>
    #include <string>
    using namespace std;

    struct ListNode
    {
    int val;
    ListNode* next;
    };

    void AddToTail(ListNode* &pHead, int value);

    int main() {
    // TODO
    ListNode* head = NULL;
    AddToTail(head, 10);
    if (head != NULL) {
    cout << head->val << endl;
    }
    else {
    cout << "head is NULL.." << endl;
    }

    }

    void AddToTail(ListNode* &pHead, int value) {
    ListNode* pNew = new ListNode();
    pNew->val = value;
    pNew->next = NULL;

    if (pHead == NULL) {
    pHead = pNew;
    }
    else {
    ListNode* p = pHead;
    while (p->next != NULL) {
    p = p->next;
    }
    p->next = pNew;
    }
    }

            只是简单的在代码2中的函数声明和定义中,第一个参数加入了"&"表示使用一个引用参数,结果如下图,check!

  • 相关阅读:
    lombok 下的@Builder注解用法
    吉特日化MES实施--三种浪费
    吉特日化MES配料工艺参数标准版-第二版
    吉特日化MES系统&生产工艺控制参数对照表
    吉特日化MES & SQL Server 无法执行数据库脚本
    吉特日化MES系统--通过浏览器调用标签打印
    吉特日化MES&WMS系统--三色灯控制协议转http
    吉特仓储管理系统-库存管理分类汇总
    “千言数据集:文本相似度”权威评测,网易易智荣登榜首
    网易有数品牌升级:聚焦数据价值,助力企业数字化创新
  • 原文地址:https://www.cnblogs.com/focus-z/p/12732884.html
Copyright © 2011-2022 走看看