前言
这是一位即将大二的大学生(
卷狗)在暑假预习数据结构时的一些学习笔记,仅供大家参考学习。水平有限,如有错误,还望多多指正。
在本篇博客中,主要讲解了在数据结构中的链表,并且给出代码讲解来实现其具体功能
链表
首先,我们要认识到这一点,链表是数据结构中最基础也是最重要的一部分,后序有很多的数据结构都是依靠链表来完成的。
定义
链表是一种离散的存储结构,所谓离散,就是指它的存储空间不连续(这一点与数组不同)
而在链表内部,存在数据域和指针域两部分——数据域存放的是节点自身需要存储的内容,指针域是链表而各个节点之间可以相互访问的关键所在。
如下图,就是一个简单的链表节点
一些专业术语
- 首节点 :第一个有效节点
- 尾节点:最后一个有效节点
- 头节点:一般会在首节点前面加一个head,叫做头节点。*头节点不存放有效数据(本文中存放的是链表元素个数),存在目的是方便于链表算法的操作
- 头指针:指向头节点的指针变量,存放头节点地址
- 尾指针:指向尾节点的指针变量
链表特点
- 是离散存储的——n个节点存储空间离散分配
- 彼此通过指针相连
- 除了首节点和尾节点以外,每一个节点只有一个前驱节点和后继节点(
针对非循环列表)
分类
-
单链表
通俗来讲 单链表就是一个串,可以从头走到尾,各个节点之间通过指针单向连接,尾节点的指针指向NULL
单链表结构如下图所示
-
双链表
与单链表不同,对于双链表而言,存在两个指针域 pre 和 next ,既可以从头走到尾,也可以从尾走到头,节点之间相互指向,尾节点的指针指向NULL
双链表结构如下图所示
-
循环链表
对于循环链表,其尾节点的指针不在指向NULL,而是指向头节点,因此构成了一个循环
在循环链表中,可以通过任意一个节点来找到其他所有节点
循环链表结构如下图所示
循环单链表:
循环双链表:
链表具体功能
对于链表的一些操作,我们主要实现以下四种功能
- 初始化创建
- 插入(头插法、尾插法)
- 删除
- 遍历
单链表代码实现
首先先利用结构体定义一个链表节点
typedef struct NODE
{
int data; //数据域
struct NODE* next; //指针域
}Node, *pNode;
初始化
所谓初始化,就是给头节点动态分配一块空间,并对其参数进行初始设置
pNode initList()
{
pNode head = (pNode)malloc(sizeof(Node));
head -> data = 0; //头节点数据域存放链表长度
head -> next = NULL;
return head;
}
插入
头插法
所谓头插法,就是在链表的头节点与首节点之间插入一个元素,如下图所示:
其实操作也很简单,我觉得最重要的是操作的顺序不能颠倒
- 先给新节点数据域赋值
- 通过
head -> next
找到首节点,让新节点指向首节点
- 将头节点的指针域指向新节点,新节点即成为新的首节点,完成头插操作。
注意第二步和第三步顺序不能调换
我们可以思考一下,由于链表的存储是不连续的,如果先让 head 的指针域指向了新节点,那么原首节点在内存中将不易找到,因此不能完成头插。
具体代码
void headInsert(pNode List, int data)
{
pNode node = (pNode)malloc(sizeof(Node)); //开创节点
node -> data = data; //新节点数据处理
node -> next = List -> next; //头插法
List -> next = node;
List -> data ++; //链表长度增加
}
尾插法
相对头插法而言,尾插法会相对简单一点,不用关注代码前后顺序的问题,重要的是如何找到尾节点,完成尾插
我的思路是定义一个计数指针变量cur
,由于尾节点指向为NULL
,所以当cur -> next
不为空时,就一直指向下一个节点,最后循环结束时cur
指向的就是尾节点,即:
// 寻找尾节点
pNode cur = List;
while(cur -> next)
{
cur = cur -> next; //循环结束时 cur是尾节点
}
然后我们只需要给新节点数据域赋值,改变原尾节点和新尾节点指向即可
具体代码
void tailInsert(pNode List, int data)
{
// 寻找尾节点
pNode cur = List;
while(cur -> next)
{
cur = cur -> next; //循环结束时 cur是尾节点
}
pNode node = (pNode)malloc(sizeof(Node));
node -> data = data;
node -> next = NULL;
cur -> next = node;
List -> data ++;
}
删除
如果想要删除一个节点,思路上也很简单:
首先我们要找到这个节点,然后将其上一个节点的指针指向下一个节点,最后再把这个节点free
掉即可,如下图所示
而在代码上,问题的关键在于——我们该如何寻找目标节点的上一个节点???
其实我们可以定义两个指针变量—— node
和 pre
,利用双指针思想,使 pre
始终 node
的上一个节点,所以当 node
找到目标节点时,pre
指向的就是目标节点的上一个节点
具体代码
#define TRUE 1
#define FALSE 0
int delet(pNode List, int data)
{
pNode pre = List;
pNode node = List -> next; //node永远是pre的下一个节点
while(node)
{
if(node -> data == data){
pre -> next = node -> next;
free(node);
List -> data --;
return TRUE;
}
pre = node;
node = node -> next;
}
return FALSE;
}
遍历
我么可以创建一个临时指针变量,通过对地址的访问,依次遍历链表,直到最后走向NULL
,结束遍历
具体代码
void printfList(pNode List)
{
pNode node = List -> next;
while(node)
{
printf("%d ", node -> data);
node = node -> next;
}
printf("\n");
}
完整代码
单链表完整代码
#include <stdio.h>
#include <stdlib.h>
#define TRUE 1
#define FALSE 0
typedef struct NODE
{
int data;
struct NODE* next;
}Node, *pNode;
pNode initList()
{
pNode head = (pNode)malloc(sizeof(Node));
head -> data = 0; //头节点数据域存放链表长度
head -> next = NULL;
return head;
}
void headInsert(pNode List, int data)
{
pNode node = (pNode)malloc(sizeof(Node)); //开创节点
node -> data = data; //新节点数据处理
node -> next = List -> next; //头插法
List -> next = node;
List -> data ++; //链表长度增加
}
void tailInsert(pNode List, int data)
{
// 寻找尾节点
pNode cur = List;
while(cur -> next)
{
cur = cur -> next; //循环结束时 cur是尾节点
}
pNode node = (pNode)malloc(sizeof(Node));
node -> data = data;
node -> next = NULL;
cur -> next = node;
List -> data ++;
}
int delet(pNode List, int data)
{
pNode pre = List;
pNode node = List -> next; //node永远是pre的下一个节点
while(node)
{
if(node -> data == data){
pre -> next = node -> next;
free(node);
List -> data --;
return TRUE;
}
pre = node;
node = node -> next;
}
return FALSE;
}
void printfList(pNode List)
{
pNode node = List -> next;
while(node)
{
printf("%d ", node -> data);
node = node -> next;
}
printf("\n");
}
int main()
{
pNode List = initList();
headInsert(List, 1);
headInsert(List, 2);
headInsert(List, 3);
tailInsert(List, 4);
tailInsert(List, 5);
tailInsert(List, 6);
printfList(List);
delet(List, 5);
delet(List, 3);
printfList(List);
return 0;
}
最后(未完待续、、、
最后感谢大家的观看,后续还会更新关于双链表、循环链表和其他数据结构的博客,如有错误,还望多多指正
未完待续、、、