链表的相关操作
文章目录
引入
我们在对数组的使用中,常常会出现一些问题:
我们在设定数组时,很难按照需要分配出恰好的空间进行存储,势必造成空间浪费。
或者在插入或删除数组元素时,由于数组连续的存储空间,我们需要移动元素,时间复杂度为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:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
示例 2:
输入: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;
}