今天我简单的讲一下数据结构中的链表,链表可以说是我们学习数据结构的基础,如果链表不能掌握的话,在往下学习数据结构的过程中会变的越来越难。那么什么是链表呢?比较官方的说法是:链表是用一组任意的存储单元存放线性表的元素,这组存储单元可以连续也可以不连续,甚至可以零散分布在内存中的任意位置。我的理解就是拿线(指针域)把一个个点(结点)连接起来。那什么是单链表呢?简单的说单链表就是只有一条线(指针域)连接他们。来看一下官方的说法是:通过每个节点的指针域将线性表的数据元素,按其逻辑次序链接在一起,由于每个节点只有一个指针域,故称为单链表。好了链表的基本意思和大体思想我们就了解到这里,下面我们来实现一下链表的创建,插入,删除,遍历,求链表长度等操作。
一、链表的创建
在上面我们说链表的时候提到的是用线把点连接起来。那么我们这里面的点又是什么呢?在C语言里我们用结构体表示一个结点。在里面存放我们的数据域和指针域。它的样子如下:
struct Node
{
int data;
struct Node *next;
};
这里面data就是我们的数据域,我们用int举例。struct Node *next是我们的指针域。好了,节点定义完了。下面我们进入正式的创建单链表的过程。不说别的先看代码:
//创建单链表
void CreateList(Node *frist,int a[],int n)
{
Node *s = NULL;
Node *d = NULL;
if(frist == NULL)//如果没有头结点创建一个头结点
{
frist = (Node *)malloc(sizeof(Node));
frist->next == NULL;
}
d = frist;
for(int i = 0;i < n; i++)//这叫尾插法
{
s = (Node *)malloc(sizeof(Node));
s->data = a[i];
s->next = NULL;
d->next = s;
d = s;
}
}
在上述代码中。一开始传入三个参数,其中Node *frist 参数是用于返回我们创建完成的链表的,int a[] 参数是创建链表时使用的数据,int n 参数表示我们传入数据的长度。然后申请了两个指针变量,s 和 d ,其中s 的作用就是代表一个新的结点。d 作用就是代表链表的最后一个结点。然后 判断 链表frist 是否有头结点,没有就申请一个头结点,有的话,就是用那个头结点。向下进入循环,在循环里才是链表真正创建的过程。首先申请一个新结点(s),并且把值赋给新结点的数据域,指针域的值赋值为 NULL ,然后把新结点(s代表)的地址交给上一个结点(d 代表),当你添加上这个新结点之后,新结点就变成了链表的最后一个结点,我们需要用d 来代表,怎么表示呢?很简单就是把新结点的地址赋值给 d 就可以了。
好了创建单链表的大体过程叙述完毕。继续向下看
二、插入元素
void Insert(Node *frist,int i, int date)
{
Node *d = frist;
Node *s = NULL;
int count = 0;
while(d!= NULL && count < i-1)
{
d = d->next;
count++;
}
if(d == NULL)
{
printf("错误\n");
}
else
{
s = (Node *)malloc(sizeof(Node));
s->data = date;
s->next = d->next;
d->next = s;
printf("插入成功\n");
}
}
在上述代码中:有三个参数,第一个参数frist 表示传入一个链表,第二个参数 i 是表示我们在 第i 个位置插入数据,第三个参数 data 就是我们需要插入的数据。在内部我们一开始定义了三个变量分别是:d 用于代表你的传入的链表;s 代表我们申请的一个新的结点,count 用于计数。然后进入一个循环,循环的目的是从头结点开始向下找,找到第i-1 的位置,这里为什么找第i-1的位置呢?因为如果你想成为第i个位置上的元素你就必须放在第i-1的后面。后面就比较简单了,是申请一个新结点(s)然后把新节点(s)的数据域赋值为date、指针域赋值为前面一个结点(d)的指针域,这里为什么要赋值前面的指针域而不是后面的结点地址呢?因为每一个结点的指针域都是储存的后面结点的地址,所以这里我们不用费劲找后面结点的地址了,只要找到前面结点的指针域就可以了。然后把新结点的地址赋值给前一个结点的指针域就可以了。
三、删除元素
void Delete(Node *frist, int i,int *data)
{
Node *p = frist;
Node *d = NULL;
int count = 0;
while(p!= NULL && count < i-1)
{
p = p->next;
count++;
}
if(p->next == NULL || p == NULL)
{
printf("删除失败\n");
}
else
{
d = p->next;
*data = d->data;
p->next = p->next->next;
free(d);
printf("删除成功\n");
}
}
删除元素和插入元素的过程基本相似,这里只简单说一下:把结点删除后,剩下两段连接的过程。这里我门还是要找所要删除结点的前一个结点。然后把前面结点的指针域也就是所要删除结点的地址赋值给临时变量(d),然后把前面结点的指针域赋值为所要删除结点的指针域就可以了,同时释放掉所要删除结点的内存就可以了。
四、遍历元素
void prit(Node *frist)
{
Node *p = frist;
while(p->next != NULL)
{
p = p->next;
printf("%d ",p->data);
}
}
遍历简单的理解就是从头结点依次向后查找结点,查找到结点接班每一个结点的指针域输出来,然后再向下寻找,知道没有结点为止(没有结点的标志就是结点的指针域为NULL)
五、求链表长度
int Lenght(Node *frist)
{
Node *p =frist;
int count = 0;
while(p->next != NULL)
{
p = p->next;
count++;
}
return count;
}
求链表的长度和遍历链表是一回事,它们的去别就是 一个是把值输出来,一个是计数不输出值。把count返回就可以了。
最后简单总结一下链表:
单链表关心的只是数据元素以及数据元素之间的逻辑关系,而不是每个数据元素的存储器的实际位置。
单链表的存储思想就是用指针表示结点之间的逻辑关系。
单链表由头指针唯一指定,整个单链表的操作必须从头指针开始进行。