C语言 单向链表反转问题

本文目录

前置

定义一个结构体,如下:

c 复制代码
typedef struct node {
	int data;    //节点中的数据
	struct node *next;     //struct node 类型的指针
} Node;    //typedef 定义的别名为 Node

使用 malloc() 来为结构体申请内存,如下:

c 复制代码
Node *head = (Node*)malloc(sizeof(Node));
head->data = 1;//为数据赋值
head->next = NULL;

我们多次使用 malloc() 函数来申请 Node 结构体大小的内存,并将所有的 Node 节点通过 next 指针连起来。这样就可以得到一个链表,由于所有的节点都指向下一个节点,所以它是单向的,这样链表被称为单向链表。head 保存了第一个节点的内存地址,通过 head 我们就可以遍历所有的 Node 节点。最后一个节点的 next 指针赋值为 NULL 作为链表的结束标志。所有节点的内存地址在内存中不一定就是连续的,它是通过指针赋值地址的方式将所有的节点连在一起的。

删除节点时,要 free(节点内存地址) 释放对应节点的内存地址,释放前要确保节点指针的断开与连接,以免造成节点丢失的问题。


完整单向链表示例:

c 复制代码
#include <stdio.h>
#include <stdlib.h>

// 定义链表节点
struct Node {
    int data;           // 节点存储的数据
    struct Node* next;  // 指向下一个节点的指针
};

// 创建新节点
struct Node* createNode(int data) {
    struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
    newNode->data = data;
    newNode->next = NULL;
    return newNode;
}

// 在头节点前面插入新节点
void insertAtHead(struct Node** head, int data) {       //struct Node **head 二级指针,指向指针的指针,如果传入 head 一级指针,我们只能修改 head 指向的内容,而无法修改指针本身,传入二级指针可以修改 head 指针本身
	/*
	指针的本质是一个变量,变量里面保存的是一个内存地址。
	一级指针 head 保存的是链表第一个 Node 节点的内存地址。
	二级指针是 head 这个变量的内存地址,*head 赋值新创建的节点的内存地址。
	*/
    struct Node* newNode = createNode(data);
    newNode->next = *head;
    *head = newNode;
}

//在链表末尾添加节点
void insertAtTail(struct Node** head, int data) {
    struct Node* newNode = createNode(data);
    if (*head == NULL) {
        *head = newNode;
    } else {
        struct Node* temp = *head;
        while (temp->next != NULL) {
            temp = temp->next;
        }
        temp->next = newNode;
    }
}

//删除头节点
void deleteAtHead(struct Node** head) {
    if (*head == NULL) {
        printf("List is empty.\n");
        return;
    }
    struct Node* temp = *head;
    *head = (*head)->next;
    free(temp);
}

//删除指定节点
void deleteNode(struct Node** head, int data) {
    if (*head == NULL) {
        printf("List is empty.\n");
        return;
    }

    struct Node* temp = *head;
    
    // 如果要删除的是头节点
    if (temp->data == data) {
        *head = temp->next;
        free(temp);
        return;
    }

    // 查找要删除的节点
    while (temp->next != NULL && temp->next->data != data) {
        temp = temp->next;
    }

    // 如果找到要删除的节点
    if (temp->next != NULL) {
        struct Node* toDelete = temp->next;
        temp->next = temp->next->next;
        free(toDelete);
    } else {
        printf("Node with data %d not found.\n", data);
    }
}

//打印所有节点
void printList(struct Node* head) {
    if (head == NULL) {
        printf("List is empty.\n");
        return;
    }

    struct Node* temp = head;
    while (temp != NULL) {
        printf("%d -> ", temp->data);
        temp = temp->next;
    }
    printf("NULL\n");
}

//销毁链表
void destroyList(struct Node* head) {
    while (head != NULL) {
        struct Node* temp = head;
        head = head->next;
        free(temp);
    }
}

int main() {
    struct Node* head = NULL;  // 初始化空链表

    // 插入节点
    insertAtHead(&head, 10);
    insertAtHead(&head, 20);
    insertAtTail(&head, 30);
    insertAtTail(&head, 40);

    // 打印链表
    printf("Linked list: ");
    printList(head);

    // 删除节点
    deleteNode(&head, 20);  // 删除值为 20 的节点

    printf("After deleting 20: ");
    printList(head);

    deleteAtHead(&head);  // 删除头节点

    printf("After deleting head: ");
    printList(head);

    // 销毁链表
    destroyList(head);
    return 0;
}



反转单向链表

描述

给定一个单链表的头结点pHead(该头节点是有值的,比如在下图,它的val是1),长度为n,反转该链表后,返回新链表的表头。

数据范围: 0 ≤ n ≤ 1000 0≤n≤1000 0≤n≤1000

要求:空间复杂度 O(1) ,时间复杂度 O(n)。

如当输入链表{1,2,3}时,

经反转后,原链表变为{3,2,1},所以对应的输出为{3,2,1}。

以上转换过程如下图所示:

示例1

输入:

{1,2,3}

返回值:

{3,2,1}

示例2

输入:

{}

返回值:

{}

说明:

空链表则输出空                 

Solution

对于单向链表的反转,关键点在在于正确处理节点的断开与连接。

而节点的断开与连接,关键点在于正确处理节点 next 指针的赋值。

在处理链表反转时,要注意不要丢失节点的指针。

代码很短很简单,但是在逻辑上要格外注意指针的赋值,以免丢失节点。

c 复制代码
/**
 * struct ListNode {
 *	int val;
 *	struct ListNode *next;
 * };
 */
/**
 * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
 *
 * 
 * @param head ListNode类 
 * @return ListNode类
 */
struct ListNode* ReverseList(struct ListNode* head ) {
    struct ListNode *pre = NULL, *cur = head, *nxt = NULL;
    while (cur) {
        nxt = cur->next;
        cur->next = pre;
        pre = cur;
        cur = nxt;
    }
    return pre;
}

时间复杂度 O(n),一次 while 循环整个链表。

空间复杂度 O(1)。

代码解释

步骤 1:初始化

pre = NULL, cur = head (1), nxt = NULL

链表结构:

NULL <- 1 -> 2 -> 3 -> 4 -> 5 -> NULL
  • nxt = cur->nextnxt = 2
  • cur->next = pre,将 1->next 指向 NULL
  • pre = curpre 移动到 1
  • cur = nxtcur 移动到 2

步骤 2:处理 2

pre = 1, cur = 2, nxt = NULL

链表结构:

NULL <- 1 <- 2 -> 3 -> 4 -> 5 -> NULL
  • nxt = cur->nextnxt = 3
  • cur->next = pre,将 2->next 指向 1
  • pre = curpre 移动到 2
  • cur = nxtcur 移动到 3

步骤 3:处理 3

pre = 2, cur = 3, nxt = NULL

链表结构:

NULL <- 1 <- 2 <- 3 -> 4 -> 5 -> NULL
  • nxt = cur->nextnxt = 4
  • cur->next = pre,将 3->next 指向 2
  • pre = curpre 移动到 3
  • cur = nxtcur 移动到 4

步骤 4:处理 4

pre = 3, cur = 4, nxt = NULL

链表结构:

NULL <- 1 <- 2 <- 3 <- 4 -> 5 -> NULL
  • nxt = cur->nextnxt = 5
  • cur->next = pre,将 4->next 指向 3
  • pre = curpre 移动到 4
  • cur = nxtcur 移动到 5

步骤 5:处理 5

pre = 4, cur = 5, nxt = NULL

链表结构:

NULL <- 1 <- 2 <- 3 <- 4 <- 5 -> NULL
  • nxt = cur->nextnxt = NULL(最后一个节点)。
  • cur->next = pre,将 5->next 指向 4
  • pre = curpre 移动到 5
  • cur = nxtcur 变为 NULL,退出循环。

图示

步骤 pre cur nxt 当前链表状态
初始 NULL 1 NULL 1 -> 2 -> 3 -> 4 -> 5 -> NULL
步骤 1 1 2 2 NULL <- 1 -> 2 -> 3 -> 4 -> 5 -> NULL
步骤 2 2 3 3 NULL <- 1 <- 2 -> 3 -> 4 -> 5 -> NULL
步骤 3 3 4 4 NULL <- 1 <- 2 <- 3 -> 4 -> 5 -> NULL
步骤 4 4 5 5 NULL <- 1 <- 2 <- 3 <- 4 -> 5 -> NULL
步骤 5 5 NULL NULL 5 -> 4 -> 3 -> 2 -> 1 -> NULL



链表内指定区间反转

描述

将一个节点数为 size 链表 m 位置到 n 位置之间的区间反转,要求时间复杂度 O(n),空间复杂度 O(1)。

例如:

给出的链表为 1→2→3→4→5→NULL, m=2,n=4,

返回 1→4→3→2→5→NULL.

数据范围: 链表长度 0<size≤1000,0<m≤n≤size,链表中每个节点的值满足 ∣val∣≤1000

要求:时间复杂度 O(n)) ,空间复杂度 O(n)

进阶:时间复杂度 O(n),空间复杂度 O(1)

示例1

输入:

{1,2,3,4,5},2,4

返回值:

{1,4,3,2,5}

示例2

输入:

{5},1,1

返回值:

{5}

Solution

在 head 节点前面加上一个辅助节点,可以方便反转操作。代码有注释:

c 复制代码
/**
 * struct ListNode {
 *	int val;
 *	struct ListNode *next;
 * };
 */
/**
 * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
 *
 * 
 * @param head ListNode类 
 * @param m int整型 
 * @param n int整型 
 * @return ListNode类
 */
struct ListNode* reverseBetween(struct ListNode* head, int m, int n ) {
    if(!head || m == n) return head;   //head为空或 m=n 则直接返回 head
    struct ListNode *start = (struct ListNode*)malloc(sizeof(struct ListNode));
    start->next = head;   //在head前面添加一个节点 start
    struct ListNode *revPre = start;   
    for (int i = 0; i < m - 1; i++)   //找到第m个节点的前一个节点 revPre
        revPre = revPre->next;
    struct ListNode *rev = revPre->next;   //rev 第m个节点
    struct ListNode *tmp = NULL;   // 临时指针
    for (int i = m; i < n; i++) {
        tmp = rev->next;   // 保存第m+1个节点的地址
        rev->next = tmp->next;      //第m个节点指向第m+2个节点
        tmp->next = revPre->next;      //第m+1个节点指向需要反转的第一个节点m
        revPre->next = tmp;      //第m节点指向第m+1节点
    }
    tmp = start->next;    // tmp 保存反转后的 head
    free(start);    //释放辅助的节点 start 的内存空间
    return tmp;    //返回 head
}

提交代码:过辣!!!

相关推荐
ZSYP-S1 小时前
Day 15:Spring 框架基础
java·开发语言·数据结构·后端·spring
yuanbenshidiaos1 小时前
C++----------函数的调用机制
java·c++·算法
唐叔在学习1 小时前
【唐叔学算法】第21天:超越比较-计数排序、桶排序与基数排序的Java实践及性能剖析
数据结构·算法·排序算法
ALISHENGYA1 小时前
全国青少年信息学奥林匹克竞赛(信奥赛)备考实战之分支结构(switch语句)
数据结构·算法
chengooooooo1 小时前
代码随想录训练营第二十七天| 贪心理论基础 455.分发饼干 376. 摆动序列 53. 最大子序和
算法·leetcode·职场和发展
jackiendsc1 小时前
Java的垃圾回收机制介绍、工作原理、算法及分析调优
java·开发语言·算法
FeboReigns2 小时前
C++简明教程(文章要求学过一点C语言)(1)
c语言·开发语言·c++
FeboReigns2 小时前
C++简明教程(文章要求学过一点C语言)(2)
c语言·开发语言·c++
游是水里的游3 小时前
【算法day20】回溯:子集与全排列问题
算法
_小柏_3 小时前
C/C++基础知识复习(43)
c语言·开发语言·c++