链表优点:链表各个节点个数可以灵活变动,学生多时可以增加节点,少时可以减少节点,链表不要求存储空间连续,空间利用率高
链表:链表中每个节点在内存中位置不一定连续,所以每一节点中一定有个字段存放下一个节点的地址,如此便可已形成链表结构
链表的每个节点在内存中位置不一定连续
每一节点中一定有一个字段存放下一个节点的地址
链表的分类:单向链表、单向循环链表、双向链表、双向循环链表
单像链表的标准结构:
struct Node
{
类型 data;//存放数据比如学生的学号
Node * next;//存放下一个节点的地址(必须有)
};
#include <stdio.h>
#include <stdlib.h>
#include <string.h>//定义单向链表的节点
struct Node
{//数据成员,可以是原始类型,也可以是struct,enum,unionchar name[32];//一定不能缺的成员struct Node * next;//指向下一个节点的指针
};int main()
{//产生3个节点struct Node stu1;struct Node stu2;struct Node stu3;//给节点的数据赋值strcpy(stu1.name,"学生A");strcpy(stu2.name,"学生B");strcpy(stu3.name,"学生C");//上面三个节点是散乱分布的,需要把他们穿成链表//stu1作为头节点,stu3作为尾节点stu1.next=&stu2;stu2.next=&stu3;stu3.next=NULL;//stu3是最后一个节点,所以next为NULL//打印链表,从链表头开始struct Node *pHead = &stu1;//头指针指向第一个节点while(pHead!=NULL){printf("%s ",pHead->name);pHead = pHead->next;//获取下一个节点的内存地址}printf("\n");return 0;
}
向链表的尾部插入节点
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>//定义单向链表的节点
struct Node
{//数据成员,可以是原始类型,也可以是struct,enum,unionchar name[32];//节点的数据成员//一定不能缺的成员struct Node *next;//指向下一个节点的指针
};int main()
{//定义两个节点指针,一个指向头节点(方便遍历),一个指向尾节点(方便插入)struct Node *pHead = NULL;struct Node *pTail = NULL;//空链表的时候//让用户可以随时输入学生while(1){printf("请输入学生姓名,quit(退出) print(打印):\n");char name[32] = {0};scanf("%s",name);if(strcmp(name,"quit")==0)break;else if(strcmp(name,"print")==0){struct Node *pNode = pHead;//不要改变pHead的值,让他一直指向头部while(pNode!=NULL){printf("%s ",pNode -> name);pNode = pNode->next;//指向下一个节点}printf("\n");}else{//struct Node stu;//可以定义节点,但是这样定义的话离开}内存就释放了,stu是局部变量//分配节点堆内存,除非free,否则一直有效struct Node *pNode = (struct Node*)malloc(sizeof(struct Node));strcpy(pNode->name,name);pNode->next = NULL;//因为这个节点即将作为尾部节点//把节点放在链表中//判断是不是第一个节点if(pHead==NULL){pHead = pNode;pTail = pNode;//第一个节点既是头,又是尾部}else{pTail -> next = pNode;//让一个尾巴的next = 新节点pTail = pNode;//让pTail始终指向最新的尾巴}}}return 0;
}
向链表头部插入节点
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>//定义单向链表的节点
struct Node
{//数据成员,可以是原始类型,也可以是struct,enum,unionchar name[32];//节点的数据成员//一定不能缺的成员struct Node *next;//指向下一个节点的指针
};int main()
{//定义两个节点指针,一个指向头节点(方便遍历),一个指向尾节点(方便插入)struct Node *pHead = NULL;struct Node *pTail = NULL;//空链表的时候//让用户可以随时输入学生while(1){printf("请输入学生姓名,quit(退出) print(打印):\n");char name[32] = {0};scanf("%s",name);if(strcmp(name,"quit")==0)break;else if(strcmp(name,"print")==0){struct Node *pNode = pHead;//不要改变pHead的值,让他一直指向头部while(pNode!=NULL){printf("%s ",pNode -> name);pNode = pNode->next;//指向下一个节点}printf("\n");}else{//struct Node stu;//可以定义节点,但是这样定义的话离开}内存就释放了,stu是局部变量//分配节点堆内存,除非free,否则一直有效struct Node *pNode = (struct Node*)malloc(sizeof(struct Node));strcpy(pNode->name,name);pNode->next = pHead;//让新节点的下一个指向之前链表的头节点(pHead)//在这个时候,pHead依然指向上一次的头节点,而此刻头节点已经变成我们的新节点//所以需要更新pHead的值pHead=pNode;//只想最新的头结点}}return 0;
}
在链表中部插入节点
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>//定义单向链表的节点
struct Node
{char name[32];struct Node *next;
};void PrintList(struct Node *pHead)//指针的拷贝,不会影响实参
{while(pHead){printf("%s ",pHead->name);pHead = pHead->next;}printf("\n");
}int main()
{//定义一条链表,两个节点A,Bstruct Node A;strcpy(A.name,"学生A");struct Node B;strcpy(B.name,"学生B");//形成一条链表A.next=&B;B.next=NULL;//在A,B之间插入新节点struct Node C;strcpy(C.name,"学生C");//插入//C.next=&B;//C->B//A.next=&C;//A->C//或C.next = (&A)->next;//或C.next = A.next;A.next = &C;//打印链表PrintList(&A);return 0;
}
删除单向链表尾部结点
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>struct Node
{char name[32];struct Node *next;
};//创建一个链表,拥有N个节点,尾插法
struct Node * CreateList(int N)
{struct Node *pHead = NULL;struct Node *pTail = NULL;for(size_t i=0; i<N; i++){struct Node *pNode = (struct Node *)malloc(sizeof(struct Node));sprintf(pNode -> name,"学生%d",i+1);pNode -> next = NULL;if(pHead == NULL)pHead = pNode;elsepTail -> next = pNode;pTail = pNode;}return pHead;//返回链表头结点的指针
}//打印一条链表
void PrintLinst(struct Node *pHead)
{while(pHead!=NULL){printf("%s ",pHead -> name);pHead = pHead -> next;//pHead的改变不影响实参,因为是参数的拷贝}printf("\n");
}//删除链表的尾部节点
struct Node * DeleteTail(struct Node *pHead)
{//需要判断这个链表是否只有一个节点,不存在倒数第二个节点if(pHead -> next == NULL){printf("当前链表只有一个节点\n");free(pHead);//删完后,这条链表一个节点也没有了return NULL;}struct Node *pTemp = pHead;//需要获取尾部节点的前一个节点指针(因为我们要把前一个节点的next设置为NULL)while(pTemp -> next -> next != NULL){pTemp = pTemp -> next;//pTemp的改变不影响实参}printf("当前节点是:%s\n",pTemp -> name);//删除最后一个节点的内存free(pTemp -> next);//将倒数第二个的next置为NULL,让倒数第二个变成尾节点pTemp -> next = NULL;return pHead;
}int main()
{struct Node *pHead = CreateList(5);PrintLinst(pHead);pHead = DeleteTail(pHead);PrintLinst(pHead);pHead = DeleteTail(pHead);PrintLinst(pHead);pHead = DeleteTail(pHead);PrintLinst(pHead);pHead = DeleteTail(pHead);PrintLinst(pHead);//接受新的pHeadpHead = DeleteTail(pHead);PrintLinst(pHead);return 0;
}
删除单向链表头部节点
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>struct Node
{char name[32];struct Node *next;
};//创建一个链表,拥有N个节点,尾插法
struct Node * CreateList(int N)
{struct Node *pHead = NULL;struct Node *pTail = NULL;for(size_t i=0; i<N; i++){struct Node *pNode = (struct Node *)malloc(sizeof(struct Node));sprintf(pNode -> name,"学生%d",i+1);pNode -> next = NULL;if(pHead == NULL)pHead = pNode;elsepTail -> next = pNode;pTail = pNode;}return pHead;//返回链表头结点的指针
}//打印一条链表
void PrintLinst(struct Node *pHead)
{while(pHead!=NULL){printf("%s ",pHead -> name);pHead = pHead -> next;//pHead的改变不影响实参,因为是参数的拷贝}printf("\n");
}//删除链表的尾部节点
//错误版本
struct Node * _DeleteTail(struct Node *pHead)
{printf("释放内存前,打印第二个节点的地址%p\n",pHead -> next);free(pHead);//释放头节点的内存printf("释放内存后,打印第二个节点的地址%p\n",pHead -> next);//此刻,pHead指向的内存已经无效pHead = pHead -> next;//让头结点的指针指向第二个节点return pHead;
}//删除链表的尾部节点
//正确版本
struct Node * DeleteTail(struct Node *pHead)
{if(pHead == NULL)return NULL;struct Node *pTemp = pHead -> next;free(pHead);//释放头节点的内存//此刻,pHead指向的内存已经无效pHead = pTemp;//让头结点的指针指向第二个节点return pHead;
}int main()
{struct Node *pHead = CreateList(5);PrintLinst(pHead);pHead = DeleteTail(pHead);PrintLinst(pHead);pHead = DeleteTail(pHead);PrintLinst(pHead);pHead = DeleteTail(pHead);PrintLinst(pHead);pHead = DeleteTail(pHead);PrintLinst(pHead);pHead = DeleteTail(pHead);PrintLinst(pHead);return 0;
}
删除单向链表中部节点
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
using namespace std;struct Node
{char name[32];struct Node *next;
};//创建一个链表,拥有N个节点,尾插法
struct Node * CreateList(int N)
{struct Node *pHead = NULL;struct Node *pTail = NULL;for(size_t i=0; i<N; i++){struct Node *pNode = (struct Node *)malloc(sizeof(struct Node));sprintf(pNode -> name,"学生%d",i+1);pNode -> next = NULL;if(pHead == NULL)pHead = pNode;elsepTail -> next = pNode;pTail = pNode;}return pHead;//返回链表头结点的指针
}//打印一条链表
void PrintLinst(struct Node *pHead)
{while(pHead!=NULL){printf("%s ",pHead -> name);pHead = pHead -> next;//pHead的改变不影响实参,因为是参数的拷贝}printf("\n");
}//根据删除指定学生名字的节点
struct Node * DeleteNodeByName(struct Node *pHead,const char *name)
{//第一步,找到要删除的节点的前一个节点struct Node *pTemp = pHead;struct Node *pLast = NULL;//保存前一个节点while(pTemp){if(strcmp(pTemp -> name,name)==0)break;//找到了就跳出循环,此刻Temp指向了要跳出的节点pLast = pTemp;pTemp = pTemp -> next;//指向下一个}if(pTemp != NULL){printf("找到了要删除的节点:%s\n",pTemp -> name);printf("找到了要删除的节点的前一个:%s\n",pLast -> name);if(pLast == NULL)//说明删除的是头结点{pHead = pTemp -> next;delete pTemp;}else{//让删除节点的前一个的next指向删除节点的下一个地址pLast -> next = pTemp -> next;//删除当前节点的内存free(pTemp);}}elseprintf("未找到了要删除的节点!\n");return pHead;
};int main()
{struct Node *pHead = CreateList(5);PrintLinst(pHead);pHead = DeleteNodeByName(pHead,"学生1");PrintLinst(pHead);return 0;
}
单项循环链表与单向链表的区别是:尾节点不是NULL,而是指向头结点,从而形成环
单向循环链表
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>struct Node
{char name[32];struct Node *next;
};int main()
{struct Node stu1;struct Node stu2;struct Node stu3;strcpy(stu1.name,"学生A");strcpy(stu2.name,"学生B");strcpy(stu3.name,"学生C");stu1.next = &stu2;stu2.next = &stu3;stu3.next = &stu1;struct Node *pHead = &stu1;struct Node *pTemp = pHead;do{if(pTemp != NULL){printf("%s ",pTemp -> name);pTemp = pTemp -> next;}}while(pTemp != pHead);printf("\n");return 0;
}
向单向循环链表的尾部插入结点