3.线性表的链式表示和实现
线性表的链式存储结构:
线性表中逻辑上相邻的数据元素可以存储在物理上不相邻的存储单元中,为了表示数据元素的逻辑关系,每个数据元素除了存储自身信息之外,还需存储相邻元素的地址信息
线性链表(单链表):
我们用一组(地址可不连续的)存储单元(结点)存储线性表的数据元素(数据域)和直接后继地址(指针域)。
静态链表:
我们预先声明一个数组作为存储空间,以游标指示相对位置,插入和删除操作不需移动元素,仅需修改游标。
循环链表:
单链表最后一个结点的指针域指向头结点,整个链表形成一个环。
设La指向头结点,则遍历结束条件:p==La或p->next=La
有时设立尾指针而不是头指针以简化某些操作,比如说:两个循环链表的合并。双向链表:
每个结点都有两个指针域,一个指向直接后继,一个指向直接前驱,即存在:d->next->prior==d->prior->next==d。
注:线性表的链式存储结构是一种顺序存储的存储结构。
线性链表的定义及其基本操作
线性链表的定义
typedef struct LNode{ ElemType data; //数据域 struct LNode *next; //指针域 }LNode, *LinkList;
线性链表的初始化
void InitList(LNode *L)//构造一个空的线性表L { L = (LNode *)malloc(sizeof(LNode)); if(!L) exit(-1); //存储空间失败 L->next = NULL; //尾结点为空 }
线性链表的插入
void InsertList(LNode *L, int data, int i)//在带头结点的单链线性表L中第i个位置之前插入元素data { LNode *p = L; int j = 0; while(p && j<i-1){ //寻找第i-1个结点 p = p->next; ++j; } if(!p||j>i-1){ //i值不合法 printf("插入的位置不合法"); } LNode *s = (LNode *)malloc(sizeof(LNode)); //生成新结点 s->data = data; s->next = p->next; //插入L中 p->next = s; } void InsetList(LNode *L,int data, int i)//不带头结点 { LNode *p = L; int j = 1; while(p && j<i-1){ //寻找第i-1个结点 p = p->next; ++j; } if(!p||j>i){ //i值不合法 printf("插入的位置不合法"); } LNode *s = (LNode *)malloc(sizeof(LNode)); //生成新结点 s->data = data; if(i==1){ //对首元结点进行特殊处理 s->next = p->next; L = s; } else{ s->next = p->next; p->next = s; } }
带头结点的优点:由于首元结点的地址存放在头结点的指针域中,所以在表中第一个位置上的特殊操作就和在表中其他位置上的操作一致,无须进行特殊处理。
线性链表的删除void DeleteList(LNode *L, int i, ElemType *data)//在带头结点的单链线性表L中,删除第i个元素,并用data返回其值 { LNode *p = L; int j = 0; while(p-next && j<i-1){ //寻找第i个结点,并令p指向其前驱 p = p->next; ++j; } if(!(p->next) || j>i-1){ //i值不合法 printf("删除的位置不合法"); } LNode *q = p->next; p->next = q->next; //删除并释放结点 *data = q->data; free(q); }
线性链表的归并
void MergeList(LNode *La, LNode *Lb, LNode *L)//La和Lb的元素按值非递减排列,归并La和Lb得到L也按非递减排列 { LNode *pa = La->next; LNode *pb = Lb->next; LNode *p = L = La; //用La的头结点作为L的头结点 while(pa && pb){ if(pa->data <= pb->data){ p->next = pa; p = pa; pa = pa->next; } else{ p->next = pb; p= pb; pb = pb->next; } } p->next = pa? pa : pb; //插入剩余段 }
静态链表的定义及其基本操作
静态链表的定义
#define Maxsize 1000 //链表的最大长度 typedef struct { ElemType data; int cur; }component,SLinkList[Maxsize];
静态链表的初始化
void InitSpace(SLinkList *space)//将一维数组space中各分量链成一个备用链表,space[0].cur为头指针 { for(int i=0;i<Maxsize-1;++i){ space[i].cur = i+1; } space[Maxsize-1].cur = 0; //"0"表示空指针 }
静态链表的插入和删除操作与单链表中的插入和删除的算法类似,所不同的是,需由用户自己实现malloc和free这两个函数。
重写的malloc函数int malloc_SL(SLinkList *space)//若备用空间链表非空,则返回分配的结点下标,否则返回0 { int i = space[0].cur; if(space[0].cur){ space[0].cur = space[i].cur; return i; //返回0,意味着备用链表中无可用结点 }
重写的free函数
void free_SL(SLinkList *space, int k)//将下标为k的空闲结点回收到备用链表 { space[k].cur = space[0].cur; space[0].cur = k; }
双向链表的定义及其基本操作
双向链表的定义
typedef struct DuLNode{ ElemType data; struct DuLNode *prior; struct DuLNode *next; }DuLNode, *DuLinkList;
双向链表的插入
void InsertList(DuLNode *L, int i, int data)//在带头结点的双联循环线性表L中第i个位置之前插入元素data { //GetElem_Du(L, i)表示返回L中第i个数据元素的地址 DuLNode *p =GetElem(L, i); if(!p){ //p=NULL,说明第i个元素不存在 printf("插入不合法"); } DuLNode *s = (DuLNode *)malloc(sizeof(DuLNode)); if(!s) exit(-1); //存储分配失败 s->data = data; s->prior = p->prior; p->prior->next = s; s->next = p; p->prior = s; }
双向链表的删除
void DeleteList(DuLinkList *L, int i, ElemType *data)//删除带头结点的双向循环线性表L的第i个元素 { //GetElem_Du(L, i)表示返回L中第i个数据元素的地址 DuLNode *p =GetElem(L, i); if(!p){ //p=NULL,说明第i个元素不存在 printf("插入不合法"); } *data = p->data; p->prior->next = p->next; p->next->prior = p->prior; }
总结:
顺序表的优点:
1.存储密度高;
2.可以高效的按下标进行操作。
链表的优点:
1.插入和删除任意一个结点不需移动大量元素,仅需修改指针;
2.存储空间可为多个链表共同享用,不需要预分配;
3.表的容量可动态修改。