[算法] 链表操作

10 篇文章 0 订阅

链表节点定义

链表相关的操作中我们定义链表节点和公共操作如LinkListCommon.hpp所示:

#ifndef LinkListCommon_hpp
#define LinkListCommon_hpp

#include <stdio.h>

typedef struct tagSNode {
    int value;
    tagSNode* pNext;

    tagSNode(int v) : value(v), pNext(NULL) {}
}SNode;

// 打印链表
void printLinkList(SNode* pHead) {
    if (pHead == NULL) {
        printf("No number....");
        return;
    }
    SNode* pCurr = pHead->pNext;
    while (NULL != pCurr && NULL != pCurr->pNext) {
        printf("%d->", pCurr->value);
        pCurr = pCurr->pNext;
    }
    printf("%d\n", pCurr->value);
}

// 回收链表
void destroyLinkList(SNode* pHead) {
    if (pHead == NULL) {
        return;
    }
    SNode* pCurr = pHead;
    SNode* pNext = pHead->pNext;
    while (pNext) {
        delete pCurr;
        pCurr = pNext;
        pNext = pNext->pNext;
    }
    if (pCurr) {
        delete pCurr;
    }
}

// 创建数字链表
SNode* createNodeList(int listLength, int radix) {
    SNode* pHead = new SNode(0);
    int i;
    for (i = 0; i < listLength; i++) {
        SNode* pNode = new SNode(rand() % radix);
        pNode->pNext = pHead->pNext;
        pHead->pNext = pNode;
    }
    return pHead;
}
#endif /* LinkListCommon_hpp */

链表相加

问题

给定两个链表,分别表示两个非负整数,他们的数字逆序存储在链表当中,且每个节点存储一个数字,计算两个数的和,并且返回和的链表头指针。例如:

输入:2->4->3、5->6->4,输出7->0->8

分析

  • 两个数都是逆序存储,正好可以从头向后依次相加,完成”两个数的的竖式计算”
  • 两个数字的求和范围是[0,18],进位最大是1,因此第i位相加不会影响到第i+2位的计算。
  • 在发现一个链表为空后,直接结束for循环,最后只需进位和较长链表的当前节点相加,较长链表的其他节点直接拷贝至和和链家即得到最终结果。

代码实现

LinkListAdd.hpp

#ifndef LinkListAdd_hpp
#define LinkListAdd_hpp

#include <stdio.h>
#include <stdlib.h>
#include "LinkListCommon.hpp"

// 链表相加
SNode* add(SNode* pHead1, SNode* pHead2) {
    SNode* pSum = new SNode(0);
    pSum->value = 0;

    // 链表尾节点
    SNode* pTail = pSum;
    SNode* pCurrSum;

    SNode* pCurr1 = pHead1->pNext;
    SNode* pCurr2 = pHead2->pNext;

    // 进位值
    int carry = 0;
    int value;

    while (pCurr1 && pCurr2) {
        value = pCurr1->value + pCurr2->value + carry;
        carry = value / 10;
        value = value % 10;

        pCurrSum = new SNode(value);

        pTail->pNext = pCurrSum;
        pTail = pCurrSum;

        pCurr1 = pCurr1->pNext;
        pCurr2 = pCurr2->pNext;
    }

    // 将较长的链表连接到链表尾部
    SNode* pLeft = pCurr1 ? pCurr1 : pCurr2;
    while (pLeft) {
        value = pLeft->value + carry;
        carry = value / 10;
        value = value % 10;

        pCurrSum = new SNode(value);

        pTail->pNext = pCurrSum;
        pTail = pCurrSum;

        pLeft = pLeft->pNext;
    }
    return pSum;
}
#endif /* LinkListAdd_hpp */

测试代码main.cpp

#include "LinkListAdd.hpp"

int main(int argc, const char * argv[]) {
    // 测试链表数字相加
    SNode* pHead1 = createNodeList(6);
    SNode* pHead2 = createNodeList(9);
    printLinkList(pHead1);
    printLinkList(pHead2);

    SNode* pSum = add(pHead1, pHead2);
    printf("Result:\n");
    printLinkList(pSum);

    destroyLinkList(pHead1);
    destroyLinkList(pHead2);

    return 0;
}

执行结果

2->0->8->3->9->7
7->2->2->5->0->9->3->8->4
Result:
9->2->0->9->9->6->4->8->4

链表翻转

问题

给定一个链表,翻转该链表从m到n的位置,要求直接翻转而分申请新空间。

  • 例如给定链表:1->2->3->4->5,m=2,n=4,返回 1->4->3->2->5。
  • 假定给出的参数满足:1<=m<=n<=链表长度。

分析

  • 空转m-1次,找到第m-1个节点,即开始翻转第一个节点的前驱,记为head;
  • 以head为起点遍历n-m次,将第i次时,将找到的节点插入到head的next中即可(头插法)。

代码实现

链表去重LinkListReversal.hpp

#ifndef LinkListReversal_hpp
#define LinkListReversal_hpp

#include <stdio.h>
#include "LinkListCommon.hpp"

void reverseLinkList(SNode* pHead, int from, int to) {
    SNode* pCurr = pHead->pNext;
    int i;
    // 空转form-1次,找到翻转起点的前一个节点和翻转的第一个节点
    for (i = 0; i < from - 1; i++) {
        pHead = pCurr;
        pCurr = pCurr->pNext;
    }
    // 起始翻转节点不需要翻转, 把后面的to-1个节点翻转
    SNode* pPre = pCurr;
    pCurr = pCurr->pNext;
    to--;
    SNode* pNext;
    for (; i < to; i++) {
        pNext = pCurr->pNext;
        pCurr->pNext = pHead->pNext;
        pHead->pNext = pCurr;
        pPre->pNext = pNext;
        pCurr = pNext;
    }
}
#endif /* LinkListReversal_hpp */

测试代码main.cpp

#include "LinkListAdd.hpp"
#include "LinkListReversal.hpp"

int main(int argc, const char * argv[]) {
    // 测试链表翻转
    SNode* pHead3 = createNodeList(15, 100);
    printLinkList(pHead3);
    printf("Reverse result:\n");
    reverseLinkList(pHead3, 6, 11);
    printLinkList(pHead3);
    destroyLinkList(pHead3);
    return 0;
}

执行结果

35->16->78->99->33->60->57->9->69->3->12->40->29->27->3
Reverse result:
35->16->78->99->33->12->3->69->9->57->60->40->29->27->3

排序链表去重

问题

给定排序的链表,删除重复的元素,重复元素只保留一个:

  • 给定: 2->3->3->5->7->8->8->8->9->9->10
  • 返回: 2->3->5->7->8->9->10

分析

有两种思路:

  1. 只保留重复元素的第一个节点,删除后面的重复元素节点
  2. 只保留重复元素的最后一个节点,删除前面的重复元素节点

两种实现方式均可,若要求删除全部重复元素,则使用第二种方法更加方便些。

代码实现

两种思路的实现代码如下所示

#ifndef LinkListDeleteDuplicateNode_hpp
#define LinkListDeleteDuplicateNode_hpp

#include <stdio.h>
#include "LinkListCommon.hpp"

// 链表去重:保留第一个重复元素
void deleteDuplicateNode1 (SNode* pHead) {
    SNode* pPre = pHead->pNext;
    SNode* pCur;
    while(pPre) {
        pCur = pPre->pNext;
        if(pCur && (pCur->value == pPre->value)) {
            pPre->pNext = pCur->pNext;
            delete pCur;
        }else {
            pPre = pCur;
        }
    }
}


// 链表去重:保留最后一个重复元素
void deleteDuplicateNode2(SNode* pHead) {
    SNode* pPre = pHead->pNext;
    SNode* pCur = pPre->pNext;
    SNode* pNext;
    while (pCur) {
        pNext = pCur->pNext;
        while (pNext && (pCur->value == pNext->value)) {
            pPre->pNext = pNext;
            delete pCur;
            pCur = pNext;
        }
        pPre = pCur;
        pCur = pNext;
    }
}
#endif /* LinkListDeleteDuplicateNode_hpp */

测试代码main.cpp

#include "LinkListDeleteDuplicateNode.hpp"

int main(int argc, const char * argv[]) {
    // 测试链表去重:保留第一个重复元素
    SNode* pHead4 = new SNode(0);
    int data[] = {2, 3, 3, 5, 7, 8, 8, 8, 9, 9, 30};
    int size1 = sizeof(data) / sizeof(int);
    for (int i = size1 - 1; i >= 0; i--) {
        SNode* pNode = new SNode(data[i]);
        pNode->pNext = pHead4->pNext;
        pHead4->pNext = pNode;
    }
    printLinkList(pHead4);
    deleteDuplicateNode1(pHead4);
    printLinkList(pHead4);
    destroyLinkList(pHead4);

    printf("\n===============================================================\n");

    // 测试链表去重:保留最后一个重复元素
    SNode* pHead5 = new SNode(0);
    // int data[] = {2, 3, 3, 5, 7, 8, 8, 8, 9, 9, 30};
    int size2 = sizeof(data) / sizeof(int);
    for (int i = size2 - 1; i >= 0; i--) {
        SNode* pNode = new SNode(data[i]);
        pNode->pNext = pHead5->pNext;
        pHead5->pNext = pNode;
    }
    printLinkList(pHead5);
    deleteDuplicateNode1(pHead5);
    printLinkList(pHead5);
    destroyLinkList(pHead5);

    printf("\n===============================================================\n");

    return 0;
}

执行结果

===============================================================
2->3->3->5->7->8->8->8->9->9->30
2->3->5->7->8->9->30

===============================================================
2->3->3->5->7->8->8->8->9->9->30
2->3->5->7->8->9->30

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

去除全部重复元素

即若发现重复元素,则删除全部重复元素。用思路2解决该问题。用一个标志位记录当前元素是否为重复元素,实现代码如下所示:

void deleteAllDuplicateNode(SNode* pHead) {
    SNode* pPre = pHead;
    SNode* pCur = pPre->pNext;
    SNode* pNext;
    // 标志pCur是否为重复元素
    bool bDup;
    while (pCur) {
        pNext = pCur->pNext;
        bDup = false;
        while (pNext && (pCur->value == pNext->value)) {
            pPre->pNext = pNext;
            delete pCur;
            pCur = pNext;
            pNext = pCur->pNext;
            bDup = true;
        }
        if (bDup) { // pCur是重复的元素,删除
            pPre->pNext = pNext;
            delete pCur;
        } else { // pCur不是重复元素,pPre往后移
            pPre = pCur;
        }
        pCur = pNext;
    }
}

链表划分

问题

给定一个链表和一个值x,将链表划分成两部分,使得划分后小于x的节点在前,大于等于x的节点在后。这两部分中要保持原链表中出现的顺序,例如

给定链表1->4->3->2->5-2和x=3,返回1->2->2->4->3->5.

分析

分别申请两个指针p1p2,小于x的添加到p1中,大于等于x的添加到p2中,最后将p2链接到p1的末端即可。
时间复杂度为O(N),空间复杂度为O(1),该问题要说明的是快速排序对于单链表存储结构仍然适用.

代码实现

代码实现如LinkListPartition.hpp所示

#ifndef LinkListPartition_hpp
#define LinkListPartition_hpp

#include <stdio.h>
#include "LinkListCommon.hpp"

void linkListPartition(SNode* pHead, int pivotKey) {
    // 两个链表的头指针
    SNode* pLeftHead = new SNode(0);
    SNode* pRightHead = new SNode(0);

    // 两个链表的尾指针
    SNode* left = pLeftHead;
    SNode* right = pRightHead;

    SNode* p = pHead->pNext;
    while (p) {
        if (p->value < pivotKey) {
            left->pNext = p;
            left = p;
        } else {
            right->pNext = p;
            right = p;
        }
        p = p->pNext;
    }

    // 将right链接到left尾部
    left->pNext = pRightHead->pNext;
    right->pNext = NULL;

    pHead->pNext = pLeftHead->pNext;

    delete pLeftHead;
    delete pRightHead;
}

#endif /* LinkListPartition_hpp */

测试代码main.cpp如下所示:

#include "LinkListPartition.hpp"

int main(int argc, const char * argv[]) {
    // 测试链表划分
    SNode* pHead6 = createNodeList(20, 100);
    printLinkList(pHead6);
    linkListPartition(pHead6, 50);
    printLinkList(pHead6);
    return 0;
}

执行结果,执行结果如下所示

57->94->91->28->45->85->36->93->72->67->21->79->49->79->33->10->67->12->26->97
28->45->36->21->49->33->10->12->26->57->94->91->85->93->72->67->79->79->67->97

单链表公共节点问题

问题

给定两个单向链表,计算两个链表的第一个公共节点,若没有公共节点,则返回空。

分析

令两链表长度为mn,不妨认为m>=n,由于两个链表从第一个公共节点到链表的尾节点是完全重合的,所以前面的m-n个节点一定没有公共节点。
先分别遍历两个链表得到他们的长度mn,长链表空转|m-n|次,同步遍历两链表,直到找到相同节点或到链表结束。
时间复杂度为O(m+n)

代码实现

代码实现如LinkListFindFirstCommonNode.hpp所示:

#ifndef LinkListFindFirstCommonNode_hpp
#define LinkListFindFirstCommonNode_hpp

#include <algorithm>
#include <stdio.h>
#include "LinkListCommon.hpp"

int calculateLength(SNode* pHead) {
    int length = 0;
    while (pHead) {
        length++;
        pHead = pHead->pNext;
    }
    return length;
}

SNode* findFirstCommonNode(SNode* pHead1, SNode* pHead2) {
    pHead1 = pHead1->pNext;
    pHead2 = pHead2->pNext;

    int length1 = calculateLength(pHead1);
    int length2 = calculateLength(pHead2);

    if (length1 > length2) {
        std::swap(pHead1, pHead2);
        std::swap(length1, length2);
    }
    // 长的链表空转length2-length1次
    for (int i = 0; i < length2 - length1; i++) {
        pHead2 = pHead2->pNext;
    }
    while (pHead1) {
        if (pHead1 == pHead2) {
            return pHead1;
        }
        pHead1 = pHead1->pNext;
        pHead2 = pHead2->pNext;
    }
    return NULL;
}
#endif /* LinkListFindFirstCommonNode_hpp */

扩展

单链表公共节点问题中,如果链表存在环,则需要使用快慢指针的方式计算公共节点(两个指针,每次分别移动一个/两个节点)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值