当前位置: 首页 > news >正文

链表 (C/C++)

链表的基本概念

什么是链表?

链表是一种动态数据结构,由一系列节点组成,每个节点包含数据和指向下一个节点的指针。

数组 vs 链表

特性数组链表
内存分配静态连续动态非连续
插入删除慢(O(n))快(O(1))
随机访问快(O(1))慢(O(n))
内存使用固定按需分配

C

链表的基本结构

节点定义

// 最基本的链表节点
typedef struct Node {int data;           // 数据域struct Node* next;  // 指针域,指向下一个节点
} Node;

链表可视化

头指针 -> [数据|next] -> [数据|next] -> [数据|next] -> NULL

3. 链表的创建和基本操作

创建新节点

Node* createNode(int data) {Node* newNode = (Node*)malloc(sizeof(Node));if (newNode == NULL) {printf("内存分配失败!\n");return NULL;}newNode->data = data;newNode->next = NULL;return newNode;
}

在链表头部插入(头插法)

void insertAtHead(Node** head, int data) {Node* newNode = createNode(data);newNode->next = *head;  // 新节点指向原头节点*head = newNode;        // 头指针指向新节点
}

头插法图示:

原链表: head -> [A|next] -> [B|next] -> NULL
插入C: 
1. 创建新节点: [C|next]
2. newNode->next = head: [C|next] -> [A|next]
3. head = newNode: head -> [C|next] -> [A|next] -> [B|next] -> NULL

在链表尾部插入(尾插法)

void insertAtTail(Node** head, int data) {Node* newNode = createNode(data);// 如果链表为空if (*head == NULL) {*head = newNode;return;}// 找到最后一个节点Node* current = *head;while (current->next != NULL) {current = current->next;}current->next = newNode;  // 最后一个节点指向新节点
}

4. 完整的链表操作示例

#include <stdio.h>
#include <stdlib.h>typedef struct Node {int data;struct Node* next;
} Node;// 创建新节点
Node* createNode(int data) {Node* newNode = (Node*)malloc(sizeof(Node));if (!newNode) {printf("内存分配失败\n");exit(1);}newNode->data = data;newNode->next = NULL;return newNode;
}// 头插法
void insertAtHead(Node** head, int data) {Node* newNode = createNode(data);newNode->next = *head;*head = newNode;
}// 尾插法  
void insertAtTail(Node** head, int data) {Node* newNode = createNode(data);if (*head == NULL) {*head = newNode;return;}Node* current = *head;while (current->next != NULL) {current = current->next;}current->next = newNode;
}// 在指定位置插入
void insertAtPosition(Node** head, int data, int position) {if (position < 0) {printf("位置无效\n");return;}if (position == 0) {insertAtHead(head, data);return;}Node* newNode = createNode(data);Node* current = *head;// 找到position-1的位置for (int i = 0; i < position - 1 && current != NULL; i++) {current = current->next;}if (current == NULL) {printf("位置超出范围\n");free(newNode);return;}newNode->next = current->next;current->next = newNode;
}// 删除节点
void deleteNode(Node** head, int data) {if (*head == NULL) {printf("链表为空\n");return;}// 如果要删除头节点if ((*head)->data == data) {Node* temp = *head;*head = (*head)->next;free(temp);return;}// 查找要删除的节点Node* current = *head;Node* prev = NULL;while (current != NULL && current->data != data) {prev = current;current = current->next;}if (current == NULL) {printf("未找到数据 %d\n", data);return;}prev->next = current->next;free(current);
}// 查找节点
Node* searchNode(Node* head, int data) {Node* current = head;while (current != NULL) {if (current->data == data) {return current;}current = current->next;}return NULL;
}// 获取链表长度
int getLength(Node* head) {int count = 0;Node* current = head;while (current != NULL) {count++;current = current->next;}return count;
}// 打印链表
void printList(Node* head) {Node* current = head;printf("链表: ");while (current != NULL) {printf("%d -> ", current->data);current = current->next;}printf("NULL\n");
}// 释放链表内存
void freeList(Node* head) {Node* current = head;while (current != NULL) {Node* next = current->next;free(current);current = next;}
}int main() {Node* head = NULL;printf("=== 链表操作演示 ===\n\n");// 尾插法添加节点insertAtTail(&head, 10);insertAtTail(&head, 20);insertAtTail(&head, 30);printf("尾插法创建链表:\n");printList(head);// 头插法添加节点insertAtHead(&head, 5);insertAtHead(&head, 1);printf("头插法添加节点后:\n");printList(head);// 在指定位置插入insertAtPosition(&head, 15, 3);printf("在位置3插入15后:\n");printList(head);// 删除节点deleteNode(&head, 20);printf("删除20后:\n");printList(head);// 查找节点Node* found = searchNode(head, 15);if (found) {printf("找到节点: %d\n", found->data);} else {printf("未找到节点\n");}// 获取长度printf("链表长度: %d\n", getLength(head));// 释放内存freeList(head);head = NULL;return 0;
}

5. 高级链表操作

反转链表

Node* reverseList(Node* head) {Node* prev = NULL;Node* current = head;Node* next = NULL;while (current != NULL) {next = current->next;  // 保存下一个节点current->next = prev;  // 反转指针prev = current;        // 移动prevcurrent = next;        // 移动current}return prev;  // 新的头节点
}

检测环(Floyd判圈算法)

c

int hasCycle(Node* head) {if (head == NULL || head->next == NULL) {return 0;}Node* slow = head;  // 慢指针,每次走一步Node* fast = head;  // 快指针,每次走两步while (fast != NULL && fast->next != NULL) {slow = slow->next;fast = fast->next->next;if (slow == fast) {return 1;  // 有环}}return 0;  // 无环
}

6. 双向链表

双向链表节点定义

typedef struct DNode {int data;struct DNode* prev;  // 指向前一个节点struct DNode* next;  // 指向下一个节点
} DNode;

双向链表操作

// 在双向链表中插入节点
void insertDNode(DNode** head, int data) {DNode* newNode = (DNode*)malloc(sizeof(DNode));newNode->data = data;newNode->prev = NULL;newNode->next = *head;if (*head != NULL) {(*head)->prev = newNode;}*head = newNode;
}// 双向链表删除
void deleteDNode(DNode** head, int data) {DNode* current = *head;while (current != NULL && current->data != data) {current = current->next;}if (current == NULL) return;if (current->prev != NULL) {current->prev->next = current->next;} else {*head = current->next;}if (current->next != NULL) {current->next->prev = current->prev;}free(current);
}

C++

单向链表的实现

节点定义

struct ListNode {int val;           // 存储的数据ListNode* next;    // 指向下一个节点的指针// 构造函数ListNode(int x) : val(x), next(nullptr) {}
};

ListNode(int x) : val(x), next(nullptr) {}

  • 构造函数:用于创建新的ListNode对象

  • ListNode(int x):构造函数名,接受一个整数参数x

  • : val(x), next(nullptr):成员初始化列表

    • val(x):将参数x的值赋给val成员

    • next(nullptr):将next指针初始化为空指针

  • {}:空的函数体(因为初始化已经在初始化列表中完成)

链表类实现

class LinkedList {
private:ListNode* head;    // 头指针public:// 构造函数LinkedList() : head(nullptr) {}// 析构函数 - 释放所有节点内存~LinkedList() {ListNode* current = head;while (current != nullptr) {ListNode* next = current->next;delete current;current = next;}}// 在链表头部插入节点void insertAtHead(int val) {ListNode* newNode = new ListNode(val);newNode->next = head;head = newNode;}// 在链表尾部插入节点void insertAtTail(int val) {ListNode* newNode = new ListNode(val);if (head == nullptr) {head = newNode;return;}ListNode* current = head;while (current->next != nullptr) {current = current->next;}current->next = newNode;}// 删除指定值的节点void deleteNode(int val) {if (head == nullptr) return;// 如果要删除的是头节点if (head->val == val) {ListNode* temp = head;head = head->next;delete temp;return;}ListNode* current = head;while (current->next != nullptr && current->next->val != val) {current = current->next;}if (current->next != nullptr) {ListNode* temp = current->next;current->next = current->next->next;delete temp;}}// 查找节点bool search(int val) {ListNode* current = head;while (current != nullptr) {if (current->val == val) {return true;}current = current->next;}return false;}// 打印链表void printList() {ListNode* current = head;while (current != nullptr) {std::cout << current->val << " -> ";current = current->next;}std::cout << "nullptr" << std::endl;}// 获取链表长度int getLength() {int length = 0;ListNode* current = head;while (current != nullptr) {length++;current = current->next;}return length;}
};

使用示例

int main() {LinkedList list;// 插入操作list.insertAtHead(3);list.insertAtHead(2);list.insertAtHead(1);list.insertAtTail(4);list.insertAtTail(5);std::cout << "链表内容: ";list.printList(); // 输出: 1 -> 2 -> 3 -> 4 -> 5 -> nullptr// 查找操作std::cout << "查找 3: " << (list.search(3) ? "找到" : "未找到") << std::endl;std::cout << "查找 6: " << (list.search(6) ? "找到" : "未找到") << std::endl;// 删除操作list.deleteNode(3);std::cout << "删除 3 后: ";list.printList(); // 输出: 1 -> 2 -> 4 -> 5 -> nullptr// 获取长度std::cout << "链表长度: " << list.getLength() << std::endl;return 0;
}

双向链表

双向链表的每个节点有两个指针:一个指向前一个节点,一个指向后一个节点。

struct DoublyListNode {int val;DoublyListNode* prev;DoublyListNode* next;DoublyListNode(int x) : val(x), prev(nullptr), next(nullptr) {}
};class DoublyLinkedList {
private:DoublyListNode* head;DoublyListNode* tail;public:DoublyLinkedList() : head(nullptr), tail(nullptr) {}// 实现类似的方法,但需要处理prev指针// ...
};

链表的优缺点

优点:

  1. 动态大小:不需要预先指定大小

  2. 高效插入/删除:在已知位置插入/删除的时间复杂度为O(1)

  3. 内存利用率:不需要连续的内存空间

缺点:

  1. 随机访问效率低:访问第n个元素需要O(n)时间

  2. 额外内存:需要额外的指针空间

  3. 缓存不友好:节点在内存中不连续

常见链表操作

反转链表

void reverse() {ListNode* prev = nullptr;ListNode* current = head;ListNode* next = nullptr;while (current != nullptr) {next = current->next;current->next = prev;prev = current;current = next;}head = prev;
}

检测环

bool hasCycle() {if (head == nullptr) return false;ListNode* slow = head;ListNode* fast = head;while (fast != nullptr && fast->next != nullptr) {slow = slow->next;fast = fast->next->next;if (slow == fast) {return true;}}return false;
}

题目:

生成10个随机数,按从大到小的顺序存入链表,最后打印链表内容。

C语言实现

#include <stdio.h>
#include <stdlib.h>
#include <time.h>// 链表节点结构体
typedef struct ListNode {int val;struct ListNode* next;
} ListNode;// 创建新节点
ListNode* createNode(int val) {ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));if (newNode == NULL) {printf("内存分配失败\n");exit(1);}newNode->val = val;newNode->next = NULL;return newNode;
}// 按从大到小顺序插入节点
void insertSorted(ListNode** head, int val) {ListNode* newNode = createNode(val);// 如果链表为空或新节点值大于头节点if (*head == NULL || val > (*head)->val) {newNode->next = *head;*head = newNode;return;}// 找到合适的插入位置ListNode* current = *head;while (current->next != NULL && current->next->val > val) {current = current->next;}// 插入新节点newNode->next = current->next;current->next = newNode;
}// 打印链表
void printList(ListNode* head) {ListNode* current = head;printf("链表内容(从大到小): ");while (current != NULL) {printf("%d ", current->val);current = current->next;}printf("\n");
}// 释放链表内存
void freeList(ListNode* head) {ListNode* current = head;while (current != NULL) {ListNode* temp = current;current = current->next;free(temp);}printf("链表内存已释放\n");
}int main() {// 初始化随机数种子srand(time(NULL));ListNode* head = NULL;printf("生成的10个随机数字: ");for (int i = 0; i < 10; i++) {int num = rand() % 100 + 1; // 生成1-100的随机数printf("%d ", num);insertSorted(&head, num); // 按顺序插入链表}printf("\n");// 打印排序后的链表printList(head);// 释放链表内存freeList(head);return 0;
}

C++实现

#include <iostream>
#include <cstdlib>
#include <ctime>// 链表节点结构体
struct ListNode {int val;ListNode* next;// 构造函数ListNode(int x) : val(x), next(nullptr) {}
};class SortedLinkedList {
private:ListNode* head;public:// 构造函数SortedLinkedList() : head(nullptr) {}// 析构函数~SortedLinkedList() {clear();}// 按从大到小顺序插入节点void insertSorted(int val) {ListNode* newNode = new ListNode(val);// 如果链表为空或新节点值大于头节点if (head == nullptr || val > head->val) {newNode->next = head;head = newNode;return;}// 找到合适的插入位置ListNode* current = head;while (current->next != nullptr && current->next->val > val) {current = current->next;}// 插入新节点newNode->next = current->next;current->next = newNode;}// 打印链表void printList() const {ListNode* current = head;std::cout << "链表内容(从大到小): ";while (current != nullptr) {std::cout << current->val << " ";current = current->next;}std::cout << std::endl;}// 清空链表(释放内存)void clear() {ListNode* current = head;while (current != nullptr) {ListNode* temp = current;current = current->next;delete temp;}head = nullptr;std::cout << "链表内存已释放" << std::endl;}
};int main() {// 初始化随机数种子std::srand(std::time(nullptr));SortedLinkedList list;std::cout << "生成的10个随机数字: ";for (int i = 0; i < 10; i++) {int num = std::rand() % 100 + 1; // 生成1-100的随机数std::cout << num << " ";list.insertSorted(num); // 按顺序插入链表}std::cout << std::endl;// 打印排序后的链表list.printList();// 析构函数会自动调用clear()释放内存// 也可以手动调用:list.clear();return 0;
}

两种实现的区别

C语言实现特点:

  1. 使用 malloc() 和 free() 进行内存管理

  2. 需要手动传递头指针的地址 (ListNode**)

  3. 需要显式调用释放函数

  4. 使用结构体和函数分离的方式

C++实现特点:

  1. 使用 new 和 delete 进行内存管理

  2. 使用类和成员函数封装链表操作

  3. 析构函数自动释放内存(RAII原则)

  4. 更好的封装性和安全性

可能的输出结果

生成的10个随机数字: 45 78 23 91 34 67 12 89 56 3 
链表内容(从大到小): 91 89 78 67 56 45 34 23 12 3 
链表内存已释放

std::srand(std::time(nullptr)); 这行代码的作用是初始化随机数生成器的种子

详细解释

1. std::time(nullptr)

  • 作用:获取当前的时间戳(从1970年1月1日到现在经过的秒数)

  • 参数nullptr 表示不需要存储时间值,只需要返回值

  • 返回值:返回当前时间的整数值,每秒都在变化

2. std::srand(seed)

  • 作用:设置随机数生成器的种子

  • 参数:一个整数种子值

  • 功能:基于给定的种子初始化随机数序列

为什么需要这样做?

问题:没有种子的随机数

// 如果不设置种子,每次运行都会生成相同的"随机"数序列
std::cout << std::rand() << std::endl;  // 每次都是相同的数字

解决方案:使用时间作为种子

// 使用当前时间作为种子,确保每次运行都不同
std::srand(std::time(nullptr));
std::cout << std::rand() << std::endl;  // 每次运行都不同

注意事项

  1. 只需要调用一次:在程序开始时调用一次即可

  2. 不要频繁调用:如果在循环中频繁调用,可能得到相同的随机数

  3. C++11更好的选择:现代C++推荐使用 <random> 库:


文章转载自:

http://Zufkn9xz.czgfn.cn
http://3qc6wkHG.czgfn.cn
http://OrLhBEda.czgfn.cn
http://HfquyB5z.czgfn.cn
http://vm43Er2y.czgfn.cn
http://5VXcxntM.czgfn.cn
http://9phr4HaZ.czgfn.cn
http://lvvx5Y9I.czgfn.cn
http://vd5bRFFx.czgfn.cn
http://nFAhBcnl.czgfn.cn
http://XHAdEQhg.czgfn.cn
http://cWErP8YS.czgfn.cn
http://Udi76vqX.czgfn.cn
http://W2tmo5HY.czgfn.cn
http://JqoFgE9r.czgfn.cn
http://RLfYKMiZ.czgfn.cn
http://PE33A7ER.czgfn.cn
http://9dcTK1l7.czgfn.cn
http://xRfCczJU.czgfn.cn
http://10PocjrY.czgfn.cn
http://bKJd0P5K.czgfn.cn
http://ltfAf6SO.czgfn.cn
http://wUrFbJJm.czgfn.cn
http://ONJeManf.czgfn.cn
http://BxLBvlGc.czgfn.cn
http://GkS1NmNV.czgfn.cn
http://8ZSji9Nm.czgfn.cn
http://DCnFHOrY.czgfn.cn
http://5lcMoSSG.czgfn.cn
http://SScNw1Xt.czgfn.cn
http://www.dtcms.com/a/374061.html

相关文章:

  • WinEdt编译tex文件失败解决办法
  • C语言第12讲
  • commons-email
  • (堆)347. 前 K 个高频元素
  • GitHub Release Monitor部署指南:实时追踪开源项目更新与自动通知
  • 重新定义音频编程:SoundFlow如何以模块化设计革新.NET音频开发生态
  • SQL 注入与防御-第八章:代码层防御
  • Miniflux 安全升级:绑定域名并开启 HTTPS
  • 标准 HTTP 状态码详解
  • STM32开发(创建工程)
  • MFC 图形设备接口详解:小白从入门到掌握
  • APM32芯得 EP.34 | 告别I2C“假死”——APM32F103硬件IIC防锁死设计
  • n8n入门
  • 静态住宅IP的特点
  • 数智之光燃盛景 共同富裕创丰饶
  • colmap+pycolmap带cuda编译
  • Nano-Bananary 搭建 使用 nano banana
  • 前端性能监控与优化:从 Lighthouse 到 APM
  • 浅聊一下微服务的网关模块
  • 硬件开发2-ARM基本概要
  • C++11第二弹(右值引用与移动语义)
  • 数电实验二连线
  • MQTT+WebSocket工业协议实战:高并发SCADA系统通信架构设计
  • Claude-Flow AI协同开发:基础入门之 AI编排
  • Android面试指南(七)
  • 西嘎嘎学习 - C++修饰符类型 - Day 5
  • 明远智睿RK3568核心板:199元解锁多行业智能新可能
  • LeetCode算法日记 - Day 36: 基本计算器II、字符串解码
  • linux系统address already in use问题解决
  • ArcGIS学习-17 实战-密度分析