数据结构之单链表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 = ss->next = p->next位置不可以调换 不然会出现后面的结点丢失的情况

3.单链表的删除

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 <stdio.h>
#include <stdlib.h>

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;
	}
}
相关推荐
AI玫瑰助手14 小时前
Python函数:默认参数的定义与注意事项
开发语言·python·信息可视化
油炸自行车14 小时前
Claude Code 错误:API Error: 400 Failed to deserialize the JSON body into the
开发语言·javascript·json·trae·claude code·api error 400
肩上风骋14 小时前
C++14特性
开发语言·c++·c++14特性
_日拱一卒14 小时前
LeetCode:994腐烂的橘子
java·数据结构·算法·leetcode·深度优先
JAVA社区16 小时前
Java高级全套教程(十)—— SpringCloudAlibaba超详细实战详解
java·开发语言·spring cloud·面试·职场和发展
弥树子16 小时前
踩坑记录:服务器内网调用接口,真实请求URL与官方公开URL不一致问题排查
开发语言·php
z落落16 小时前
C# ToCharArray + foreach遍历 + String与StringBuilder
开发语言·c#
Bluetooth73016 小时前
c语言一维数组
c语言
学代码的真由酱16 小时前
Java多用户一对一网页聊天室-测试报告
java·开发语言·功能测试·测试
人道领域16 小时前
【LeetCode刷题日记】669.修剪二叉搜索树
开发语言·python·算法