通过学习翁恺老师,起初很懵圈,看了好几遍视频课程+动手一遍一遍的敲出来,最后勉强懂一点点。 以下是我的学习笔记,与大家分享,如果有不妥的地方,欢迎大家在评论区指出来,一起学习进步!!!
0.链表的是什么
链表是一种物理存储单元上非连续、非顺序的存储结构。数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
线性表的顺序存储结构缺点是每一次插入和删除元素,大量元素的移动会导致时间效率低下。为了改进顺序存储结构的缺点,引入链式存储结构,即为链表。
链式存储结构的特点是用一组任意的存储单元来存储线性表中的数据元素。这样在插入和删除元素时,可以通过直接修改指针完成操作,时间效率大大提高。但因为链式存储结构的存储单元不连续,所以需要通过指针来访问它的后续元素。
为了表示每个数据元素与其直接后继数据元素之间的逻辑关系,我们需要存出一个其直接后继的存储位置。我们把存储数据元素信息的域成为数据域,把存储后继位置的域称为指针域,这两部分构成一个节点。
n个节点链接成一个链表,即为线性表的链式存储结构。因为每个节点只有一个指针域,所以又将这样的链表称为单链表。
1.创建链表
创建一个结构体表示链表的结点类型
typedef struct_node{
int value;
struct_node *next; //下一个数据类型同样是一个value,一个指针
}Node;
2.在链表后面添加新数据
//往链表里面加元素:输入一个数据,制造一个结点,加到链表的后面
#include<stdio.h>
#include<stdlib.h>
//创建一个结构体表示链表的结点类型
typedef struct_node{
int value;
struct_node *next; //下一个数据类型同样是一个value,一个指针
}Node;
int main()
{
Node* head=NULL;
int number;
do{
scanf("%d",&number);
if(number!=-1){
//add to the linked-list
Node*p=(Node*)malloc(sizeof(Node)); //申请一块内存,使p为新数据的地址
//新创的数据结构的两个成员
p->value=number; //把数据存到新创的结构体的成员value里面 ;
p->next=NULL; //因为新输入的数据一定是作为最后一个的,所以他的next成员是NULL
//find the last 找到原来最后一个数据
Node* last=head;
//判断新数据是不是第一个数据 head==NULL则为第一个数据
if(last){ // 新数据不是第一个数据
while(last->next){ //最后一个数据的next为NULL
last=last->next; //让last从head不断地指向下一个,并等于next,直到最后一个数据next为NULL为止
}
//attach 让上一个的成员next指向新加的这个数据
last->next=p; //last已经指向最后一个数据了,last指向结构的成员next等于p
} else{ // 新加的数据是第一个数据
head=p; //让头指针等于新加的数据的地址
}
}while(number!=-1);
}
return 0;
}
3.链表的函数
方法一,向函数传头指针的指针进去,即可以对头指针修改
#include<stdio.h>
#include<stdlib.h>
//定义一个结点结构
typedef struct _node{
int value;
struct _node* next;
}Node;
Node* add(Node**head,int number);
int main()
{
Node* head=NULL;
int number;
//不断输入number,直到-1停止
do{
scanf("%d",&number);
if(number!=-1){
add(&head,number); //把新数据加到链表的后面
}
}while(number!=-1);
return 0;
}
//把新数据加到链表的后面
Node* add(Node** pHead,int number)
{
//创建新结点
Node* p=(Node*)malloc(sizeof(Node));
//对新结点的两个成员填上数据
p->value=number;
p->next=NULL;
//把新结点和原来的最后一个数据衔接上,即让last的next指向新结点地址
Node* last=*pHead;
//判断新数据是不是链表的第一个
if(last){
while(last->next){
last=last->next; // <---不是
}
last->next=p;
}else{
*pHead=p; //是第一个,让头指针指向新结点
}
return *pHead;
}
方法二 ,建立一个新的结构,放链表的头指针
好处:我们定义了一个数据结构来存放整个链表的头指针,我们可有其他的扩充
比如存放链表的最后一个数据的指针,可以有更多的功能
#include<stdio.h>
#include<stdlib.h>
//定义一个结点结构
typedef struct _node{
int value;
struct _node* next;
}Node;
typedef struct _list{ //建立一个新的结构,放链表的头指针
Node* head;
}List;
Node* add(Node**head,int number);
int main()
{
List list;
list .head=NULL;
int number;
//不断输入number,直到-1停止
do{
scanf("%d",&number);
if(number!=-1){
add(&list.head,number); //把新数据加到链表的后面
}
}while(number!=-1);
return 0;
}
//把新数据加到链表的后面
Node* add(Node** plist,int number)
{
//创建新结点
Node* p=(Node*)malloc(sizeof(Node));
//对新结点的两个成员填上数据
p->value=number;
p->next=NULL;
//把新结点和原来的最后一个数据衔接上,即让last的next指向新结点地址
Node* last=*plist;
//判断新数据是不是链表的第一个
if(last){
while(last->next){
last=last->next; // <---不是
}
last->next=p;
}else{
*plist=p; //是第一个,让头指针指向新结点
}
return *plist;
}
4.链表的查找
链表的遍历
Node* p;
for(p=plist->head;p;p=p->next){
printf("%d\t",p->value);
}
链表的查找
Node* p;
int result=1;
for(p=list.head;p;p=p->next){
if(p->value==number){
printf("晚上好,找到啦\n");
result=0;
break;
}
}
if(result){
printf("啊呀没有找到呜呜呜\n");
}
5.链表的删除
我们查找到number,然后把他删除,需要做什么事情呢?
两件事:1.让number前面的结点指针指向number后面的结点
2.把number申请的动态内存free掉
现在在单向链表中,无法直接知道number前面结点的成员next ,(指针p指向number)
解决方法:
a. 定义另外一个指针Node * q,让它在指向前面那个结点
b. 让q->next=p->next
代码如下
Node* q;
for(q=NULL,p=list.head;p;q=p,p=p->next){ //p不断地指向下一个,直到p->value==number
//在p移动之前,让q=p;让q也跟着移动,保持q一直是p的前一个
if(p->value==number){
if(q){
q->next=p->next;
}else{
list.head=p->next;
}
free(p);
break;
}
}
注意
我们需要保证 ”->“ 左边的指针是有效值,即不能是NULL
如果会出现NULL的情况,我们要分两情况:
NULL的时候;
不是NULL的时候。
在上面进行链表的删除的时候,就出现这种情况
”->“ 的左边有p和q,我们对每个进行讨论
p作为for循环的条件,如果p为NULL,则不会进入循环,所以隐藏条件p不是NULL
q则没有办法保证q不是NULL,要对q分情况
6.链表的清除
链表malloc内存空间,我们最后总要想办法free整个链表,怎么做尼
解决方法:
a. 让q指向p的下一个结点;
b. free p.
代码如下
for(p=list.head;p;q=p){
q=p->next;
free(p);
}
7.总代码
#include<stdio.h>
#include<stdlib.h>
//创建链表
typedef struct _node{
int value;
struct _node* next;
}Node;
//建立一个新的结构,放链表的头指针
typedef struct _list{
Node* head;
}List;
//自定义的函数声明
Node* add(Node**head,int number);
void print(List *plist);
int main()
{
List list;
list .head=NULL;
int number;
//不断输入number,直到-1停止
do{
scanf("%d",&number);
if(number!=-1){
add(&list.head,number); //把新数据加到链表的后面
}
}while(number!=-1);
print(&list); //链表遍历输出
printf("\n");
//链表的查找
scanf("%d",&number);
Node* p;
int result=1;
for(p=list.head;p;p=p->next){
if(p->value==number){
printf("哇塞找到啦\n");
result=0;
break;
}
}
if(result){
printf("啊呀没有找到呜呜呜\n");
}
//链表的删除
Node* q;
for(q=NULL,p=list.head;p;q=p,p=p->next){
if(p->value==number){
if(q){
q->next=p->next;
}else{
list.head=p->next;
}
free(p);
break;
}
}
//链表的清除
for(p=list.head;p;q=p){
q=p->next;
free(p);
}
return 0;
}
//把新数据加到链表的后面
Node* add(Node** plist,int number)
{
//创建新结点
Node* p=(Node*)malloc(sizeof(Node));
//对新结点的两个成员填上数据
p->value=number;
p->next=NULL;
//把新结点和原来的最后一个数据衔接上,即让last的next指向新结点地址
Node* last=*plist;
//判断新数据是不是链表的第一个
if(last){
while(last->next){
last=last->next; // <---不是
}
last->next=p;
}else{
*plist=p; //是第一个,让头指针指向新结点
}
return *plist;
}
//链表遍历输出
void print(List *plist)
{
Node* p;
for(p=plist->head;p;p=p->next){
printf("%d\t",p->value);
}
}