单链表翻转的多种方法实现(c语言)

来源:互联网 发布:s1728gwr-4p 端口镜像 编辑:程序博客网 时间:2024/06/06 16:53

    写在前面: 

 

单链表

 

  用一组地址任意的存储单元存放线性表中的数据元素。

  以元素(数据元素的映象) + 指针(指示后继元素存储位置)= 结点(表示数据元素 数据元素的映象)

  以结点的序列表示线性表称作线性链表(单链表)

  单链表是一种顺序存取的结构,为找第 i 个数据元素,必须先找到第 i-1 个数据元素。

  因此,查找第 i 个数据元素的基本操作为:移动指针,比较 j i

  单链表

  1、链接存储方法

  链接方式存储的线性表简称为链表(Linked List)。

  链表的具体存储表示为:

   用一组任意的存储单元来存放线性表的结点(这组存储单元既可以是连续的,也可以是不连续的)

   链表中结点的逻辑次序和物理次序不一定相同。为了能正确表示结点间的逻辑关系,在存储每个结点值的同时,还必须存储指示其后继结点的地址(或位置)信息(称为指针(pointer)或链(link)

  注意:

  链式存储是最常用的存储方式之一,它不仅可用来表示线性表,而且可用来表示各种非线性的数据结构。

  2、链表的结点结构

  ┌──┬──┐

  datanext

  └──┴──┘

  data--存放结点值的数据域

  next--存放结点的直接后继的地址(位置)的指针域(链域)

     

      c语言对节点的描述:

      

      /*定义数据元素*/

      typedef char ElemType;

      /*定义节点*/

      typedef struct node_tag

      {

       ElemType data;

       struct node_tag *next;

      }Node,*Link;

 

  注意:

  链表通过每个结点的链域将线性表的n个结点按其逻辑顺序链接在一起的。

  每个结点只有一个链域的链表称为单链表(Single Linked List)。

 

 

问题:

     相信很多学习过数据结构的人都会遇到同样的一个问题:将一个单链表翻转输出(如图1).

   

      reverse

                                     (1)

正文:

 

当然这个算不上是困难的算法.实现起来可以有多种方法.

    1.新建一个数组把原链表的内容copy到数组和新链表中.从而实现了链表的翻转.

    2.新建一个单链表把原链表的内容逐个头插法插入新链表中.从而实现了链表的翻转.

    3.一次遍历单链表.将原链表翻转

 

下面就以上的三种方法分别实现.代码如下:

 

/******link.h*******/

 

#ifndef _LINK_H_

#define _LINK_H_

 

/*定义数据元素*/
typedef int ElemType;
/*
定义节点
*/
typedef struct node_tag Node;
struct node_tag
{
 ElemType data;
 Node* next;
};
typedef Node* Link;

 

//创建链表L,节点数为num

//创建成功返回节点数.否则返回-1;

int create_link(Link *L,unsigned num);

//如果L存在,L的头部插入元素e;

//插入成功返回1,否则返回0;

int insert_front(Link *L,ElemType e);

//如果L存在,遍历L.

void traverse_link(const Link L,void (*visit)(ElemType));

#endif

 

 

/*********link.c***********/

 

#include <stdlib.h>

#include <stdio.h>

#include "link.h"

 

//创建链表L,节点数为num
//
创建成功返回节点数.否则退出程序

int create_link(Link *L,unsigned num)
{
 Link lnk = (Node *)malloc(sizeof(Node));
 if(!lnk)
 {
  fputs("create link",stderr);
  exit(1);
 }
 lnk->data = num;//
链表的头结点存放链表的长度.
 lnk->next = NULL;
 (*L) = lnk;
 
 Link tmp_lnk = lnk;
 unsigned i;//
创建链表

 for(i = 0;i < num;i++)
 {
  Link tmp_lnk1 = (Node*)malloc(sizeof(Node));
  if(!tmp_lnk1)
  {
   fputs("create link",stderr);
   exit(1);
  }
  tmp_lnk1->data = i +1;
  tmp_lnk1->next = NULL;
  //
新节点插入链表的尾部
  tmp_lnk->next = tmp_lnk1;
  tmp_lnk = tmp_lnk->next;
 }
 return num;
}
//
如果L存在,L的头部插入元素e;
//
插入成功返回1,否则返回
0;
int insert_front(Link *L,ElemType e)
{
 if ((*L) == NULL)
 {
  return 0;
 }
 Link lnk = (Node*)malloc(sizeof(Node));
 if (!lnk)
 {
  fputs("create link",stderr);
  exit(1);
 }
 lnk->data = e;
 //
新节点的next指向链表头部的下个节点

 lnk->next = (*L)->next;
 //
新节点插入到链表的头部
 (*L)->next = lnk;
 //
链表的长度自增1;
 (*L)->data ++;
 return 1;
}
//
如果L存在,遍历
L.
void traverse_link(const Link L,void (*visit)(ElemType))
{
 Link p = L->next;
 if(L == NULL)
  return;
 //
从链表头结点的下个节点开始遍历链表

 while(p)
 {
  //
访问节点的 data
  visit(p->data);
  p = p->next;
 }
}

 

/**********test.c***********/

 

#include <stdlib.h>

#include <stdio.h>

#include "link.h"

 

//打印输出元素e

void print_elem(ElemType e);

//创建新链表rst翻转链表L

void reverse1(Link *rst,const Link L);

//创建数组rst,翻转链表L

void reverse2(Node rst[],const Link L);

//不创建新链表翻转链表L

void reverse3(Link L);

const int LINK_LENGTH = 3;

 

int main(int argv,char*argc[])
{
 
 Link lnk_head = NULL;
 Link lnk_rst1 = NULL;
 
 int lnk_len = create_link(&lnk_head,LINK_LENGTH);
 
 Node lnk_rst2[LINK_LENGTH];
 
 printf("
链表
lnk_head:/n");
 traverse_link(lnk_head,print_elem);
 
 reverse1(&lnk_rst1,lnk_head);
 printf("
翻转后
lnk_rst1:/n");
 traverse_link(lnk_rst1,print_elem);
 
 printf("
链表
lnk_head:/n");
 traverse_link(lnk_head,print_elem);
 reverse2(lnk_rst2,lnk_head);
 
 printf("
翻转后
lnk_rst2:/n");
 int i = 0;


 for(i = LINK_LENGTH-1;i >= 0;i--)
 {
  printf("%d/n",lnk_rst2[i]);
 }
 
 
 
 printf("
链表
lnk_head:/n");
 
 traverse_link(lnk_head,print_elem);
 
 
 
 reverse3(lnk_head);
 
 printf("
翻转后
lnk_head:/n");
 
 traverse_link(lnk_rst1,print_elem);
 
 return 0;
 
}


//
打印输出元素e

void print_elem(ElemType e)

{
 
 printf("%d/n",e);
 
}

//创建新链表rst翻转链表L

void reverse1(Link *rst,const Link L)

{
 
 if(*rst == NULL)
    {
  
        (*rst) = (Node *)malloc(sizeof(Node));
  
  if((*rst) == NULL)
   
  {
   
   fputs("Create node%s/n",stderr);
   
   exit(1);
   
  }
  (*rst)->next = NULL;
  
    }
 
    Link tmp = L->next;
 
    while(tmp)
  
 {
  
  insert_front(rst,tmp->data);
  
  tmp = tmp ->next;
  
 }
 
}

//创建数组rst,翻转链表L

void reverse2(Node rst[],const Link L)

{
 
 Link tmp = L->next;

//修改之处

if(L->data == 0)return;
 int i ;

 for(i = 0;tmp;i++,tmp = tmp->next)
  
 {
  
  rst[i].data = tmp->data;
  
  rst[i].next = NULL;
  
 }
 
}

//不创建新链表翻转链表L

void reverse3(Link L)

{
 Link phead,tmp,pt;
 //
指向L的头结点

 Link tail = L;
 //phead
指向L的下个节点
 phead = L->next;
 //pt
指向phead的下个节点.L的下个节点的下个节点.
 //
也是链表L的第3个节点

//修改之前代码,存在错误

//pt = phead->next;

//以下为修改后代码

if(phead == NULL)return;

 pt = phead->next;

if(pt == NULL)return;

 tmp = pt->next;
 //
使L的头结点与第二个节点断开.并作为phead的尾节点

 phead->next = NULL;
 

 while (tmp)
 { 
  //
使pt pt所指的下个节点断开
.
  //
断开后,使pt的下个节点指向
phead;
  pt->next = phead;
  //phead
向后移动

  phead=pt;
  //pt
指针向后移动
  pt = tmp;
  //tmp
指针向后移动
  tmp = tmp->next;
 }
 
 //tmp
移动到末尾,但此时pt指向原链表L倒数第二个节点
 pt->next = phead;
 //phead
再次移动指向原链表L的末尾指针
 phead=pt;
 //pt=tmp=NULL
 pt = tmp;
 //
将原链表的头结点添加到反转后的节点的头部
 tail->next = phead;
 phead = tail;
}

 

以上的代码在vc6.0上编译通过;

 

总结:

 

      以上三种方法虽然都可以实现单链表的翻转.时间复杂度可以说都是O(n),但是各有优缺点.

reverse1 在实现的时候,也只是遍历了一次原链表,但是却要新创建一个链表,来保存原链表的反转内容,相当于原链表的副本.虽然时间复杂度上为O(n),但却浪费了空间,而且还有链表的头插入,带来指针的移动额外的开支.

reverse2reverse1 一样在时间复杂度上为O(n),但是翻转的内容是用Node数组保存,这样失去了链表的实质,再者,链表的长度本应是不固定的.所以要适应不同的链表,数组的长度使不能确定的.因此,要使用此方法需要明确知道单链表的长度.否则,在做反序输出数组时,可能造成数组下标越界.

reverse3与前两者相比,也是遍历一次原链表.但是它在遍历的同时改变了原链表的指针方向,是原链表的节点改变成指向原来相反的地方.这样做就达到了翻转的目的.而且时间复杂度依然是O(n).且不需要另外开辟新的空间来存放新的链表.这样做的好处在不改变原链表节点存放数据内容外,而且没有对数据的操作,只是对指针的操作.避免了改写原链表存放的数据.

 

好了,这是今天对翻转链表操作的一点总结.

 

 //tmp = pt->next;

原创粉丝点击