一、基本概念
链表:
两种线性结构之一
单链表 双链表 循环单链表 循环双链表
有头 无头增删改查
pHead 传参给 head 单向值传递
函数中修改head 没有影响 pHead
1.注:
添加链表数据时,一个要判断链表本身是否为NULL,一个要判断链表里面是否有数据(即头结点是否为空)
2.当pMove为空的时候,再进来判断pMove->data访问中断!!!!
solution:(利用短路性质,将后面的条件提前) 3.有关C语言传参的性质:(为何此处的要写二级指针!!!)
发现,头结点肯定是会产生一个拷贝本的,但是指针域存放的地址是拷贝过来了,所以可以正常变动后面的(同时修改原件),修改头head的时候,需要传入二级指针。
有关拷贝头节点后,其指针域的值(即除了头部是拷贝本,其余位置均是原链表的部分->相当于产生了两个头结点!!)----->>>>>下面为例证代码
#include<cstdio>
#include<assert.h>
#include<stdlib.h>
struct Node
{
int data;
struct Node* Next;
};
struct Node* initHead()
{
struct Node* t = (struct Node*)malloc(sizeof(struct Node));
assert(t);
t->Next = NULL;
return t;
}
struct Node* createNode(int data)
{
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
assert(newNode);
newNode->data = data;
newNode->Next = NULL;
return newNode;
}
void print(struct Node* list)
{
printf(" 头结点的指针域为:%p", &list->Next);
}
void push_back(struct Node* list, int data)
{
struct Node* pNew = createNode(data);
if (pNew == NULL)return;
struct Node* pMove = list;
while (pMove->Next != NULL)
{
pMove = pMove->Next;
}
pMove->Next = pNew;
}
int main()
{
struct Node* list = initHead();
push_back(list, 555);
printf("main中的head的指针域为:%p\n", &list->Next);
print(list);
return 0;
}
输出:
main中的head的指针域为:00000131EB6FF148
头结点的指针域为:00000131EB6FF148
4.删除某元素第一次出现的结点:勿忘记头结点要单独考虑
二、用C语言的单链表两种实现(封装写法&二级指针写法)
#include <stdio.h>
#define IS_LIST 1
//链表节点类型
struct Node
{
int data;
struct Node* Next;
};
#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 push_back(struct List* pList, int push_backData);
#else
void push_back(struct Node** head, int push_backData);
#endif
#if IS_LIST
//头插法 push_front
void push_front(struct List* pList, int push_backData);
#else
void push_front(struct Node** head, int push_backData);
#endif
#if IS_LIST
//中间插入 insert 新节点插入到pos位置的后面
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);
#if IS_LIST
void travel(struct List* pList);
#else
void travel(struct Node* head);
#endif
//从 head链表中找到 findData 并修改为 changeData 如果找不到 直接结束
void changeNodeData(struct Node** head, int findData, int changeData);
//删除
//删除链表中第一个数据为deleteData的节点
void deleteNodeData(struct Node** head, int deleteData);
//删除链表中第pos个节点
void deleteNodePos(struct Node** head, int pos);
//删除链表中第一个节点
void deleteHead(struct Node** head);
int main()
{
return 0;
}
//删除
//删除链表中第一个数据为deleteData的节点
void deleteNodeData(struct Node** head, int deleteData)
{
if (head == NULL || *head == NULL)return;
struct Node* pMove = *head;
struct Node* pre = pMove;
while (pMove != NULL && pMove->data != deleteData) /*注意顺序!!!*/
{
pre = pMove;
pMove = pMove->Next;
}
if (pMove == NULL)return;
else if (pMove == *head)
{
deleteHead(head); //删除的时候,头结点要单独考虑
}
else/*正常找到了,pMove指向的即为待del的node*/
{
struct Node* pDel = pMove;//存一下需要删除的结点
pre->Next = pMove->Next;
free(pDel);
pDel = NULL;
}
}
//删除链表中第一个节点
void deleteHead(struct Node** head)
{
if (NULL == head) return;
//1 临时存储要删的节点
struct Node* pDel = *head;
//2 *head 的下一个节点要成为新的头节点
*head = (*head)->Next;
//3 释放内存
free(pDel);
pDel = NULL;
}
//删除链表中第pos个节点
void deleteNodePos(struct Node** head, int pos)
{
if (NULL == head || pos < 0) return;
if (0 == pos)
{
deleteHead(head);
return;
}
//2 临时存储pos节点地址
struct Node* pDel = findPos(*head, pos);
if (NULL == pDel) return;
//1 先找到下标为pos-1节点
struct Node* pDelPrev = findPos(*head, pos - 1);
if (NULL == pDelPrev) return;
//3 pos-1节点的next指针指向pos的下一个节点
pDelPrev->Next = pDel->Next;
//4 释放pos节点内存
free(pDel);
}
//从 head链表中找到 findData 并修改为 changeData 如果找不到 直接结束
void changeNodeData(struct Node** head, int findData, int changeData)
{
//作业3
/*一点思考:新产生了一个链表头(但是指针域仍然不变)!
此处应该用到二级指针,若不用(头部)会产生拷贝本,是无法改动头部的元素的,
注:指针域是拷贝过来了,依旧能得到目标地址(故能改到除了头部节点的元素)*/
if (head == NULL||*head==NULL)return;
struct Node* pMove = *head;
while (pMove != NULL&&pMove->data != findData ) /*注意顺序!!!*/
{
pMove = pMove->Next;
}
if (pMove == NULL)return;
else
{
pMove->data = changeData;
}
}
//从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->Next;
}
return pTemp;
}
#if IS_LIST
//中间插入 insert 新节点插入到下标为pos位置(第pos+1个节点)的后面
void insertNode(struct List* pList,int pos,int Data)
{
//作业2:
if (pList == NULL)return ;
if (pos == 0)
{
push_front(pList, Data);
return;//勿忘直接退出
}
struct Node* pMove = pList->pRoot;
struct Node* pre = pMove;
for (int i = 0; i < pos; i++)
{
if (pMove == NULL)return;
pre = pMove;/*记录前一个节点*/
pMove = pMove->Next;/*注:若完整的执行完了所有for,出去时,pMove应该移动到了下标为pos的位置*/
}/*pre在下标pos-1的位置*/
struct Node* pNew = createNode(Data);
if (pNew == NULL)return;
pNew->Next = pMove;
pre->Next = pNew;
}
#else
void insertNode(struct Node** head, int pos, int insertNodeData)
{
if (NULL == head) return;
if (NULL == *head || 0 == pos)
{
push_front(head, insertNodeData);
return;
}
struct Node* pPrev = findPos(*head, pos);//1 找到第pos+1个节点,下标为pos,为后一个前一个节点
if (NULL == pPrev) return;
//2 创建新节点
struct Node* pNew = createNode(insertNodeData);
if (pNew == NULL)return;
//3 新节点的Next指针指向 pPrev的Next
pNew->Next = pPrev->Next;
//4 pPrev的Next指向新节点
pPrev->Next = pNew;
}
#endif
#if IS_LIST
//头插法 push_front
void push_front(struct List* pList, int Data)
{
//作业 课后去实现
if (pList == NULL) return;
struct Node* pNew = createNode(Data);
if (pNew == NULL)return;
pNew->Next = pList->pRoot;
pList->pRoot = pNew;
/*注意:本处区分push_back,无需进行对链表中有无元素进行分类,均一样!!!*/
}
#else
void push_front(struct Node** head, int Data)
{
if (NULL == head) return;//防呆
//1 创建新节点
struct Node* pNew = createNode(Data);
//2 新节点的Next指向head指向的节点
pNew->Next = *head;
//3 *head 被pNew赋值
*head = pNew;
}
#endif
#if IS_LIST
void travel(struct List* pList)
{
struct Node* pTemp = pList->pRoot;
printf("list:");
while (pTemp)
{
printf("%d ", pTemp->data);
pTemp = pTemp->Next;
}
printf("\n");
}
#else
void travel(struct Node* head)
{
struct Node* pTemp = head;
printf("list:");
while (pTemp){
printf("%d ", pTemp->data);
pTemp = pTemp->Next;
}
printf("\n");
}
#endif
#if IS_LIST
//尾插法 push_back
void push_back(struct List* pList, int push_backData)
{
if (NULL == pList) return;//防呆
//1 创建新节点
struct Node* pNew = createNode(push_backData);
if (NULL == pNew) return;//防呆
if (pList->pRoot) //->区分plist==NULL,一个整个链表是空的,一个是链表已经init但是没有任何数据,即头结点不存有其他数据
{//如果链表不为空
struct Node* pTemp = pList->pRoot;
while (pTemp->Next != NULL) //2 找到尾结点
{
pTemp = pTemp->Next;
}
//3 尾节点的Next指针指向新节点 ++
pTemp->Next = pNew;
}
else
{//如果 链表为空
//2 pList的成员指向新节点
pList->pRoot = pNew;
}
}
#else
void push_back(struct Node** head, int push_backData)
{
//1 创建新节点
struct Node* pNew = createNode(push_backData);
if (NULL == pNew) return;//防呆
if (*head)
{//如果链表不为空
//2 找到尾节点
struct Node* pTemp = *head;
while (pTemp->Next != NULL){
pTemp = pTemp->Next;
}
//3 尾节点的Next指针指向新节点
pTemp->Next = pNew;
}
else
{//如果 链表为空
//2 pList的成员指向新节点
*head = pNew;
}
}
#endif
#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->Next = NULL;
return pNew;
}
其他写法参照:
无头单链表的再封装写法
无头单链表的再封装写法解析https://blog.csdn.net/weixin_60569662/article/details/122878134
无头单链表的“二级指针”写法
无头单链表的二级指针写法解析https://blog.csdn.net/weixin_60569662/article/details/123041179
三、有序链表的构建、合并、反转
数据结构 --- 有序链表的构建、合并、反转https://blog.csdn.net/weixin_60569662/article/details/124679538
注:此处传参为何是一级指针传入? ->采用有头链表,头结点不存放数据,后面的指针域相同 (因为采用有头链表,此文的所有push_sort等操作去判断边界条件时,都会产生影响!!)
四、 双向链表
数据结构 --- c语言实现双向链表https://blog.csdn.net/weixin_60569662/article/details/125495131比较值得注意的细节:
删除节点的时候,一定要记得删头or删尾的时候,考虑本链表是不是curSize为1,同时要处理一下,list的head和tail都需要保证是nullptr。
(简单来说:
①你把尾结点free,但是pre节点的next(也就是tail)需要置为空,当然这是在pre不为null的情况下
②如果pre为空->删完之后,没有元素了,list存放头结点的指针也需要置为null)
五、循环链表
数据结构 --- c语言实现双向循环链表https://blog.csdn.net/weixin_60569662/article/details/125737109
六、广义表
七、C++类中类写法
注意:成员函数的返回值类型为类中类类型,在类外进行实现,需要typename+类名修饰。
#pragma once
#include <iostream>
using namespace std;
template <class T>
class MyList
{
struct Node
{
T data;
Node* pNext;
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 MyList<T>::Node* MyList<T>::_findPos(int pos)
{
Node* pPos = pHead;
for (int i = 0; i < pos; i++)
{
if (NULL == pos) return NULL;
pPos = pPos->pNext;
}
return pPos;
}
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
{//pTemp为NULL
pHead = pNew;
}
}
template <class T>
void MyList<T>::addNode(const T& data)
{
//1 创建新节点
Node* pNew = new Node(data);
//2 新节点的next指针指向原来头节点
pNew->pNext = pHead;
//3 新节点成为头节点
pHead = pNew;
}
template <class T>
void MyList<T>::insertNode(int pos, int insertNodeData)
{
if (pos < 0) return;
if (NULL == pHead || 0 == pos)
{
addNode(insertNodeData);
return;
}
//新建节点
Node* pNew = new Node(insertNodeData);
//找到pos节点
Node* pPrev = _findPos(pos);
//新节点成为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;
}
template <class T>
//删除链表中第pos个节点
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;
}