一.基本的数据结构:单链表(Singly Linked List)
什么是单链表?
下面是百度百科给出的官方解释:
单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。链表中的数据是以结点来表示的,每个结点的构成:元素(数据元素的映象) + 指针(指示后继元素存储位置),元素就是存储数据的存储单元,指针就是连接每个结点的地址数据。
其实通俗的来讲,单链表就是结构体变量与结构体变量的连接,结构体变量就是单链表的节点。但是这个结构体是具有一定特色的,其特色为:这些结构体变量都是由数据域和指针域组成的。其中数据域储存了这个节点的数据,指针域用于指向下一节点。
具有“特色”的结构体变量
/*由数据域和指针域组成的结构体*/
struct Node
{
int data; //数据域,可以是任意类型的数据
struct Node* next; //指针域
};
单链表正是由上面这种结构体变量连接而成的。
下面我们来创建结构体变量:
//创建三个结构体变量 node1 node2 node3
struct Node node1 = { 1,NULL };
struct Node node2 = { 2,NULL };
struct Node node3 = { 3,NULL };
在这里我们为结构体变量分别赋值1,2,3;,并且让他们全部指向空。此时的结构体变量互不相干,那么怎么将其连接起来构成一个单链表呢?看下面这张图:
结构体变量怎样连接成一个链表
看完这张图后我们脑海中应该有了思路了。我们可以使Node1的指针域指向Node2,Node2的指针域指向Node3……,这样我们就用前一个结点的指针域指向下一个结点的方法构成了一个以Node1为链表开端的指针了。
代码实现如下
/*结构体变量构成链表*/
node1.next = &node2;
node2.next = &node3;
通过以上内容我们就了解了如何用结构体变量创建了一个单链表,但是这种链表是静态链表。
我们真正用到的是动态链表,这就需要用到malloc()进行动态内存申请.
二. 动态创建一个链表
动态创建一个链表:动态内存申请+模块化设计
1.创建一个链表(创建一个表头表示整个链表)
2.创建结点
3.插入结点
4.删除结点
5.打印遍历结点(测试)
1.创建一个链表(表头)
在这里我们可以用一个结构体指针headNode通过动态内存申请(malloc)指向结构体变量,这样这个结构体指针就指向了链表表头。即创建了一个链表。
/*创建链表表头*/
struct Node* createList()
{
struct Node* headNode = (struct Node*)malloc(sizeof(struct Node));
//初始化结构体变量
//作为有表头链表,其表头的数据域不赋值
headNode->next = NULL;
return headNode;
}
这里将创建表头的过程封装成了函数createList()
2.创建结点
由于表头是特殊的一个节点(首节点),那么我们在创建节点时的过程与创建表头的过程相似。
struct Node* createNode(int data)//创建节点
{
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
//节点初始化
newNode->data = data;
newNode->next = NULL;
return newNode;
}
3.插入节点
头插法插入节点的过程
这里使用的头插法,即后来插入的节点都被存储在了链表的首
//插入
//参数列表的确定:插入哪个列表,插入节点的数据是?
void insertNodeByHead(struct Node* headNode, int data)
{
struct Node* newNode = createNode(data);//调用createNode()创建新节点
newNode->next = headNode->next; //流程图中的圈1过程
headNode->next = newNode; //流程图中的圈2过程
}
4.指定位置删除
在指定位置删除中我们只需要根据参照的值找到对应的节点posNode,然后使posNode的前一个节点PosNodeFront连接(它的next成员指向)posNode->next即可。然后我们将为posNode所分配的动态内存释放就完成了这一过程。
代码:
void deleteNodeByAppoin(struct Node* headNode, int posData)
{
struct Node* posNode = headNode->next;
struct Node* posNodeFront = headNode;
if (posNode == NULL)
{
printf("链表为空,无法删除.\n");
return;
}
else
{
while (posNode->data != posData)
{
posNodeFront = posNode;
posNode = posNode->next;
if (posNode == NULL)
{
printf("未找到指定数据,无法删除.\n");
return;
}
}
posNodeFront->next = posNode->next;
free(posNode);
}
}
5.打印节点
打印遍历流程图
打印节点时我们从头(该链表为有表头链表,第一个节点不存储数据域,此处的“头”指第二个节点)开始一个节点一个节点往后打印因此我们需要一个Node类型的结构体指针:pMode。初始时pMode指向链表的第二个节点。即pMode=headList->next。然后我们判断一下pMode是否为空,若pMode为空。这说明链表仅有一个无数据域的表头,提醒用户,数据为空。反之则进行打印pMode的数据域。然后将pMode移动到下一个节点(pMode=pMode->next)。继续打印遍历整个链表。
打印遍历节点的函数代码:
void printList(struct Node* headList)//打印节点
{
struct Node* pMode;
pMode = headList->next;
if (pMode == NULL)
printf("数据为空.\n");//如果headList的next成员为空的话说明该链表就一个表头,无数据
else
while (pMode)
{
printf("%d", pMode->data); //打印pMode的数据域
pMode = pMode->next; //让pMode指向下一个节点
}
printf("\n");
}
完整代码:
这里我在主函数中写入了一些测试。通过运行结果我们可以看出代码的可靠性
#include<stdio.h>
#include<stdlib.h>
struct Node
{
int data; //数据域,可以是任意类型的数据
struct Node* next; //指针域
};
struct Node* createList()//创建链表表头
{
struct Node* headNode = (struct Node*)malloc(sizeof(struct Node));
//初始化结构体变量
headNode->next = NULL;
return headNode;
}
struct Node* createNode(int data)//创建节点
{
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = data;
newNode->next = NULL;
return newNode;
}
//插入--------------增
//参数列表的确定:插入哪个列表,插入节点的数据是?
void insertNodeByHead(struct Node* headNode, int data)
{
struct Node* newNode = createNode(data);//调用createNode()创建新节点
newNode->next = headNode->next; //流程图中的圈1过程
headNode->next = newNode; //流程图中的圈2过程
}
//删除结点 --------------删
void deleteNodeByAppoin(struct Node* headNode, int posData)
{
struct Node* posNode = headNode->next;
struct Node* posNodeFront = headNode;
if (posNode == NULL)
{
printf("链表为空,无法删除.\n");
return;
}
else
{
while (posNode->data != posData)
{
posNodeFront = posNode;
posNode = posNode->next;
if (posNode == NULL)
{
printf("未找到指定数据,无法删除.\n");
return;
}
}
posNodeFront->next = posNode->next;
free(posNode);
}
}
void printList(struct Node* headList)//打印节点
{
struct Node* pMode = headList->next;
if (pMode == NULL)
printf("数据为空.\n");//如果headList的next成员为空的话说明该链表就一个表头,无数据
else
while (pMode)
{
printf("%d\t", pMode->data); //打印pMode的数据域
pMode = pMode->next; //让pMode指向下一个节点
}
printf("\n");
}
int main(void)
{
struct Node* list = createList();
insertNodeByHead(list, 1);
insertNodeByHead(list, 2);
insertNodeByHead(list, 3);
printList(list);
printf("输入需要删除的节点的数据:");
deleteNodeByAppoin(list, 5);
printf("执行删除操作后列表为:");
printList(list);
printf("输入需要删除的节点的数据:");
printf("执行删除操作后列表为:");
deleteNodeByAppoin(list, 2);
printList(list);
return 0;
}
cmd框: