写在前面:
单链表
用一组地址任意的存储单元存放线性表中的数据元素。
以元素(数据元素的映象) + 指针(指示后继元素存储位置)= 结点(表示数据元素 或 数据元素的映象)
以“结点的序列”表示线性表称作线性链表(单链表)
单链表是一种顺序存取的结构,为找第 i 个数据元素,必须先找到第 i-1 个数据元素。
因此,查找第 i 个数据元素的基本操作为:移动指针,比较 j 和 i
单链表
1、链接存储方法
链接方式存储的线性表简称为链表(Linked List)。
链表的具体存储表示为:
① 用一组任意的存储单元来存放线性表的结点(这组存储单元既可以是连续的,也可以是不连续的)
② 链表中结点的逻辑次序和物理次序不一定相同。为了能正确表示结点间的逻辑关系,在存储每个结点值的同时,还必须存储指示其后继结点的地址(或位置)信息(称为指针(pointer)或链(link))
注意:
链式存储是最常用的存储方式之一,它不仅可用来表示线性表,而且可用来表示各种非线性的数据结构。
2、链表的结点结构
┌──┬──┐
│data│next│
└──┴──┘
data域--存放结点值的数据域
next域--存放结点的直接后继的地址(位置)的指针域(链域)
c语言对节点的描述:
/*定义数据元素*/
typedef char ElemType;
/*定义节点*/
typedef struct node_tag
{
ElemType data;
struct node_tag *next;
}Node,*Link;
注意:
①链表通过每个结点的链域将线性表的n个结点按其逻辑顺序链接在一起的。
②每个结点只有一个链域的链表称为单链表(Single Linked List)。
问题:
相信很多学习过数据结构的人都会遇到同样的一个问题:将一个单链表翻转输出(如图1).
(图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),但却浪费了空间,而且还有链表的头插入,带来指针的移动额外的开支.
reverse2与reverse1 一样在时间复杂度上为O(n),但是翻转的内容是用Node数组保存,这样失去了链表的实质,再者,链表的长度本应是不固定的.所以要适应不同的链表,数组的长度使不能确定的.因此,要使用此方法需要明确知道单链表的长度.否则,在做反序输出数组时,可能造成数组下标越界.
reverse3与前两者相比,也是遍历一次原链表.但是它在遍历的同时改变了原链表的指针方向,是原链表的节点改变成指向原来相反的地方.这样做就达到了翻转的目的.而且时间复杂度依然是O(n).且不需要另外开辟新的空间来存放新的链表.这样做的好处在不改变原链表节点存放数据内容外,而且没有对数据的操作,只是对指针的操作.避免了改写原链表存放的数据.
好了,这是今天对翻转链表操作的一点总结.
//tmp = pt->next;
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/youngway520/archive/2009/10/23/4716365.aspx