C++ 链表

news2025/1/23 15:02:06

目录

链表结构

一,单链表

1.实现基本的增删查改

 2.对链表进行一些操作

(1)删除等于给定值的所有节点。

(2)翻转链表

(3) 返回中间节点的地址

(4)倒数第k个节点 

 (5)合并有序链表

 (6)分割链表

(7)链表回文

(8)链表相交 

 (9)环形链表

二,双向链表

1.增删查改


虽然C++中有list容器,但是在某些oj题中会出现有关链表的题,所以写一篇C++链表。

省去太过官方的定义,只做最简单易懂的介绍。

链表结构

一个数据所在的内存块被分为两个部分,第一个部分放数据,而第二个部分则放下一个数据的地址,以此来连接各个数据,最后一个内存块放的地址为NULL。这样的一个内存块叫做节点。

在代码中,链表的一个节点是这样的:

struct ListNode
{
	int data;
	ListNode* next;//结构体指针
};

链表有一定的缺陷:每存放一个数据都需要伴随下一个数据的地址并且不支持随机访问。

一,单链表

先来看最简单的单链表。

1.实现基本的增删查改

#include<algorithm>
#include<iostream>
#include<vector>
using namespace std;
struct ListNode
{
	int data;
	ListNode* next;//结构体指针
};
void Listprintf(ListNode* phead)
{
	ListNode* cur=phead;
	while (cur != NULL)
	{
		cout << cur->data << "->";
		cur = cur->next;
	}
}
void Listpushback(ListNode** pphead, int x)
{
	ListNode* newnode = new ListNode{ x,NULL };
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		ListNode* tail=  *pphead;
		while(tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}
void test_1()
{
	ListNode* phead = NULL;
	Listpushback(&phead, 1);
	Listpushback(&phead, 2);
	Listpushback(&phead, 3); 
	Listprintf(phead);
}
int main()
{
	test_1();
	return 0;
}

运行结果:

在这段代码中有一些需要注意的地方,比如:

在Listpushback这个函数中,参数是二级指针,如果写成一级指针:

void Listpushback(ListNode* pphead, int x)
{
	ListNode* newnode = new ListNode{ x,NULL };
	if (pphead == NULL)
	{
		pphead = newnode;
	}
	else
	{
		ListNode* tail=  pphead;
		while(tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

则结果会变成:

没有任何输出。

原因是原本的指针phead是没有任何指向的,这个指针没有指向某一个地址,而是一个空指针,在函数传参的时候,如果参数pphead是一级指针,则pphead也是空指针,改变pphead,并不会影响到phead,所以最终一通操作下来,phead还是空指针,输出结果也就是空的。

下面再增加一些功能:

#include<algorithm>
#include<iostream>
#include<vector>
using namespace std;
struct ListNode
{
	int data;
	ListNode* next;//结构体指针
};
void Listprintf(ListNode* phead)
{
	ListNode* cur=phead;
	while (cur != NULL)
	{
		cout << cur->data << "->";
		cur = cur->next;
	}
	cout << "NULL" << endl;
}
//尾插
void Listpushback(ListNode** pphead, int x)
{
	ListNode* newnode = new ListNode{ x,NULL };
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		ListNode* tail=  *pphead;
		while(tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}
//头插
void Listpushfront(ListNode** pphead, int x)
{
	ListNode* newnode = new ListNode{ x,NULL };
	newnode->next = *pphead;
	*pphead = newnode;
}
//尾删
void Listpopback(ListNode** pphead)
{
	if (*pphead == NULL)
	{
		return;
	}
	if ((*pphead)->next == NULL)
	{
		delete(*pphead);
		*pphead = NULL;
	}
	else
	{
		ListNode* tail = *pphead;
		ListNode* prev = NULL;
		while (tail->next)
		{
			prev = tail;
			tail = tail->next;
		}
		delete(tail);
		tail = NULL;
		prev->next = NULL;
	}
}
//头删
void Listpopfront(ListNode** pphead)
{
	if (*pphead == NULL)
	{
		return;
	}
	else
	{
		ListNode* newnode = (*pphead)->next;
		delete(*pphead);
		*pphead = newnode;
	}
}
//查找元素,返回值是地址
ListNode* Listfind(ListNode* phead, int x)
{
	ListNode* cur = phead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		else
		{
			cur = cur->next;
		}
	}
	return NULL;
}
//插入元素,在pos的前一个位置插入
//配合Listfind使用,具体使用见test_insert函数
void Listinsert(ListNode** phead, ListNode* pos, int x)
{
	ListNode* newnode = new ListNode{ x,NULL };
	if (*phead == pos)
	{
		newnode->next = (*phead);
		*phead = newnode;
	}
	else
	{
		ListNode* posprev = *phead;
		while (posprev->next != pos)
		{
			posprev = posprev->next;
		}
		posprev->next = newnode;
		newnode->next = pos;
	}
}
//单链表并不适合在前一个位置插入,因为运算较麻烦,会损失效率
//包括c++中为单链表提供的库函数也只有一个insert_after而没有前一个位置插入
//在后一个位置插入相对简单
void Listinsert_after(ListNode** phead, ListNode* pos, int x)
{
	ListNode* newnode = new ListNode{ x,NULL };
	newnode->next = pos->next;
	pos->next = newnode;
}
//删除指定位置的节点
void Listerase(ListNode** pphead, ListNode* pos)
{
	if (*pphead == pos)
	{
		*pphead = pos->next;
		delete(pos);
	}
	else
	{
		ListNode* prev = *pphead;
		while (prev->next!=pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		delete(pos);
	}
}
//释放链表
void Listdestory(ListNode** pphead)
{
	ListNode* cur = *pphead;
	while(cur)
	{
		ListNode* next = cur->next;
		delete(cur);
		cur = next;
	}
	*pphead = NULL;
}
void test_insert()
{
	ListNode* phead = NULL;
	Listpushback(&phead, 1);
	Listpushback(&phead, 2);
	Listpushback(&phead, 3);
	Listprintf(phead);
	ListNode* pos = Listfind(phead, 2);
	if (pos != NULL)
	{
		Listinsert(&phead, pos, 20);
	}
	Listprintf(phead);
	pos = Listfind(phead, 2);
	if (pos != NULL)
	{
		Listinsert_after(&phead, pos, 20);
	}
	Listprintf(phead);
	Listdestory(&phead);
}
void test_find()
{
	ListNode* phead = NULL;
	Listpushback(&phead, 1);
	Listpushback(&phead, 2);
	Listpushback(&phead, 3);
	Listprintf(phead);
	ListNode* pos = Listfind(phead, 2);
	if (pos != NULL)
	{
		pos->data = 20;//Listfind不仅能查找,也能借此修改,这也是函数返回地址的原因
	}
	Listprintf(phead);
	Listdestory(&phead);
}
void test_erase()
{
	ListNode* phead = NULL;
	Listpushback(&phead, 1);
	Listpushback(&phead, 2);
	Listpushback(&phead, 3);
	Listprintf(phead);
	ListNode* pos = Listfind(phead, 2);
	if (pos != NULL)
	{
		Listerase(&phead, pos);
	}
	Listprintf(phead);
	Listdestory(&phead);
}
void test_pop_and_push()
{
	ListNode* phead = NULL;
	Listpushback(&phead, 1);
	Listpushback(&phead, 2);
	Listpushback(&phead, 3);
	Listprintf(phead);
	Listpushfront(&phead, 1);
	Listpushfront(&phead, 2);
	Listpushfront(&phead, 3);
	Listprintf(phead);
	Listpopback(&phead);
	Listpopfront(&phead);
	Listprintf(phead);
	Listdestory(&phead);
}
int main()
{
	//test_pop_and_push();
	test_find();
	//test_insert();
	//test_erase();
	return 0;
}

test_pop_and_push()测试结果:

 test_find()测试结果:

 test_insert()测试结果:

 test_erase()测试结果:

 2.对链表进行一些操作

(1)删除等于给定值的所有节点。

例如:在一条链表中删除值为6的节点。

#include<algorithm>
#include<iostream>
#include<vector>
using namespace std;
struct ListNode
{
	int data;
	ListNode* next;//结构体指针
};
void Listprintf(ListNode* phead)
{
	ListNode* cur=phead;
	while (cur != NULL)
	{
		cout << cur->data << "->";
		cur = cur->next;
	}
	cout << "NULL" << endl;
}
void Listpushback(ListNode** pphead, int x)
{
	ListNode* newnode = new ListNode{ x,NULL };
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		ListNode* tail=  *pphead;
		while(tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}
ListNode* creatlist()
{
	ListNode* phead = NULL;
	Listpushback(&phead, 1);
	Listpushback(&phead, 9);
	Listpushback(&phead, 6);
	Listpushback(&phead, 8);
	Listpushback(&phead, 6);
	Listpushback(&phead, 2);
	Listpushback(&phead, 3);
	return phead;
}
ListNode* removeElements(ListNode* head, int x)
{
	ListNode* prev = NULL;
	ListNode* cur = head;
	while (cur)
	{
		if (cur->data == x)
		{
			if (cur == head)//如果第一个元素就是要删除的,进行头删
			{
				head = cur->next;
				delete(cur);
				cur = head;
			}
			else
			{
				prev->next = cur->next;
				delete(cur);
				cur = prev->next;
			}
		}
		else
		{
			prev = cur;
			cur = cur->next;
		}
	}
	return head;
}
int main()
{
	ListNode*phead = creatlist();//先创建一条链表
	Listprintf(phead);
	phead = removeElements(phead, 6);//删除值为6的节点
	Listprintf(phead);
	return 0;
}

 自测结果:

 当然如果是一条全为6的链表也是可以完成删除的:

这里再说一下为什么删除元素和创建链表这两个函数的参数不是二级指针,因为这两个函数是要返回一个指针的。 

(2)翻转链表

比如:将1->2->3->4->5翻转为5->4->3->2->1

翻转链表的方式很多,这里只写两种作为参考:

第一种,翻转指针。

这种方法的逻辑是定义三个指针,一个用来纪录当前位置的前一个位置,一个用来纪录当前位置,一个用来纪录当前位置的后一个位置,再通过将当前位置指向下一个位置的指针修改为指向上一个位置,再往后迭代,以达到翻转链表的目的。

为什么要三个指针呢,因为将当前位置指向前一个位置之后,就找不到当前位置的下一个位置了,所以需要第三个指针来指向下一个位置。

代码部分由于只有测试函数不一样,其余代码差异不大,所以仅贴出测试函数部分:

ListNode* reverseList(ListNode* head)
{
	if (head == NULL)
	{
		return NULL;
	}
	ListNode* prev, * cur, * next;
	prev = NULL;
	cur = head;
	next = cur->next;
	while (cur)
	{
		cur->next = prev;//翻转指针

		//往后迭代
		prev = cur;
		cur = next;
		if (next)//这里是因为当cur指向最后一个节点的时候,next就已经是NULL了,这个时候如果再执行next=next->next则会出现错误
		{
			next = next->next;
		}
	}
	return prev;
}

自测结果:

测试结果(测试网站:牛客网):

 第二种方式,创建新链表进行头插

这种方法的逻辑是将原链表中的节点取下来头插到新链表newlist中,同样的,需要三个指针,一个为NULL,即为新链表newlist,一个纪录原链表从头开始的地址,一个纪录下一个位置。将原链表第一个节点取下来对newlist进行头插,再取第二个第三个,往后迭代。

ListNode* reverseList(ListNode* head)
{
	ListNode* cur = head;
	ListNode* newlist = NULL;
	ListNode* next = NULL;
	while (cur)
	{
		next = cur->next;
		//头插
		cur->next = newlist;
		newlist = cur;
		//往后迭代
		cur = next;
	}
	return newlist;
}

自测结果:

 测试结果(测试网站:牛客网):

(3) 返回中间节点的地址

如果有两个中间节点,返回第二个中间节点。

这种操作比较简单,最容易想到的方法就是先遍历一次整个链表找出有几个节点,再遍历一次找出中间节点

但是如果要求只能遍历一次呢,所以这里不采用遍历两次的方法,而采用快慢指针的方式只遍历一次。

逻辑就是定义两个指针,一个走的更慢,一次走一步,另一个走的更快,一次走两步。

ListNode* middleNode(ListNode* head)
{
	ListNode* slow, * fast;
	slow = fast = head;
	while (fast && fast->next)
	{
		slow = slow->next;
		fast = fast->next->next;
	}
	return slow;
}

自测结果:

(4)倒数第k个节点 

输入一个链表和k,输出从倒数第k个节点到最后一个。

思路和快慢指针相似,定义两个指针,一个指针先走k步。

ListNode* findK(ListNode* head, int k)
{
	ListNode* fast, * slow;
	fast = slow = head;
	while(k--)
	{
		if (fast == NULL)//如果当fast等于NULL时k仍不为0,则k大于链表长度
		{
			return NULL;
		}
		fast = fast->next;
	}
	while (fast)
	{
		fast = fast->next;
		slow = slow->next;
	}
	return slow;
}

自测结果:

测试结果(测试网站:牛客网):

 (5)合并有序链表

 思路比较简单,依次比较链表中的节点,将较小的节点尾插到新链表。

当然还有一种做法是将一个链表直接插入到另一个链表中,这种方式画图画起来倒是简单,但是实际写起来很麻烦,这里不推荐这种写法,也不会写这种。

ListNode* mergeTwoList(ListNode* l1, ListNode* l2)
{
	if (l1 == NULL)//如果一个链表为空,则返回另一个
	{
		return l2;
	}
	if (l2 == NULL)
	{
		return l1;
	}
	ListNode* head = NULL;
	ListNode* tail = NULL;
	while (l1 && l2)
	{
		if (l1->data < l2->data)
		{
			if (head == NULL)
			{
				head = tail =l1;
			}
			else
			{
				tail->next = l1;
				tail = l1;
			}
			l1 = l1->next;
		}
		else
		{
			if (head == NULL)
			{
				head = tail = l2;
			}
			else
			{
				tail->next = l2;
				tail = l2;
			}
			l2 = l2->next;
		}
	}
	if (l1)
	{
		tail->next = l1;
	}
	if(l2)
	{
		tail->next = l2;
	}
	return head;
}

自测结果:

我这里是将两条1->2->3->4->5->6的链表合并。

测试结果(测试网站:牛客网):

 可以看到这种不带哨兵位的写法也还是比较麻烦,要判断头为不为空,所以下面再写一种带哨兵位的写法。

带哨兵位(也叫带头)就是我们去给链表多定义一个头结点,这个节点不存储有效数据。

ListNode* mergeTwoList(ListNode* l1, ListNode* l2)
{
	if (l1 == NULL)//如果一个链表为空,则返回另一个
	{
		return l2;
	}
	if (l2 == NULL)
	{
		return l1;
	}
	ListNode* head = NULL, * tail = NULL;
	head = tail = new ListNode;//哨兵位的头结点
	while (l1 && l2)
	{
		if (l1->data < l2->data)
		{
		    tail->next = l1;
		    tail = l1;
		    l1 = l1->next;
	 	}
		else
		{
			tail->next = l2;
			tail = l2;
			l2 = l2->next;
		}
	}
	if (l1)
	{
		tail->next = l1;
	}
	if (l2)
	{
		tail->next = l2;
	}
	ListNode* list = head->next;
	delete(head);
	return list;
}

自测结果:

测试结果(测试网站:牛客网):

 (6)分割链表

 给定一个值x,将所有小于x的节点排在其余节点之前,且不能改变原来的数据顺序,返回重新排列后的链表头指针。

思路:定义两条链表,一条将小于x的所有节点按照原顺序连接成链表,另一条将大于x的所有节点按照原顺序连接成链表,最后再合起来。

ListNode* partition(ListNode* phead, int x)
{
	ListNode* lesshead, * lesstail, * greaterhead, * greatertail;

	lesshead = lesstail = new ListNode;//定义一个哨兵位头结点,方便尾插
	lesstail->next = NULL;
	greaterhead = greatertail = new ListNode;
	greatertail->next = NULL;

	ListNode* cur = phead;
	while (cur)
	{
		if (cur->data < x)
		{
			lesstail->next = cur;
			lesstail = cur;
		}
		else
		{
			greatertail->next = cur;
			greatertail = cur;
		}
		cur = cur->next;
	}
	lesstail->next = greaterhead->next;
	greatertail->next = NULL;//举个例子,这样一条链表:1->4->15->5,现在给的x是6,那么排序后15应该在最后,正因如此,重新排序后15的next是没变的,仍然指向5,不手动将next改为NULL,就会成环,无限排下去。
	
	ListNode* newhead = lesshead->next;
	delete(lesshead);
	delete(greaterhead);
	return newhead;
}

自测结果:

测试结果(测试网站:力扣):

(7)链表回文

判断链表是否回文。

思路,先找到一条链表的中点,将后半段逆置,设置两个指针,一个从头开始,一个从逆置后的头开始,逐个判断直到最后。

图:

节点奇数个:

节点偶数个:

代码:

ListNode* middleNode(ListNode* head)
{
	ListNode* slow, * fast;
	slow = fast = head;
	while (fast && fast->next)
	{
		slow = slow->next;
		fast = fast->next->next;
	}
	return slow;
}
ListNode* reverseList(ListNode* head)
{
	ListNode* cur = head;
	ListNode* newlist = NULL;
	ListNode* next = NULL;
	while (cur)
	{
		next = cur->next;
		//头插
		cur->next = newlist;
		newlist = cur;
		//往后迭代
		cur = next;
	}
	return newlist;
}
bool check(ListNode* head)
{
	ListNode* mid = middleNode(head);
	ListNode* rhead = reverseList(mid);
	ListNode* curHead = head;
	ListNode* curRhead = rhead;
	while(curHead&&curRhead)
	{
		if (curHead->data != curRhead->data)
			return false;
		else
		{
			curHead = curHead->next;
			curRhead = curRhead->next;
		}
	}
	return true;
}

自测结果:

测试结果(测试网站:牛客网): 

 

(8)链表相交 

 给两个单链表的头结点,找出并返回两个单链表相交的起始节点,没有交点则返回null。

最简单粗暴的思路就是将A链表的每个节点和B链表的每个节点挨着挨着比较,这里不使用这一种。 

还有一种思路就是,找到A和B的尾结点对比,如果一样则有交点,如果不一样则没有交点,有交点的情况下又该怎样找到相交的起始节点?

如果两条链表在相交之前的节点个数一样的话,那么找到相交的起始节点就很简单,定义两个指针同时向前走直到相等即可,所以我们在找链表A和B的尾结点的时候,同时纪录下A和B的长度,用长的减去短的得到一个数x,再分别为两条链表定义头指针,让长的那条链表的头指针先走x步,那么就可以当做是在相交之前节点数一样了。

ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) 
{
        if(pHead1==NULL)
        {
            return NULL;
        }
        if(pHead2==NULL)
        {
            return NULL;
        }
        ListNode* tail1=pHead1;
        ListNode* tail2=pHead2;
        int len1=1,len2=1;
        while(tail1->next)
        {
            len1++;
            tail1=tail1->next;
        }
        while(tail2->next)
        {
            len2++;
            tail2=tail2->next;
        }
        if(tail1!=tail2)//不相交
        {
            return NULL;
        }
        int gap=abs(len1-len2);
        ListNode* longlist=pHead1;
        ListNode* shortlist=pHead2;
        if(len1<len2)
        {
            longlist=pHead2;
            shortlist=pHead1;
        }
        while(gap--)//长的先走差距步,再同时走找交点
        {
            longlist=longlist->next;
        }
        while(longlist!=shortlist)
        {
            longlist=longlist->next;
            shortlist=shortlist->next;
        }
        return longlist;
    }

测试结果(测试网站:牛客网):

 (9)环形链表

判断链表是否带环,并且返回环的入口节点。

判断是否带环的思路比较简单,定义两个指针,一个走得快,一次走两步,一个走得慢,一次走一步,如果不带环,那么走得快的最终会走到NULL,如果带环,那么走得快的会和走得慢的相遇。

要找环的入口节点,也需要定义两个指针,一个从链表头开始,一个从快慢指针相遇的位置开始,他们同时出发,当这两个指针相遇时,所在的位置就是入口节点。

这里做简单的证明,假设链表头到入口节点距离是L,快慢指针相遇的位置meetnode距离入口节点为x

 假设C是环的长度,当快慢指针相遇时,快指针走的距离是L+N*C+x,N是快指针走的圈数,慢指针走的距离是L+x,因为快指针一次走两步,慢指针一次走一步,所以快指针走的距离是慢指针的两倍,所以有:

L+N*C+x=2(L+x)

得到:

L+x=N*C

L=(N-1)*C+C-x

其中C-x即为meetnode到入口节点的距离,所以当头指针和从meetnode出发的指针相遇时,头指针走了L,从meetnode出发的指针走了C-x,相遇点即为入口节点。

代码:

ListNode* EntryNodeOfLoop(ListNode* pHead) {
        ListNode*fast=pHead;
        ListNode*slow=pHead;
        while(fast&&fast->next)
        {
            slow=slow->next;
            fast=fast->next->next;
            if(fast==slow)
            {
                ListNode*meet=fast;
                while(meet!=pHead)
                {
                    meet=meet->next;
                    pHead=pHead->next;
                }
                return pHead;
            }
        }
        return NULL;
    }

测试结果(测试网站:牛客网):

这里再说下还有另一种处理方式,就是将meetnode作为链表的尾,meetnode的next作为链表的头,就转化成了两条相交链表找第一个相交节点的问题,这种方式写起来会麻烦一些,这里也不做代码实现。

二,双向链表

 双向链表的一个数据所在的内存块是被分成了三部分,除了像单链表那样一部分存储数据,一部分存储下一个数据的地址之外,还要有一部分来存储上一个数据的地址。

在代码中如下:

struct ListNode
{
	int data;
	ListNode* next;//结构体指针
	ListNode* prev;
};

双向链表的结构:

其中最常用的是双向带头(哨兵位)循环链表

 

后面的代码中双向链表也都是这种。

1.增删查改

  这里多说一句,很多人都会下意识的认为在带哨兵位的链表中,哨兵位是一条链表开始的位置,但是实际上哨兵位的下一位才是。

#include<algorithm>
#include<iostream>
#include<vector>
using namespace std;
struct ListNode
{
	int data;
	ListNode* next;//结构体指针
	ListNode* prev;
};
ListNode* Initlist()//初始化双向带头(哨兵)循环链表
{
	ListNode* phead=new ListNode;
	phead->next = phead;
	phead->prev = phead;//定义一个哨兵位,自己的next和prev指向自己
	return phead;
}
void pushback(ListNode* phead,int x)//尾插
{
	ListNode* tail = phead->prev;//循环链表,哨兵位的前一个就是尾
	ListNode* newhead = new ListNode;
	newhead->data = x;

	newhead->next = phead;
	newhead->prev = tail;
	tail->next = newhead;
	phead->prev = newhead;
}
void popback(ListNode* phead)//尾删
{
	if (phead->next == phead)//链表为空,不能再删了
		return;

	ListNode* tail = phead->prev;
	ListNode* tailprev = tail->prev;

	tailprev->next = phead;
	phead->prev = tailprev;

	delete(tail);
}
void pushfront(ListNode* phead,int x)//头插
{
	ListNode* next = phead->next;
	ListNode* newnode = new ListNode;
	newnode->data = x;

	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = next;
	next->prev = newnode;
}
void popfront(ListNode* phead)//头删
{
	if (phead->next == phead)//链表为空,不能再删了
		return;

	ListNode* next = phead->next;
	ListNode* dnext = next->next;

	phead->next = dnext;
	dnext->prev = phead;

	delete(next);
}
ListNode* listFind(ListNode* phead, int x)//查找
{
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
			return cur;
		cur = cur->next;
	}
	return NULL;
}
void insertList(ListNode* pos, int x)//指定位置插入
{
	ListNode* prev = pos->prev;
	ListNode* newnode = new ListNode;
	newnode->data = x;

	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;
}
void eraseList(ListNode* pos)//指定位置删除
{
	ListNode* posNext = pos->next;
	ListNode* posPrev = pos->prev;

	posNext->prev = posPrev;
	posPrev->next = posNext;
	delete(pos);
	pos = NULL;
}
void printlist(ListNode* phead)
{
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		cout << cur->data << " ";
		cur = cur->next;
	}
	cout << endl;
}
void test_pop_push()
{
	ListNode* phead = Initlist();
	pushback(phead, 1);
	pushback(phead, 2);
	pushback(phead, 3);
	printlist(phead);
	popback(phead);
	popback(phead);
	printlist(phead);
	pushfront(phead, 1);
	pushfront(phead, 2);
	pushfront(phead, 3);
	printlist(phead);
	popfront(phead);
	popfront(phead);
	printlist(phead);
}
void test_find_insert_erase()
{
	ListNode* phead = Initlist();
	pushback(phead, 1);
	pushback(phead, 2);
	pushback(phead, 3);
	printlist(phead);
	ListNode* p = listFind(phead, 2);
	insertList(p, 20);
	printlist(phead);
	p = listFind(phead, 20);
	eraseList(p);
	printlist(phead);
}
int main()
{
	//test_pop_push();
	test_find_insert_erase();
	return 0;
}

test_pop_push()测试结果:

test_find_insert_erase()测试结果:

其中最重要的是insert()和erase()函数,在双向带头循环链表中,这两个函数是可以分别和头尾插,头尾删函数复用的,即可以修改成:

void pushback(ListNode* phead,int x)//尾插
{
	insertList(phead,x);
}
void popback(ListNode* phead)//尾删
{
	if (phead->next == phead)//链表为空,不能再删了
		return;

	eraseList(phead->prev);
}
void pushfront(ListNode* phead,int x)//头插
{
	insertList(phead->next, x);
}
void popfront(ListNode* phead)//头删
{
	if (phead->next == phead)//链表为空,不能再删了
		return;

	eraseList(phead->next);
}

最后再说一下为什么双向链表中的函数的参数是一级指针而不是像之前单链表那样是二级指针的问题 ,这是因为在我写的双向链表中是带了头的,也就是带了哨兵位,所以不需要考虑传进来的是空指针,改变形参不会影响变量的问题,而在我写单链表的时候是没有带头的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1330185.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

运维管理平台哪个好?如何挑选合适的运维管理平台?

运用运维管理平台来处理一些内部后勤事务或者对外的售后服务&#xff0c;是现在很多企业采用的管理方法&#xff0c;优势是成本较低&#xff0c;效率更高。那么&#xff0c;运维管理平台哪个比较好呢&#xff1f; 选择运维管理平台要先找准自己的需求&#xff0c;然后才能选出合…

Android JNI入门到基础

一、JNI项目创建 AS创建项目时选择NativeC 会创建一个基本的JNI项目 MainActivity中写java层的native方法 具体实现在cpp文件中 native-lib.cpp #include <jni.h> #include <string>extern "C" JNIEXPORT jstring JNICALL Java_com_cn_techvision_j…

增强客户获取能力:探索 B 端影片行销的影响

01 B端企业短视频营销的价值与难点 短视频营销在当今的市场中越来越受到重视。有数据显示&#xff0c;越来越多的市场人将尝试增加短视频营销的预算&#xff0c;并且在2023年&#xff0c;每5个市场人中就有1个人将尝试短视频营销。相较于内容深、信息量大的长视频&#xff0c;…

CRC循环冗余校验

CRC循环冗余检验&#xff1a; 一般会给定生成多项式&#xff0c;通过生成多项式确定P&#xff08;除数&#xff09;的值&#xff08;e.g. P ( X ) X 3 X 2 1 P(X)X^3X^21 P(X)X3X21表示除数 P 1101 P1101 P1101&#xff09; 带传送的数据记为M&#xff0c;M有k位。n位冗…

高级数据结构 <二叉搜索树>

本文已收录至《数据结构(C/C语言)》专栏&#xff01; 作者&#xff1a;ARMCSKGT 目录 前言正文二叉搜索树的概念二叉搜索树的基本功能实现二叉搜索树的基本框架插入节点删除节点查找函数中序遍历函数析构函数和销毁函数(后序遍历销毁)拷贝构造和赋值重载(前序遍历创建)其他函数…

多数据库切换?设计模式--抽象工厂引导下思路

缘起 某日&#xff0c;部门Leader找到小明&#xff1a;“小明&#xff0c;我们公司不是用的SQL Server的数据库吗&#xff0c;但是后面可能会改&#xff0c;比如去使用Access或Mysql或其他的&#xff0c;你觉得该怎么去设计这个代码呢&#xff1f;” 小明一脸所思&#xff0c…

亚马逊测评的重要性和技术选择

亚马逊测评是指卖家通过各种途径&#xff0c;如测评平台、社区、红人等&#xff0c;联系到亚马逊的买家&#xff0c;让其对卖家的产品进行评价和留下真实的综合评价&#xff0c;这对于跨境电商卖家来说非常重要&#xff0c;因为亚马逊的排名和转化率很大程度上取决于产品的评价…

什么是数据资产化?数据怎样成为资产?怎样进入资产负债表?

财政部发布的《企业数据资源相关会计处理暂行规定》将从2024年1月1日起开始实施&#xff0c;为企业数据资源入表提供了基本指引&#xff0c;数据资产化有望迎来爆发期。什么是数据资产化&#xff0c;怎样让数据成为资产&#xff0c;成为了众多国有企业、上市公司关心的问题。 —…

应用全局的UI状态存储AppStorage

目录 1、概述 2、StorageProp 2.1、观察变化和行为表现 3、StorageLink 3.1、观察变化和行为表现 4、从应用逻辑使用AppStorage和LocalStorage 5、从UI内部使用AppStorage和LocalStorage 6、不建议借助StorageLink的双向同步机制实现事件通知 6.1、推荐的事件通知方式…

KiCad 类型为电源输出和电源输出的引脚已连接

环境&#xff1a; KiCad 版本&#xff1a;7.0.6 操作系统版本&#xff1a;Win10 错误描述&#xff1a; KiCad 原理图 ERC 检查啊出现错误&#xff0c;错误提示下&#xff1a; 类型为电源输出和电源输出的引脚已连接。 错误原因&#xff1a; 电源输出和电源输出连接到了一起…

电商裂变营销的新策略:工会排队

电商行业已经发展了很多年了&#xff0c;一些基本的营销手段大家也是见识过的&#xff0c;比如&#xff1a;打折、满减、618、双十一、双十二等等。但是很多人把东西都屯到这种节日下单&#xff0c;算下来发现根本没便宜多少&#xff0c;有的反而更贵了&#xff0c;因为这是商家…

实在智能斩获钛媒体2023全球创新评选科技类「 大模型创新应用奖」

近日&#xff0c;历时三天的钛媒体2023 T-EDGE全球创新大会以“新视野新链接”为主题在北京隆重举办。作为科创领域全新高度的年度盛事&#xff0c;大会吸引了AI各产业链近百位海内外创投人、尖端企业家、商业领袖和国际嘉宾齐聚一堂&#xff0c;围绕新一轮AI革命、智慧数字化、…

AI时代Python量化交易实战:ChatGPT引领新时代

文章目录 《AI时代Python量化交易实战&#xff1a;ChatGPT让量化交易插上翅膀》关键点内容简介作者简介购买链接 《AI时代架构师修炼之道&#xff1a;ChatGPT让架构师插上翅膀》关键点内容简介作者简介 赠书活动 《AI时代Python量化交易实战&#xff1a;ChatGPT让量化交易插上翅…

【Mubert AI】快速自动生成免版税音乐

关于Mubert Mubert Mubert是一款很专业音乐创作工具&#xff0c;许多创作者和艺术家都在用它。 生成的音乐质量很高&#xff0c;使用方法也非常简单。 开始制作音乐 在主页选择“立即生成曲目”&#xff0c;无需登录立刻就可以进入音乐生成模式。 你可以根据需要&#xff0…

【模式识别】探秘判别奥秘:Fisher线性判别算法的解密与实战

​&#x1f308;个人主页&#xff1a;Sarapines Programmer&#x1f525; 系列专栏&#xff1a;《模式之谜 | 数据奇迹解码》⏰诗赋清音&#xff1a;云生高巅梦远游&#xff0c; 星光点缀碧海愁。 山川深邃情难晤&#xff0c; 剑气凌云志自修。 目录 &#x1f30c;1 初识模式识…

自动生成数控加工的轨迹刀具轨迹阿基米德螺旋线(3D)

文章目录 1. 阿基米德螺旋线2. 生成步骤目标: 基于点云自动生成阿基米德螺旋线轨迹点 针对的是半球形模型效果 1. 阿基米德螺旋线 阿基米德螺旋线(Archimedean spiral)是一种数学曲线,由古希腊数学家阿基米德(Archimedes)在公元前225年左右首次研究和描述。这条曲线的方…

Ubuntu 常用命令之 clear 命令用法介绍

&#x1f4d1;Linux/Ubuntu 常用命令归类整理 clear命令在Ubuntu系统下用于清除终端屏幕的内容。这个命令没有任何参数&#xff0c;它的主要作用就是清理终端屏幕上的所有信息&#xff0c;使得屏幕看起来像是新打开的一样。 使用clear命令非常简单&#xff0c;只需要在终端中…

微前端样式隔离、sessionStorage、localStorage隔离

1、样式隔离 前端样式不隔离&#xff0c;会产生样式冲突的问题&#xff0c;这个点在qiankun也存在 子应用1修改一个样式 button {background: red&#xff01;important&#xff1b; }其它应用也会受到影响 qiankun的css隔离方案&#xff08;shadow dom&#xff09; shadow …

MySQL报错:1366 - Incorrect integer value: ‘xx‘ for column ‘xx‘ at row 1的解决方法

我在插入表数据时遇到了1366报错&#xff0c;报错内容&#xff1a;1366 - Incorrect integer value: Cindy for column name at row 1&#xff0c;下面我演示解决方法。 根据上图&#xff0c;原因是Cindy’对应的name字段数据类型不正确。我们在左侧找到该字段所在的grade_6表&…

分布式系统架构设计之分布式数据管理

随着互联网时代的不断发展&#xff0c;分布式系统架构成为支撑大规模用户和高并发访问的基础。在构建分布式系统时&#xff0c;分布式系统有着一系列的要求以及对应的核心技术&#xff0c;涉及到数据管理、通信安全性、性能优化、可扩展性设计以及架构演进与版本管理等很多方面…