数据结构之链表及实现
线性表的链式表示和实现
线性表的顺序存储结构的特点是逻辑关系上相邻的两个元素在物理位置上也相邻。正由于这种特点,在做插入和删除操作时,需移动大量元素。
链式存储:不要求逻辑上相邻的元素在物理位置上也相邻,特点是用一组任意的存储单元存储线性表的数据元素(可以是连续的,也可以是不连续的)。
为了表示每个数据元素ai与其直接后继元素a i+1 之间的逻辑关系,对数据元素a来说,除了存储其本身的信息之外,还需要存储一个指示其直接后继的信息(即直接后继的存储位置)。这两部分信息组成数据元素ai的存储映像,称为结点。包括两个域:数据域和指针域。每个结点只包含一个指针域,故又称为线性链表或单链表。
头结点:在单链表的第一个元素结点之前设置的一个结点,数据域可以不存在任何信息,指针域指向单链表第一个元素的结点。
首元结点:单链表中第一个有数据元素的结点,如果单链表有头结点,则首元结点为头结点的下一个结点;如果单链表没有头结点,则首元结点就是单链表的第一个结点。
头指针:指示链表中第一个结点(即第一个数据元素的存储映像)的存储位置。如果单链表有头结点,则头指针指向头结点如果单链表没有头结点,则头指针指向首元结点。
。
用线性链表表示线性表是,数据元素之间的逻辑关系是由结点中的指针指示的。单链表可由头指针唯一确定。
//------ 线性表的单链表存储结构 ------ typedef struct LNode{ ElemType data; struct LNode *next; }LNode,*LinkList; //------ 线性表的静态单链表存储结构 (用数组)------ /* 假设S为SLinkList型变量,则是S[0].cur指示第一个结点在数组中的位置,若设i=S[0].cur, 则S[i].data存储线性表表的第一个数据元素,且S[i].cur指示第二个结点在数组中的位置。 i=S[i].cur 的操作实为指针后移(类似p=p->next) */ #define MAXSIZE 100 //链表的最大长度 typedef struct LNode{ ElemType data; int cur; }component,SLinkList[MAXSIZE]; //------ 单链表 GetElem ------ Status GetElem_L(LinkList L,int i,ElemType &e){ //L为带头结点的单链表的头指针 //当第i个元素存在时,其值赋给e并返回OK,否则返回ERROR p = L->next;j=1; //初始化,p指向第一个结点,j为计数器 while(p && j < i){ // 顺指针向后查找,直到p指向第i个元素或者p为空 p = p->next; ++j; } if(!p || j > i) return ERROR; //第i个元素不存在 e = p->data; //取第i个元素 return OK; } //GetElem_L //------ 单链表 ListInsert------ Status ListInsert_L(LinkList L,int i,ElemType &e){ //在带头结点的单链线性表中第i个位置插入元素e p = L; j = 0; while(p && j < i-1){ //寻找第i-1个结点 p = p->next; ++j; } if(!p || j > i-1) return ERROR;//i小于1或者大于表长加1 s = (LinkList)malloc(sizeof(LNode)); //生成新结点 s->data = e; s->next = p->next; p->next = s; return OK; }//LinkInsert_L //------ 单链表 ListDelete------ Status ListDelete_L(){ //在带头结点的单链线性表L中,删除第i个元素,并由e返回其值 p = L; j = 0; 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; }//ListDelete_L //------ 单链表 逆向建立单链表------ void CreateList_L(LinkList &L,int n){ //逆位序输入n个元素的值,建立带表头结点的单链线性表L L = (LinkList)malloc(sizeof(LNode)); L->next = NULL; //先建立一个带头结点的单链表 for(i = n;i > 0;--i){ p =(LinkList)malloc(sizeof(LNode));//生成新结点 scanf(&p->data); //输入元素值 p->next = L->next; L->next = p; //插入到表头 } }//CreateList_L //------ 单链表的合并------ void MergeList_L(LinkList &La,LinkList &Lb,LinkList &Lc){ //已知单链表La和Lb的元素按值非递减有序排列 //归并La和Lb得到新的单链表Lc,Lc的元素也按值非递减排列有序 pa = La->next; pb = Lb->next; 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的头结点 } //MergeList_Sqq
循环链表:表中最后一个结点的指针域指向头结点,整个链表形成一个环。
操作和线性链表基本一致,差别仅在于算法中的循环条件不是p或p->next是否为空,而是它们是否等于头指针。
在单链表中,NextElem的执行时间为O(1) ,而PriorElem的执行时间为O(n)。为了克服单链表这种单向性的缺点,可以利用双向链表。
双向链表:双向链表的结点中有两个指针域,其一指向直接后继,另一指向直接前驱。
//------ 线性表的双向链表存储结构 ------ typedef struct DuLNode{ ElemType data; struct DuLNode *prior; struct DuLNode *next; }DuLNode,*DuLinkList; //双向链表的ListLength GetElem LocateElem仅仅设计一个方向的指针,与线性链表操作相同 //------ 双向链表 insert------ Status ListInseret_DuL(DuLinkList &L,int i,ElemType e){ //在带头结点的双链循环线性表中第i个位置之前插入元素e //i的合法值为1<=i<=表长+1 if(!(p = GetElemP_DuL(L,i))) //在L中确定插入位置 return ERROR; if(!(s = (DuLinkList)malloc(sizeof(DuLNode)))) return ERROR; s->data = e; s->prior = p->prior; p->prior->next = s; s->next = p; p->prior = s; return OK; }//ListInseret_DuL //------ 双向链表 delete------ Status ListDelete_DuL(DuLinkList &L,int i,ElemType &e){ //删除带头结点的双链循环链表L的第i个元素, i的合法值为1<=i<=表长+1 if(!(p = GetElemP_DuL(L,i))) return ERROR;//p=NULL ,即第i个元素不存在 e = p->data; p->prior->next = p->next; p->next->prior = p->prior; free(p); return OK; }//ListDelete_DuL