C语言数据结构之单链表
一级目录
二级目录
三级目录
一、基础知识
表中结构具有线性关系,我们称之为线性表,线性表有两大实现方式,一是上一篇文章讲解的顺序表,第二个就是这篇文章我们讲解的链表。链表又有很多种类,单链表,循环链表,双向链表等。
单链表在逻辑上是连续的一段存储结构,但是在物理上不是连续的,结点之间不是在一段连续的空间中,这些结点之间的联系是通过指针的不断指向来完成的。
- 每个结点包含一个数据域
data
和指针域next
,数据域存储数据,指针域指向下一个结点的首地址。 - 一般来说,头结点的
data
域可以什么信息都不存储,这样方便对链表进行操作,也可以保存最后一个结点的首地址(这样可以方便找到最后一个结点),首元结点才是数据开始的第一个结点,就相当于数组的下标为0
的意思。 - 链表不存在说链表存满这种说法,和顺序表不同的是,我们不是一次性开辟一段地址连续的存储空间,而是需要一个结点来创建一个结点,并且这些结点在物理层面上不是连续的。
- 操作单链表实际上就是操作指针进行不断的指向,在脑海中,一定需要明确指针这时候在哪里,在干什么。
二、单链表的实现
1. 结点结构体和需要的宏定义
# define True 1
# define False 0
# typedef struct SeqLink
{
int data;//数据域
struct SeqLink* next;//指针域
}Link;
2.创建一个头结点
Link* CreatLink()
{
Link* p = (Link*)malloc(sizeof(Link));
if( p == NULL)
{
exit(1);//结点创建失败
}
memset(p,0,sizeof(Link));
return p;
}
3.判断是否为空
如果说头结点的next
指向空,说明没有首元结点,说明这个链表是空的。
int IsEmpty(Link* p)
{
if(p->next == NULL)
{
return True;
}
return False;
}
4. 打印单链表
定义一个指针指向首元结点,然后开始循环,当这个结点不为空的时候说明这个结点存在,所以打印并指针向下一个指向。
void PrintLink(Link* p)
{
if(IsEmpty(p) == True)
{
printf("空的怎么打印你教教我\n");
return;
}
Link* tmp = p->next;
while(tmp!=NULL)
{
printf("%d ",tmp->data);
tmp = tmp->next;
}
putchar('\n');
}
5. 头插
一定要注意赋值的先后顺序,如果先让头结点指向了新结点node,那么就丢失了原本的首元结点的地址了。
void InsertHead(Link* p,int num)
{
Link* node = (Link*)malloc(sizeof(Link));
if(node == NULL)
{
return;
}
node->data = num;
node->next = p->next;
p->next = node;
}
6. 尾插
第一步,遍历到最后一个结点。
第二步,让最后一个结点连接上新的结点
第三步,让新结点指向空
void InsertBack(Link* p,int num)
{
if(IsEmpty(p) == True)
{
return;
}
Link* node = (Link*)malloc(sizeof(Link));
Link* tmp = p->next;
while(tmp!=NULL)
{
tmp = tmp->next;
}
tmp->next = node;
node->data = num;
node->next = NULL;
}
创建了一个Link*
类型的tmp
的作用是操作的时候保留下来了头指针,要不然就会找不到这个链表了。
7. 指定位置插入
和尾插类似,只不过从遍历到最后一个元素变成遍历到指定位置这个元素。
我将插入的这个结点叫i
,前面的结点叫i-1
,后面的叫i+1
需要注意的是,需要先将i
指向i+1
,再让i-1
指向i
;
如果这个顺序颠倒过来,就会丢失i+1
的位置。
void InsertPosition(Link* p,int num)
{
Link* tmp =p;
Link* node = (Link*)malloc(sizeof(Link));
int i = 0;
while(tmp->next!=NULL)
{
if(i==index)
{
node->next = tmp->next;
node->data = num;
tmp->next = node;
return;
}
i++;
tmp = tmp->next;
}
return;
}
8. 头删
void PopHead(Link* p)
{
if(IsEmpty(p) == True)
{
return;
}
Link* tmp = p->next;//首元结点
p->next = tmp->next;
free(tmp);
tmp = NULL;
}
9. 尾删
尾删需要两个指针来完成,一个指向最后一个结点(要删除的)
另一个指向它的前一个结点,我们让前一个结点指向空,并且释放掉最后一个结点就可以了。
void PopBack(Link* p)
{
if(IsEmpty(p) == True)
{
return;
}
Link* tmp1 = p;
Link* tmp2 = p->next;
while(tmp2!=NULL)
{
tmp1 = tmp1->next;
tmp2 = tmp2->next;
}
tmp1->next = NULL;
free(tmp2);
tmp2 = NULL;
}
10.指定位置删除
还是需要两个结点,和尾删理解差不多,找到这个元素之后,让前一个元素指向它的下一个元素,然后释放掉这个元素。
需要注意的是下标是否超出原本链表长度。
void PopPosition(Link* p,int index)
{
if(TsEmpty(p) == True)
{
return;
}
int i;
Link* tmp1 = p;
Link* tmp2 = p->next;
while(tmp2!=NULL)
{
i++;
tmp2 = tmp2->next;
}
if(index>i||index<0)
{
return;//索引错误
}
i = 0;
tmp2 = p->next;
while(i<index)
{
i++;
tmp2 = tmp2->next;
tmp1 = tmp1->next;
}
tmp1->next = tmp2->next;
free(tmp2);
tmp2 = NULL;
}
11.指定元素删除
从头到尾遍历单链表,找到这个元素就删除这个结点,没找到继续往后遍历,知道单链表结束。
void PopNum(Link* p,int num)
{
if (IsEmpty(p) == True)
{
printf("空的我删你妈你问问问\n");
return;
}
Link* tmp1 = p;
Link* tmp2 = p->next;
while(tmp2!=NULL)
{
if(tmp2->data = num)
{
tmp1->next = tmp2->next;
free(tmp2);
tmp2 = NULL;
return;
}
tmp1 = tmp1->next;
tmp2 = tmp2->next;
}
}
12.更改指定位置的数据
遍历到这个结点,将这个结点的data重新赋值即可
void ChangePosition_Num(Link* p,int index,int new)
{
if (IsEmpty(p) == True)
{
return;
}
int i = 0;
Link* tmp = p->next;
while(i<index)
{
i++;
tmp = tmp->next;
if(tmp == NULL)
{
printf("下标超出\n");
return;
}
}
tmp->data = new;
}
13.查询元素位置
遍历单链表,找到这个元素了就返回这个下标。
int SearchNum_Position(Link*p,int num)
{
Link* tmp = p->next;
int i =0;
while(tmp!=NULL)
{
if(tmp->data == num)
{
return i;
}
i++;
tmp = tmp->next;
}
return False;
}
14.通过下标查询元素值
遍历单链表,找到下标所对应的结点,返回这个结点的数据域。
int SearchPosition_Num(Link* p,int index)
{
int i =0;
Link* tmp = p->next;
while(tmp!=NULL)
{
if(i == index)
{
return tmp->data;
}
i++;
tmp = tmp->next;
}
return False;
}
三、总结
- 操作单链表实际上就是指针的不断指向,我们要在脑海中对这个指针目前指向哪里,他其中包含什么数据要清楚。
- 单链表一个结点就包含两个数据,一个数据域,一个指向下一个结点的指针域。
- 在进行某些删除和插入结点的操作的时候,一定要注意断开连接和建立新连接的顺序,否则会造成丢失后面数据的局面出现。