数据结构之单链表C语言

<>

单链表

定义单链表是由节点组成的,每个节点包含两部分,一部分是数据域,用于存储数据元素;另一部分是指针域,用于存储指向下一个节点的指针。通过指针将各个节点依次连接起来,形成一个链式结构,每个结点结点都有一个指针指向下一个结点,最后一个节点的指针通常指向空,表示链表的结束。

1.单链表结构体

c 复制代码
//单链表的结构体
struct LNode{
	ElemType data;//定义单链表结点类型
	struct LNode *next;//定义指向下一个节点 
};
typedef struct LNode LNode;
typedef struct LNode LinkList;
||
typedef struct LNode{
	ElemType data;//定义单链表结点类型
	struct LNode *next;//定义指向下一个节点 
}LNode,*LinkList;

typedef关键字--数据类型重命名

typedef<数据类型><别名>

为了增加代码的维护性对部分代码进行包装

c 复制代码
/**
 * tool function
 * 根据索引遍历链表,找到第i个节点
 * @param temp_p 链表头指针
 * @param i 要查找的位置
 * @param temp_j 输出参数,记录实际遍历到的位置
 * @return 找到的节点指针,如果位置不合法或链表为空则返回NULL
 */
LNode* traverseByIndex(LNode* temp_p, int i, int &temp_j){
    LNode* p = temp_p;
    int j = 0;
    while (p!= NULL && j < i) {
        p = p->next;
        j++;
    }
    temp_j = j;
    return p;
}

2.单链表的插入

c 复制代码
/**
 * 在单链表的第i个位置插入元素e
 * @param L 链表头指针的引用
 * @param i 插入位置(从1开始计数)
 * @param e 要插入的元素
 * @return 插入成功返回true,否则返回false
 */
bool LinkListInsert(LinkList &L, int i, ElemType e) {
    int j = 0;
    LNode* p = traverseByIndex(L, i - 1, j); // 遍历到第i-1个节点,为插入做准备
    if (p == NULL) { // 非法区域,位置i超出链表范围或链表为空
        return false;
    }
    LNode* s = (LNode*)malloc(sizeof(LNode)); // 申请要插入的结点
    s->data = e;
    s->next = p->next;
    p->next = s;
    return true; 
}

时间复杂度分析 :插入操作主要的时间开销在于找到第i - 1个节点,调用了traverseByIndex函数,最坏情况下需要遍历i - 1个节点,因此时间复杂度为O (i ),在最坏情况下(即i等于链表长度n)时间复杂度为O (n )。而插入节点本身的操作(修改指针指向)时间复杂度为O(1)。

注意\] :`p->next = s` 与 `s->next = p->next`位置不可以调换 不然会出现后面的结点丢失的情况 #### 3.单链表的删除 ![image-20250426135725444](https://i-blog.csdnimg.cn/img_convert/7604add052ea1263e62ce8b5d9b690b3.png) ```c /** * 删除单链表的第i个位置的元素,并将删除的元素值存储到e中 * @param L 链表头指针的引用 * @param i 删除位置(从1开始计数) * @param e 用于存储删除的元素值 * @return 删除成功返回true,否则返回false */ bool ListDelete(LinkList &L, int i, ElemType &e) { int j = 0; LNode* p = traverseByIndex(L, i - 1, j); if (p == NULL || j > i - 1) { // 指向空区域或i不合法,位置i超出链表范围或链表为空 return false; } LNode* q = p->next; e = q->data; p->next = q->next; free(q); return true; } ``` **时间复杂度分析** :删除操作主要的时间开销在于找到第`i - 1`个节点,调用了`traverseByIndex`函数,最坏情况下需要遍历`i - 1`个节点,因此时间复杂度为(O(i)),在最坏情况下(即`i`等于链表长度`n`)时间复杂度为(O(n))。而删除节点本身的操作(修改指针指向和释放内存)时间复杂度为(O(1))。 #### 4.获取链表元素(位置\|\|元素) ```c /** * 根据位置获取单链表中的元素 * @param L 链表头指针的引用 * @param i 要获取的元素位置(从1开始计数) * @return 找到的节点指针,如果位置不合法或链表为空则返回NULL */ LNode* GetElemByIndex(LinkList &L, int i) { int j = 0; LNode* p = traverseByIndex(L, i, j); return p; // p为NULL或者想查找的结点 } /** * 根据值获取单链表中的元素 * @param L 链表头指针的引用 * @param e 要查找的值 * @return 找到的节点指针,如果未找到则返回NULL */ LNode* GetElemByDate(LinkList &L, ElemType e) { LNode* p = L; while (p!= NULL && p->data != e) { p = p->next; } return p; // p为NULL或者想查找的结点 } ``` * `GetElemByIndex`函数:主要依赖于`traverseByIndex`函数,最坏情况下需要遍历`i`个节点,因此时间复杂度为(O(i)),在最坏情况下(即`i`等于链表长度`n`)时间复杂度为(O(n))。 * `GetElemByDate`函数:需要从链表头开始依次遍历,直到找到值为`e`的节点或者遍历到链表末尾,最坏情况下需要遍历整个链表,因此时间复杂度为(O(n))。 #### 5.头插法 顾名思义就是不断在第一个位置去插入元素 ```c /** * 头插法创建单链表 * @param L 链表头指针的引用 * @return 创建好的链表头指针 */ LinkList List_HeadInsert(LinkList &L){ LNode *s = NULL; ElemType x = 0; printf("请输入插入的元素,输入-1停止插入\n"); scanf("%d", &x); while (x!=-1) { s = (LNode*)malloc(sizeof(LNode)); s->data = x; s->next = L->next; L->next = s; scanf("%d", &x); } return L; } ``` #### 6.尾插法 顾名思义就是不断在表尾位置去插入元素 ```c /** * 尾插法创建单链表 * @param L 链表头指针的引用 * @return 创建好的链表头指针 */ LinkList List_RearInsert(LinkList &L){ LNode *s = NULL; LNode *q = L; ElemType x = 0; printf("请输入插入的元素,输入-1停止插入\n"); scanf("%d", &x); while (x!=-1) { s = (LNode*)malloc(sizeof(LNode)); s->data = x; q->next = s; s->next = NULL; q = q->next; scanf("%d", &x); } return L; } ``` #### 7.代码实现 ```c #include #include typedef int ElemType; // 单链表的结构体 typedef struct LNode { ElemType data; // 定义单链表结点类型 struct LNode *next; // 定义指向下一个节点 } LNode, *LinkList; // 单链表功能 bool InitList(LinkList &L); // 初始化单链表 int Length(LinkList L); // 求表长 LNode* GetElemByIndex(LinkList &L, int i); // 获取第i个结点 LNode* GetElemByDate(LinkList &L, ElemType e); // 根据值获得结点 bool LinkListInsert(LinkList &L, int i, ElemType e); // 在第i个结点插入 bool ListDelete(LinkList &L, int i, ElemType &e); // 删除第i个结点 LNode* traverseByIndex(LNode *temp_p, int i,int &temp_j);//temp_j:遍历到的位置 LinkList List_HeadInsert(LinkList &L);//头插法 LinkList List_RearInsert(LinkList &L);//尾插法 //遍历单链表 void traverse(LinkList L); int main() { LinkList L = NULL; ElemType e = 0; int choice, index; InitList(L); // 初始化链表 LNode* nodeByIndex = NULL; LNode* nodeByDate = NULL; LNode* p = NULL; LNode* q = NULL; while (1) { printf("\n请选择操作:\n"); printf("1. 求表长\n"); printf("2. 按位置获取元素\n"); printf("3. 按值获取元素\n"); printf("4. 插入元素\n"); printf("5. 删除元素\n"); printf("6. 遍历\n"); printf("7. 头插法\n"); printf("8. 尾插法\n"); printf("9. 退出\n"); scanf("%d", &choice); switch (choice) { case 1: printf("链表长度为:%d\n", Length(L)); break; case 2: printf("请输入要获取元素的位置:"); scanf("%d", &index); nodeByIndex = GetElemByIndex(L, index); if (nodeByIndex != NULL) { printf("位置 %d 的元素值为:%d\n", index, nodeByIndex->data); } else { printf("位置不合法或链表为空\n"); } break; case 3: printf("请输入要获取元素的值:"); scanf("%d", &e); nodeByDate = GetElemByDate(L, e); if (nodeByDate != NULL) { printf("值为 %d 的元素已找到,其值为:%d\n", e, nodeByDate->data); } else { printf("未找到值为 %d 的元素\n", e); } break; case 4: printf("请输入要插入的位置:"); scanf("%d", &index); printf("请输入要插入的元素值:"); scanf("%d", &e); if (LinkListInsert(L, index, e)) { printf("元素 %d 已成功插入到位置 %d\n", e, index); } else { printf("插入失败,位置不合法或链表为空\n"); } break; case 5: printf("请输入要删除的位置:"); scanf("%d", &index); if (ListDelete(L, index, e)) { printf("已删除位置 %d 的元素,其值为:%d\n", index, e); } else { printf("删除失败,位置不合法或链表为空\n"); } break; case 6: traverse(L); break; case 7: L = List_HeadInsert(L); break; case 8: L = List_RearInsert(L); break; case 9: // 释放链表内存 p = L; while (p != NULL) { q = p; p = p->next; free(q); } return 0; default: printf("无效的选择,请重新输入\n"); break; } } return 0; } bool InitList(LinkList &L) { L = (LNode*)malloc(sizeof(LNode));// 头结点 L->next = NULL;// 相邻节点赋值为null L->data = 0;// 头结点数据域用来记录表长度 return true; } int Length(LinkList L) { int len = 0; LNode* p = L; while (p->next != NULL) { p = p->next; len++; } return len; } LNode* GetElemByIndex(LinkList &L, int i) { int j = 0; LNode* p = traverseByIndex(L,i,j); return p; // p为NULL或者想查找的结点 } LNode* GetElemByDate(LinkList &L, ElemType e) { LNode* p = L; while (p!= NULL && p->data != e) { p = p->next; } return p; // p为NULL或者想查找的结点 } bool LinkListInsert(LinkList &L, int i, ElemType e) { int j = 0; LNode* p = traverseByIndex(L,i-1,j); if (p == NULL) { // 非法区域 return false; } LNode* s = (LNode*)malloc(sizeof(LNode));// 申请要插入的结点 s->data = e; s->next = p->next; p->next = s; return true; } bool ListDelete(LinkList &L, int i, ElemType &e) { int j = 0; LNode* p = traverseByIndex(L,i-1,j); if (p == NULL || j > i - 1) { // 指向空区域或i不合法 return false; } LNode* q = p->next; e = q->data; p->next = q->next; free(q); return true; } LinkList List_HeadInsert(LinkList &L){ LNode *s = NULL; ElemType x = 0; printf("请输入插入的元素,输入-1停止插入\n"); scanf("%d",&x); while(x!=-1){ s = (LNode*)malloc(sizeof(LNode)); s->data = x; s->next = L->next; L->next = s; scanf("%d",&x); } return L; } LinkList List_RearInsert(LinkList &L){ LNode *s = NULL; LNode *q = L; ElemType x = 0; printf("请输入插入的元素,输入-1停止插入\n"); scanf("%d",&x); while(x!=-1){ s = (LNode*)malloc(sizeof(LNode)); s->data = x; q->next = s; s->next = NULL; q = q->next; scanf("%d",&x); } return L; } /**tool function*/ LNode* traverseByIndex(LNode* temp_p, int i,int &temp_j){ LNode* p = temp_p; int j = 0; while (p!= NULL && j < i) { p = p->next; j++; } temp_j = j; return p; } void traverse(LinkList L){ LNode* p = L->next; while(p!=NULL){ printf("%d\t",p->data); p = p->next; } } ```

相关推荐
星星火柴9364 分钟前
工 厂 模 式
开发语言·c++·设计模式
CD-i22 分钟前
Java中修饰类的关键字
java·开发语言
南玖yy39 分钟前
内存安全的攻防战:工具链与语言特性的协同突围
开发语言·c++·人工智能·安全·c++23·c++基础语法
muyouking111 小时前
Rust多线程性能优化:打破Arc+锁的瓶颈,效率提升10倍
开发语言·性能优化·rust
沐知全栈开发1 小时前
Scala 循环
开发语言
o不ok!2 小时前
Spark-小练试刀
开发语言·前端·javascript
x_feng_x3 小时前
Java从入门到精通 - Java入门
java·开发语言
留思难3 小时前
Python生活手册-文件二进制:从快递柜到生鲜冷链的数据保鲜术
开发语言·python
猿榜编程3 小时前
Python Selenium 完全指南:从入门到精通
开发语言·python·selenium