【数据结构】二、单链表的基本操作(C语言)

目录

引用头指针的好处: 

1.结点的定义和初始化单链表

2.判断单链表是否为空表 

3.销毁单链表 

4.清空单链表,头结点和头指针还在

5.求单链表表长

6.取单链表中指定位置的数据

7.按值查找,返回数据所在的地址,时间复杂度为O(n)​编辑

8.按值查找,返回数据所在位置序号,时间复杂度为O(n)

9.在第pos个元素之前插入数据元素e

10.删除第i个结点,时间复杂度为O(1),因为不需要移动结点,只需修改指针

11.单链表的建立:头插法

12.单链表的建立:尾插法

13.合并两个链表 


引用头指针的好处: 

1.使得在链表的第一个位置上的操作和在表的其他位置上的操作一致。

2.空表和非空表的处理一致。 

1.结点的定义和初始化单链表

#include <stdio.h>

//定义单链表的结点
typedef struct Lnode{    //node是结点的英文
    Elemtype data;    //存放数据,Elemtype是虚拟的类型,真实情况这里可以是int、float等类型
    struct Lnode* next;    //存放指向下一个结点的指针,而这个指针的类型是struct Lnode*型
}Lnode    //将Lnode定义为struct Lnode型
typedef struct Lnode* Linklist;    //Linklist表示指向struct Lnode这个结构体的指针
//L链表可以用Linklist L或Lnode *L表示, 指向某个结点的指针可以用Lnode *p或Linklist p表示



//初始化单链表,L是一个指向单链表的指针
void initlist(Linklist L)
{
    L = (Linklist)malloc(sizeof(Lnode)); //头结点的类型为Lnode,头指针的类型为Linklist
    L->next = NULL;
}
//总结初始化:
//1.生成一个头结点(分配空间),将头指针L指向头结点,也就是头指针L赋值为头结点的地址
//2.将头结点的指针域赋值为空NULL

2.判断单链表是否为空表 

//判断单链表是否为空表
int is_Emptylist(Linklist L)
{
    //L指向的是头结点,头结点的指针域(L->next)为空,没有下一个结点,就是空表
    if(L->next == 0)    
        return 0;    //是空表
    else
        return 1;    //不是空表
}

3.销毁单链表 

//销毁单链表
void destroylist(Linklist L)
{
    Lnode* p;    //定义一个指针p来指向结点,而结点是Lnode型,所以p的类型为Lnode *
    while(L != NULL)    //NULL是最后一个结点的指针域,所以当L为NULL时表示最后一个结点已经销毁了
    {
        p = L;    //将L的值赋给p,这样p也指向了L指向的结点
        L = L->next;    //将下一个结点的地址放在当前结点的指针域(L->next)里,这样L就指向了下一个结点
        free(p);    //销毁p指向的结点
    }
}
//总结销毁单链表:
//1.引入一个指针p来指向结点
//2.将L的值赋给p
//3.将L指向下一个结点
//4.销毁p指向的结点
//5.将L的值赋给p,此时L不再指向头结点,而是下一个结点,所以p指向了下一个结点
//6.将L指向下一个结点
//重复p指向L结点,L指向下一个结点,销毁p指向的结点,p指向L结点.......直到L为NULL

4.清空单链表,头结点和头指针还在

//清空单链表,头结点和头指针还在
void clearlist(Linklist L)
{
    Lnode *p,*i;    //引入两个指针变量指向结点,p指向当前要清除的结点,i指向p的下一个结点
    p = L->next;    //将p指向首元结点,从首元结点开始清空,因为要保留头结点
    while(p != NULL)    /*最后一个结点时,p->next(为NULL)赋给i,最后一个结点清空后
                          i(为NULL)赋给p,这样就表示整个表就清空了*/
    {
        i = p->next;    //i指向了下一个结点,这样上一个结点就可以清除了
        free(p);    //清除结点
        p = i;    //将p也指向i指向的结点
    }
    L->next = NULL;    //将头结点的指针域设为NULL
}
//总结清空单链表
//1.将p指向首元结点(头结点的下一个结点)
//2.将p结点的指针域(next)赋给i指针,使i指向p的下一个结点
//3.清空p指针指向的结点
//4.将i赋给p,使p指向i指向的结点
//5.将p结点的指针域赋给i指针,使i再指向下一个结点,以此重复到p为NULL
//6.将L指向的结点,也就是头结点的指针域设为NULL

5.求单链表表长

//求单链表表长
int listlength(Linklist L)
{
    Lnode* p;
    int i = 0;    //用来计数,表示有几个结点,记得赋初值为0
    p = L->next;    //L->next指向的是首元结点,所以p指向首元结点
    while(p != NULL)    
    {
        i++;    //第一次进循环,p已经是L->next了,如果p为null就代表没有首元结点,不会进入循环,此时i为0。
        p = p->next;    //将p指向下一个结点
    }
    return i;    //i就是表长
}

6.取单链表中指定位置的数据

//取单链表中指定位置的数据
Elemtype GetElemList(Linklist L, int pos) 
{
    Lnode* p;
    int k = 1;    //k当做计数,初值为1,因为pos不是下标,所以不能为0
    p = L->next;    //从首结点开始找
    while((p != NULL)&&(k<pos))    //p为NULL退出循环表示链表找完了也没找到或者链表是空的。k等于pos时退出循环,表示找到了
    {
        p = p->next;
        k++;
    }
    if((p == NULL)||(k>pos))    //k>pos代表pos为0或负数,因为k初值为1。
        return ERROR;    //找不到
    return p->data;    //找到了会跳出循环,直接执行此语句将找到的值return回去。没找到会执行上面的if语句,不会执行此语句
}

7.按值查找,返回数据所在的地址,时间复杂度为O(n)

//按值查找,返回数据所在的地址
Lnode* SearchPos(Linklist L, Elemtype e)
{
    Lnode* p;
    p = L->next;    //从首元结点开始找
    while((e != p->data)&&(p != NULL))
    {   
        p = p->next;
    }
    return p;  //若找不到,就意味着p为NULL,此时返回NULL。找到了,返回的p就是数据所在地址
}

8.按值查找,返回数据所在位置序号,时间复杂度为O(n)

//按值查找,返回数据所在位置序号
int SearchNum(Linklist L, Elemtype e)
{
    Lnode* p;
    int k = 1;    //用来计数
    p = L->next;    //从首元结点开始找
    while((p != NULL)&&(e != p->data))
    {
        p = p->next;
        k++;
    }
    if(e == p->data)
        return k;    //找到了,返回位置序号
    else
        return ERROR;    //else就是p为NULL,找不到
}

9.在第pos个元素之前插入数据元素e

 找到第pos-1个结点的时间复杂度为O(n),但之后,不管在第pos-1位置插入几个元素,时间复杂度都为O(1)。

//在第pos个元素之前插入数据元素e
void insertlist(Linklist L, Elemtype e, int pos)
{
    int j = 1;    //用来计数
    Lnode* p, *s;    //s用来指向新加入的结点
    p = L;    //从头结点开始,因为如果是在第1个元素之前插入,就应该是在头结点的后面插入
    while((p != NULL)&&(j<pos))    /* j=pos时循环结束,此时表示找到了,且p指向的是pos的前一个结点。或者,当p=NULL时
                                     循环结束,表示找不到*/
    {
        p = p->next;    //找下一个结点
        j++;
    }
    if((p == NULL)||(j>pos))    /* pos不能为0或负数,所以j>pos是return ERROR。且pos不能大于表长,当pos大于表长的情况
                                  就是循环结束后p==NULL,此时应return ERROR*/
        return ERROR;    
    s = (Linklist)malloc(sizeof(Lnode));    //给新插入的结点开辟空间,用来存放e
    s->data = e;    //将e存放在新结点的数据域里
    s->next = p->next;    //将s的指针域指向原来的下一个结点
    p->next = s;    //将p的指针域指向新的结点
}

10.删除第i个结点,时间复杂度为O(1),因为不需要移动结点,只需修改指针

找到第i-1个结点的时间复杂度为O(n),但之后不管插入几个元素,时间复杂度为O(1)。

void DeleteNode(Linklist L, int i)
{
    Lnode *p, *s;
    int j = 0;
    p = L;
    while((p != NULL)&&(j<i-1))    //找第i-1个结点
    {
        p = p->next;
        j++;
    }
    if((p == NULL)||(j>i-1))
        return ERROR;
    s = p->next;    //循环退出后p指向i-1个结点。这里是将第i个结点的地址暂存到s里
    p->next = s->next;    //将p指向的结点里面存下一个结点的地址
    free(s);    //删除s指向的结点
}

11.单链表的建立:头插法

时间复杂度为O(n)

void createlist_H(Linklist L, int n)    //n为创建的结点个数
{
    int i;
    Lnode* p;
    L = (Linklist)malloc(sizeof(Lnode));    //给头指针分配空间,也就是创建头结点
    L->next = NULL;    //最开始是空表,所以没有首元结点的地址L->next为NULL
    for(i = n; i>0; i--)    //循环n次,创建n个结点
    {
        p = (Lnode*)malloc(sizeof(Lnode));    //p作为新结点
        scanf("%d",&p->data);    //让用户输入数据
        p->next = L->next;    //将L后面的结点(头结点后的结点)连在p的后面
        L->next = p;    //将p插在头部作为首元结点
    }
}

12.单链表的建立:尾插法

时间复杂度为O(n)

void createlist_R(Linklist L, int n)
{
    Lnode *r, *p;    //r始终指向最后一个结点。p始终作为尾结点,插入到尾部
    int i;
    L = (Linklist)malloc(sizeof(Lnode));
    L->next = NULL;
    r = L;    //首先是空表,最后一个结点也就是头结点,所以r=L
    for(i = 0; i < n; i++)    //循环n次
    {
        p = (Lnode*)malloc(sizeof(Lnode));    //开辟新结点
        scanf("%d", &p->data);    //用户输入数据
        p->next = NULL;    //p作为尾结点所以指针域为NULL
        r->next = p;    //将p插入到当前链表的尾部
        r = p;    //r始终指向尾结点
    }
}

13.合并两个链表 

Linklist mergeTwoLists(Linklist list1, Linklist list2) {
    //创建一个新链表,此链表是带头结点的链表,list1和list2不是
    Linklist list3 = new ListNode();
    //创建一个新结点来指向链表3
    ListNode* p3 = list3;
    //当list1遍历完或list2遍历完时,循环退出
    while ((list1 != nullptr) && (list2 != nullptr))
    {
        //list1的元素小
        if (list1->val <= list2->val)
        {
            //将list1指向的结点插入链表3
            p3->next = list1;
            //将指向链表3的结点指向下一个结点
            p3 = p3->next;
            //list1指向下一个结点
            list1 = list1->next;
        }
        //list2的元素小
        else
        {
            //将list2指向的结点插入链表3
            p3->next = list2;
            //将指向链表3的结点指向下一个结点
            p3 = p3->next;
            //list2指向下一个结点
            list2 = list2->next;
        }
    }
    //循环结束,表示有一个链表已经遍历完了,而且因为是有序链表,所以直接将另一个链表的剩下的结点插入到链表3里
    //或者用三目运算符:list3->next = p1 ? p1 : p2;
    if (list1 == nullptr)
    {
        p3->next = list2;
    }
    else
    {
        p3->next = list1;
    }
    return list3->next;
}
  • 45
    点赞
  • 249
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
单链表是一种常见的数据结构,用于存储一系列的元素。在C语言中,可以通过定义一个结构体来表示单链表的节点,每个节点包含一个数据域和一个指针域,指向下一个节点。 首先,我们需要定义单链表的节点结构体: ```c struct ListNode { int data; // 数据域 struct ListNode* next; // 指针域,指向下一个节点 }; ``` 接下来,我们可以实现一些基本操作,比如创建单链表、插入节点、删除节点和遍历单链表等。 创建单链表的函数如下所示,可以根据给定的数组创建一个单链表: ```c struct ListNode* createLinkedList(int arr[], int size) { struct ListNode* head = NULL; // 头节点指针 struct ListNode* tail = NULL; // 尾节点指针 for (int i = 0; i < size; i++) { struct ListNode* newNode = (struct ListNode*)malloc(sizeof(struct ListNode)); newNode->data = arr[i]; newNode->next = NULL; if (head == NULL) { head = newNode; tail = newNode; } else { tail->next = newNode; tail = newNode; } } return head; } ``` 插入节点的函数如下所示,可以在指定位置插入一个新节点: ```c void insertNode(struct ListNode** head, int data, int position) { struct ListNode* newNode = (struct ListNode*)malloc(sizeof(struct ListNode)); newNode->data = data; newNode->next = NULL; // 插入到链表头部 if (position == 0) { newNode->next = *head; *head = newNode; return; } struct ListNode* curr = *head; struct ListNode* prev = NULL; int count = 0; // 找到插入位置的前一个节点 while (curr != NULL && count < position) { prev = curr; curr = curr->next; count++; } // 插入到链表中间或尾部 if (curr != NULL) { newNode->next = curr; } prev->next = newNode; } ``` 删除节点的函数如下所示,可以删除指定位置的节点: ```c void deleteNode(struct ListNode** head, int position) { if (*head == NULL) { return; } struct ListNode* curr = *head; struct ListNode* prev = NULL; int count = 0; // 找到要删除的节点 while (curr != NULL && count < position) { prev = curr; curr = curr->next; count++; } // 删除头节点 if (prev == NULL) { *head = curr->next; } else { prev->next = curr->next; } free(curr); } ``` 遍历单链表的函数如下所示,可以将单链表中的元素依次输出: ```c void traverseLinkedList(struct ListNode* head) { struct ListNode* curr = head; while (curr != NULL) { printf("%d ", curr->data); curr = curr->next; } } ``` 以上是单链表基本操作实现,你可以根据需要调用这些函数进行单链表的操作。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值