手机版
你好,游客 登录 注册 搜索
背景:
阅读新闻

单链表的基本操作大全之C语言实现

[日期:2018-01-29] 来源:Linux社区  作者:men_wen [字体: ]

单链表的基本操作大全之C语言实现

1. 单链表的定义

链表是通过一组任意的存储单元来存储线性表中的数据元素,这些存储单元可以是连续的也可以是不连续的。为了建立起数据元素之间的关系,对于每个数据元素除了存放数据元素自身的信息外,还必须有包含的指示该元素直接后继元素存储位置的信息,这两部分信息组成一个结点,即每个结点都有至少包括两个域,一个域存储数据元素信息,称为数据域,另一个域存储直接后继的地址,称为指针域

    typedef struct node{
        int data;    //数据域
        struct node *next;    //指针域
    }NODE;

当n个元素的线性表通过每个结点的指针域连接成了一条“链子”,我们形象的称之为链表。

2. 建立单链表

建立单链表有两种方法,一种是头插法,一种是尾插法。
在主函数中调用函数时,使用一个NODE *类型的指针接收函数的返回值。

2.1初始化一个链表结点

每次malloc的结点都要初始化,因此写成一个函数。

    //初始化一个节点
    NODE *initnode(NODE *pnode, int data)
    {
            pnode = (NODE *)malloc(sizeof(NODE));
            pnode->data = data;//初始化数据域
            pnode->next = NULL;//初始化指针域为NULL

            return pnode;
    }

2.2头插法建立链表

在链表的头部插入结点建立单链表,称为“头插法”。

    //创建一个新节点(头插法)
    NODE *createlink_byhead(NODE *phead, int data)
    {
            NODE *pnode = initnode(pnode, data);//初始化一个结点
            NODE *ptmp = phead;

        if(NULL == phead){    //链表为空,直接返回初始化的结点
                return pnode;
        }else{
                phead = pnode;    //将新申请的结点插在phead后面,也就是整个链表的最前面
                pnode->next = ptmp;
        }

        return phead;
}

因为每次新结点都会插在链表的头部,则数据读入的顺序和线性表中的逻辑顺序正好相反。

2.3尾插法建立链表

在链表的尾部插入结点建立单链表,简称”尾插法“。

    //创建一个链表节点(尾插法)
    NODE *createlink_bytail(NODE *phead, int data)
    {
            NODE *pnode = initnode(pnode, data);//初始化结点
            NODE *ptmp = phead;

        if(NULL == phead){//当链表为空,直接返回初始化的结点
                return pnode;
        }else{
                while(ptmp->next != NULL){//找到最后一个结点,插在尾部
                        ptmp = ptmp->next;
                }
                ptmp->next = pnode;
        }

        return phead;
    }

尾插法读入的数据与线性表中的逻辑顺序相同

3. 链表的长度

    //求链表长度
    int linklen(NODE *phead)
    {
            int len = 0;//计数器
            NODE *ptmp = phead;

            while(ptmp != NULL){//遍历链表,计数器加1
                    len++;
                    ptmp = ptmp->next;
            }

            return len;
    }

这个算法的时间复杂度为O(n)

4. 按值查找操作

   //按值查找
    NODE *searchnode(NODE *phead, int key)
    {
        NODE *ptmp = phead;

        if(NULL == phead){    //链表为空,直接返回
                return NULL;
        }
        while(ptmp->data != key && ptmp->next != NULL){//没找到key && 链表没找完
                ptmp = ptmp->next; //指针移动
        }
        if(ptmp->data == key) //找到key,返回该结点地址
                return ptmp;
        if(ptmp->next == NULL)//没找到,返回空
                return NULL;
    }

这个算法的时间复杂度为O(n)

5.插入操作

插入操作也分为两种,一种是插在目标值得前面,简称“前插法”,另一种是插在目标值的后面,简称“后插法”。

5.1前插法

 //向前插入
    NODE *insertnode_bypre(NODE *phead, int data, int key)
    {
        NODE *pnode = initnode(pnode, data);//初始化插入的结点
        NODE *ptmp = phead;

        if(NULL == phead){//链表为空,直接返回初始化的结点
                return pnode;
        }else if(phead->data == key){//处理第一个结点是否为目标结点
                phead = pnode;
                pnode->next = ptmp;
        }else{
                while(ptmp->next != NULL && ptmp->next->data != key){//循环遍历,没找完 && 没找到
                        ptmp = ptmp->next;
                }
                if(NULL == ptmp->next){//没找到的情况
                        printf("insert key NOT FOUND\n");
                }else{//把新节点插入目标结点的前面
                        ptmp->next->data == key;
                        pnode->next = ptmp->next;
                        ptmp->next = pnode;
                }
        }
        return phead;
    }

前插法单独处理了第一个结点,是因为前插法会改变phead的值,而且前插法查找目标结点必须知道目标节点的前驱结点的next值,所以从第二个结点开始处理。

5.2后插法

   //向后插入
    NODE *insertnode_byback(NODE *phead, int data, int key)
    {
        NODE *pnode = initnode(pnode, data);//初始化插入的结点
        NODE *ptmp = searchnode(phead, key);//查找到目标结点

        if(NULL == ptmp){//处理链表为空,或没找到
                printf("link is empty or not found key\n");
                return phead;
        }
        if(NULL == ptmp->next){//如果key为最后一个结点
                ptmp->next = pnode;
        }else{ //将新节点插入在目标结点的后面
                pnode->next = ptmp->next;
                ptmp->next = pnode;
        }

        return phead;
}

后插法要单独处理尾结点的情况,因为插入操作不相同。
前插法和后插法的时间复杂度都为O(n)

6.删除操作

  //删除节点
    NODE *delnode(NODE *phead, int key)
    {
        NODE *ptmp = phead;
        NODE *tmp = NULL;

        if(NULL == phead){//处理链表为空的情况
                printf("link is empty, del fail\n");
                return NULL;
        }else if(phead->data == key){//单独处理第一个结点
                phead = phead->next;
                free(ptmp);
                ptmp = NULL;
        }else{
                while(ptmp->next != NULL && ptmp->next->data != key){ //没找完 && 没找到
                        ptmp = ptmp->next;
                }
                if(NULL == ptmp->next){ //没找到
                        printf("del key NOT FOUND\n");
                        return phead;
                }
                if(ptmp->next->data == key){ //找到目标结点删除之
                        tmp = ptmp->next;
                        ptmp->next = tmp->next;
                        free(tmp);
                        tmp = NULL;
                }
        }
        return phead;
}

单独处理第一个结点还是因为要知道要删除目标结点的前驱结点
删除操作算法时间复杂度为O(n)

7.链表的倒置

链表的倒置算法思路:一次取出原链表中的每一个结点,将其作为第一个结点插入新链表中。

  //链表的倒置
    NODE *reverselink(NODE *phead)
    {
        NODE *ptmp = NULL;//创建一个新链
        NODE *tmp = NULL;

        if(NULL == phead){//处理链表为空
                printf("link is empty\n");
                return NULL;
        }else{
                while(phead != NULL){//将旧链上的结点链到新链上
                        tmp = phead;
                        phead = phead->next;
                        tmp->next = ptmp;
                        ptmp = tmp;
                }
        }
        return ptmp;
}

8.链表的排序(冒泡排序)

    //链表排序(冒泡)
    NODE *linksort(NODE *phead)
    {
        NODE *p = NULL; //i
        NODE *q = NULL; //j
        NODE *r = NULL; 
        NODE tmp; 

        for(p = phead; p; p = p->next){
                for(q = p->next; q; q = q->next){
                        if(p->data > q->data){
                                tmp = *p;//整个结点交换
                                *p = *q;
                                *q = tmp;
                                r = p->next;//将指针换回去
                                p->next = q->next;
                                q->next = r;
                        }
                }
        }

        return phead;
    }

冒泡排序的时间复杂度为O(n²)

下面是补充:

1. 递归打印链表

void show_linklist(PNODE *phead) 
{
        if(phead == NULL){
                return;
        }else{
                printf("%d\t", phead->data);
                show_linklist(phead->pnext);
                //将此行放在printf的前面则递归打印倒置链表
        }
}

2.链表快速排序

PNODE *divide_two_Qsort(PNODE *phead, PNODE *ptail)
{
        int key = phead->data;
        PNODE *p = phead;
        PNODE *q = phead->pnext;

        while(q != ptail){ 
                if(q->data < key){
                        p = p->pnext;
                        int tmp;
                        tmp = p->data;
                        p->data = q->data;
                        q->data = tmp;
                }       
                q = q->pnext;
        }
        int tmp = p->data;
        p->data = phead->data;
        phead->data = tmp;

        return p;
}

void quicksort_linklist(PNODE *phead, PNODE *ptail)
{
        if(phead != ptail){
                PNODE *pmid = divide_two_Qsort(phead, ptail);
                quicksort_linklist(phead, pmid);
                quicksort_linklist(pmid->pnext, ptail);
        }
}

3. 递归计算链表长度

int getlistlen_recursion(PNODE *phead)
{
        if(phead == NULL)
                return 0;
        else
                return 1+getlistlen_recursion(phead->pnext);
}

4.反转链表(非递归)

PNODE *invert_linklist(PNODE *phead)
{
        if(phead == NULL || phead->pnext == NULL){
                return phead;
        }else{
                PNODE *p = phead;
                PNODE *q = NULL;
                PNODE *r = NULL;
                while(p != NULL){
                        q = p->pnext;//保存下一个节点
                        p->pnext = r;//让该节点指向上一个节点
                        r = p;//上一个节点走到当前节点
                        p = q;//当前节点走到下一个节点
                }
                return r;
        }
}

5.反转链表(递归)

PNODE *invert_linklist_recursion(PNODE *phead)
{
        if(phead == NULL || phead->pnext == NULL){
                return phead;
        }else{
                PNODE *p = phead;//保存头节点
                PNODE *q = p->pnext;//保存下一个节点
                phead = invert_linklist_recursion(q);//递归到最后一个几点,返回转置后链表的地址
                q->pnext = p;//让后面的节点指向前一个节点
                p->pnext = NULL;//每次递归返回都赋值为空,最后一次返回将转置后的节点的pnext赋值为空
                return phead;
        }
}

6.合并两个有序链表(非递归)

PNODE *merge_linklist(PNODE *phead1, PNODE *phead2)
{
        if(phead1 == NULL){
                return phead2;
        }else if(phead2 == NULL){
                return phead1;
        }

        PNODE *phead = NULL;

        if(phead1->data < phead2->data){
                phead = phead1;
                phead1 = phead1->pnext;
        }else{
                phead = phead2;
                phead2 = phead2->pnext;
        }//让phead找到最小的头节点

        PNODE *pcur = phead;//要比较节点(phead1 或 phead2)的上一个节点
        while(phead1 != NULL && phead2 != NULL){
                if(phead1->data < phead2->data){
                        pcur->pnext = phead1;
                        pcur = phead1;
                        phead1 = phead1->pnext;
                }else{
                        pcur->pnext = phead2;
                        pcur = phead2;
                        phead2 = phead2->pnext;
                }
        }

        pcur->pnext = ((phead1 == NULL) ? phead2 : phead1);

        return phead;
}

7.合并两个有序链表(递归)

PNODE *merge_linklist_recursion(PNODE *phead1, PNODE *phead2)
{
        if(phead1 == NULL){
                return phead2;  
        }else if(phead2 == NULL){
                return phead1;
        }       
        PNODE *phead = NULL;
        if(phead1->data < phead2->data){
                phead = phead1;
                phead->pnext = merge_linklist_recursion(phead1->pnext, phead2);
        }else{  
                phead = phead2;
                phead->pnext = merge_linklist_recursion(phead1, phead2->pnext);
        }
        return phead;
}

8.找到链表中间节点

PNODE *find_midnode(PNODE *phead)
{
        if(phead == NULL || phead->pnext == NULL){
                return phead;
        }

        PNODE *p1 = phead;
        PNODE *p2 = phead;
        while(p2->pnext != NULL){
                p1 = p1->pnext;//p1走一步
                p2 = p2->pnext;
                if(p2->pnext != NULL){
                        p2 = p2->pnext;//p2走两步
                }
        }
        return p1;
}

9.判断链表是否有环

int is_circularlinklist(PNODE *phead)
{
        if(phead == NULL || phead->pnext == NULL){
                return 0;       
        }
        PNODE *p1 = phead;
        PNODE *p2 = phead;
        while(p1 != NULL && p2 != NULL){
                p2 = p2->pnext; 
                if(p1 == p2)
                        return 1;
                p2 = p2->pnext;
                p1 = p1->pnext;
        }
        return 0;
}

10.释放链表

void destory_linklist(PNODE *phead) 
{
        PNODE *ptmp;
        while(phead != NULL){
                ptmp = phead;
                phead = phead->pnext;
                free(ptmp);
                ptmp = NULL;
        }
}

本文永久更新链接地址http://www.linuxidc.com/Linux/2018-01/150641.htm

linux
相关资讯       单链表 
本文评论   查看全部评论 (0)
表情: 表情 姓名: 字数

       

评论声明
  • 尊重网上道德,遵守中华人民共和国的各项有关法律法规
  • 承担一切因您的行为而直接或间接导致的民事或刑事法律责任
  • 本站管理人员有权保留或删除其管辖留言中的任意内容
  • 本站有权在网站内转载或引用您的评论
  • 参与本评论即表明您已经阅读并接受上述条款