写在开头:基于对指针的学习与应用,写下该文章记录单向链表的各种操作。
操作系统:ubuntu
编译工具:vim
链表是由程序员自己通过动态申请内存得到的数据结构,在单向链表中,头节点没有前仆,尾节点没有后继,也就是说头节点就是单向链表的开头,尾节点意味着单向链表的结束。一般头节点不用来存储数据,而用来指向链表的第一个节点,头节点中的数据可以用来存储链表元素的个数,个数可以方便我们后面进行遍历。
链表是由节点构成的,因此我们需要先创建节点的结构体:
struct Node
{
int data; //节点的数据
struct Node *next; //保存下一个节点的地址
};
刚才说到链表是通过动态申请得到的数据结构,需要用malloc函数,当然申请了内存就需要释放(free)啦。
链表是从头节点开始的,因此先写一个头节点创建函数;
struct Node * list_init()
{
struct Node* head=(struct Node*)malloc(sizeof(struct Node*));
head->data=0;//头字节的数据用来存储个数,也可以用(*head).data=0;进行赋值,下面同样如此
head->next=NULL;//头指针目前没有指向,因此设置为空
return head;
}
在list_init()函数中,我们使用了malloc()函数区申请内存,因此要引用头文件#include <stdlib.h>
至此,节点的结构体和头节点我们已经创建完毕。接下来的操作就是链表的插入、删除、遍历、逆序输出。
一、单向链表的插入
链表的插入有两种方法:头插法和尾插法。
1.头插法:在链表的头部插入新节点,具体思路就是让新节点的指针指向头节点的指针,因为头结点的指针中保存了链表第一个元素的地址,现在让新插入的节点的指针指向第一个元素,然后让头节点指向新插入的节点,此时新插入的节点就变成了链表的第一个元素。
void head_insert(struct Node* list,int data)
{
struct Node* temp=(struct Node*)malloc(sizeof(struct Node*)); //申请内存
temp->data=data; //赋值
temp->next=list->next; //让新节点指向头节点指向的第一个节点的地址
list->next=temp; //头节点指向新的节点作为第一个节点
}
既然我们已经写好了头插的函数,那么想要看见效果我们需要实现遍历链表打印。
void print(struct Node* list)
{
struct Node* temp=list->next;//从第一个节点开始
printf("list:");
while(temp)
{
printf("%d ",temp->data);//打印当前节点的值
temp=temp->next; //指向下一个节点
}
printf("\n");
}
在main函数中,我们可以调用头插法函数和打印函数查看效果:
int main(int argc, char *argv[])
{
struct Node* list=list_init();//头节点创建
head_insert(list,1); //头插法插入数据1
print(list); //查看当前链表中的元素
head_insert(list,2); //头插法插入数据2
print(list);
head_insert(list,3);
print(list);
head_insert(list,4);
print(list);
head_insert(list,5);
print(list);
return 0;
}
具体运行截图如下:
2.尾插法:在链表的尾部插入元素,因此当我们想要通过尾插法插入节点的时候,需要先遍历链表到最后一个。
void tail_insert(struct Node* list,int data)
{
struct Node* temp=(struct Node*)malloc(sizeof(struct Node*));//申请内存
temp->data=data;
temp->next=NULL; //由于是尾插法,因此该节点就是末尾,它的指针域就是NULL
struct Node* ergodic=list->next; //ergodic是用来遍历链表到最后的
while(ergodic->next)
ergodic=ergodic->next;
ergodic->next=temp; //让原链表的最后一项的指针指向我们新创建的节点
}
在主函数中调用:
int main(int argc, char *argv[])
{
struct Node* list=list_init(); //头节点
head_insert(list,1); //头插法
print(list);
head_insert(list,2);
print(list);
head_insert(list,3);
print(list);
head_insert(list,4);
print(list);
head_insert(list,5);
print(list);
tail_insert(list,6); //尾插法
print(list);
tail_insert(list,7);
print(list);
tail_insert(list,8);
print(list);
tail_insert(list,9);
print(list);
return 0;
}
运行截图如下:
二、单向链表的遍历
void print(struct Node* list)
{
struct Node* temp=list->next;//从第一个节点开始
printf("list:");
while(temp)
{
printf("%d ",temp->data);//打印当前节点的值
temp=temp->next; //指向下一个节点
}
printf("\n");
}
三.单向链表的删除:当我们想要删除链表中某个特定值或者特定位置的时候,当然是从头遍历找到到某个值或者位置,然后让拥有该值的节点的上一个节点指向该节点的下一个节点,然后把这个节点占用的内存空间释放。
1.删除特定值
void delete(struct Node* list,int data)
{
struct Node* pre=list; //被删除的节点的前一个节点
struct Node* del=pre->next; //被删除的节点
while(del)
{
if(data==del->data)
{
pre->next=del->next; //当前节点的上一个节点指向当前节点的下一个节点
free(del);
}
pre=del; //不断更新前一个节点
del=del->next; //当还没找到被删除的节点,不断地向下一个节点寻找
}
}
main函数中的代码如下:
int main(int argc, char *argv[])
{
struct Node* list=list_init();
head_insert(list,1);
print(list);
head_insert(list,2);
print(list);
head_insert(list,3);
print(list);
head_insert(list,4);
print(list);
head_insert(list,5);
print(list);
tail_insert(list,6);
print(list);
tail_insert(list,7);
print(list);
tail_insert(list,8);
print(list);
tail_insert(list,9);
print(list);
delete(list,5);
printf("deleted list:");
print(list);
delete(list,9);
printf("deleted list:");
print(list);
delete(list,1);
printf("deleted list:");
print(list);
return 0;
}
运行截图:
目前的代码仅能删除一个元素,未实现删除重复的元素,上述的链表元素都是不同元素组成的,后续敲出来再来更新(如果有大佬也可以指点一下,欢迎指正和交流)
2.删除特定位置(未完成待更新)
四、单向链表的反转输出:链表的反转输出需要借助三个节点指针,一个用于记录删除节点的上一个节点的地址,一个是用于遍历链表的,还有一个用于辅助遍历的。这样说明并不够明朗,请具体看代码实现:
void reverse(struct Node* list)
{
struct Node *pre,*temp,*next;
pre=NULL;
temp=list->next; //temp将从链表的第一项开始遍历
while(temp)
{
next=temp->next; //next用于保存temp指向的下一个节点,辅助temp向链表继续遍历
temp->next=pre; //实现反转的思想是把原来的指向下一个节点的连接断开,然后指向上一个节点
//因此pre指针就用于保存上一个节点的地址
pre=temp; //pre保存现在这个节点的地址,用于下一个节点来指向它
temp=next; //temp靠next遍历
}
list->next=pre; //遍历结束后,原来的头变成了尾,原来的尾变成了头,由于pre保存了最后一个
//节点的地址,因此头指针直接指向pre。
}
运行截图: