数据结构2-单向链表

摘要:本文详细介绍了链表的 11 种常见操作实现方法,包括链表元素查找、链表节点修改、尾插法插入节点、链表销毁、查找中间节点、查找倒数第 k 个节点、删除未知头节点的中间节点、链表倒置、链表冒泡排序、链表选择排序以及判断链表是否有环及环相关操作。每种操作均包含步骤说明和代码实现,涵盖了链表的基础操作与高级应用。

一、链表元素的查找

        在链表中找到指定的第一个元素

1、步骤:

  1. 沿用遍历思想,每次访问一个节点元素判断是否为要找的节点
  2. 符合条件返回该节点地址
  3. 到最后依旧没有找到符合条件的节点,返回NULL

2、代码实现:

1)函数体内容:

/* 找到符合要求的第一个元素节点地址 */
linknode *find_linklist(linknode *phead, datatype tmpdata)
{
        linknode *ptmpnode = NULL;


        ptmpnode = phead->pnext;
        while (ptmpnode != NULL)
        {
                if (ptmpnode->data == tmpdata)
                {
                        return ptmpnode;
                }
                ptmpnode = ptmpnode->pnext;
        }


        return NULL;
}

2)main函数中的调用

// 返回值是指针地址,需要在main函数中设置一个变量去接收这个值

int main(void)

{

        linknode *p = NULL;

        p = find_linklist(plinklist);

        printf("%p\n",p);

        return 0;

}

二、链表的修改

1、步骤:沿用遍历思想,找到符合条件的元素修改为新的值

2、代码实现:

1)函数体内容:

/* 将符合条件的旧值修改为新值 */
int update_linklist(linknode *phead, datatype olddata, datatype newdata)
{
        linknode *ptmpnode = NULL;


        ptmpnode = phead->pnext;
        while (ptmpnode != NULL)
        {
                if (ptmpnode->data == olddata)
                {
                ptmpnode->data = newdata;
                 }
                ptmpnode = ptmpnode->pnext;
        }
        return 0;

}

2)main函数中的调用:

// 直接在main函数中调用,调用完后调用show_linklist函数就能看见结果

三、链表的尾插法

        在链表末尾插入一个元素

1、步骤:

  1. 申请节点空间
  2. 将数据存放到节点中
  3. 将节点中地址赋值为NULL
  4. 找到最后一个节点
  5. 最后一个节点的pnext赋值为新申请节点

2、代码实现:

1)函数体内容:

/* 尾插法插入节点元素 */
int insert_tail_linklist(linknode *phead, datatype tmpdata)
{
        linknode *ptmpnode = NULL;
        linknode *plastnode = NULL;


        //申请节点
        ptmpnode = malloc(sizeof(linknode));
        if (NULL == ptmpnode)
        {
                perror("fail to malloc")

                return -1;
        }


        //将节点空间中的元素赋值
        ptmpnode->data = tmpdata;
        ptmpnode->pnext = NULL;


        //找到最后一个节点元素
        plastnode = phead;
        while (plastnode->pnext != NULL)
        {
                plastnode = plastnode->pnext;
        }


        //将最后一个节点的pnext指向新申请节点
        plastnode->pnext = ptmpnode;
        return 0;
}

2)注意事项:

        如若是第一个插入链表的数据,其前一个phead->pnext是一个空(null),无法链接后面,所以就直接设置将找到的最后一个链表节点直接指向下一个指针( plastnode = phead;

        这样无论是刚开始第一次使用尾插法还是在有链表节点元素后面插入都可以实现。

四、链表的销毁

        将所有的链表节点空间都释放掉

1、步骤:

  1. 定义两个指针pfreenode和ptmpnode都指向头结点
  2. ptmpnode向后走
  3. 再释放pfreenode指向的节点
  4. 再将pfreenode指向ptmpnode指向的空间

2、代码实现:

1)函数体内容:

/* 链表的销毁 */
int destroy_linklist(linknode **pphead)
{
        linknode *pfreenode = NULL;
        linknode *ptmpnode = NULL;


        ptmpnode = *pphead;
        pfreenode = *pphead;
        while (ptmpnode != NULL)
        {
                ptmpnode = ptmpnode->pnext;
                free(pfreenode);
                pfreenode = ptmpnode;
         }
        *pphead = NULL;


        return 0;

}

2)注意事项:

        链表的节点空间释放完我们需要将主函数的指针初始化(令其等于NULL),也要将该指针指向的空间地址也初始化。所以我们在传参数的时候需要给主函数的指针取地址,传二级指针过去。

main.c  destroy_linklist(&plinklist);

五、查找链表中间节点

1、步骤:

  1. 设置两个指针变量指向phead
  2. 快指针每次走2步,慢指针每次都1步
  3. 快指针走到末尾,慢指针走到中间

2、代码实现:

/* 查找链表中间节点 */
linknode *find_midnode(linknode *phead)
{
        linknode *pslow = NULL;
        linknode *pfast = NULL;


        pslow = pfast = phead->pnext;
        while (pfast != NULL)
        {
                pfast = pfast->pnext;
                if (NULL == pfast)
                {
                        break;
                }


                pfast = pfast->pnext;
                if (NULL == pfast)
                {
                        break;
                }
                pslow = pslow->pnext;
        }


        return pslow;
}

六、查找链表倒数第k个节点

1、步骤:

  1. 快指针先走k步
  2. 慢指针和快指针每次走一步
  3. 快指针到达末尾,慢指针少走k步,即倒数第k个元素

2、代码实现:

/* 查找链表倒数第k个节点 */
linknode *find_last_kth_node(linknode *phead, int k)
{
        int i = 0;
        linknode *pfast = NULL;
        linknode *pslow = NULL;

        pfast = phead->pnext;
        for (i = 0; i < k && pfast != NULL; i++)
        {
                pfast = pfast->pnext;
        }


        if (NULL == pfast)
        {
        return NULL;
        }


        pslow = phead->pnext;
        while (pfast != NULL)
        {
        pfast = pfast->pnext;
        pslow = pslow->pnext;
        }


        return pslow;
}

七、节点数据覆盖法 - 删除未知头节点地址的链表中间节点

1、步骤:

  1. 将指针指向的下一个节点的值覆盖当前节点的值
  2. 删除下一个节点

2、代码实现:

/* 删除指定节点 */
int delete_linknode(linknode *ptmpnode)
{
        linknode *pnextnode = NULL;


        pnextnode = ptmpnode->pnext;
        ptmpnode->data = pnextnode->data;
        ptmpnode->pnext = pnextnode->pnext;
        free(pnextnode);


        return 0;
}

八、链表倒置

1、步骤:

  1. 将原链表断开
  2. 将所有的元素依次使用头插法插入

2、代码实现

/* 链表倒置 */
int reverse_linklist(linknode *phead)
{
        linknode *pinsertnode = NULL;
        linknode *ptmpnode = NULL;  


        //将链表从头结点处断开
        ptmpnode = phead->pnext;
        phead->pnext = NULL;


        //依次将所有元素使用头插法插入链表中
        while (ptmpnode != NULL)
        {
                pinsertnode = ptmpnode;
                ptmpnode = ptmpnode->pnext;
                pinsertnode->pnext = phead->pnext;
                phead->pnext = pinsertnode;
        }


        return 0;
}

九、链表排序 - 冒泡排序

1、步骤:

  1. 采用冒泡排序思想,定义两个指针,相邻两个元素比较
  2. 指针循环向后走,直到ptmpnode2为NULL,即等于pend,循环停止
  3. pend赋值为tmpnode1的节点地址
  4. 下一轮就可以少比1次
  5. 循环将所有大的元素找到,剩余一个小的元素即可

2、代码实现:

/* 链表的冒泡排序 */
int bubble_sort_linklist(linknode *phead)
{
        linknode *ptmpnode1 = NULL;
        linknode *ptmpnode2 = NULL;
        linknode *pend = NULL;
        datatype tmpdata ;                //不初始化是因为是想通用性使用
        if (NULL == phead->pnext || NULL == phead->pnext->pnext )
        {
        return 0;
        }


        while (1)
        {
                ptmpnode1 = phead->pnext;
                ptmpnode2 = phead->pnext->pnext;
                if (pend == ptmpnode2)
                {
                        break;
                }


                while (ptmpnode2 != pend)
                {
                        if (ptmpnode1->data > ptmpnode2->data)
                        {
                                tmpdata = ptmpnode1->data;
                                ptmpnode1->data = ptmpnode2->data;
                                ptmpnode2->data = tmpdata;
                        }
                        ptmpnode1 = ptmpnode1->pnext;
                        ptmpnode2 = ptmpnode2->pnext;
                }
                pend = ptmpnode1;
        }
        return 0;
}

十、链表排序 - 选择排序

1、步骤:

  1. pswapnode指向要交换的节点
  2. pminnode假设的最小值
  3. ptmpnode和后续节点比较

2、代码实现:

/* 链表的选择排序 */
int select_sort_linklist(linknode *phead)
{
        linknode *pswapnode = NULL;
        linknode *ptmpnode = NULL;
        linknode *pminnode = NULL;
        datatype tmpdata;


        if (NULL == phead->pnext || NULL == phead->pnext->pnext)
        {
        return 0;
        }
        pswapnode = phead->pnext;


        while (pswapnode->pnext != NULL)
        {
                pminnode = pswapnode;
                ptmpnode = pswapnode->pnext;


                while (ptmpnode != NULL)
                {
                        if (ptmpnode->data < pminnode->data)
                        {
                                pminnode = ptmpnode;
                        }
                        ptmpnode = ptmpnode->pnext;
                }


                if (pswapnode != pminnode)
                {
                        tmpdata = pswapnode->data;
                        pswapnode->data = pminnode->data;
                        pminnode->data = tmpdata;
                }
                pswapnode = pswapnode->pnext;
        }


        return 0;
}

十一、判断链表是否有环

         判断链表是否有环、计算环的环长、找到环入口的位置

1、步骤:

  1.  链表是否有环:
    1. 定义两个指针:快指针(每次走2步)和慢指针(每次走1步)
    2. 快指针 - 慢指针 == 环长 即相遇,快指针和慢指针相等即为链表有环
  2. 计算环长:
    1. 定义一个指针从环相遇点开始走一圈,直到走该该节点为止
    2. 每走一个节点计数,最终可得到环长
  3. 获得环的入口位置:
    1. 公式推导出:a = c + (n-1)*l
    2. 定义一个指针从相遇点开始每次走一步,定义一个指针从开头每次走一步
    3. 两个指针相遇的位置即为环入口位置

2、代码实现

/* 1.判断链表是否有环
2.计算环长
3.找到环的入口位置
*/
int circle_linklist(linknode *phead, int *pis_circle, int *pcirlen, linknode **ppnode)

{
        linknode *pfast = NULL;
        linknode *pslow = NULL;
        linknode *ptmpnode = NULL;
        linknode *pstartnode = NULL;
        int cnt = 1;


        /* 判断是否有环 */
        pfast = phead->pnext;
        pslow = phead->pnext;
        while (1)
        {
                pfast = pfast->pnext;
                if (NULL == pfast)
                {
                        break;
                }


                pfast = pfast->pnext;
                if (NULL == pfast)
                {
                        break;
                }


                pslow = pslow->pnext;
                if (pfast == pslow)
                {
                        break;
                }
        }
        if (NULL == pfast)
        {
                *pis_circle = 0;
                return 0;
        }
        else
        {
                *pis_circle = 1;
        }


        /* 统计环长 */
        ptmpnode = pslow->pnext;
        while (ptmpnode != pslow)
        {
                cnt++;
                ptmpnode = ptmpnode->pnext;
        }
        *pcirlen = cnt;


        /* 找到环入口 */
        pstartnode = phead->pnext;
        ptmpnode = pslow;
        while (pstartnode != ptmpnode)
        {
                pstartnode = pstartnode->pnext;
                ptmpnode = ptmpnode->pnext;
        }

        *ppnode = ptmpnode;


        return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值