C++ 链表 (linked list)

链表的概念

链表,顾名思义,是以链状形式存储的一系列数据。链状结构的特点是环环相扣
从一个节点只能走到相邻的下一个节点。链表中的每个节点我们一般用一个结构体来表示,每个节点实际存储位置在内存中是随机的,而不一定是顺序的。我们可以把链表的节点想象成购物商场的储物柜。商场里有很多储物柜,一个储物柜就像一个链表节点:

struct ListNode
{
	int data;
} A;

储物柜上的编号就像该节点在内存中实际存储的位置:

&A

现在我开了一个柜子A(编号可能是66,也可能是5201314,who cares):

ListNode A;

拿到一个钥匙(指向结构体的指针):

ListNode pA = &A;

把东西(数据)丢进去:

A.data = 1;

可是东西(数据)太多了,我只好再开几个柜子。

ListNode B,C,D,E;
...  //同上

东西(数据)放完了,此时我手上有五把钥匙:

ListNode *pA,*pB,*pC,*pD,*pE;

可是我不想带这么多钥匙。我灵光一闪,依次把下一个柜子的钥匙放在上一个柜子里,于是现在柜子变成了这样:

struct ListNode
{
	int data;
	ListNode * next;
}

依次放完后:

A.next = pB, B.next = pC, C.next = pD, D.next= pE, E.next = NULL; 

我手上只有一把钥匙,也就是A柜的钥匙pA
因为有一些柜子被其他人占用了,所以我的这些柜子的编号并不是连续的。

但是我的这种存放策略就不用关心编号的事情了,甚至我都不需要记住我一共开了多少个柜子。我只需要按照钥匙的顺序来依次开柜子拿东西,一直拿到某个柜子里面没有钥匙了(NULL):

for(ListNode *p = pA ; p->next!=NULL,; p = p->next )
	printf("%d ",p->data);

当我有新的东西要放进去时,只需要再开一个柜子F,然后把F的钥匙放在E中即可:

ListNode F, *pF = &F;
F.data=6;
E.next = pF;

如果我想让柜子F在B,C之间(插入节点),即A->B->F->C->D->E,只需要把C柜的钥匙(指向C的指针)放入F,把F柜的钥匙放入B即可:

F.next = pC;
B.next = pF;

如果我想不使用C柜(删除节点),我只需要把D柜的钥匙放入B:

B.next = D;

以上就是链表的工作原理
双向链表也就是在此基础上每个节点还存有上一个节点的地址,这样可以往两个方向挨个儿访问。


实现

实际在编程当中,我们往往不会像上面那样每个节点都声明,赋值,节点的个数也一般是不确定的。

添加节点到末尾

基于C++的语法特性,我们可以这样建立一个链表并且读入数据:

ListNode * pA = new ListNode();  //开辟一块新的内存空间,类似C中的 malloc
ListNode * p = pA;
for(int i=0;i<n;i++,p=p->next)
{
	cin >> p->data;
	p->next = new ListNode();
}

把添加节点到链表末尾的功能封装到结构体里面:

struct ListNode {
    int val;
    ListNode* next;
    ListNode(int x) : val(x), next(NULL) {}
    void push_back(int data)    //在尾部添加元素
    {
        ListNode* p = this;
        while (p->next != NULL) p = p->next;
        p->next = new ListNode(data);
    }
};

在指定位置插入节点:

两种情况,第一种我们需要插入到指定数据内容的节点后面,比如说我不知道他是第几个节点,但我知道他的data是2(如果有多个data为2的节点,那么会插入到第一个data为2的节点后面):


int insert(ListNode * pB,ListNode * pA,int data)	//把B节点插入到A链表中数据为data的节点后面
{
	int pos = 0;
	while(pA!=NULL &&  pA->val != data) {pA=pA->next; pos++;} //往后找val为data的节点
	if(pA==NULL) return -1;  //没有指定的节点,返回-1
	else{
		if(pA->next != NULL) pB->next=pA->next; //如果该节点不是最后一个节点那么pB->next应该赋值为原本pA后面的节点的地址	
		else pB->next=NULL;    //如果pA指向的是最后一个节点,那么pB指向的节点的next指针应该置为空
		pA->next=pB;	//pA指向节点的next指针改为指向B节点
		return pos;		//返回插入节点的索引
		}
	}	
}


第二种我们需要插入到指定的“索引”后面,比如插入到第三个节点后面:

int insert(ListNode * pB,ListNode * pA,int pos)	//插入到索引为pos的节点后面
{
	for(int i=0;i<=pos;i++,pA=pA->next)	//移动到索引为pos的节点 
		if(pA==NULL) return -1;
	if(pA->next!=NULL) pB->next=pA->next;	//最后一个节点的情况
	else pB->next = NULL				//非最后一个节点的情况
	pA->next=pB;
	return pos;			
}

只要把pA改为this指针就可以把整个函数放入结构体中,在此省略,读者可根据需求自行完成


删除指定节点

这个需求和插入节点其实很像了,但是要注意我们操作的是单向链表
,不能倒退,在删除的时候我们应该用pA->next来判断而不是用pA来判断是不是指定的节点(想一想,为什么)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值