数据结构之单链表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;
	}
}
相关推荐
Darling噜啦啦3 天前
列表转树算法深度解析:从 Map 到 Reduce 的两种实现,面试高频考点
数据结构·算法·面试
LDR0064 天前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言
雪碧聊技术4 天前
Tree.js是什么?一文讲透
开发语言·javascript·ecmascript
码云数智-园园4 天前
C++20 Modules 模块详解
java·开发语言·spring
swordbob4 天前
NIO的channel中什么是 fd(File Descriptor,文件描述符)
java·开发语言·nio
小小工匠4 天前
Redis - 事务机制:能实现 ACID 属性吗
数据结构·redis·性能优化·并发·持久化
源分享4 天前
Java线程同步的多种实现方法(非常详细)
java·开发语言·jvm
Luminous.4 天前
C语言--day30
c语言·开发语言
玖玥拾4 天前
C/C++ 数据结构(七)栈、容器适配器
c语言·数据结构·c++··容器适配器
何以解忧,唯有..4 天前
Go语言循环语句详解:for、range与循环控制
开发语言·算法·golang