数据结构(4):双向链表的基本操作

简单说明


双向链表,要考虑头,要考虑尾。

删除、插入元素的时候,常常分情况讨论。

分情况讨论肯定是不会错的。大不了麻烦一点嘛。

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是最后一个节点。
	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qcyfred

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值