本文已参与「新人创作礼」活动,一起开启掘金创作之路。
🔥 本文由 程序喵正在路上 原创,在稀土掘金首发!
💖 系列专栏:数据结构与算法
🌠 首发时间:2022年9月1日
🦋 欢迎关注🖱点赞👍收藏🌟留言🐾
🌟 一以贯之的努力 不得懈怠的人生
链表简介
数组和链表是所有数据结构的基础,链表是很重要的两种线性结构之一
链表可以分为单链表、双链表、循环单链表、循环双链表四种,每一种又可以分为有头和无头两种
单链表就是相邻两个节点之间只有一个指针连接,而有头单链表就是第一个节点(也就是头节点)不存储数据,后面才存储数据的链表,无头单链表就是头节点存储有数据
请注意:实际运用中,有头链表是不用的
至于循环单链表和循环双链表,区别就在于循环链表的尾节点会指向第一个节点
无头单链表准备工作
创建一个后缀为 .c 的文件,你可能为问为什么要写成 C语言 的?
如果我们是写成 C++ 的话,那风格就完全不一样了,我们得把这个链表写成一个类
我们一般将链表的节点分为两部分:数据域和指针域,前者用来存储数据,后者用来存储指针
其实,指针域也不一定要存储指针,我们用数组下标也是可以的,这同样是一个链表,是为什么呢?
首先,你要明白,指针是指向一块内存段的变量,那么只要能指向一个内存段的,就可以称为指针,它不需要一定是一个指针变量
#include <stdio.h>
//链表节点类型
struct Node{
int data;
struct Node* pNext;
};
//链表类型
struct List{
struct Node* pRoot;
};
//初始化链表
void initList(struct List* list);
//创建链表节点并返回
struct Node* createNode(int newData);
int main(){
//创建一个链表,两种方法
struct List list;
struct Node* pHead = NULL;
while (1);
return 0;
}
//初始化链表
void initList(struct List* list){
list->pRoot = NULL;
}
//创建链表节点并返回
struct Node* createNode(int newData){
//开内存
struct Node* pNew = (struct Node*)malloc(sizeof(struct Node));
//判断内存是否申请成功
if (NULL == pNew) { //防御性编程
printf("申请内存失败");
return NULL;
}
//数据赋值
pNew->data = newData;
pNew->pNext = NULL;
//返回
return pNew;
}
无头单链表尾插法实现一
给链表添加节点有三种方式:头插法、尾插法、中间插法,我们先来实现尾插法
尾插法实现一的思路为:
- 判断链表是否为空
- 创建新节点
- 找到原链表的尾节点
- 原尾节点的 pNext 指向新节点
#include <stdio.h>
#define IS_LIST 1
//链表节点类型
struct Node{
int data;
struct Node* pNext;
};
#if IS_LIST
//链表类型
struct List{
struct Node* pRoot;
};
#endif
#if IS_LIST
//初始化链表
void initList(struct List* list);
#endif
//创建链表节点并返回
struct Node* createNode(int newData);
#if IS_LIST
//增,尾插法 push_back 两种方法
void appendNode(struct List* pList, int appendNodeData);
#else
void appendNode(struct Node* pHead, int appendNodeData);
#endif
//主函数
int main(){
#if IS_LIST
//创建一个链表
struct List list;
initList(&list);
//测试尾插法
for (int i = 0; i < 10; i++){
appendNode(&list, i);
}
#else
struct Node* pHead = NULL;
//测试第一个尾插法
for (int i = 0; i < 10; i++){
appendNode(pHead, i);
}
#endif
while (1);
return 0;
}
#if IS_LIST
//初始化链表
void initList(struct List* list){
list->pRoot = NULL;
}
#endif
//创建链表节点并返回
struct Node* createNode(int newData){
//开内存
struct Node* pNew = (struct Node*)malloc(sizeof(struct Node));
//判断内存是否申请成功
if (NULL == pNew) { //防御性编程
printf("申请内存失败");
return NULL;
}
//数据赋值
pNew->data = newData;
pNew->pNext = NULL;
//返回
return pNew;
}
#if IS_LIST
//增,尾插法
void appendNode(struct List* pList, int appendNodeData){
//如果链表为空
if (NULL == pList) return; //防呆
//1、创建新节点
struct Node* pNew = createNode(appendNodeData);
if (NULL == pNew) return; //防呆
if (pList->pRoot){ //如果链表不为空
//2、找到尾节点
struct Node* pTemp = pList->pRoot;
while (pTemp->pNext != NULL){
pTemp = pTemp->pNext;
}
//3、pNext指向新节点
pTemp->pNext = pNew;
}
else {
//2、pList的成员指向新节点
pList->pRoot = pNew;
}
}
#else
void appendNode(struct Node* pHead, int appendNodeData){
}
#endif
在主函数的 for 循环这里打个断点,然后运行程序,再将 list 拖到监视窗口中:
按 F10 运行几次,发现没有问题:
无头单链表尾插法实现二
尾插法实现二的思路差不多:
- 注意函数参数要传地址进去,不能传值,比如原来的
void appendNode(struct Node* head, int appendNodeData);
其实是单向值传递 - 判断链表是否为空
- 创建新节点
- 找到尾节点
- 原尾节点的 pNext 指向新节点
#include <stdio.h>
#define IS_LIST 0
//链表节点类型
struct Node{
int data;
struct Node* pNext;
};
#if IS_LIST
//链表类型
struct List{
struct Node* pRoot;
};
#endif
#if IS_LIST
//初始化链表
void initList(struct List* list);
#endif
//创建链表节点并返回
struct Node* createNode(int newData);
#if IS_LIST
//增,尾插法 push_back 两种方法
void appendNode(struct List* pList, int appendNodeData);
#else
void appendNode(struct Node** head, int appendNodeData);
#endif
//主函数
int main(){
#if IS_LIST
//创建一个链表
struct List list;
initList(&list);
//测试第一个尾插法
for (int i = 0; i < 10; i++){
appendNode(&list, i);
}
#else
struct Node* pHead = NULL;
//测试第二个尾插法
for (int i = 0; i < 10; i++){
appendNode(&pHead, i);
}
#endif
while (1);
return 0;
}
#if IS_LIST
//初始化链表
void initList(struct List* list){
list->pRoot = NULL;
}
#endif
//创建链表节点并返回
struct Node* createNode(int newData){
//开内存
struct Node* pNew = (struct Node*)malloc(sizeof(struct Node));
//判断内存是否申请成功
if (NULL == pNew) { //防御性编程
printf("申请内存失败");
return NULL;
}
//数据赋值
pNew->data = newData;
pNew->pNext = NULL;
//返回
return pNew;
}
#if IS_LIST
//增,尾插法
void appendNode(struct List* pList, int appendNodeData){
//如果链表为空
if (NULL == pList) return; //防呆
//1、创建新节点
struct Node* pNew = createNode(appendNodeData);
if (NULL == pNew) return; //防呆
if (pList->pRoot){ //如果链表不为空
//2、找到尾节点
struct Node* pTemp = pList->pRoot;
while (pTemp->pNext != NULL){
pTemp = pTemp->pNext;
}
//3、pNext指向新节点
pTemp->pNext = pNew;
}
else {
//2、pList的成员指向新节点
pList->pRoot = pNew;
}
}
#else
void appendNode(struct Node** head, int appendNodeData){
//1、创建新节点
struct Node* pNew = createNode(appendNodeData);
if (NULL == pNew) return; //防呆
if (*head){ //如果链表不为空
//2、找到尾节点
struct Node* pTemp = *head;
while (pTemp->pNext != NULL){
pTemp = pTemp->pNext;
}
//3、pNext指向新节点
pTemp->pNext = pNew;
}
else {
//2、pList的成员指向新节点
*head = pNew;
}
}
#endif
在主函数的 for 循环这里打个断点,然后运行程序,再将 pHead 拖到监视窗口中:
按 F10 运行几次,发现没有问题:
遍历
借鉴尾插法中的思路,很简单就可以写出遍历的函数
//遍历声明
#if IS_LIST
void travel(struct List* pList);
#else
void travel(struct Node* head);
#endif
int main(){
#if IS_LIST
struct List list;
initList(&list);
for (int i = 0; i < 10; i++){
appendNode(&list, i);
travel(&list);
}
#else
struct Node* pHead = NULL;
for (int i = 0; i < 10; i++){
appendNode(&pHead, i);
travel(pHead);
}
#endif
while (1);
return 0;
}
//遍历实现
#if IS_LIST
void travel(struct List* pList){
struct Node* pTemp = pList->pRoot;
printf("list:");
while (pTemp){
printf("%d ", pTemp->data);
pTemp = pTemp->pNext;
}
printf("\n");
}
#else
void travel(struct Node* head){
struct Node* pTemp = head;
printf("list:");
while (pTemp){
printf("%d ", pTemp->data);
pTemp = pTemp->pNext;
}
printf("\n");
}
#endif
测试结果如下:
无头单链表头插法
实现头插法的思路很简单:
- 创建新节点 pNew
- 新节点的 pNext 指向链表的头节点
- 头节点被 pNew 赋值
//头插法 push_front
#if IS_LIST
void addNode(struct List* pList, int addNodeData);
#else
void addNode(struct Node** head, int addNodeData);
#endif
int main(){
#if IS_LIST
struct List list;
initList(&list);
for (int i = 0; i < 10; i++){
addNode(&list, i);
travel(&list);
}
#else
struct Node* pHead = NULL;
for (int i = 0; i < 10; i++){
addNode(&pHead, i);
travel(pHead);
}
#endif
while (1);
return 0;
}
//头插法 push_front
#if IS_LIST
void addNode(struct List* pList, int addNodeData){
if(NULL == pList) return; //防呆
//1、创建新节点
struct Node* pNew = createNode(addNodeData);
if (NULL == pNew) return; //防呆
//2、新节点的pNext指向链表的头节点
pNew->pNext = pList->pRoot;
//3、头节点被pNew赋值
pList->pRoot = pNew;
}
#else
void addNode(struct Node** head, int addNodeData){
if (NULL == head) return;//防呆
//1 创建新节点
struct Node* pNew = createNode(addNodeData);
//2 新节点的pNext指向head指向的节点
pNew->pNext = *head;
//3 *head 被pNew赋值
*head = pNew;
}
#endif
测试结果如下,将 IS_LIST 的值改为 0 后,另一种方法同样测试成功:
无头单链表中间插入
//中间插入 insert,新节点插入到pos(下标)位置的后面
#if IS_LIST
void insertNode(struct List* pList, int pos, int insertNodeData);
#else
void insertNode(struct Node** head, int pos, int insertNodeData);
#endif
//从head链表中找到pos节点并返回,找不到返回NULL
struct Node* findPos(struct Node* head, int pos);
int main(){
#if IS_LIST
struct List list;
initList(&list);
for (int i = 0; i < 10; i++){
addNode(&list, i);
travel(&list);
}
insertNode(&list, 2, 666);
travel(&list);
insertNode(&list, 0, 999);
travel(&list);
insertNode(&list, 22, 5678);
travel(&list);
#else
struct Node* pHead = NULL;
for (int i = 0; i < 10; i++){
addNode(&pHead, i);
travel(pHead);
}
insertNode(&pHead, 2, 666);
travel(pHead);
insertNode(&pHead, 0, 999);
travel(pHead);
insertNode(&pHead, 22, 5678);
travel(pHead);
#endif
while (1);
return 0;
}
//从head链表中找到pos节点并返回,找不到返回NULL
struct Node* findPos(struct Node* head, int pos){
if (NULL == head) return;
struct Node* pTemp = head;
for (int i = 0; i < pos; i++){
if (NULL == pTemp) return NULL;
pTemp = pTemp->pNext;
}
return pTemp;
}
//中间插入 insert,新节点插入到pos(下标)位置的后面
#if IS_LIST
void insertNode(struct List* pList, int pos, int insertNodeData){
if (NULL == pList) return;
if (NULL == pList->pRoot || 0 == pos) {
addNode(pList, insertNodeData);
return;
}
struct Node* pPrev = findPos(pList->pRoot , pos - 1); //找到
if (NULL == pPrev) return;
//创建新节点
struct Node* pNew = createNode(insertNodeData);
//新节点的pNext指针指向pPrev的pNext
pNew->pNext = pPrev->pNext;
//pPrev的pNext指向新节点
pPrev->pNext = pNew;
}
#else
void insertNode(struct Node** head, int pos, int insertNodeData){
if (NULL == head) return;
if (NULL == *head || 0 == pos) {
addNode(head, insertNodeData);
return;
}
struct Node* pPrev = findPos(*head, pos - 1); //找到
if (NULL == pPrev) return;
//创建新节点
struct Node* pNew = createNode(insertNodeData);
//新节点的pNext指针指向pPrev的pNext
pNew->pNext = pPrev->pNext;
//pPrev的pNext指向新节点
pPrev->pNext = pNew;
}
#endif
测试结果如下:
无头单链表删除
删除的思路为:
- 临时保存要删除的节点,方便后面删除,不保存的话后面无法删除会造成内存泄漏
- 目标节点的前一个节点的 pNext 要指向目标节点的后一个节点
- 释放目标节点
//删除链表中第pos个节点
void deleteNodePos(struct Node** head, int pos);
//删除链表中第一个节点
void deleteHead(struct Node** head);
int main(){
#if IS_LIST
struct List list;
initList(&list);
for (int i = 0; i < 10; i++){
addNode(&list, i);
travel(&list);
}
insertNode(&list, 2, 666);
travel(&list);
insertNode(&list, 0, 999);
travel(&list);
insertNode(&list, 22, 5678);
travel(&list);
#else
struct Node* pHead = NULL;
for (int i = 0; i < 10; i++){
addNode(&pHead, i);
travel(pHead);
}
insertNode(&pHead, 2, 666);
travel(pHead);
insertNode(&pHead, 0, 999);
travel(pHead);
insertNode(&pHead, 22, 5678);
travel(pHead);
deleteNodePos(&pHead, 0);
travel(pHead);
deleteNodePos(&pHead, 10);
travel(pHead);
#endif
while (1);
return 0;
}
//删除链表中第一个节点
void deleteHead(struct Node** head){
if (NULL == head) return;
//临时存储要删的节点
struct Node* pDel = *head;
//*head的下一个是要成为新的头节点的
*head = (*head)->pNext;
//释放内存
free(pDel);
}
//删除链表中第pos个节点
void deleteNodePos(struct Node** head, int pos){
if (NULL == head || pos < 0) return;
if (0 == pos){
deleteHead(head);
return;
}
//临时存储pos节点地址
struct Node* pDel = findPos(*head, pos);
if (NULL == pDel) return;
//找到pos-1节点
struct Node* pDelPrev = findPos(*head, pos - 1);
if (NULL == pDelPrev) return;
//pos-1节点的next指针指向pos的下一个节点
pDelPrev->pNext = pDel->pNext;
//释放pos节点内存
free(pDel);
}
删除链表中第 pos 个节点测试成功:
无头单链表模板类
接下来我们用 C++ 的风格将上面的功能实现一遍
新建一个项目,创建一个 MyList.h 的文件,在其中实现所有的功能:
#pragma once
#include <iostream>
using namespace std;
template <class T>
class MyList{
struct Node{
T data;
Node* pNext;
Node(){
this->data = NULL;
pNext = NULL;
}
Node(const T& data){
this->data = data;
pNext = NULL;
}
};
Node* pHead;
public:
MyList(){
pHead = NULL;
}
//尾插法
void appendNode(const T& data);
//头插法
void addNode(const T& data);
//中间插入
void insertNode(int pos, int insertNodeData);
//遍历
void travel();
//删除链表中第pos个节点
void deleteNodePos(int pos);
//删除链表中第一个节点
void deleteHead();
private:
//找某一个节点
Node* _findPos(int pos);
};
//找某一个节点
template <class T> //返回泛型类型要加typename
typename MyList<T>::Node* MyList<T>::_findPos(int pos){
Node* pTemp = pHead;
for (int i = 0; i < pos; i++){
if (NULL == pos) return NULL;
pTemp = pTemp->pNext;
}
return pTemp;
}
//尾插法
template <class T>
void MyList<T>::appendNode(const T& data){
//1、创建新节点
Node* pNew = new Node(data);
//2、找到尾节点
Node* pTemp = pHead;
if (pTemp){
while (pTemp->pNext){
pTemp = pTemp->pNext;
}
//3、新节点插入到尾节点之后
pTemp->pNext = pNew;
}
else{
pHead = pNew;
}
}
//头插法
template <class T>
void MyList<T>::addNode(const T& data){
//1、创建新节点
Node* pNew = new Node(data);
if (NULL == pNew) return;
//2、新节点的next指针指向原来头节点
pNew->pNext = pHead;
//3、新节点成为头节点
pHead = pNew;
}
//中间插入,新节点插入到pos(下标)位置的后面
template <class T>
void MyList<T>::insertNode(int pos, int insertNodeData){
if (pos < 0) return;
if (NULL == pHead || 0 == pos){
addNode(insertNodeData);
return;
}
//找到pos节点
Node* pPrev = _findPos(pos);
//新建节点
Node* pNew = new Node(insertNodeData);
//新节点成为pos节点后面的节点
pNew->pNext = pPrev->pNext;
pPrev->pNext = pNew;
}
//遍历
template <class T>
void MyList<T>::travel(){
Node* pTemp = pHead;
cout << "list:";
while (pTemp){
cout << pTemp->data << " ";
pTemp = pTemp->pNext;
}
cout << endl;
}
//删除链表中第pos个节点
template <class T>
void MyList<T>::deleteNodePos(int pos){
if (NULL == pHead || pos < 0) return;
if (0 == pos){
deleteHead();
return;
}
Node* pDelPrev = _findPos(pos - 1);
if (NULL == pDelPrev) return;
Node* pDel = _findPos(pos);
if (NULL == pDel) return;
pDelPrev->pNext = pDel->pNext; //断开
delete pDel;
}
//删除链表中第一个节点
template <class T>
void MyList<T>::deleteHead(){
if (NULL == pHead) return;
Node* pDel = pHead;
pHead = pDel->pNext;
delete pDel;
}
新建一个 main.cpp 的测试文件:
#include "MyList.h"
int main(){
MyList<int> l;
for (int i = 0; i < 10; i++){
l.addNode(i);
l.travel();
}
cout << "插入666到第6个后面后:" << endl;
l.insertNode(5, 666); //插入666到第6个后面
l.travel();
cout << "删除第一个后:" << endl;
l.deleteHead(); //删第一个
l.travel();
cout << "删除下标为5的节点后:" << endl;
l.deleteNodePos(5);
l.travel();
while (1);
return 0;
}
测试成功:
循环双链表
新建一个项目,创建一个 list.h 的文件,这里只实现了尾插法和遍历的功能,你可以自己尝试写一下其他功能
#pragma once
#include <iostream>
using namespace std;
template <class T>
class MyList{
struct Node{
T data;
//两个指针
Node* pNext; //指向下一个节点
Node* pPrev; //指向前一个节点
Node(){
this->data = NULL;
pNext = pPrev = NULL;
}
Node(const T& data){
this->data = data;
pNext = pPrev = NULL;
}
};
Node* pHead;
Node* pTail;
public:
MyList(){
pHead = pTail = NULL;
}
//尾插法
void appendNode(const T& data);
//头插法
void addNode(const T& data);
//中间插入
void insertNode(int pos, int insertNodeData);
//遍历
void travel();
//删除链表中第pos个节点
void deleteNodePos(int pos);
//删除链表中第一个节点
void deleteHead();
private:
//找某一个节点
Node* _findPos(int pos);
};
//找某一个节点
template <class T> //返回泛型类型要加typename
typename MyList<T>::Node* MyList<T>::_findPos(int pos){
Node* pTemp = pHead;
for (int i = 0; i < pos; i++){
if (NULL == pos) return NULL;
pTemp = pTemp->pNext;
}
return pTemp;
}
//尾插法
template <class T>
void MyList<T>::appendNode(const T& data){
//1、创建新节点
Node* pNew = new Node(data);
if (pTail){ //不是空链表
//新节点连接到尾节点后头
pTail->pNext = pNew;
pNew->pPrev = pTail;
//更新pTail
pTail = pNew;
//维持循环双链表结构
pTail->pNext = pHead;
pHead->pPrev = pTail;
}
else{
pHead = pTail = pNew;
}
}
//头插法
template <class T>
void MyList<T>::addNode(const T& data){
}
//中间插入,新节点插入到pos(下标)位置的后面
template <class T>
void MyList<T>::insertNode(int pos, int insertNodeData){
}
//遍历
template <class T>
void MyList<T>::travel(){
Node* pTemp = pHead;
cout << "list:";
if (NULL == pHead){
cout << endl;
return;
}
if (pHead == pTail){
cout << pHead->data << endl;
return;
}
while (pTemp != pTail){
cout << pTemp->data << " ";
pTemp = pTemp->pNext;
}
cout << pTail->data << endl;
}
//删除链表中第pos个节点
template <class T>
void MyList<T>::deleteNodePos(int pos){
}
//删除链表中第一个节点
template <class T>
void MyList<T>::deleteHead(){
}
新建一个 main.cpp 的测试文件:
#include "list.h"
int main(){
MyList<int> l;
for (int i = 0; i < 10; i++){
l.appendNode(i);
l.travel();
}
while (1);
return 0;
}
测试成功: