数据结构之链表
链表的定义
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作复杂。 (百度)
链表:一种链式存储的线性表,用一组地址任意的存储单元存放线性表的数据元素,称存储单元为一个结点。
链表的形式:结点地址{data,next}
链表的形式基本知道后,链表的使用离不开指针。关于指针 和 指针指向的空间 是有区别的
指针不能看成是一个地址,指针是用来存放地址的。指针指向的空间不是地址,是一个还未使用开辟出来的空间。
ps:这之上是去年写了一个开头,今年复习的时候感觉链表的操作围绕头插,尾插,头删,尾删这几个操作来做。还是理解学会这几个操作更好的去理解链表
链表的初始化
#pargma once
#include <stdio.h>
typedef int SLDatatype;
typedef struct Node{
SLDatatype value;
struct Node *next;
}
typedef struct SList{
Node *first;//定义一个头结点
}
初始化我个人觉得难点可能在于一开始比较想不通结构体内定义next。结构体调用自己定义一个next指针变量是为了表示链表下一个的指向。在指针域指向下一个链表的指针域。(自行体会)
链表的两种插入
//链表的头插
void SListPushFront(SList *s, SLDatatype v){
Node *node = (Node*)malloc(sizeof(Node));//插入肯定要开辟一个新空间
node->value = v;
node->next = s->first;
s->first = node;
}
//链表的尾插
void SListPushBack(SList *s, SLDatatype v){
Node *node = (Node*)malloc(sizeof(Node));
node->value = v;
if(s->first == NULL){
s->first = node;
}
else{
Node *c = s->first;
while(c->next != NULL){
c = c->next;
}
c->next = node;
}
}
while循环的意义在于找到最后一个结点,当找到最后一个结点之后,将最后一个结点的next指向node
//链表的任意位置插入
void SListInsertAfter(Node *pos, SLDataType v){
Node *node = (Node*)malloc(sizeof(Node));
node->value = v;
node->next = pos->next;
pos->next = node;
}
从这个图中来看在pos结点与后一个结点之间插入一个新结点。让新结点的指向为pos结点的指向,然后让pos结点的指向为新结点。新的结点就被插入之中了。
链表的删除
//链表的头删
void SListPopFront(SList *s){
assert(s->first != NULL);
if(s->first->next == NULL){
s->first = NULL;
}
Node *second = s->first->next;
free(s->first);
s->first = second;
}
头删很容易理解,分成两种情况。只有一个结点的情况下,直接让头结点为空。如果不是只有一个结点的情况下将头结点指向的下一个结点先标记一下,释放掉头结点,在将标记的结点设为头结点。
//链表的尾删
void SListPopBack(SList *s){
assert(s->first != NULL);
if(s->first->next == NULL){
free(s->first);
s->first = NULL;
}
else{
Node *c = s->first;
while(c->next->next != NULL){
c = c->next;
}
free(c->next);
c->next = NULL;
}
}
其实仔细看尾删跟尾插有点相似,只不过尾删需要找到倒数第二个结点。然后释放到第一个结点,再将第一个结点赋为空。
//链表的任意位置之后结点删除
void SListEarseAfter(Node *pos){
Node *next = pos->next->next;
free(pos->next);
pos->next = next;
}
假设三个结点,先将最后一个结点标记,之后释放掉中间结点,在让第一个结点指向最后一个结点。
链表的另外两种删除
与之前顺序表相似的地方,删除遇到的一个链表中的数据域和删除链表中所有的数据域
//删除遇到的第一个数据域
void SListRemove(SList *s, SLDataType v){
if(s->first == NULL){
return;
}
if(s->first->value == v){
Node *second = s->first->next;
free(s->first);
s->first = second;
}
else{
Node *c = s->first;
while(c->next != NULL){
if(c->next->value == v){
Node *next = c->next;//先将头结点的后一个结点设为next
c->next = c->next->next;//这时候将头结点的后二个结点next转换为头结点的后一个结点
free(next);//释放掉后一个结点
return;
}
c = c->next;
}
}
}
------------------------------------------------------------------------------------------------
//删除遇到的所有数据域
void SListRemoveAll(SList *s, SLDataType v){
if(s->first == NULL){
return;
}
if(s->first->value == v){
Node *next = s->first;
s->first = s->first->next;
free(next);
}
else{
Node *c = s->first;
while(c->next != NULL){
if(c->next->value == v){
Node *next = c->next;
c->next = c->next->next;
free(next);
}
else{
c = c->next;//用到else是如果不等于数据域的话就继续向后走,再到下一个结点去判断数据域
}
}
}
}
上面代码其实很相似,只不过要考虑差距在哪,其实就是调用了几个不同的判断语句。
链表的核心就是这些了。我认为这些如果不能理解一定多画图!!!!画图太有助于理解了!!!!
而且必须多敲代码!!!!我都敲了好几遍!!!!终于在今天写起来比较顺手了!!!!