链表简介
链表(Linked List)是一种常见的数据结构,用于存储和组织数据。它通过使用指针将数据节点连接在一起,形成一个链式结构。每个节点包含两个部分:数据和指向下一个节点的指针。
链表的主要特点是动态性和灵活性。与数组不同,链表的大小可以根据需要动态增长或缩小,这使得链表在处理插入和删除操作时更为高效。然而,链表的访问和搜索操作相对较慢,因为需要遍历链表来查找特定的节点。
以下是链表的几种常见类型:
- 单向链表(Singly Linked List):每个节点包含数据和指向下一个节点的指针。链表的最后一个节点指针为空(NULL),表示链表的结束。
- 双向链表(Doubly Linked List):每个节点包含数据、指向前一个节点的指针和指向下一个节点的指针。这种结构使得在链表中的任意位置进行插入和删除操作更加方便。
- 循环链表(Circular Linked List):最后一个节点的指针指向链表的第一个节点,形成一个循环。这种结构可用于模拟循环队列或循环缓冲区。
链表的操作包括以下几种常见的操作:
- 插入(Insertion):在链表的特定位置插入新的节点。需要更新节点的指针来维护链表的连接。
- 删除(Deletion):从链表中删除指定位置的节点。需要更新节点的指针来重新连接链表。
- 遍历(Traversal):按顺序访问链表中的每个节点,以便读取或处理节点的数据。
- 搜索(Search):在链表中查找包含特定数据的节点。需要遍历链表并逐个比较节点的数据。
链表的优点包括:
- 动态性:链表的大小可以根据需要动态调整,插入和删除操作相对高效。
- 灵活性:链表可以在任意位置进行插入和删除操作,不受固定大小的限制。
链表的缺点包括:
- 访问效率:访问链表中的特定节点需要遍历整个链表,相对数组等数据结构的访问效率较低。
- 内存开销:由于每个节点需要存储额外的指针信息,链表在内存消耗方面比数组更高。
在嵌入式软件工程中,链表常用于动态内存分配、实现缓冲区、任务调度等场景。了解链表的原理和操作可以帮助你更好地处理数据结构和算法相关的问题。
实现
定义链表节点结构体
struct Node
{
int data; // 存储节点的数据
struct Node* next; // 存储指向下一个节点的指针
};
在链表末尾插入新节点
插入新节点函数输入一个链表地址的地址和一个数据,希望在该链表末尾插入一个新节点。
步骤:
- 创建新节点存入新数据。
- 如果传入的链表为空,则将新节点设置为头节点。
- 如果传入链表不为空,则遍历找到最后一个节点,将最后一个节点的指针指向新节点。
void insert(struct Node** head, int data)
{
// 创建新节点
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = data; // 设置新节点的数据为传入的数据
newNode->next = NULL; // 将新节点的下一个指针置为空
// 如果链表为空,则将新节点设置为头节点
if (*head == NULL)
{
*head = newNode; // 将头指针指向新节点
}
else
{
// 找到链表的最后一个节点
struct Node* curr = *head;
// 从头节点开始遍历
while (curr->next != NULL)
{
curr = curr->next; // 移动到下一个节点
}
// 在最后一个节点后插入新节点
curr->next = newNode;// 将最后一个节点的下一个指针指向新节点
}
}
遍历并打印链表节点的值
void printList(struct Node* head)
{
struct Node* curr = head; // 从头节点开始遍历
while (curr != NULL)
{
// 循环直到到达链表末尾(指针为空)
printf("%d ", curr->data); // 打印当前节点的数据
curr = curr->next;// 移动到下一个节点
}
printf("\n");
}
###题目描述
题目解答
个人心得:
- 函数输入的参数可以直接拿来用,不用担心更改了原先的传入值。在本题中l1、l2就可以直接当做一个临时指针来用,不用在函数内重新定义新指针。
- 指针->各项。
- 判断指针是否为空可以直接放入判断语句中不用再加==null。
- 链表结尾需指向null。
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2){
int tempnum = 0;
struct ListNode* l3 = NULL;
struct ListNode* l3tempaddr = NULL;
while(l1 || l2)
{
//这里使用条件运算符 `?:` 来检查当前节点是否存在。如果 `l1` 不为 `NULL`,则将 `l1` 的值赋给变量 `n1`,否则将 `n1` 的值设置为 0。同样地,对于 `l2` 也做同样的处理。
int n1 = l1 ? l1 -> val : 0;
int n2 = l2 ? l2 -> val : 0;
//这里根据新链表是否为空来进行不同的处理。如果新链表为空(即头节点 `head` 为空),表示是第一个节点,需要创建头节点,并将 `head` 和 `tail` 都指向该节点。然后设置节点的值为 `sum` 对 10 取余的结果,并将节点的指针 `next` 设置为 `NULL`。
如果新链表已经有节点(即 `head` 不为空),表示需要添加新节点到链表末尾。首先,为新节点分配内存,并将 `tail->next` 指向新节点。然后设置新节点的值为 `sum` 对 10 取余的结果,并将 `tail` 指针移动到新节点,最后将新节点的指针 `next` 设置为 `NULL`。
if(!l3)
{
l3 = l3tempaddr = malloc(sizeof(struct ListNode));
l3tempaddr -> val = (n1 + n2)% 10;
l3tempaddr -> next = NULL;
}
else
{
l3tempaddr -> next = malloc(sizeof(struct ListNode));
l3tempaddr -> next -> val = (n1 + n2 + tempnum)% 10;
l3tempaddr -> next -> next = NULL;
l3tempaddr = l3tempaddr -> next;
}
tempnum = (n1 + n2 + tempnum)/ 10;
if(l1)
{
l1 = l1 -> next;
}
if(l2)
{
l2 = l2 -> next;
}
}
//当传入两链表都为空但是仍有进位时需创建一位
if(tempnum > 0)
{
l3tempaddr -> next = malloc(sizeof(struct ListNode));
l3tempaddr -> next -> val = tempnum;
l3tempaddr -> next -> next = NULL;
}
return l3;
}