单向链表反转的几种操作方法

单向链表反转的几种操作方法


今天看了关于链表的几个操作,其中算法导论中10.2-7中要求使用非递归的方法进行链表的反转操作,于是就尝试着写,研究了一下,除了非递归的方法外,递归当然也可以解决,并且可以分为单参数与双参数,在非递归算法中,可以使用两个指针变完成反转操作(头插法),下面贴出代码: 首先定义Node结构:

1 struct Node {
2   Node *next;
3   int x;
4 };

非递归的反转方法:

 1 Node* RevereseLa(Node * head) //非递归
 2 {
 3  if(head==NULL||head->next==NULL)
 4  {
 5      return head;
 6  }
 7  Node *prv=NULL;
 8  Node *cur=head;
 9  Node *tmp;
10  while(cur->next)
11  {
12      tmp=prv;
13      prv=cur;
14      cur=cur->next;
15      prv->next=tmp;
16  }
17  cur->next=prv;
18  head->next=cur;
19  return head;
20 }

从非递归方法可以看出其中采用了三个指针,一个保存cur当前指针,prv保存,tmp作为临时指针存储,其过程利用画图比较容易理解。每次反转cur与prv之间的指针即可。注意此处使用了三个指针,下面利用头插法,即可实现两个指针反转:

 1 Node* Revereseh(Node *head) //头插法  仅利用两个指针
 2 {
 3  Node *nnode= new Node();
 4  nnode->next=NULL;
 5  Node *p = head->next;
 6  //Node *tmp;
 7  while(p!=NULL)
 8  {
 9      head->next=p->next;
10      p->next=nnode->next;
11      nnode->next=p;
12      p=head->next;
13  }
14  head->next=nnode->next;
15  delete nnode;
16  return head;
17 }

这个方法是在待字闺中群中一个同学说明的,之前实现时仍然使用了一个临时指针,但是可以看到在新建了node后,head->next始终没有利用到,因此可以利用head->next当作临时存储,然后利用指针p遍历链表,最终将head指向正确的位置即可。

此处的理解可以看作是重新建立一个新的链表,利用nnode存储上一个节点的指针,然后遍历链表,将当前遍历的节点的下一个节点存到临时区域,然后将当前节点指向上一个节点(nnode)然后更新nnode,与当前节点,变进行下一次循环。


从以上的两个设计中可以看出,这种循环的设计应当注意两点,一点是初始条件的设置,在算法一中其初始条件为prv为空,cur为头节点;算法二中初始条件为nnode为空,p为链表的第一个节点

二是寻找循环不变式,即固定的规律。

一下两种分别为单参数与双参数的递归法

 1 Node *RevereseLr1(Node *node) //单参数递归
 2 {
 3  if(node==NULL||node->next==NULL)
 4      return node;
 5  Node *nextnode=node->next;
 6  node->next=NULL;
 7  Node *reversenode = RevereseLr1(nextnode);
 8  nextnode->next=node;
 9  return reversenode;
10 }

递归的调用比较巧妙,首先先走到链表的末端,然后不断的返回修改next的指向,我的理解是,这个递归相当于一个栈的作用,每次都保留当前的结点状态,当递归结束后,在不断的从末端出栈,因而保证了反向的顺序。因此根据栈的理解,没遍历到一个节点,便将这个节点的next节点压栈(及递归)然后将其next指向NULL,直到遍历到末端时,开始出栈,便将上一个(末端的为head)指向当前出栈的节点,知道最后一个出完。 值得注意的是,此处返回的不是head指针,而是第一个节点。

 1 void RevereseLr(Node *p,Node *head) //双参数递归
 2 {
 3  if(p->next==NULL)
 4  {
 5      head->next=p;
 6      return;
 7  }
 8  RevereseLr(p->next,head);
 9  p->next->next=p;
10  p->next=NULL;
11 }

此处为双参数的方法,即将head传入,到了末端时,将head指对。这个程序比较简化,我是参考一片博客上写的,觉得很好! 以下是测试代码:

 1 int main()
 2 {
 3  Node* head = new Node();
 4  Node* tmp = head;
 5  for (int i = 0; i < 10; i++)
 6  {
 7      tmp->next=new Node();
 8      tmp->next->x=i;
 9      tmp->next->next=NULL;
10      tmp=tmp->next;
11  }
12  //head=RevereseLa(head);// a非递归算法返回的为head->next
13  //tmp=head->next;
14  //////////////////////
15  //RevereseLr(head->next,head);// 双参数的递归算法
16  //tmp=head->next;
17  ////////////////
18  //tmp=RevereseLr1(head); //单参数的递归算法
19  //////////////
20  tmp=Revereseh(head);  //头插法反转 利用两个指针
21  tmp=tmp->next;
22  while(1)
23  {
24      
25      if(tmp->next==NULL)
26          break;
27      std::cout<<tmp->x<<std::endl;
28      tmp=tmp->next;
29  }
30 }

可以看到,使用非递归的方式解决反转方式的空间复杂度更低,而递归的用到了栈,因此比较消耗内存,其复杂度均为O(n)递归解决一些抽象问题时代码简洁优美,但不见得性能上很好。



Previous     Next
SureD /
Published under (CC) BY-NC-SA in categories 算法  tagged with 算法 
分享到: 更多
>