单链表的相关操作

链表的相关操作

引入

我们在对数组的使用中,常常会出现一些问题:

我们在设定数组时,很难按照需要分配出恰好的空间进行存储,势必造成空间浪费。

或者在插入或删除数组元素时,由于数组连续的存储空间,我们需要移动元素,时间复杂度为O(n),效率低下

为了避免以上关于数组的缺陷,我们在遇到问题时可以使用链表进行数据的操作。

概念

线性表:

1.线性表是有限个个具有相同特性的数据元素,通常以顺序表和链式结构的形式存储

2.除了开头与结尾两个节点,序列中的每一个元素都有其唯一的前驱和后继

顺序表:

即线性表的顺序储存,是分配一块连续的内存去依次存放元素的线性结构,例:数组

链表:

内存不连续的线性表,即逻辑上相邻的数据元素,在储存位置上不一定相邻,内存与内存之间用指针相连。

使用链式结构储存的优点

  • 空间利用率较高,可在需要空间时在进行申请
  • 通过指针来连接数据元素,在删除或插入时不需要移动任何数据节点,只需要移动指针,时间复杂度低

链式存储结构的缺点

  • 存储数据的密度较小,每个节点的指针需要额外占用空间,当节点储存的数据较小时,指针所占的空间比重就会较大
  • 对链表中任意节点进行操作都要从头指针依次查找,存在一定复杂度

对于顺序表还是链式表都有其独特的优点,并无高低,在实际应用中,需要我们根据需求来选择使用


单链表操作

请添加图片描述

单链表的结构

零.单链表的初始化
typedef struct *Node{
    int data;
    struct Node* next;
} Node;
Node *Initlist(){
	Node* list=(Node*)malloc(sizeof(Node));//通过动态分配产生节点,分配的地址为随机的
	list->data=0;
	list->next=NULL;
	return list;
}
一.增加
1.头插法

顾名思义,就是在链表的头部插入数据节点,断开头节点的指针使其指向插入的数据,再将插入数据的指针指向头节点原先指针指向的位置。

void HeadInsert(Node* list,int data){
	Node* node=(Node*)malloc(sizeof(Node));
     if (node == NULL) {
        // 内存分配失败,进行相应处理
        return;
    }
	node->data=data;
	node->next=list->next;
	list->next=node;
	list->data++;//头节点储存该链表的节点个数
}
2.尾插法

顾名思义,就是在链表的头部插入数据节点,通过循环,找到位于链表尾部的数据节点(即 list -> next = NULL),在进行数据插入,将链表尾部的next指向插入的数据,再将插入数据的指针指向NULL

void TailInsert(Node* list,int data){
	Node* node=(Node*)malloc(sizeof(Node));
	 if (node == NULL) {
        // 内存分配失败,进行相应处理
        return;
    }
	Node* head = list;
	node->data = data;
    node->next = head; 
	list=list->next;
	while(list->next){
		list = list->next;
	}
	list->next = node;
	head->data++;
}
3.在指定位置进行插入
void insert_at_position(struct ListNode** head, int position, int value) {
    //在position之后插入新节点
    struct ListNode* new_node = (struct ListNode*)malloc(sizeof(struct ListNode));
    new_node->data = value;
    new_node->next = NULL;

    if (position == 0) {
        //若position指向头结点,则直接进行插入
        new_node->next = *head;
        *head = new_node;
        return;
    }

    struct ListNode* current = *head;
    for (int i = 0; i < position - 1 && current != NULL; i++) {
        current = current->next;//找到插入位置
    }

    if (current == NULL) {
        return;
    }

    new_node->next = current->next;
    current->next = new_node;
}
二.删除

只需要找到相应节点,将对应节点的前一个指针next指向这个节点的后续,再将该节点的内存释放掉,就完成了链表数据的删除。

int Delete(Node* list,int data){
    Node* pre = list;
    Node* curr = list->next;
    while(curr){
        if(curr->data == data){
            pre->next = curr->next;
            free(curr);//释放内存
            list->data--;
            return TRUE;
        }
        pre = curr;
        curr = curr->next;
    }
    return FLASE;
}

三.遍历
1.打印

通过循环不断按顺序打印出链表每一个节点的数据,直到最后将链表遍历完成。

void PrintList(Node* list){
	list=list->next;
	while(list){
		printf("%d->",list->data);
		list = list->next;
	}
	printf("NULL\n");
}
2.查找
Node* Find(Node* list, int data){
    Node* head = list ;
    while( list ){
        if( list->data == data){
            return list ;//返回该节点的地址
        }
        list = list -> next ;
    }
    return NULL;
}

题目

1.LCR 024. 反转链表

给定单链表的头节点 head ,请反转链表,并返回反转后的链表的头节点。

示例 1:

img

输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]

示例 2:

img

输入:head = [1,2]
输出:[2,1]

示例 3:

输入:head = []
输出:[]

提示:

  • 链表中节点的数目范围是 [0, 5000]
  • -5000 <= Node.val <= 5000

**进阶:**链表可以选用迭代或递归方式完成反转。你能否用两种方法解决这道题?

解题思路

我们可以反转指针的指向,即从前往后地把链表节点指针的指向反转。

请添加图片描述

链表指针的初始化

请添加图片描述

经过第一次循环后指针的指向情况

struct ListNode* reverseList(struct ListNode* head){
    struct ListNode* pre =NULL;
    struct ListNode* curr=head;
    while(curr){
        struct ListNode* next = curr->next;
        curr->next = pre;
        pre = curr;
        curr = next;
    }
        return pre;
}
递归法
struct ListNode* reverseList(struct ListNode* head){
    struct ListNode* pre =NULL;
    if (head == NULL || head->next == NULL) {
        return head;
    }
    
    // 递归调用反转链表函数
    struct ListNode *newHead = reverseList(head->next);
    
    // 将当前节点的下一个节点的指针指向当前节点,实现反转
    head->next->next = head;
    head->next = NULL;
    
    return newHead;  // 返回新的头节点
}

1 —> 2 —> 3为例,以该节点储存的数据为指向链表的名字

链表的头节点为1,我们将其指向的next作为参数传入递归函数reverseList

 struct ListNode *newHead = reverseList(2);
    2->next = 1;
    1->next = NULL;

链表的头节点为2,我们将其指向的next作为参数传入递归函数reverseList

 struct ListNode *newHead = reverseList(3);
    3->next = 2;
    2->next = NULL;

链表的头节点为1,我们将其指向的next为空,直接返回3

if(3->next == NULL ){
    return 3;
}

此时链表翻转成功

2.链表的冒泡排序

对链表中的数据进行冒泡排序,程序如下所示。

struct ListNode* sortList(struct ListNode* head) {
    if (head == NULL || head->next == NULL) {
        return head;  // 如果链表为空或者只有一个节点,无需排序,直接返回
    }
    struct ListNode* curr = head;
    int flag = 1; //标志是否发生转换
    do{
        flag = 0 ;
        curr = head ; //重新返回首位
        while( curr->next != NULL ){
            if( curr->val > curr->next->val ){
                int temp = curr->val;
                curr->val = curr->next->val;
                curr->next->val = temp ;
                flag = 1 ;
            }
            curr = curr->next ;
        }

    }while(flag);
    return head;
}
  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值