Chapter 2 | Linked Lists--返回单链表倒数第n个元素及删除中间的某个节点

原创 2013年12月05日 13:23:24

2.2    Implement an algorithm to find the nth to last element of a singly linked list.

译文:实现一个算法返回单链表中倒数第n个元素

第一反应是递归,但还是分析一下,单链表返回第n个元素很好办,直接从头结点开始遍历n个。这里返回倒数第n个元素,我们总不能先得出这个链表的元素个数N,然后遍历第N-n+1个吧。这种次序颠倒的问题我们可以想到另一种数据结构——栈,栈有个特点就是先进后出,我们可以遍历一遍链表,将其中的元素逐个压栈,然后再将各个元素弹出来,那么第n个弹出来的元素就是返回值。如果要是再显式地写一个栈来实现的话,就复杂化了,看到栈,我们就得联想到递归,这个自动使用栈的方式,所以我们解决这个问题的第一个方法便是使用递归。

思路比较简单,就是不断遍历,不断“压栈”,遍历完后,就“出栈”,n不断减1,减为0时,此时弹出的元素就是倒数第n个元素。代码如下

LINK_NODE *Node = NULL;
int n;
void findNthToLast(LINK_NODE *pHead)
{
	if (NULL == pHead)
		return;
	
	findNthToLast(pHead->next); 
	--n;
	if (0 == n)
		Node = pHead;
}
递归有时让人摸不着头脑,因为我们有人是通过分析堆栈,分析一个一个函数的调用过程和输出结果来分析递归算法的,简单的还好,要是多几级,这种方式只会把自己弄晕。其实递归本质上也是函数的调用,调用自己的函数和其他函数都是差不多的,没本质区别,函数调用总会将一些临时信息(主要是函数参数和返回值)压栈保存,压栈只是为了函数能够正确的返回,我们使用递归时,我们感兴趣的信息变量(递归时一般就是函数形参和返回值)就在不断地压栈出栈,所以我们在理解递归的时候可结合栈这一数据结构来分析。递归运行时可以概括为“能进则进,不进则退”,进的时候压栈,进不了我就退,就不断的出栈。所以递归的效率一直被人诟病,但我们不能忘记它的好,可正常使用的递归必须要有终止条件,不能无限制的递归下去,不然最后就是栈溢出了。

除了递归,这里还提供另一个方法,要返回单链表中倒数第n个元素,我们可以借用两个指向链表节点的指针,并使其间隔距离为n-1,将这两个指针同步向链表尾移动,当前面那个移动到尾部时,那么后面那个就是指向倒数第n个链表节点。代码如下

LINK_NODE* findNthToLast(LINK_NODE *pHead, int n)
{
	if ((NULL == pHead) || (n < 1))
		return NULL;

	LINK_NODE *ptr_front = pHead, *ptr_behind = pHead;
	
	for (int i = 0; i < n; ++i)
	{
		if (NULL == ptr_front)
			return NULL;          //表明链表的长度小于n
		ptr_front = ptr_front->next;
	}

	while (ptr_front != NULL)
	{
		ptr_front = ptr_front->next;
		ptr_behind = ptr_behind->next;
	}
	return ptr_behind;
}
2.3    Implement an algorithm to delete a node in the middle of a single linked list, given only access to that node.

          EXAMPLE    Input: the node ‘c’ from the linked list a->b->c->d->e       Result: nothing is returned, but the new linked list looks likea->b->d->e
译文:实现一个算法来删除单链表的中间结点,只给出指向那个结点的指针

          例子  输入:指向链表 a->b->c->d->e 中结点c的指针       结果:无返回值,得到一个新链表 a->b->d->e  

这里要清楚的是题目只给出一个指向要删除结点的指针,并没有给出整个链表的头结点指针,所以我们不能直接删除这个结点,否则链表就断了。这是单链表,我们也不能定位被删结点的前面那个结点,所以我们只能从该结点后面的结点做文章,我们只能遍历到被删结点之后的结点。对于链表 a->b->c->d->e,我们仅知道指向c结点的指针,要删除c之后的结点很简单,无论是d还是e,比如删除d,只需node *t = c->next; c->next = t->next; delete t;即可,得到的链表就是 a->b->c->e,与我们预期要得到的链表 a->b->d->e,仅相差一个结点,我们平时基本上都是通过修改结点里面的指针成员指向来修改链表,其实修改里面的数据成员也能达到修改链表的效果,结点结构都是一样的,重要是结点里面的数据。这里我们在上面的基础上就通过修改结点c中的数据成员,将d中的数据传给c就可以了。

bool deleteNode(LINK_NODE *c)
{
	if ((NULL == c) || (NULL == c->next))
		return false;     //没有考虑被删结点为尾结点的情况
 
	LINK_NODE *t = c->next;
	c->data = t->data;
	c->next = t->next;
	delete t;
	return true;
}
这是利用被删除结点后面的那个结点来进行处理的,那如果被删除的结点恰是最后一个尾结点呢,由于其后面没有结点,我们就不能采用这种方法来进行处理。要是直接删除尾结点c会怎样勒,我们要知道 delete c 或 free (c) 并不是真正意义的完全删除那个结点(其他也是一样),只是一个”解绑的关系“,就是解除c 这个指针与它指向的内存地址数据之间的关系,一旦解除了,c 对它所指向的内存就没有使用权了,这块内存就可以被别人使用了(不是说别人可以用里面的数据),这就是内存释放,不过这个内存地址里面的数据还是原来c关联的数据,但跟c没有半毛钱关系了,当然其实跟谁也没有关系,因为谁也找不到(没有指针与它相关联,你申明一个变量它也会有地址,世界是物质的),只有重新分配(系统和手动)的时候,用到了这个内存的时候,这个内存就可以重新使用了。delete c之后 c 本身还是原来的数据,即c还是指向原来的内存地址单元,但是不能访问这个地址单元里面的数据了,这个指针没有关联任何数据,这就是”野指针“,所以在删除指针的时候,通常要将其设置为NULL,这样主要是为在校验的时候有作用。上面嘀咕了一大堆,不知道啰嗦清楚了没。

所以上面直接删除c,然后在打印的时候会出错(我调试运行的时候会出错,原因上面说了)。这里再针对上面不能删除尾结点的情况进行修改,先贴代码

bool deleteNode(LINK_NODE **pNode)
{
	
	if ((NULL == *pNode))
		return false;
	if (NULL == (*pNode)->next)
	{
		delete (*pNode);
		*pNode = NULL;
		return true;
	}
	LINK_NODE *t = (*pNode)->next;
	(*pNode)->data = t->data;
	(*pNode)->next = t->next;
	delete t;
	return true;
}
函数形参是对其实参进行了一份拷贝,内容一样,但是地址不一样,所以需要指针来处理,本身是指针,就是指针的指针了。这里根据题目情况适当进行了修改,在主程序中在这样申明调用即可

LINK_NODE **pNode = &c ;
	      //这里c为给定的链表中要被删除的结点
	if (deleteNode(pNode))
	{
		PrintLinkNode(pLinkNode);
	}	
	else
		cout << "failure" << endl;
既然用了双指针,那么也可用指针的引用来实现

bool deleteNode(LINK_NODE *&pNode)
{
	
	if ((NULL == pNode))
		return false;
	if (NULL == pNode->next)
	{
		delete (pNode);
		pNode = NULL;
		return true;
	}
	LINK_NODE *t = pNode->next;
	pNode->data = t->data;
	pNode->next = t->next;
	delete t;
	return true;
}
注意的是,在主程序中调用的时候,直接用给定的c结点作为参数传入,不要通过声明另一个变量传入,不然引用的就不是c了。

版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

Chapter 2 | Linked Lists--实现两个单链表数据的和

2.4     You have two numbers represented by a linked list, where each node contains a single digit. ...

cc150:实现一个算法从一个单链表中返回倒数第n个元素

实现一个算法从一个单链表中返回倒数第n个元素。 解答 这道题的考点在于我们怎么在一个单链表中找到倒数第n个元素? 由于是单链表,所以我们 没办法从最后一个元素数起,然后数n个得到...

编写程序,要求通过一次遍历找到单链表中倒数第 n 个节点

单链表的建立和遍历,比较适合才开始学数据结构的人

LintCode 找到单链表倒数第n个节点

给出链表 3->2->1->5->null和n = 2,返回倒数第二个节点的值1. 设置2个指针,当第一个指针从表头向前走到第n-1个节点时,第二个指针开始从表头出发。当第一个指针走到尾节点时,...

查找单链表中倒数第n个节点

http://www.nowamagic.net/librarys/veda/detail/496 通过一次遍历找到单链表中倒数第n个节点,链表可能相当大,可使用辅助空间,但是辅助空间的数目必须固...

【LeetCode-面试算法经典-Java实现】【019-Remove Nth Node From End of List(移除单链表的倒数第N个节点)】

【019-Remove Nth Node From End of List(移除单链表的倒数第N个节点)】【LeetCode-面试算法经典-Java实现】【所有题目目录索引】原题  Given a l...

CareerCup之2.2 寻找单链表倒数第n个元素

【题目】 原文: 2.2 Implement an algorithm to find the nth to last element of a singly linked list. 译文:...

单链表倒数第节点

  • 2014年10月10日 20:07
  • 2KB
  • 下载

取单链表倒数第k个元素

  • 2011年12月07日 11:57
  • 7KB
  • 下载

单链表查找倒数第N个元素&输出中间元素

1: 单链表中查找倒数第n个元素 通过一次遍历找到单链表中倒数第n个节点,链表可能相当大,可使用辅助空间,但是辅助空间的数目必须固定,不能和n有关。 单向链表的特点是遍历到末尾后不能反向重数N个节...
  • rtyytr
  • rtyytr
  • 2011年09月14日 21:00
  • 2279
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Chapter 2 | Linked Lists--返回单链表倒数第n个元素及删除中间的某个节点
举报原因:
原因补充:

(最多只允许输入30个字)