结构体 链表 快速入门
一.结构体快速入门
之前学到结构体章节的时候糊里糊涂,课后练习也很少,只会复制粘贴,很多都没去思考。
现在又重新学到找个章节还是想对其理解加深一点。
一.定义结构体的几种方式
1.
struct book
{
char a;
char b[];
};
使用时 : struct book a;定义了一个结构体变量a
2.
typedef struct book book;
struct book
{
char a;
char b[10];
};
book 就指代结构体类型了, book a;定义了一个结构体变量a
3.
typedef struct book
{
char a;
char b[10];
}book;
结合1和2,两句简化成一句,用法同2
4.
struct book
{
char a;
char b[10];
}book[10];
typedef struct book book;
在定义一个结构体的同时,创建了一个结构体变量book[10];
最后一句typedef是为了以后再次创建结构体变量时省事用,
且最大原因其实是 既定义结构体同时又创建一个结构体变量不能用typedef
二.结构体变量如何访问结构体内容
struct book
{
char a;
char b[10];
}book[10];
结构体指针是 *b->a;或者 (*b).a
为什么不用 *b.a 访问呢,因为C语言 * 和 . 都是运算符,但 . 的优先级高
先算右边咋算,b是一个指针,只有在b是一个值的时候才能用 . 先取地址的值就能 如上
这里简单介绍 book[0].a;(加小数点 .)
二.链表如此简单?
1.啥是链表,结构体里放指针就是链表。
看到这的应该都看了一,下面我就用自己常用的定义结构体的方式写。
typedef struct Node
{
int data;
struct Node * next;
}Node;
联想记忆法嘛,像我,从连锁店挑选店铺,搜索店铺地址,去吃了下次就记住名字了嘛。
像那个香飘飘,一年卖下来的奶茶连起来绕地球好几圈儿。
链表 是动态存储分配 运行时分配空间的 当不能确定容量时使用是极好的 但这是不连续的 零散分布
单链表的存储特点:1.逻辑次序和物理次序不一定相同 2.元素之间的逻辑关系用指针表示。
笔者个人认为 在敲代码之前 先画一个简易的示意图来说明比较好
单链表的结点结构: 数据域+指针域; 指针域的指针就是指向下一个同类型结点的地址
tepedef struct node
{
Datatype data;
struct node *next;
}Node,*Link;
当你 Link p;就是声明了一个结构体指针变量 Noed s;是结构体s p=&s;p就指向了s;
在使用时先申请空间 p=(Link)malloc(sizeof(Node)); 等价于 p=(struct noed *)malloc(sizeof(Node));
当初始化结束后,想访问数据元素, (*p).data || p->data 不能 *p.data 上面已经叙述了,这里不再赘述 指针域 p->next;
为了简化单链表的代码操作:使用 头指针和尾标志 头:指向第一个结点的地址, 尾:终端结点的指针域为空
先引入一个头节点, 数据域无意义,只使用指针域 当 指针域为NULL时,称空表 else 非空表
(注:假定链表已创立)
(1)单链表的遍历操作 计数操作
void displayNode(Link head)
{
p=head->next;
count = 0;
while(p!=NULL)
{
printf("%d",p->data);
p=p->next;
count++;
}
return count;
}
这个时候可以求单链表的个数
(2)单链表得查找操作
int queryNode(Link head,DataType x)
{
p=head->next;
while(p!=NULL)
{
if(p->data==x)
{
print(data);
return true;
}
p=p->next
}
return false;
}
(3)单链表得插入操作
已知两个结点,我想在其中插入,如何实现
声明一个新的结点 互换数据域和指针域即可,所以画图很重要 这里笔者奉劝各位 自己画一下就懂了
(注意一点,新结点仍然要申请空间;注意二点,分析边界情况———表头,表尾)
算法描述 (由于单链表带头节点,表头,表中,表尾三种情况的操作语句一致)
noed=(Link)malloc(sizeof(Node));
node->data = x;
node->next = p->next;
p->next = node;
1.工作指针P初始化
2.查找第i-1个结点并使工作指针p指向该节点
3.若查找不成功,则返回false,
否则生成一个元素值为x的新节点s;
将新节点s插入到结点p之后;
返回true;
void insertNode(Link head,int i,DataType x)
{
p = head;
count = 0;
while(p!=NULL&&count<i-1)
{
p=p->next;
count++;
}
if(P==NULL)
return false;
else
{
node = (Link)malloc(sizeof(Node));
node->data = x;
node->next = p->next;
p->next = node;
return true;
}
}
2.单链表的实现
(注意,事先定义数组来存数据)
(1)创建一个单链表——头插法;理论学完了,搞点实际的。
头插法就是将插入结点插在头节点的后面,这个存入的顺序和数组是相反的,编译打印的时候注意。
Link newList(DataType a[],int n)
{
head=(Link)malloc(sizeof(Node));
head->next=NULL;
for(i=0;i<n;i++)
{
node=(Link)malloc(sizeof(Node));
node->data=a[i];
node->next=head->next;
head->next=node;
}
return head;
}
(2)创建一个单链表——尾插法
将待插入结点插在终端结点的后面
初始化head结点,新结点依次插在后面就行,切勿忘记初始化时第一个next指向NULL
Link newList(DataType a[],int n)
{
head = (Link)malloc(sizeof(Node));
head->next = NULL;
rear = head;
for(i=0;i<n;i++)
{
node=(Link)malloc(sizeof(Node));
node->data=a[i];
rear->next=node;
rear=node;
}
rear->next=NULL;
return head;
}
(3)单链表结点的删除
①.先找到要删除的位置
简述 q->next = p->next; free(p); 简化图像 q p next (删除 p)
②.如何保证 p ,q 指针一前一后
同时移动指针一次:q=p;p=p->next;
(在查找过程中,如果发现p所指向的结点data值不是要找的x,则p,q同时后移;一旦找到,则执行删除操作。)
(在查找过程中,如果若一直没有找到data域为x的结点,q没事,p为空,此时退出循环,返回false)
③.删除时要分析边界情况——要删的表为空表时
比如 head=NULL,或者只有头结点
(如果发现待删除的表是空表,则提前返回false)
算法描述:
1.判断是否是空表,如果是空表返回false;
2.工作指针p,q初始化;
3.若p指针不为空,则继续下列循环:
3.1如果找到data域等于x的结点,则:
3.1.1摘链,将结点p的从链表上摘下;
3.1.2释放被删结点;
3.1.3提前返回true,代表删除成功
否则
3.2.1 q 移动到 p 所在的位置
3.2.2 p 移动到下一个结点
④.循环结束,说明没找到和x相等的结点,则返回false;
(笔者感受:这就是学习的魅力,前人栽树后人乘凉,感谢大佬.自己写的时候考虑从来没有这么完善)
bool deleteNode(Link head,DataType x)
{
if(head==NULL||head->next==NULL)
return false;
p=head->next;
q=head;
while(p!=NULL)
{
if(p->data==x)
{
q->next=p->next;
free(p);
return true;
}
else
{
q=p;
p=p->next;
}
}
return false;
}
(4)单链表的释放
将单链表中所有结点的存储空间释放(好处多多)(头结点不保留)
算法描述
q=head;
head=head->next;
free(q);
问(代码循环退出的条件是什么呢?)
(笔者认为是head->next=NULL)
3.循环链表
首尾相接,循环链表,单循环很容易理解,尾连首即可。链表操作同上。
为了避免死循环,判断条件由不为空变成不为head即可。