继顺序表之后,我们需要讨论一下数据结构的链式表。顺序表的特点是逻辑上相邻的元素在物理位置上也相邻,通常我们用数组来表示这种存储结构,这种存储结构简单,易于理解。但是同时必须看到它的缺点,那就是在作
插入和删除操作时,需要移动大量的数据
,这必然造成效率低下。因此,产生了链式表,链式表不需要逻辑上相邻的元素在物理位置上也相邻,因此效率大大提高。另一方面,链表不需要像顺序表那样对空间进行预先的分配规划,而可以由程序员根据需求即时生成并且适时释放,因而这种方式实际上可以更为高效的利用紧缺的存储空间,这也是它被广泛使用的原因。也正是如此,链表比顺序表理解起来要更加复杂。
链式表主要分为线性链表,静态链表,循环链表和双向链表。一开始一定要搞清楚每一种链表的数据结构定义。
链式表的特点是用一组任意的存储单元存储线性表的数据元素,因为这些存储单元在物理上不一定是连续的,所以每一个数据元素除了包含数据信息外,还需要包含至少其它元素的位置信息。我们称这样的存储单元为
结点,节点由数据域和指针域组成。
线性链表的节点中只包含一个指针域,因此又成为单链表。单链表最后一个结点的指针域为NULL.这个和循环链表是不同的。
单链表的数据结构可以定义为
typedef struct LNode{
ElemType data;
struct LNode *next;
}LNode,*LinkList;
一般来说,单链表都包含一个头节点,头节点可以不存储任何信息,也可以存储诸如线性表的长度等附加信息。
静态链表是用数组来进行描述的链表这种方法便于在不设“指针”类型的高级程序设计语言中来使用。
静态链表的数据结构定义如下:
#define MAXSIZE 1000
typedef struct{
ElemType data;
int cur;
}component,SLinkList[MAXSIZE];
这种方式仍然需要事先分配一个较大的空间,但是仍然具有线性链表的在插入删除等操作方面的优点。不过个人感觉没有指针那么灵活,用的不是很多,不作为优先选择。
循环链表的特点是表中最后一个结点的指针域指向头节点,整个链表形成一个环。因此,从表中的任意一个结点出发均可以找到表中的其他节点。
循环链表的数据结构和双向链表是相同的。
由于以上的链表结构只有一个指示直接后继的指针域,因此从某个结点出发只能往后查找其它结点,如果查找该之前的节点,则只能从表头查起,效率显然很低。为了克服这种缺点,出现了双向链表。
双向链表具有连个指针域,一个指向直接后继,一个指向直接前驱。
双向链表的数据结构定义如下:
typedef struct DulNode{
ElemType data;
struct DulNode *prior;
struct DulNode *next;
}DulNode, *DuLinkList;
注意,双向链表也有循环表,并且有两个环。
由于链表在空间上的合理利用和插入、删除操作上的有点,在很多场合都作为首先的存储结构。但是由于实际应用的不同,我们必须要
合理的定义和使用链表的存储结构,做到灵活处理,而不能生搬硬套。
以下是根据实际应用定义的线性链表的类型和抽象数据类型的实现,可以作为参考。
线性链表的数据结构类型定义:
typedef struct LNode
{
ElemType data;
struct LNode *next;
}LNode,*Link, *Position;
typedef struct
{
Link head,tail;
int len;
}LinkList;
/*
Description: allocate a node which is pointed by p and the value of the node is e.
*/
Status MakeNode(Link &p, ElemType e)
{
p = (Link)malloc(sizeof(LNode));
if(p == NULL)
{
return ERROR;
}
p->data = e;
p->next = NULL;
return OK;
}
/*
Description: construct a vacant linear list which only has a head node.
*/
Status InitList(LinkList &L)
{
int e = 0;
MakeNode(L.head,e);
L.len = 0; // the length of a Linear List is zero.
L.tail = L.head;
return OK;
}
/*
Description: release the space pointed by p.
*/
Status FreeNode(Link &p)
{
free(p);
return OK;
}
/*
Description: destroy Linear List L and it doesn't exist anymore.
*/
Status DestroyList(LinkList &L)
{
int i;
Link p;
Link s;
for(i=0;i<L.len;i++)
{
p = L.head->next;
s = p->next;
FreeNode(p);
L.head->next = s;
}
FreeNode(L.head);
return OK;
}
/*
Description: Make the List become a vacant list and release the space.
*/
Status ClearList(LinkList &L)
{
DestroyList(L);
InitList(L);
return OK;
}
/*
Description: if h pointed to the head node, insert node s before the first node.
*/
Status InsFirst(Link h, Link s)
{
s->next = h->next;
h->next = s;
return OK;
}
/*
Description: delete the first node in the Linear List and return the address by p.
*/
Status DelFirst(Link h, Link &p)
{
Link s;
s = h->next;
h->next = s->next;
s->next = NULL;//remember to modify point s->next, as s has already lose relation with the primary list.
p = s;
return OK;
}
/*
Description: Link the nodes beginning with s to the end of L and modify the tail of L.
use point p to trace s.
*/
Status Append(LinkList &L, Link s)
{
Link p = NULL;
L.tail->next = s;// this is very important, we must link s to L.
p = s;
s = s->next;
while(s)
{
p = s;
s = s->next;
}
L.tail = p;
return OK;
}
/*
Description: remove the tail of Linear List L and return the tail address by q, modify the tail of Linear L.
*/
Status Remove(LinkList &L, Link &q)
{
Link s;
q = L.tail;
s = L.head->next;
while(s->next != L.tail)
{
s = s->next;
}
FreeNode(L.tail);
s->next = NULL;
L.tail = s;
return OK;
}
/*
Description: add the node pointed by s to the position that is previous to the node pointed by p in the Linear List L.
modify the point p and make it pointed to the new inserted node.
*/
Status InsBefore(LinkList &L, Link &p, Link s)
{
Link k;
k = L.head->next;
while(k->next != p)
{
k = k->next;
}
k->next = s;
s->next = p;
p = s;
return OK;
}
/*
Description: Insert the node into the position after the node pointed by p. Modify the point p.
*/
Status InsAfter(LinkList &L, Link &p, Link s)
{
s->next = p->next;
p->next = s;
p = s;
return OK;
}
/*
Description: renew the element pointed by p with e.
*/
Status SetCurElem(Link &p, ElemType e)
{
p->data = e;
return OK;
}
/*
Description: return the element pointed by p.
*/
ElemType GetCurElem(Link p)
{
return p->data;
}
/*
Description: if List L is a vacant list, return true, or return false.
*/
Status ListEmpty(LinkList L)
{
if(L.len == 0)
{
return OK;
}
else
{
return ERROR;
}
}
/*
Description: return the number of the element in the Linear List.
*/
int GetListLength(LinkList L)
{
return L.len;
}
/*
Description: Get the position of the head node in the Linear List.
*/
Position GetHead(LinkList L)
{
return L.head;
}
/*
Description: Get the position of the last node in the Linear List.
*/
Position GetLast(LinkList L)
{
return L.tail;
}
/*
Description: Get the position which is previous to the node pointed by p.
*/
Position PriorPos(LinkList L, Link p)
{
Link k;
k = L.head;
while(k->next != p && k->next != NULL)
{
k = k->next;
}
if(k->next == p)
{
return k;
}
else
{
return NULL;
}
}
/*
Description: Get the position next to the node pointed by p.
*/
Position NextPos(LinkList L, Link p)
{
if(p->next != NULL)
{
return p->next;
}
else
{
return NULL;
}
}
/*
Description: return the position of the node i in the Linear List using p and return OK. if i is invalid, return ERROR.
*/
Status LocatePos(LinkList L, int i, Link &p)
{
int j = 1;
Link k = L.head->next;
if(i<1 || i>L.len)
{
return ERROR;
}
while(k!= NULL && j != i)
{
j++;
k = k->next;
}
p = k;
return OK;
}
/*
Description: return the position of the node whose element fits the compare relationship with element e in the list.
*/
Position LocateElem(LinkList L, ElemType e, Status(*compare)(ElemType, ElemType))
{
Link k;
k = L.head->next;
while(!compare(k->data,e) && k!= NULL)
{
k = k->next;
}
if(k == NULL)
{
return NULL;
}
return k;
}
/*
Description: use the function visit() to process every element in the List. If visit() returns ERROR, then the function return
ERROR.
*/
Status ListTraverse(LinkList L, Status(*visit)(ElemType))
{
Link s;
s = L.head->next;
while((visit(s->data)) && (s!=NULL))
{
s = s->next;
}
if(s == NULL)
{
return OK;
}
else
{
return ERROR;
}
}
以下是一个测试程序:
已知链表La,Lb中的元素按照非递减的顺序排列,归并La,Lb到新的链表Lc,使Lc中的元素也按照非递减的顺序排列。
int compare(ElemType a, ElemType b)
{
if(a <= b)
{
return ERROR;
}
else
{
return OK;
}
}
/*
Description: insert the elment e into the position i in the List
*/
Status ListInsert_L(LinkList &L, int i, ElemType e)
{
Link h,s;
if(!LocatePos(L,i-1,h))
{
return ERROR;
}
if(!MakeNode(s,e))
{
return ERROR;
}
InsFirst(h,s);
return OK;
}
/*
Description: Merge La and Lb into List Lc. The elements in Lc grow from small to big.
*/
Status MergeList_L(LinkList &La, LinkList &Lb, LinkList &Lc, int(*compare)(ElemType, ElemType))
{
Link ha = NULL;
Link hb = NULL;
Link pa = NULL;
Link pb = NULL;
Link q = NULL;
int a,b;
if(!InitList(Lc))
{
return ERROR;
}
ha = GetHead(La);
hb = GetHead(Lb);
pa = NextPos(La,ha);
pb = NextPos(Lb,hb);
while(pa && pb)
{
a = GetCurElem(pa);
b = GetCurElem(pb);
if((*compare)(a,b) <= 0)
{
DelFirst(ha,q);
Append(Lc,q);
pa = NextPos(La,ha);
}
else
{
DelFirst(hb,q);
Append(Lc,q);
pb = NextPos(Lb,hb);
}
}
if(pa)
{
Append(Lc,pa);
}
else
{
Append(Lc,pb);
}
FreeNode(ha);
FreeNode(hb);
return OK;
}
void main(void)
{
int i = 0;
int ea = 6;
int eb = 4;
Link p = NULL;
Link s = NULL;
LinkList La,Lb,Lc;
La.head = NULL;
La.tail = NULL;
Lb.head = NULL;
Lb.tail = NULL;
//Creat the list La,Lb.
InitList(La);
InitList(Lb);
for(i=0;i<ListLength;i++)
{
MakeNode(p,ea++);
Append(La,p);
La.len++;
}
for(i=0;i<ListLength;i++)
{
MakeNode(p,eb++);
Append(Lb,p);
Lb.len++;
}
p = La.head->next;
while(p!=NULL)
{
cout << p->data;
p = p->next;
}
cout<< endl;
p = Lb.head->next;
while(p!=NULL)
{
cout << p->data;
p = p->next;
}
cout<< endl;
MergeList_L(La,Lb,Lc,compare);//the function name can be used as the parameter to transfer.
s = Lc.head->next;
while(s != NULL)
{
printf("%d",s->data);
s = s->next;
}
}
在过程中碰到的问题都是关于地址的,链表中最重要的也是地址问题。非常重要的一点是,因为内存是由我们自己分配和管理的,如果我们不进行释放,那么内存中的数据是不会改变的。我们是按照
地址来操作该地址所对应的内存空间中的内容。