在学习链表的之前大家首先要搞清楚三个东西:typedef、结构体、链表节点的结构及链表与数组的区别。
一:typedef与define
初始入门大家一接触C/C++,看代码中一定会看到的东西就是这个,而且基本上就处在除了导入库函数和宏定义之后的第一行。其实啊,大家可以将typedef理解为小名。
//我们来给int起一个小名,叫A
typedef int A ;
A x; //此时的A 就相当于int ,而x就是int型。所以A x == int x;
//还记的结构体的基本形式嘛? struct定义结构体的标志,而Node是结构体的名字
struct firstStruct{
int data;
Node *next;
};
//还记得此时如果我们来定义一个结构体类型的数据该怎么定义?
int main(){
struct firstStruct A;
A.data = 1;
return 0;
}
//上面的定义数据库类型是不是感觉异常麻烦?那我们来给结构体起一个小名,就叫Node吧。
typedef struct Node{
int data;
Node *next;
}Node;
//此时我们再定义一个结构体类型数据的时候就用小名就可以了。
int main(){
Node A;//此时的Node 就相当于struct Node
A.data = 1;//通过用小名,我们来定义结构题数据就很方便。
}
//define的意思其实也可以理解为换个名字,来举个例子!
#define A 50 //A就可以代替50这个数
#define (a,b) (a>b?a:b) //赋予a与b的值,则(a,b)就可以直接比较出大小
二:结构体
//定义一:
typedef struct Node{
int data;
Node *next;
}Node;
//定义二:
typedef struct Node{
int data;
Node *next;
}* node;
//怎么样,我说准了没?一开始接触这个点一定蒙蔽。他们有什么区别呢?
insert_list(Node *a){}//由结构体定义一定义
insert_list(node a){}//由结构体二定义
有没有看出来区别?其实他们两个就是这样的区别而已。因为链表用的是指针,所以你在为每一个节点创建数据结构的时候就一定要用到结构体类型的指针。
三:链表节点的结构及链表与数组的区别
大家在学习C语言的时候最常用的就是数组了,但是其实链表的作用也是非常大的。首先来给大家share一下数组和链表的结构有哪些不同!
3.1:结构上的不同
首先链表是链式的存储结构,而数组则是顺序的存储结构。数组是把所有的元素依次的存储,而链表则是通过结构体指针来链接元素与元素。对于基本操作来说,链表的插入与删除就较为简单,不用移动每一个元素,但是寻找起来就很费劲,寻找一个元素,需要访问其前边所有的元素。而数组的话,是支持随机访问的,寻找起来也是很方便的,但是插入和删除就比较麻烦,首先数组是设定固定长度的,当达到最大长度时候,扩充数组的长度就没有链表方便。
3.2:相同点
两种结构均可实现顺序存储。
3.3:什么时候用数组?什么时候用链表?
如果你需要的一块动态的存储空间,且数据的多少不确定,变化比较大就可以使用链表。但是如果你不需要频繁插入删除操作,切对空间长度没有要求的时候就推荐是用数组。
3.4:链表的组成
链表是由一系列的节点组成。每个节点包含两个部分:数据域和指针域。数据域是用来存储数据元素的,指针域则是存储下一个节点地址的。且在物理的存储结构上是非连续、非顺序的存储结构,每个元素的逻辑顺序也是通过结构体中的指针链接次序实现的。
3.5:关于链表的基本操作
-
链表数据结构的定义
-
此结构体中定义了一个integer型的数据,即为每个节点数据域存放的数据类型。定义了一个结构体类型的指针,通过此指针来链接各节点。
-
在定义结构体的时候,我们通常会看到两种定义方法
-
//定义方法一: typedef struct Node{ int data; Node *next; }Node; //定义方法二: typedef struct Node{ int data; Node *next; }*node;
很多人一开始对这两个很蒙蔽,这有什么区别?
-
简单来说就是当你使用方法二的时候,定义函数中形参中节点类型时,只需这样表示 Node Head。当使用方法一来定义形参中的结点类型时,只需这样表示:Node *Head 。两者表示等价。
-
-
typedef struct LinkList{
int date;
struct LinkList *next;
}linkList;
- 链表的创建:
- 初始化含有n个的节点的链表,思想:通过循环先将当前节点初始化,之后不断用当前节点指向末尾节点,并将末尾节点的指针域设为空。
linkList * creat_LinkList(int n){
linkList *PHead ,*PCurrent, *PEnd; //创立头节点PHead、当前节点PCurrent、末尾节点PEnd
PHead = (linkList *)malloc( sizeof(linkList));
if( PHead == NULL){
return NULL;
}
PEnd = PHead; //开始阶段,未插入节点前只有头节点,所以末尾节点和头节点是一个。
//此时开始创建节点,
for (int i = 1; i <= n; i++)
{
//初始化当前节点
PCurrent = (linkList *)malloc( sizeof(linkList));
printf("please input value of the NO%d date !",i);
scanf("%d",PCurrent->date);
PCurrent -> next = NULL;
//当前节点指向末尾节点。
PEnd -> next = PCurrent;
PEnd = PCurrent;
}
//将末尾节点的指针域设为空。
PEnd->next = NULL;
return PHead;
}
- 修改链表节点的值
- 思想:传入链表和要修改的链表节点,用变量flag来标记节点,用暂时替代链表replace取代传入链表,与flag同事进行遍历,如果当flag和传入链表节点相同则找到要修改的节点,进行修改。
void alter_LinkListNode(linkList *List ,int alter_position){
int flag = 0;
linkList * replace = List;
//运用while循环来判定是否找到要修改节点的位置,找到则跳出循环。replace则是要修改的节点。
while (flag < alter_position && replace != NULL )
{
replace = replace ->next ;
flag++;
}
if( replace != NULL){
printf("please input date of you want !");
scanf("%d",&replace -> date);
}else
{
printf("you want alter the date doesn't exist!");
}
}
- 删除链表节点:
- 此时跟大家说一下,删除的操作如图所示:
如图所示: 删除链表节点其实就是将要【 删除节点的上一个节点 】的指针域所指向的下一节点的地址改成 【 删除节点的下一个节点 】的地址,此操作之后,将要删除的节点的内存空间释放掉。
思想:首先要了解free的概念。C语言中并没有像JAVA一样的废弃内存空间回收机制,所以对于不用的内存空间需要手动释放,这时便用到了free。在删除链表节点的时候,很多人都会直接写成pre_Node -> next = pre_Node -> next -> next,然后free(pre_Node -> next)。然而此时你释放掉pre_Node ->next之后,后面的链表节点就找不到了,因为上一步你是直接把pre_Node -> next直接释放掉了,这样整个链表就断掉,后面当然就找不到了。free(pre_Node) 是指删除pre_Node指向节点所占的内存,不是删除pre_Node本身所占内存。所以我们要定义一个临时的节点pre_Node,来代替要删除节点的上一节点。
void delete_LinkListNode( linkList *List , int deleteNode_date){
linkList * delete_Node ,* pre_Node = List;
while ( pre_Node != NULL )
{
if( pre_Node -> next -> date == deleteNode_date && pre_Node != NULL ){
delete_Node = pre_Node ->next; //此时delete_Node和pre_Node -> next指向的都是pre_Node->next存储空间的地址。
//这时free(delete_Node)之后,还有pre_Node -> next保留以保证链表不会断掉。
pre_Node ->next = delete_Node ->next;
free(delete_Node);
printf("删除成功!");
}else
{
pre_Node = pre_Node -> next;
}
}
}
- 插入链表节点
- 如图所示:和删除的操作基本相同,在插入之前节点p的指针域p->next指向的是节点H,在插入节点之后,插入的节点就相当于H的位置,所以此时只需要把H节点与插入的节点连接起来,同时将插入的节点与P链接起来就可以了。即为:q -> next = p -> next ; p -> next = q ; 此时两个步骤顺序是不可以更改的,如果你先将p -> next = q ; 然后将q -> next = p ->next ; 此时是把插入节点和插入节点的下一节点连接了起来,而上一节点和插入节点就断掉了,切记!!!!!
通过形参传入链表以及要插入链表节点的位置,以及插入节点数据域的数据。如果遍历链表找到标记与节点相同,则证明找到,否则继续遍历。
void insert_LinkLstNode(linkList *List , int insert_position ,int insert_date){
int flag = 0; //flag用于标记节点,判断时候找到插入位置
linkList * q ,* p = List;
if ( insert_position < 0 || p == NULL)
{
printf("插入失败");
}
else
{
while (p != NULL)
{
if (insert_position == flag )
{
q = (linkList *)malloc(sizeof(linkList));
q -> date = insert_date;
q -> next = p -> next;
p -> next = q;
break;
}
else
{
flag++;
p = p -> next;
}
}
}
}
- 打印列表
-
打印链表就比较简单了,只需要遍历即可。
-
//打印链表。思想:打印列表便比较简单,直接传入一个头指针,之后遍历整个链表进行输入。
void print_LinkList(linkList * H){
linkList * P = H ->next;
printf("the value of date is : %5d\n",P->date);
while (P != NULL)
{
P = P->next;
}
}
下面是经过Dev C++运行通过的链表操作代码:
#include<stdio.h>
#include<stdlib.h>
typedef struct LNode{
int index;
struct LNode *next;
}LNode;
LNode* init_Head(){
LNode* Head;
Head = (LNode*)malloc(sizeof(LNode));
Head->index = NULL;
return Head;
}
void creat_Element(LNode* Head , int n){
LNode *r , *end;
int index = 0;
r = Head;
for(int i = 0 ; i < n ; i++)
{
printf("请输入第%d个结点的index!\n",i+1);
end = (LNode *)malloc(sizeof(LNode));
scanf("%d",&index);
end->index = index;
r->next = end;
r = r->next;
}
r->next = NULL;
}
void delete_Element(LNode* Head , int e_index){
LNode *Var , *delet_elem;
int count = 0;
Var = Head;
if(Var->next == NULL)
{
printf("空链表!\n");
return;
}
while(Var->next != NULL)
{
++count;
if(Var->next->index == e_index)
{
printf("已找到所要删除的结点为:%d!\n",count);
delet_elem = Var->next;
Var->next = delet_elem->next;
free(delet_elem);
}
else
{
Var = Var->next;
if(Var == NULL)
{
printf("所要删除节点不存在!\n");
return;
}
}
}
}
void alter_Element(LNode* Head , int alter_index , int needed_index){
LNode* Var;
Var = Head;
int count = 0;
if(Var->next == NULL)
{
printf("空链表!\n");
return;
}
while(Var->next != NULL)
{
Var = Var->next;
++count;
if(Var->index == alter_index)
{
printf("所需修改结点为第%d个结点\n",count);
Var->index = needed_index;
return;
}
}
printf("未找到所要修改的结点!\n");
}
void find_Element(LNode* Head ,int e_index){
LNode* Var;
int count = 0;
Var = Head;
if(Var->next == NULL)
{
printf("空链表!\n");
return;
}
while(Var->next != NULL)
{
++count;
Var = Var->next;
if(Var->index == e_index)
{
printf("查找的结点为第%d个结点!index为:%d\n",count,Var->index);
return;
}
}
printf("查找的结点不存在!\n");
}
void print_LinkList(LNode* Head){
LNode* Var;
int i = 1;
Var = Head;
if(Var->next == NULL)
{
printf("空链表!\n");
return;
}
while(Var->next != NULL){
Var = Var->next;
printf("第%d个结点index : %d\n",i,Var->index);
++i;
}
}
int main()
{
LNode* Head;
int num = 0,delete_index = 0,find_index = 0,alter_index = 0,needed_index = 0;
Head = init_Head();
printf("请输入要添加元素的个数!\n");
scanf("%d",&num);
creat_Element(Head , num);
print_LinkList(Head);
printf("请输入删除结点的index!\n");
scanf("%d",&delete_index);
delete_Element(Head , delete_index);
print_LinkList(Head);
printf("请输入修改结点的index!\n");
scanf("%d",&alter_index);
printf("请输入所需修改后的index!\n");
scanf("%d",&needed_index);
alter_Element(Head,alter_index,needed_index);
print_LinkList(Head);
printf("请输入查找结点的index!\n");
scanf("%d",&find_index);
find_Element(Head , find_index);
print_LinkList(Head);
return 0;
}
如果您看完有任何不理解的地方,或者有任何补充,欢迎在下方留言!