简单说明
双向链表,要考虑头,要考虑尾。
删除、插入元素的时候,常常分情况讨论。
分情况讨论肯定是不会错的。大不了麻烦一点嘛。
1. 是第一个元素吗?
2. 是最后一个元素吗?
3. 是中间的元素吗?
应该没什么大问题。画个图,把所有指针都处理清楚,就好。
删掉最后一个元素时,还要把原来倒数第二个元素的next置为nullptr,以示list结束。
删掉第一个元素时,要把原来第二个元素的prev设为nullptr。
要删掉中间的元素时,被删的叫p,那么p->prev->next = p->next; p->next->prev = p->prev; delete p;
基本操作
/************************************************************************/
/*
双向链表
*/
/************************************************************************/
#include <iostream>
using namespace std;
typedef struct Node
{
int data;
Node * prev;
Node * next;
} Node, * DList;
// 反向输出list (递归)
void printReverseListRecursion(DList list) {
if (list)
{
printReverseListRecursion(list->next);
cout<<list->data<<" ";
}
}
// 反向输出list (非递归)
void printReverseList(DList list) {
DList p = list;
if (!p)
{
return ;
}
// 先走到底
while (p->next)
{
p = p->next;
}
while(p) {
cout<<p->data<<" ";
p = p->prev;
}
cout<<"\n";
}
// 输出链表
void printList(DList list) {
while (list)
{
cout<<list->data<<" ";
list = list->next;
}
cout<<"\n";
}
// 插入一个元素(假设要求在指定的元素前面插入)
// 假设肯定有这个元素
DList insertElemBeforeTarget(DList list, int target, int data) {
if (!list)
{
cout<<"链表为空"<<endl;
return nullptr;
}
DList p = list;
while ((p!=nullptr) && (p->data!=target))
{
p = p->next;
} // 直到找到这个元素位置
if (p==nullptr)
{
cout<<"没有"<<target<<"这个元素"<<endl;
return list;
}
Node * newNode = new Node;
newNode->data = data;
newNode->next = p;
Node * prePreNode = p->prev; // 保存插入元素之前,p前面的结点
if (prePreNode) // 不是在第一个元素之前插入
{
newNode->prev = prePreNode;
prePreNode->next = newNode;
p->prev = newNode;
} else { // 在第一个元素之前插入
p->prev = newNode;
newNode->prev = nullptr;
list = newNode;
}
return list;
}
// 删掉一个元素
DList removeElem(DList list, int target) {
if (!list)
{
cout<<"链表为空"<<endl;
return nullptr;
}
// 找到这个元素
DList p,q;
p = list;
while ((p!=nullptr) && (p->data != target))
{
p = p->next;
}
if (!p)
{
cout<<"没有要删除的这个元素"<<endl;
return list;
}
// 找到了这个元素
// 1. 如果是首元素
if (p->prev==nullptr)
{
q = p->next;
delete p;
list = q;
}
// 2. 如果是末尾元素
else if(p->next == nullptr) {
(p->prev)->next = nullptr; // 先上一个元素设为末尾元素
delete p;
}
// 3. 中间的元素
else {
Node* prevNode = p->prev;
prevNode->next = p->next;
(p->next)->prev = prevNode;
delete p;
}
return list;
}
int main () {
const int N = 10;
DList list,head;
Node * node = new Node;
node->data = 1;
node->prev = nullptr;
node->next = nullptr;
list = node;
head = list;
DList p = list;
for ( int i = 2;i<=N;i++)
{
Node * newNode = new Node;
newNode->data = i;
newNode->next = nullptr;
p->next = newNode;
newNode->prev = p;
p = p->next;
}
// 遍历
printList(head);
// printReverseListRecursion(head);cout<<"\n";
// printReverseList(head);
// 在指定元素前插入一个元素
head = insertElemBeforeTarget(head,10,777); // 如果是在第一个元素插入呢…
printList(head);
// 删除一个指定的元素
head = removeElem(head,10);
head = removeElem(head,777);
printList(head);
system("pause");
return 0;
}
销毁链表
调用销毁链表这个函数以后,希望head指向的list确实被删除了,同时head还被置为nullptr。
下面有三种做法!
非常具有迷惑性。
1. 值传递。
2. 指针传递。
3. 引用传递。
// 销毁list --> 值传递
void destroyList(DList list) {
if (!list)
{
cout<<"链表为空"<<endl;
} else {
DList p = list;
DList q;
while (p)
{
q = p->next; // 保存下一个元素
cout<<"即将删掉"<<p->data<<endl;
delete p;
p = q;
}
list = nullptr;
}
}
// 销毁整个list
// 指针传递!
void destroyList3(DList * list) {
if (!list)
{
cout<<"链表为空"<<endl;
} else {
DList p = *list;
DList q;
while (p)
{
q = p->next; // 保存下一个元素
cout<<"即将删掉"<<p->data<<endl;
delete p;
p = q;
}
*list = nullptr;
}
}
// 销毁整个list
// 引用传递,可以修改外面list自己的值
void destroyList2(DList & list) {
if (!list)
{
cout<<"链表为空"<<endl;
return;
} else {
DList p = list;
DList q;
while (p)
{
q = p->next; // 保存下一个元素
cout<<"即将删掉"<<p->data<<endl;
delete p;
p = q;
}
list = nullptr;
}
}
在main里面调用销毁链表的函数,来销毁main里面的list(头指针存的值)。
1. 三种做法都可以让list里的元素销毁。
2. 但是,第1种做法,只是值传递,main里的list的值不会变。不会被置为nullptr。
非常具有迷惑性!看起来,list本来就是指针,为什么用指针传递还不能改变实参的值?
第一种做法,其实不是指针传递!!只是值传递!
要用指针传递才能改变实参的值!
本例里面,尽管实参本来是个指针p,但作为函数的输入参数,也只是形参!
是把这个指针的值,assign到函数内部的list一个新的指针里面。改变新指针,并不会改变外面的实参。
为了满足上述需求,要用指向指针的指针**pp,*pp才能改变实参的值!或者就用引用传递。
反转链表
// 把双向list反转
DList reverse(DList list) {
if (!list) // 0个
{
return nullptr;
} else if (!(list->next)) { // 1个
return list;
} else { // 大于或等于2个
DList p = list; // 哥
DList q; // 弟 --> 滞后的指针
while (p)
{
q = p;
Node * temp;
temp = p->next;
p->next = p->prev;
p->prev = temp;
p = p->prev; // 前进 注意:反转以后,前进是要输入 p = p->prev;
}
return q; // 注意,返回的是滞后的指针。因为p已经为nullptr了,它的滞后指针q是最后一个节点。
}
}