数据结构69:链表逆置,链表反转,链表翻转

链表翻转,简单地理解,就是将链表的头部结点变为链表的尾部结点,与此同时将原链表的尾部结点变成头部结点。如下图所示:

图 1 链表翻转示意图

提示:H 为头指针,图示中的链表无头结点,头指针直接指向首元结点。

将链表进行翻转的方法有多种,本节给大家介绍两种实现方法。

方法一

实现链表翻转最直接的方法就是:从链表的头部开始遍历每个结点,改变每个结点的指向,即将原本指向下一个结点的指针改为指向上一个结点。

唯一比较特殊的是,链表中的首元结点(第一个结点)前面没有结点,所以在改变其指针指向的时候,要将其指针指向 NULL。

提示:原链表中的首元结点经过翻转后,会变为新链表的最后一个结点,所以其指针自然要指向 NULL。

具体实现过程为:

首先设置一个新的链表,用于接收旧链表上的结点,该新链表初始状态为一个空链表,如下图所示:

图 2 链表翻转过程一

遍历原链表,将结点依次插入到新链表的头部。要完成这一步操作,我们需要新添加两个指针(分别命名为 P 和 temp):

  • P 指针用于遍历链表,并将遍历到的结点插入到新链表中;
  • temp 指针永远指向指针 P 所在结点的下一个结点,充当原链表在每次移除头部结点后的新头指针;

注意:使用头指针 H 遍历也可行(因为翻转完成后,旧链表旧不存在了),但是不建议大家这么做,对头指针的使用,大家要养成良好的习惯,避免头指针的滥用。

新添加指针 P 和 temp 后的链表,如下图所示:

图 3 链表翻转过程二

以上的准备工作完成,链表的翻转就可以开始了。在遍历原链表的过程中,对每个结点都做如下操作:

  • 将temp 移动至指针 p 所指结点的下一个结点,即 temp = P->next;
  • 将该结点插入到新链表的头部,即 p->next=NEWH;
  • 令指针 NEWH 指向该结点(即指针 p 指向的结点),即令 NEWH = p;
  • 令指针 P 指向 temp 指针指向的位置,即 P= temp;

注意:第一步必须要在第四步的前面,否则会导致最终temp指向 NULL 的next,发生错误。

遍历每个结点的过程示意图如下:

翻转结点 1 ,则图 3 经过以上 4 步调整后,变为:

图 4 翻转结点 1

翻转结点 2,则上图经过 4 步调整后,变为下图:

图 5 翻转结点 2

翻转结点 3 时,则上图经过 4 步调整后,变为下图:

图 6 翻转结点 3

当翻转完最后一个结点 3 之后,由于指针 P 指向的为 NULL ,说明没有结点可以翻转,即遍历结束。(指针 P 为NULL ,为遍历结束的判断条件)。

通过对各个结点做如上的操作,当遍历结束,以 NEWH 指针作为头指针的链接即为翻转后的链表。

方法一的实现函数代码为:

link *reverselink(link *H){
  // 判断链表 H 是否存在
  if(H == NULL || H->next == NULL)  {
    return H;
  }
  // 创建指针 P 作为遍历链表的指针
  link *p = H;
  // 创建一个新的链表,用于存储翻转链表,只不过该链表初始状态为NULL
  link *newH = NULL;
  // 遍历指针 P 为 NULL 作为遍历结束的标志
  while(p != NULL)  {
    // 第 1 步实现的代码
    link *temp = p->next;
    // 第 2 步
    p->next = newH;
    // 第 3 步
    newH = p;
    // 第 4 步
    p = temp;
  }
  return newH;
}

方法二

另一种方法较前一种比较复杂,需要使用到递归的思想。

方法一是依次遍历链表,更改每个结点的指向,最后一个结点为翻转链表的头部结点。而方法二则完全倒过来,其实现过程为:先通过递归的思维找到链表的头部,然后再改变每个结点的指向,最终到达链表翻转的目的。

方法二的代码实现函数为:

// 链表翻转的函数
link *reverseList(link * H){
  // 如果指针 H 是否存在
  if(H == NULL || H->next == NULL)  {
    return H;
  }
  // 递归查找新链表的头,找到用赋值给 newH
  link *newH = reverseList(H->next);
  // 递归完成后,H 初始状态为 NewH 的上一个结点。
  // 在一步步弹栈的过程中,始终另 H 指向的结点作为新链表的最后一个结点
  H->next->next = H;
  // 在链接到新链表之后,要割去 H 所指结点与下一结点的联系,否则会使新链表产生环
  H->next = NULL;
  // 返回新链表所指头部的指针。
  return newH;
}

代码运行过程分析:

4-6行:对于给定的指针 H ,首先判断指针 H 是否存在,如果指针 H 不存在,或者指针 H 只含有一个结点,则直接返回,即指针 H 不需要翻转;

第 8 行:函数体内调用自身,是典型的递归。通过不断查找指针 H 所指结点的下一个结点,最终会找到链表的最后一个结点作为函数的返回值,而此结点恰恰就是翻转后链表的首元结点(第一个结点),所以我们用一个新指针 newH 来充当新链表的头指针。

当通过递归找到对应的 newH 结点时,相应地,参数指针 H 也被递归至 newH 所指结点的上一结点处,如下图所示:


图 7 递归结束时的示意图

注意:头指针 H 和参数指针 H 完全不是一码事,作用域不同:参数 H 只在函数体内有效,而头指针 H 为主函数中声明。

第 11行到最后:此部分代码为指针 H 逐层返回时才会运行的代码,在逐层返回的过程中,通过不断地将H所指结点赋值给 H->next->next,并且每次赋值结束后,都取消 H 所指结点的指向,最终达到翻转链表的目的。

自图 7 开始,在指针 H 第一次返回时,此部分的代码运行示意图为:

指针 H 再次返回时,此部分的代码运行示意图为:

最终完成了链表的翻转,函数返回新链表的头指针 newH 。

本节完整代码

#include <stdio.h>
#include <stdlib.h>

typedef struct Link{
  // 数据域
  int count;
  // 指针域
  struct Link *next;
}link;
link *initLink(){
  // 存储的数据,根据数据的特点,创建适当的数组
  int element[3] = {1, 2, 3};
  // 创建首元结点
  link *head=(link*)malloc(sizeof(link));
  head->count = element[0];
  // for循环中需要一个指针temp,从头结点开始依次链接新创建的结点
  link *temp = head;
  // for循环逐个创建链表,并用temp两两链接
  for (int i=1; i<3; i++)   {
    // 循环创建结点
    link *a = (link*)malloc(sizeof(link));
    // 初始化结点
    a->count = element[i];
    a->next = NULL;
    // 另指针 temp 每次循环都链接新的结点,并随后指向该结点
    temp->next = a;
    temp = temp->next;
  }
  return head;
}
// 方法一实现链表翻转
link *reverselink(link *H){
  // 判断链表 H 是否存在
  if(H == NULL || H->next == NULL)  {
    return H;
  }
  // 创建指针 P 作为遍历链表的指针
  link *p = H;
  // 创建一个新的链表,用于存储翻转链表,只不过该链表初始状态为NULL
  link *newH = NULL;
  // 遍历指针 P 为 NULL 作为遍历结束的标志
  while(p != NULL)  {
    // 第 1 步实现的代码
    link *temp = p->next;
    // 第 2 步
    p->next = newH;
    // 第 3 步
    newH = p;
    // 第 4 步
    p = temp;
  }
  return newH;
}
// 方法二实现链表翻转
link *reverseList(link *H){
  // 如果指针 H 是否存在
  if(H == NULL || H->next == NULL)  {
    return H;
  }
  // 递归查找新链表的头,找到用赋值给 newH
  link *newH = reverseList(H->next);
  // 递归完成后,H 初始状态为 NewH 的上一个结点。
  // 在一步步弹栈的过程中,始终另 H 指向的结点作为新链表的最后一个结点
  H->next->next = H;
  // 在链接到新链表之后,要割去 H 所指结点与下一结点的联系,否则会使新链表产生环
  H->next = NULL;
  // 返回新链表所指头部的指针。
  return newH;
}
//遍历链表的输出函数
void display(link *H){
  while(H)  {
    printf("%d", H->count);
    H = H->next;
  }
  printf("\n");
}
int main ()
{
  // 初始化链表
  link *H = initLink();
  display(H);
  // 使用方法一对链表 H 进行翻转
  link *dnewH = reverselink(H);
  display(dnewH);
  // 使用方法二对链表 dnewH 进行再次翻转
  link *newH = reverseList(dnewH);
  display(newH);
  return 0;
}
运行结果:

123
321
123

原文地址:https://www.cnblogs.com/ciyeer/p/9075873.html

时间: 2024-11-07 17:21:43

数据结构69:链表逆置,链表反转,链表翻转的相关文章

单链表逆置Java

package com.kpp; /** * 单链表逆置 * 将单链表从第一个结点和第二个节点之间断开,然后将第2,3...个结点依次插入第一个结点之前 * @author kpp * */ class LNode{ private String data; private LNode next; } public class LinkedListReverse { private static void reverse(LNode head){ if(head == null||head.ne

数据结构-链表逆置(c++模板类实现)

链表结点类模板定义: 1 template <class T> 2 class SingleList; 3 template <class T> 4 class Node 5 { 6 private: 7 T element; 8 Node<T> *link; 9 friend class SingleList<T>; 10 }; 链表类末班定义: 1 template <class T> 2 class SingleList 3 { 4 pub

03 单链表逆置

单链表逆置 方法: <1>断开第一个数据节点和第二个数据节点的链接 <2>将后面的节点通过头插法的思想插在头节点后面 1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 5 // 数据结构 6 typedef struct node 7 { 8 int data; 9 struct node *next; 10 }linkList; 11 12 // 创建单链表,并

链表——逆置

给定程序中,函数fun的功能是将带头结点的单向链表逆置,即若原链表中从头至尾结点数据与依次为2.4.6.8.10,逆置后,从头至尾结点数据依次为10.8.6.4.2. 请在程序的下画线处填入正确的内容并将下画线删除,使程序得出正确的结果. 试题程序. 1 #define N 5 2 typedef struct node 3 { 4 int data; 5 struct node *next; 6 } NODE; 7 void fun(NODE *h) 8 { 9 NODE *p, *q, *r

链表逆置For Java

Java版链表逆置 定义数据结构: /** * 链表的数据结构 */ class LinkedListArray { /** * value */ Object value; /** * 下个节点 */ LinkedListArray next = null; public void setValue(Object value) { this.value = value; } public void setNext(LinkedListArray next) { this.next = next

C语言单链表逆置的代码实现 (简单易懂版)

嗯,,这是自己写的第一篇博客哈,写的不好大家不要见怪,主要是想把自己的一些思想分享给大家.也欢迎大家指出错误,一同进步. 话不多说,直接先说想法.要把一个单链表逆置,可以大致分为下列几步.先创建一个链表.然后要考虑到链表的逆置实现.最后是链表的输出.有了这样过几步大概的想法之后,我们便要来一步步的实现啦.嗯,,创建链表就不说了,大家都会.  然后呢就是链表的逆置,这里我是采用的就地逆置法,,嗯,反正我是这么叫的,大家可以参考一下.当然啦,你得考虑到函数的形参和返回值以及指针的交接,这里如果出了问

单链表逆置

重写单链表逆置,熟能生巧- #include <iostream> #include <cstdlib> using namespace std; typedef struct List{ int num; struct List *next; }ListNode,*pListNode; void display(ListNode *pHead) { while(pHead) { cout<<pHead->num<<"--"; pH

java实现单链表逆置

1 class LNode 2 { 3 public LNode next; 4 public int data; 5 } 6 /*逆置链表*/ 7 class Nizhi 8 { 9 private static LNode head = new LNode();; 10 private static LNode node; 11 private static LNode tail; 12 private static int index; 13 private static LNode ne

c++中的双向链表写法,主要实现(增删查改,链表逆置,构造函数,运算符重载,等)

本文主要内容 1)介绍c++中双向链表的写法. 2)实现双向链表中数据的增.删.查.改.链表逆置.链表输出 3)介绍类中构造函数.析构函数.运算符重载函数的写法 接下来介绍双向链表的成员函数:这写函数放在头文件中,方便编写 #pragma once #include<iostream> using namespace std; #include<assert.h> typedef int DataType; class ListNode  //节点 { public: ListNo

PTA 链表逆置

6-3 链表逆置 (20 分) 本题要求实现一个函数,将给定单向链表逆置,即表头置为表尾,表尾置为表头.链表结点定义如下: struct ListNode { int data; struct ListNode *next; }; 函数接口定义: struct ListNode *reverse( struct ListNode *head ); 其中head是用户传入的链表的头指针:函数reverse将链表head逆置,并返回结果链表的头指针. 裁判测试程序样例: #include <stdi