线性表的链式表示和实现

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线性表的链式表示和实现相关的知识,希望对你有一定的参考价值。

本节内容讨论的线性表的另一种表示方法----链式存储结构,由于它不要求逻辑上相邻的元素在物理位置上也相邻,因此它没有顺序存储结构所具有弱点,但同时也失去了顺序表可随机存取的优点。

 

1.线性链表

线性表的链式存储结构的特点是用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的)。因此,为了表示每个数据元素ai与其直接后续数据元素ai+1之间的逻辑关系,对数据元素ai来说,除了存储其本身的信息之外,还需存储一个指示直接后继的信息(即直接后继的存储位置)。这两部分信息组成数据元素ai的存储映像,称为结点(node)。它包括两个域:其中存储数据元素的信息域称为数据域;存储直接后继的存储位置的域称为指针域。指针域中存储的信息称为指针或链。n个结点链结成一个链表,即为线性表

(a1,a2,…..,an)

 

的链式存储结构。又由于此链表的每个结点中只包含一个指针域,故又称线性链表或单链表。

用线性链表表示线性表时,数据元素之间的逻辑关系是由结点中的指针指示的。换句话说,指针为数据元素之间的逻辑关系的映像,则逻辑上相邻的两个数据元素其存储的物理位置不要求紧邻,因此,这种存储结构为非顺序映像或链式映像。单链表可由头指针惟一确定,在C语言中可用“结构指针”来描述:

typedef struct LNode{

    ElemType data;

    struct LNode *next;

 

} LNode,*LinkList;

 

2.循环链表

循环链表(circular linked list)是另一种形式的链式存储结构。它的特点是表中最后一个结点的指针域指向头结点,整个链表形成一个环。从表中任一结点出发均可找到表中其他结点。

循环链表的操作和线性链表基本一致,差别仅在于算法中的循环条件不是p或p->next是否为空,而是它们是否等于头指针。但有的时候,若在循环链表中设立尾指针而不设头指针,可使某些操作简化。

例如:将两个线性表合并成一个表时,仅需将一个表的表尾和另一个表的表头相接。

 

3.双向链表

以上讨论的链式存储结构的结点中只有一个指示直接后继的指针域,由此,从某个结点出发只能顺指针往后寻查其他结点。若要寻查结点的直接前趋,则需从表头指针出发。换句话说,在单链表中,NextElem的执行时间为O(1),而PriorElem的执行时间为O(n)。为克服单链表这种单向性的缺点,可利用双向链表(double linked list),即在双向链表的结点中有两个指针域,其一指向直接后继,另一指向直接前趋,在C语言中的可描述如下:

typedef struct DuLNode{

    ElemType data;

    struct DuLNode *prior;

    struct DuLNode *next;

 

} DuLNode,*DuLinkList;

 

 

演示1:

算法:将两个有序链表并为一个有序链表(该程序的代码,可以放在cFree环境中运行) 

  假设头指针为La和Lb是单链表分别线性表La和Lb的存储结构,现要归并La和Lb得到单链表Lc,按照MergeList的思想,需设立3个指针pa,pb和pc,其中pa和pb分别指向La表和Lb表中当前待比较插入的结点,而pc指向Lc表中当前最后一个结点,若pa->data≤pb->data,则将pa所指结点链接到pc所指结点之后,否则将pb所指结点链接到pc所指结点之后。显然,指针的初始状态为:当La和Lb为非空表时,pa和pb分别指向La和Lb表中的第一个结点,否则为空;pc指向空表Lc中的头结点。由于链表的长度为隐含的,则第一个循环执行的条件是pa和pb皆非空,当其中一个为空时,说明有一个表的元素已归并完,则只要将另一个表的剩余段链接在pc所指结点之后即可。

提示:在归并两个链表为一个链表时,不需要另建新表的结点空间,而只需将原来两个链表中结点之间的关系解除,重新按元素值非递减的关系将所有结点链接成一个链表即可。

运行效果如下图所示(如果编译器不能编译成功,可放在CFree环境中编译运行即可):

 技术分享技术分享技术分享

 

//将两个有序链表并为一个有序链表算法,该程序也可以cFree环境运行。
// c1.h (程序名)
#include<string.h>
#include<ctype.h>
#include<malloc.h> // malloc()等
#include<limits.h> // INT_MAX等
#include<stdio.h> // EOF(=^Z或F6),NULL
#include<stdlib.h> // atoi()
//#include<io.h> // eof()
#include<math.h> // floor(),ceil(),abs()
//#include<process.h> // exit()

 

// 函数结果状态代码
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
// #define OVERFLOW -2 因为在math.h中已定义OVERFLOW的值为3,故去掉此行,退出时用,0正常结束,非0非正常终止
typedef int Status; // Status是函数的类型,其值是函数结果状态代码,如OK等
typedef int Boolean; // Boolean是布尔类型,其值是TRUE或FALSE

 

typedef int ElemType;

 


// c2-2.h 线性表的单链表存储结构
typedef struct LNode
{
ElemType data;
struct LNode *next;
}LNode,*LinkList;;
// 另一种定义LinkList的方法

 

 

 


// bo2-2.cpp 单链表线性表(存储结构由c2-2.h定义)的基本操作(12个)
Status InitList(LinkList &L)
{ // 操作结果:构造一个空的线性表L
L=(LinkList)malloc(sizeof(LNode)); // 产生头结点,并使L指向此头结点
if(!L) // 存储分配失败
exit(OVERFLOW);
L->next=NULL; // 指针域为空
return OK;
}

 

Status DestroyList(LinkList &L)
{ // 初始条件:线性表L已存在。操作结果:销毁线性表L
LinkList q;
while(L)
{
q=L->next;
free(L);
L=q;
}
return OK;
}

 

Status ClearList(LinkList L) // 不改变L
{ // 初始条件:线性表L已存在。操作结果:将L重置为空表
LinkList p,q;
p=L->next; // p指向第一个结点
while(p) // 没到表尾
{
q=p->next;
free(p);
p=q;
}
L->next=NULL; // 头结点指针域为空
return OK;
}

 

Status ListEmpty(LinkList L)
{ // 初始条件:线性表L已存在。操作结果:若L为空表,则返回TRUE,否则返回FALSE
if(L->next) // 非空
return FALSE;
else
return TRUE;
}

 

int ListLength(LinkList L)
{ // 初始条件:线性表L已存在。操作结果:返回L中数据元素个数
int i=0;
LinkList p=L->next; // p指向第一个结点
while(p) // 没到表尾
{
i++;
p=p->next;
}
return i;
}

 

Status GetElem(LinkList L,int i,ElemType &e) // 比较j和i并后指移指针p算法

 

{ // L为带头结点的单链表的头指针。当第i个元素存在时,其值赋给e并返回OK,否则返回ERROR
int j=1; // j为计数器
LinkList p=L->next; // p指向第一个结点
while(p&&j<i) // 顺指针向后查找,直到p指向第i个元素或p为空
{
p=p->next;
j++;
}
if(!p||j>i) // 第i个元素不存在
return ERROR;
e=p->data; // 取第i个元素
return OK;
}

 

int LocateElem(LinkList L,ElemType e,Status(*compare)(ElemType,ElemType))
{ // 初始条件: 线性表L已存在,compare()是数据元素判定函数(满足为1,否则为0)
// 操作结果: 返回L中第1个与e满足关系compare()的数据元素的位序。
// 若这样的数据元素不存在,则返回值为0
int i=0;
LinkList p=L->next;
while(p)
{
i++;
if(compare(p->data,e)) // 找到这样的数据元素
return i;
p=p->next;
}
return 0;
}

 

Status PriorElem(LinkList L,ElemType cur_e,ElemType &pre_e)
{ // 初始条件: 线性表L已存在
// 操作结果: 若cur_e是L的数据元素,且不是第一个,则用pre_e返回它的前驱,
// 返回OK;否则操作失败,pre_e无定义,返回INFEASIBLE
LinkList q,p=L->next; // p指向第一个结点
while(p->next) // p所指结点有后继
{
q=p->next; // q为p的后继
if(q->data==cur_e)
{
pre_e=p->data;
return OK;
}
p=q; // p向后移
}
return INFEASIBLE;
}

 

Status NextElem(LinkList L,ElemType cur_e,ElemType &next_e)
{ // 初始条件:线性表L已存在
// 操作结果:若cur_e是L的数据元素,且不是最后一个,则用next_e返回它的后继,
// 返回OK;否则操作失败,next_e无定义,返回INFEASIBLE
LinkList p=L->next; // p指向第一个结点
while(p->next) // p所指结点有后继
{
if(p->data==cur_e)
{
next_e=p->next->data;
return OK;
}
p=p->next;
}
return INFEASIBLE;
}

 

Status ListInsert(LinkList L,int i,ElemType e) // 算法:在带头结点的单链表线性表L中,删除第i个元素,并由e返回其值。不改变L
{ // 在带头结点的单链线性表L中第i个位置之前插入元素e
int j=0;
LinkList p=L,s;
while(p&&j<i-1) // 寻找第i-1个结点
{
p=p->next;
j++;
}
if(!p||j>i-1) // i小于1或者大于表长
return ERROR;
s=(LinkList)malloc(sizeof(LNode)); // 生成新结点
s->data=e; // 插入L中
s->next=p->next;
p->next=s;
return OK;
}

 

Status ListDelete(LinkList L,int i,ElemType &e) // 算法:从空表的初始状态起,依次建立各元素结点,并逐个插入链表,不改变L
{ // 在带头结点的单链线性表L中,删除第i个元素,并由e返回其值
int j=0;
LinkList p=L,q;
while(p->next&&j<i-1) // 寻找第i个结点,并令p指向其前趋
{
p=p->next;
j++;
}
if(!p->next||j>i-1) // 删除位置不合理
return ERROR;
q=p->next; // 删除并释放结点
p->next=q->next;
e=q->data;
free(q);
return OK;
}

 

Status ListTraverse(LinkList L,void(*vi)(ElemType))
// vi的形参类型为ElemType,与bo2-1.cpp中相应函数的形参类型ElemType&不同
{ // 初始条件:线性表L已存在
// 操作结果:依次对L的每个数据元素调用函数vi()。一旦vi()失败,则操作失败
LinkList p=L->next;
while(p)
{
vi(p->data);
p=p->next;
}
printf("\n");
return OK;
}

 

void CreateList(LinkList &L,int n) // 算法:从表尾到表头逆向建立单链表的算法,其时间复杂度为O(n)
{ // 逆位序(插在表头)输入n个元素的值,建立带表头结构的单链线性表L
int i;
LinkList p;
L=(LinkList)malloc(sizeof(LNode));
L->next=NULL; // 先建立一个带头结点的单链表
printf("请输入%d个数据\n",n);
for(i=n;i>0;--i)
{
p=(LinkList)malloc(sizeof(LNode)); // 生成新结点
scanf("%d",&p->data); // 输入元素值
p->next=L->next; // 插入到表头
L->next=p;
}
}

 

void CreateList2(LinkList &L,int n)
{ // 正位序(插在表尾)输入n个元素的值,建立带表头结构的单链线性表
int i;
LinkList p,q;
L=(LinkList)malloc(sizeof(LNode)); // 生成头结点
L->next=NULL;
q=L;
printf("请输入%d个数据\n",n);

for(i=1;i<=n;i++)
{
p=(LinkList)malloc(sizeof(LNode));
scanf("%d",&p->data);
q->next=p;
q=q->next;
}
p->next=NULL;
}

 

void MergeList(LinkList La,LinkList &Lb,LinkList &Lc)// 算法
{ // 已知单链线性表La和Lb的元素按值非递减排列。
// 归并La和Lb得到新的单链线性表Lc,Lc的元素也按值非递减排列
LinkList pa=La->next,pb=Lb->next,pc;
Lc=pc=La; // 用La的头结点作为Lc的头结点
while(pa&&pb)
if(pa->data<=pb->data)
{
pc->next=pa;
pc=pa;
pa=pa->next;
}
else
{
pc->next=pb;
pc=pb;
pb=pb->next;
}
pc->next=pa?pa:pb; // 插入剩余段
free(Lb); // 释放Lb的头结点
Lb=NULL;
}

 

void visit(ElemType c) // ListTraverse()调用的函数(类型要一致)
{
printf("%d ",c);

 

}

 

int main()
{
setbuf(stdout,NULL);
int n=5;
LinkList La,Lb,Lc;
printf("按非递减顺序, ");
CreateList2(La,n); // 正位序输入n个元素的值
printf("La="); // 输出链表La的内容
ListTraverse(La,visit);
printf("按非递增顺序, ");
CreateList(Lb,n); // 逆位序输入n个元素的值
printf("Lb="); // 输出链表Lb的内容
ListTraverse(Lb,visit);
MergeList(La,Lb,Lc); // 按非递减顺序归并La和Lb,得到新表Lc
printf("Lc="); // 输出链表Lc的内容
ListTraverse(Lc,visit);
}

 

以上是关于线性表的链式表示和实现的主要内容,如果未能解决你的问题,请参考以下文章

线性表的链式表示和实现

线性表的链式存储

数据结构与算法线性表的链式表示和实现,超详细C语言版

数据结构2:链表

用C语言编写链式存储结构下实现线性表的创建,插入,删除,按值查找

第二章:3.线性表---静态链表的表示和实现