1. 教程上的概念![在这里插入图片描述](https://img-blog.csdnimg.cn/20190429115200232.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1lvdW5nX19GYW4=,size_16,color_FFFFFF,t_70)
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190429122524569.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1lvdW5nX19GYW4=,size_16,color_FFFFFF,t_70)
教材来源:殷人昆 数据结构:用面向对象方法与C++语言描述 (第二版)
2. 博客参考
C++链表学习笔记:
https://blog.csdn.net/u011411195/article/details/51150102
关于链表中头指针和头结点的理解:
https://blog.csdn.net/weixin_41413441/article/details/79063738
C++ 链表:
https://blog.csdn.net/shuwenting/article/details/81479520
链表节点的增加和删除:
https://blog.csdn.net/happyhuidi/article/details/82685948
链表的定义:
struct node {
int data;
struct node *next;
};
注意:一般算法中点的都是结点。
教材中写的就是结点而非节点。
节点呢,被认为是一个实体,有处理能力,比如,网络上的一台计算机;而结点则只是一个交叉点,像“结绳记事”,打个结,做个标记,仅此而已,还有就是,要记住:一般算法中点的都是结点,不要写错成节点,我发现剑指Offer上就写成节点了,这种写法是错误的。
概念总结
1. 头指针
(1)链表的名字就是其头指针,当没有头结点时,头指针所指的data就是第一个结点所存的数
(2)头指针是链表中必须存在的(2)头指针中存储的是第一个节点的地址,若链表中存在头结点,那么头指针存储的是头结点的地址,若头结点不存在,则存储的是首元结点的地址
2. 头结点
(1)头结点不是必须存在的
(2)头结点中可以不存储data,也可以存储如链表长度等
3. 结点
结点由两部分组成,一部分存储的为值即Value,另外一部分存储了一个指针,指针存储的是下一个节点的地址
下图为带有头结点的示意图:
下图为没有头结点的单链表的示意图:
深刻理解:带头结点和不带头结点的区别 使用头结点的优势
https://blog.csdn.net/qq_24118527/article/details/81317410
2. 在循环遍历链表时要定义一个临时的指针
该临时指针用于接收头指针的地址
ListNode* pNode = *pHead;
为什么? 要这么做,应该找个头指针我们可能后面还要用,如果直接用头指针参与循环遍历,则头指针的地址会被覆盖,最后找不到了。导致我们后期无法使用头指针用来遍历输出链表。
参考博客:https://blog.csdn.net/liu16659/article/details/70184399
3. 删除链表中值为value的结点
首先明确一点,是下面哪种情况。
1.删除第一个含有value的结点
2.删除所有含有值为value的结点
下面是这两种情况的实现
1. 删除第一个含有value的结点
void RemoveNode(ListNode** pHead,int value)
{
if(pHead == nullptr || *pHead == nullptr)
return;
//定义一个ListNode指针,用于释放要删除的结点
ListNode* pToBeDeleted = nullptr;
if((*pHead)->m_nValue == value)
{
pToBeDeleted = *pHead;
*pHead = *pHead->m_pNext;
}
else
{
// 定义一个临时的指针,用于遍历链表以找到要删除的结点(如果存在的话)的前一个结点
ListNode* pNode = *pHead;
//&&具有短路的功能
while(pNode->m_pNext != nullptr && pNode->m_pNext->m_nValue != value)
pNode = pNode->m_pNext;
//pNode->m_pNext != nullptr还得加上,不然万一上面的while是因为pNode->m_pNext == nullptr而终止(表明没有找到value),若不加上,则pNode->m_pNext->m_nValue == value这句就会空指针异常。
//为啥需要这个if判断,怕万一没找到带有value值的结点while以pNode->m_pNext == nullptr结束的。
if(pNode->m_pNext != nullptr && pNode->m_pNext->m_nValue == value)
{
pToBeDeleted = pNode->m_pNext;
pNode->m_pNext = pNode->m_pNext->m_pNext;
}
}
if(pToBeDeleted != nullptr)
{
delete pToBeDeleted;
pToBeDeleted = nullptr;
}
}
我们要删除节点A,必须找到结点A的前一个结点,然后对其后继结点进行值判断,所以循环里用的是pNode->m_pNext->m_nValue,这里的pNode指的得是当前结点,但进行值判断的是其后继结点。while()循环中pNode->m_pNext != nullptr是用了判别当前结点是否到达末尾。
&&具有短路的功能,即如果第一个表达式为false,则不再计算第二个表达式,例如,对于pNode->m_pNext != nullptr && pNode->m_pNext->m_nValue != value表达式,当pNode->m_pNext 为nullptr时,后面的表达式不会执行,所以不会出现NullPointerException
另外
虽然我们必须得找到要删除的结点A的前一个结点,但是就只对当前结点的值(value)进行判别也是可以的,感觉还更容易理解一些。代码如下:
node *del(node *head, int num)
{
node *p1, *p2;
p2 = new node;
p1 = head;
while (num != p1->data && p1->next != NULL)
{
p2 = p1; //保留当前结点p1的前一个结点
p1 = p1->next;// p1和p2位置: p2->p1
}
//判断是否p1的值是我们寻找的。若不是,就证明上面的while结束是因为遍历到末尾了,也没找到值为data的结点。
if (num == p1->data)
{
if (p1 == head)// 删除头结点
{
head = p1->next;
delete p1;
p1 = nullptr;
}
else
{
p2->next = p1->next;
delete p1;
p1 = nullptr;
}
}
else
{
cout << num << " could not been found in the current single linker!" << endl;
}
return head;
}
p2的作用是保留当前结点p1的前一个结点。
2.删除所有含有值为value的结点
void deleteSpecialData(Node* &L,int x) {
Node* r;
r = L;
while(r->next!=NULL){//当没有到达链表尾部的时候,继续循环
if(r->next->data == x){
Node* q;//申请一个指针q
q=r->next;//保存L->next的指针位置
r->next = r->next->next;//改变链表的指针
delete(q);//释放节点空间q
}
else
r= r->next;//往下循环
}
}