单链表

前言

顺序表是用一组地址连续的存储单元来保存数据的,所以它具有随机存取的特点。即查找快速,但是做插入或删除动作是,需要移动大量元素,效率较低。
链表是线性表的链式存储结构,它相比于顺序表,在插入和删除元素时,效率要高很多。
每个数据单元有两部分组成,一个是数据域,存储数据值;另一个是指针域,指向下一个数据单元。这样的数据单元叫做结点。
链表可分为单链表、双链表、循环链表。

基本算法

  • 创建链表
    顺序表存储结构的创建就是数组的初始化,即声明一个类型和大小的数组并赋值。单链表是一种松散的存储结构,动态的,同时指针域,必须为指针域赋值。创建链表的过程就是动态生成链表的过程。从空表开始,依次建立各个元素结点,逐个插入链表。
    头插法:采用插队的方式,始终让新建结点在第一的位置上。类似堆栈。
    这里写图片描述
    假设头指针后有数据,往里插入结点即可。
LNode* pTempHead = *ppHead; 
pTempHead->pNext = NULL;
for (int i= 0;i<num;i++)
{
    LNode* item = new LNode();//生成新的结点
    item->data = i;
    //这是头插法,就是在链表顶端往下插入
    item->pNext = pTempHead->pNext;
    pTempHead->pNext = item;//插入到表头,新插入的结点在表头   

}

数组数据显示是:4,3,2,1,0
尾插法:每次新的结点都插在终端结点的后面。

LNode* pTempHead = *ppHead;
    //建立一个带头结点的单链表
    for (int i= 0;i<num;i++)
    {
        LNode* item = new LNode();//生成新的结点
        item->data = i;     
        pTempHead->pNext = item;//将表尾终端结点的指针指向新的结点,此时pphead中添加了结点,item在最后端
        pTempHead = item;//继续让pTempHead指向ppHead的终端。

    }   
    pTempHead->pNext = NULL;//尾插法是在这设置为null

链表数据显示:0,1,2,3,4.
- 插入结点
核心算法就是如图中
这里写图片描述
现将p的后继结点保存,再赋值给s的后继结点,就成了s->Next = p->Next 。此时将s添加到链表中:p->Next = s; 单链表的表头和表尾的特殊情况,操作时相同的。

//插入元素
void SingleList::InsertElem(LNode** ppHead,int pos,int data)
{
    pLinkList pTempHead = *ppHead;//指向需要操作的数据链
    if (pTempHead == NULL || pTempHead->pNext == NULL)
    {
        return;
    }
    pLinkList item;
    int i = 0;
    //选找到制定位置的结点,有效的可以在表尾和表头插入结点
    while(pTempHead && i< pos)
    {
        pTempHead = pTempHead->pNext;
        i++;
    }
    //位置不合适。有可能到了表尾此处不能为pTempHead->pNext,当为表尾是next没有分配内存,导致溢出。
    if (!pTempHead ||i>pos)
    {
        return;
    }
    //进行插入操作
    item = new LNode();
    if (!item)
    {
        return;
    }
    item->data = data;
    item->pNext = pTempHead->pNext;
    pTempHead->pNext = item;

}
  • 删除结点
    删除结点就是将他的前级结点的指针绕过即可。实际上就一步:p->next = p->next-next;同时释放点指针。
    这里写图片描述
//删除元素
void SingleList::RemoveElem(LNode** ppHead,int pos,int* getData)
{
    pLinkList pTempHead = *ppHead;//指向需要操作的数据链
    if (pTempHead == NULL || pTempHead->pNext == NULL)
    {
        return;
    }
    pLinkList item;
    int i = 0;
    //选找到制定位置的结点,此处是pTempHead->pNext,有效的能删除表尾,防止访问到表尾的next
    while(pTempHead->pNext && i< pos)
    {
        pTempHead = pTempHead->pNext;
        i++;
    }
    //位置不合适。有可能到了表尾,如果和插入时为ptempHead,此时item的item->next会报错
    if (!pTempHead->pNext ||i>pos)
    {
        return;
    }
    item = pTempHead->pNext;
    *getData = item->data;
    //item->pNext = pTempHead->pNext;
    pTempHead->pNext = item->pNext;
    //释放资源
    delete item;
}

在删除结点和插入结点时,注意循环跳出的条件,两者不一样。

代码实例

返回类型函数名称参数功能
voidInitNode(LNode** ppHead)初始化结构体
voidCreateList(LNode** ppHead,int num)创建链表
voidDestroyList(LNode** ppHead)清空链表
voidInsertElem(LNode** ppHead,int pos,int data)插入元素
voidRemoveElem(LNode** ppHead,int pos,int* getData)移除元素
boolIsEmptyList(LNode* pHead)是否为空
intGetElem(LNode* pHead,int pos,int* data)获取制定位置元素
voidPrintList(LNode* pHead)输出链表
voidReverseList(LNode* pHead,LNode ** outList)反转链表

void SingleList::InitNode(LNode** ppHead)
{
    if (*ppHead)
    {
        DestroyList(ppHead);
    }
    LNode* p = new LNode();
    p->data = 0;
    p->pNext = NULL;
    *ppHead = p;
}
//创建链表
void SingleList::CreateList(LNode** ppHead,int num)
{
    InitNode(ppHead);
    LNode* pTempHead = *ppHead;
    //建立一个带头结点的单链表
    //pTempHead = new LNode();
    //pTempHead->pNext = NULL;
    for (int i= 0;i<num;i++)
    {
        LNode* item = new LNode();//生成新的结点
        item->data = i;
        //这是头插法,就是在链表顶端往下插入
        //item->pNext = pTempHead->pNext;
        //pTempHead->pNext = item;//插入到表头,新插入的结点在表头
        //这是头插法
        //item->pNext = pTempHead;//将表尾终端节点的指针指向新的结点,此时item是多个点
        //pTempHead = item;//将item赋值给item
        pTempHead->pNext = item;
        pTempHead = item;

    }

    pTempHead->pNext = NULL;//尾插法是在这设置为null
    //*ppHead = pTempHead;
}
//销毁链表
void SingleList::DestroyList(LNode** ppHead)
{
    pLinkList pTempHead = *ppHead;
    //前期判断,已经为空,返回
    if (pTempHead == NULL||pTempHead->pNext == NULL)
    {
        return;
    }
    pLinkList item,item1;
    item = pTempHead->pNext;
    while(item)//没到表尾
    {
        item1= item->pNext;
        delete item;
        item = item1;
    }
    pTempHead->pNext = NULL;
}
//插入元素
void SingleList::InsertElem(LNode** ppHead,int pos,int data)
{
    pLinkList pTempHead = *ppHead;//指向需要操作的数据链
    if (pTempHead == NULL || pTempHead->pNext == NULL)
    {
        return;
    }
    pLinkList item;
    int i = 0;
    //选找到制定位置的结点
    while(pTempHead && i< pos)
    {
        pTempHead = pTempHead->pNext;
        i++;
    }
    //位置不合适。有可能到了表尾此处不能为pTempHead->pNext,当为表尾是next没有分配内存,导致溢出。
    if (!pTempHead ||i>pos)
    {
        return;
    }
    //进行插入操作
    item = new LNode();
    if (!item)
    {
        return;
    }
    item->data = data;
    item->pNext = pTempHead->pNext;
    pTempHead->pNext = item;

}
//删除元素
void SingleList::RemoveElem(LNode** ppHead,int pos,int* getData)
{
    pLinkList pTempHead = *ppHead;//指向需要操作的数据链
    if (pTempHead == NULL || pTempHead->pNext == NULL)
    {
        return;
    }
    pLinkList item;
    int i = 0;
    //选找到制定位置的结点,此处是pTempHead->pNext,有效的能删除表尾,防止访问到表尾的next
    while(pTempHead->pNext && i< pos)
    {
        pTempHead = pTempHead->pNext;
        i++;
    }
    //位置不合适。有可能到了表尾,如果和插入时为ptempHead,此时item的item->next会报错
    if (!pTempHead->pNext ||i>pos)
    {
        return;
    }
    item = pTempHead->pNext;
    *getData = item->data;
    //item->pNext = pTempHead->pNext;
    pTempHead->pNext = item->pNext;
    //释放资源
    delete item;
}
//判断单链表是否为空
bool SingleList::IsEmptyList(LNode* pHead)
{
    if (NULL == pHead||NULL == pHead->pNext)
    {
        return true;
    }
    else
        return false;
}
//获取单链表位置为pos的元素,从头开始找,直到第i元素为止,时间复杂度为N。
int SingleList::GetElem(LNode* pHead,int pos,int* data)
{
    int j = 1;//计数器
    if (pHead == NULL||pos<1)
    {
        return -1;
    }
    pLinkList item;//声明一个结点
    item = pHead->pNext;//指向头结点
    while(item && j<pos)
    {
        item =pHead->pNext;
        j++;
    }
    *data = item->data;
    return 1;
}

#pragma endregion

#pragma region 链表扩展操作
//从尾到头打印链表
void SingleList::PrintList(LNode* pHead)
{
    //此处是反转打印链表,可以利用堆栈
    stack<LNode*> stackList ;
    pLinkList item = pHead;
    while(item != NULL)
    {
        //压入堆栈
        stackList.push(item);
        item = item->pNext;
    }
    while(!stackList.empty())
    {
        //导出数据
        item = stackList.top();
        cout<<item->data;
        stackList.pop();
    }
}

void SingleList::PrintList1(LNode* pHead)
{
    if (NULL == pHead || NULL == pHead->pNext) {

        printf("LinkList is empty\n");

        return;

    }

    LNode *p = pHead->pNext;

    printf("LinkList:");

    while (p) {

        printf(" %d", p->data);

        p = p->pNext;

    }

    printf("\n");
}
//反转链表
void SingleList::ReverseList(LNode* pHead,LNode ** outList)
{
    if (pHead == NULL||pHead->pNext == NULL)
    {
        return;
    }
    pLinkList pTempHead = *outList;
    pTempHead = new LNode();
    pLinkList pCurrent = pHead;
    while(pCurrent)
    {
        LNode * item = pCurrent;
        //采用尾插法,创建链表即可;
        item->pNext = pTempHead;
        pTempHead = item;
        pCurrent = pCurrent->pNext;
    }   

}

面试题

查找单链表中的倒数第K个结点(k > 0)
思路:使用两个指针,先让前面的指针走到正向第k个结点,这样前后两个指针的距离差是k-1,之后前后两个指针一起向前走,前面的指针走到最后一个结点时,后面指针所指结点就是倒数第k个结点。

// 查找单链表中倒数第K个结点
LNode * RGetKthNode(LNode * pHead, unsigned int k) // 函数名前面的R代表反向
{
    if(k == 0 || pHead == NULL) // 这里k的计数是从1开始的,若k为0或链表为空返回NULL
        return NULL;

    LNode * pAhead = pHead;
    LNode * pBehind = pHead;
    while(k > 1 && pAhead != NULL) // 前面的指针先走到正向第k个结点
    {
        pAhead = pAhead->m_pNext;
        k--;
    }
    if(k > 1 || pAhead == NULL)     // 结点个数小于k,返回NULL
        return NULL;
    while(pAhead->m_pNext != NULL)  // 前后两个指针一起向前走,直到前面的指针指向最后一个结点
    {
        pBehind = pBehind->m_pNext;
        pAhead = pAhead->m_pNext;
    }
    return pBehind;  // 后面的指针所指结点就是倒数第k个结点
}

查找单链表的中间结点
思路:设置两个指针,只不过这里是,两个指针同时向前走,前面的指针每次走两步,后面的指针每次走一步,前面的指针走到最后一个结点时,后面的指针所指结点就是中间结点。

这里写代码片// 获取单链表中间结点,若链表长度为n(n>0),则返回第n/2+1个结点
ListNode * GetMiddleNode(LNode * pHead)
{
    if(pHead == NULL || pHead->m_pNext == NULL) // 链表为空或只有一个结点,返回头指针
        return pHead;

    L * pAhead = pHead;
    L * pBehind = pHead;
    while(pAhead->m_pNext != NULL) // 前面指针每次走两步,直到指向最后一个结点,后面指针每次走一步
    {
        pAhead = pAhead->m_pNext;
        pBehind = pBehind->m_pNext;
        //防止表尾,内存溢出.走了两步
        if(pAhead->m_pNext != NULL)
            pAhead = pAhead->m_pNext;
    }
    return pBehind; // 后面的指针所指结点即为中间结点
}

已知两个单链表pHead1 和pHead2 各自有序,把它们合并成一个链表依然有序

// 合并两个有序链表
ListNode * MergeSortedList(ListNode * pHead1, ListNode * pHead2)
{
    //如果head1为空,返回head2
    if(pHead1 == NULL)
        return pHead2;
    //如果head2为空,返回head1,这些处理防止后面访问链表中元素时,内存溢出
    if(pHead2 == NULL)
        return pHead1;
    ListNode * pHeadMerged = NULL;
    //比较大小,确定合并链表的表头指向谁。
    if(pHead1->m_nKey < pHead2->m_nKey)
    {
        pHeadMerged = pHead1;
        pHeadMerged->m_pNext = NULL;
        pHead1 = pHead1->m_pNext;
    }
    else
    {
        pHeadMerged = pHead2;
        pHeadMerged->m_pNext = NULL;
        pHead2 = pHead2->m_pNext;
    }
    ListNode * pTemp = pHeadMerged;
    //开始向合并链表中添加节点
    while(pHead1 != NULL && pHead2 != NULL)
    {
        if(pHead1->m_nKey < pHead2->m_nKey)
        {
            //head1中元素小,通过尾插法,将小的结点先放在链表尾。
            //将head1插入链表中
            pTemp->m_pNext = pHead1;
            //head1下移,继续循环
            pHead1 = pHead1->m_pNext;
            //让temp始终指向表尾,也是就next
            pTemp = pTemp->m_pNext;
            //表尾设置为null.
            pTemp->m_pNext = NULL;
        }
        else
        {
            pTemp->m_pNext = pHead2;
            pHead2 = pHead2->m_pNext;
            pTemp = pTemp->m_pNext;
            pTemp->m_pNext = NULL;
        }
    }
    if(pHead1 != NULL)
        pTemp->m_pNext = pHead1;
    else if(pHead2 != NULL)
        pTemp->m_pNext = pHead2;
    return pHeadMerged;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值