第22讲 自定义数据类型的应用——链表

第22节 自定义数据类型的应用——链表

1.链表的概念和分类
1、链表的概念和分类
►链表是一种存储空间能动态进行增长或缩小的数据结构。 
►链表主要用于两个目的:一是建立不定长度的数组。二是链表可以在不重新安排整个存储结构的情况
 下,方便且迅速地插入和删除数据元素。    
►链表广泛地运用于数据管理中。
    
►首先设计一种称为结点(node)的数据类型: 
    struct NODE { //结点数据类型 
        ElemType data; //数据域 
        NODE *link; //指针域 };
►这个结构体类型中,data成员表示数据域,代表结点的数据信息。
    
►ElemType可以是简单的内置数据类型,也可以是复杂的数据类型,如
    typedef struct tagElemType { //复杂的数据元素类型 
        ...... //任意数目、任意组合、任意类型的数据成员 
    } ElemType; 
        
►数据域是链表中的信息对象(元素),实际应用中结合具体要求设计其数据类型。为方便介绍,将
 ElemType简单设定为int型,即 
    typedef int ElemType; //简单的数据元素类型
        
►link成员表示指针域,存放另一个结点的地址,是链表中的组织者。假定有一个NODE类型的对象指针
 L,将一个新结点的地址赋给L的link成员,则L可以通过它的link成员“链接”到新结点上,重复这个
 过程可以得到链表结构。

在这里插入图片描述

►链表的分类: 
  ►(1)单链表 
    ►单链表每个结点包含一个指向直接后继结点的指针域,其形式为:
    	struct LNode { //单链表结点类型 
            ElemType data; //数据域 
            LNode *next; //指针域:指向直接后继结点 
        };
        typedef LNode* LinkList; //LNode为单链表结构体类型,LinkList为单链表指针类型
	►next指向直接后继结点,由它构成了一条链。
        
►指针L指向单链表头结点,头结点指向开始结点,开始结点又指向下一个结点,……,直到最后一个尾结
点。尾结点的next为0,表示 NULL指针,约定单链表的结点的next为0时表示尾结点。上述链表称为带
头结点的单链表,若开始结点为头结点,则称这样的链表为不带头结点的单链表。

在这里插入图片描述

  ►(2)双链表 
    ►双链表每个结点包含指向前驱结点和指向直接后继结点的指针域,其形式为:
          struct DNode { //双链表结点类型 
              ElemType data; //数据域 
              DNode *prev,*next; //指针域:分别指向前驱结点和直接后继结点 
          };
		 typedef DNode *DLinkList; //DNode为双链表结构体类型,DLinkList为双链表指针类型
      
►指针L指向双链表头结点,其每个结点分别有指向前一个结点和后一个结点的指针。沿着next指针,头
结点指向开始结点,开始结点又指向下一个结点,……,直到尾结点,尾结点的next为0。沿着 prev指针,
尾结点指向前一个结点,直到头结点head,头结点的prev为0。约定双链表的结点的next为0时表示尾结
点,prev为0时表示头结点。双链表也有带头结点和不带头结点之分。

在这里插入图片描述

  ►(3)循环链表 
	►若单链表尾结点指向头结点而不是0,则该链表是循环单链表。
      
	►同理,若双链表尾结点next指向头结点而不是0,头结点prev指向尾结点而不是0,则该链表是循
     环双链表。

在这里插入图片描述

2、创建单链表
►通过前面介绍的内存动态分配技术可以产生新结点的内存单元,例如:
	LinkList p; //链表指针 
	p=new LNode;//分配LNode类型内存单元且将地址保存到p中

►创建链表常用两种方法:头插法和尾插法。 
    ►(1)头插法建立链表CreateLinkF(&L,n,input()) 
        ►该方法先建立一个头结点*L,然后产生新结点,设置新结点的数据域;再将新结点插入到当前
    	链表的表头,直至指定数目的元素都增加到链表中为止。其步骤为: 
            ►①创建头结点*L,设置*L的next为0。 
            ►②动态分配一个结点s,输入s的数据域。 
            ►③将s插入到开始结点之前,头结点之后。 
            ►④重复②~④步骤加入更多结点。
         1 #include <iostream> 
         2 using namespace std; 
         3 typedef int ElemType; //简单的数据元素类型 
         4 struct LNode { //单链表结点类型 
         5      ElemType data; //数据域 
         6      LNode *next; //指针域:指向直接后继结点 
         7 }; 
         8 typedef LNode* LinkList; //LNode为单链表结构体类型,LinkList为单链表指针类型 
         9 void input(ElemType *ep) //实现数据域元素输入的定制函数 
        10 { //在函数中可以写更加复杂、任意形式、任意数目的输入 
        11      cin>>*ep; 
        12 }
        13 void CreateLinkF(LinkList *L,int n,void(*input)(ElemType*)) 
        14 { //头插法创建单链表,调用input输入函数输入数据 
        15      LinkList s; 
        16      *L=new LNode;//创建头结点 
        17      (*L)->next=NULL; //初始时为空表 
        18      for (; n>0; n--) { //创建n个结点链表 
        19          s=new LNode; //创建新结点 
        20          input(&s->data); //调用input输入数据域 
        21          s->next=(*L)->next; //将s增加到开始结点之前 
        22          (*L)->next=s; //头结点之后 
        23      } 
        24 } 
        25 int main() 
        26 { 
        27      LinkList L; int n; cin>>n; 
        28      CreateLinkF(&L,n,input); 
        29 }
  ►(2)尾插法建立链表CreateLinkR(&L,n,input()) 
      ►头插法建立的链表中结点的次序与元素输入的顺序相反,若希望两者次序一致,可采用尾插法建
      立链表。该方法是将新结点插到当前链表的末尾上,其步骤为: 
          ►①创建头结点*L,设置*L的next为0,且令指针p指向*L。 
          ►②动态分配一个结点s,输入s的数据域。 
          ►③将s插入到当前链表末尾。 
          ►④重复②~④步骤加入更多结点。
		1 #include <iostream> 
         2 using namespace std; 
         3 typedef int ElemType; //简单的数据元素类型 
         4 struct LNode { //单链表结点类型 
         5      ElemType data; //数据域 
         6      LNode *next; //指针域:指向直接后继结点 
         7 }; 
         8 typedef LNode* LinkList; //LNode为单链表结构体类型,LinkList为 单链表指针类型 
         9 void input(ElemType *ep) //实现数据域元素输入的定制函数 
        10 { //在函数中可以写更加复杂、任意形式、任意数目的输入 
        11      cin>>*ep; 
        12 }
        13 void CreateLinkR(LinkList *L,int n,void(*input)(ElemType*)) 
        14 { //尾插法创建单链表,调用input输入函数输入数据 
        15      LinkList p,s; 
        16      p=*L=new LNode; //创建头结点 
        17      for (; n>0; n--) { //创建n个结点链表 
        18          s=new LNode; //创建新结点 
        19          input(&s->data); //调用input输入数据域 
        20          p->next=s, p=s; //将s插入到当前链表末尾 
        21      } 
        22      p->next=NULL; //尾结点 
        23 } 
        24 int main() 
        25 { 
        26      LinkList L; int n; cin>>n; 
        27      CreateLinkR(&L,n,input); 
        28 }
►(3)销毁链表DestroyList(&L) 
    ►按照动态内存的使用要求,当不再使用链表时或程序结束前,需要将创建链表时分配的所有结点的
    内存释放掉,即销毁链表。
    ►销毁链表的步骤如下:
        ►①若*L为0,表示已到链尾,销毁链表结束。 
        ►②令指针p指向结点*L的next,释放内存*L。 
        ►③*L置换为p,即*L指向直接后继结点,重复①~③步骤直至销毁链表结束。
         1 void DestroyList(LinkList *L) //销毁单链表L 
         2 { 
         3      LinkList q,p=*L; //p指向头结点 
         4      while(p!=NULL) { //若不是链尾继续 
         5          q=p->next; //指向直接后继结点 
         6          delete p; //释放结点存储空间 
         7          p=q; //直接后继结点 
         8      } 
         9      *L=NULL; //置为空表 
        10 }
2.链表的运算
3、链表的运算
►(1)链表遍历ListTraverse(L,visit()) 
    ►与数组不同,链表不是用下标而是用指针运算查找数据元素的。通过链表的头指针L可以访问开始
    结点p=L->next,令p=p->next,即p指向直接后继结点,如此循环可以访问整个链表中的全部结点。
    
    ►链表遍历算法的实现步骤为: 
        ►①令指针p指向L的开始结点。 
        ►②若p为0,表示已到链尾,遍历结束。 
        ►③令p指向直接后继结点,即p=p->next。重复②~③步骤直至遍历结束。
    
    ►链表遍历的算法如下: 
        1 void ListTraverse(LinkList L, void(*visit)(ElemType*)) 
        2 { //遍历L中的每个元素且调用函数visit访问它 
        3       LinkList p=L->next; //p指向开始结点 
        4       while(p!=NULL) { //若不是链尾继续 
        5           visit(&(p->data)); 
        6           p=p->next; //p指向直接后继结点 
        7       } 
        8 }
    ►其中visit是函数指针。当调用ListTraverse遍历结点时,通过调用 visit()对每个结点完成定制
     的操作。 
        1 void visit(ElemType *ep) //实现链表遍历时结点访问的定制函数 
        2 { //在函数中对结点*ep实现定制的操作,例如输出 
        3 		cout<<*ep<<" "; 
        4 }
    
►(2)查找结点,返回链表中满足指定数据元素的位序 LocateElem(L,e,compare()) 
    ►应用遍历算法查找链表结点,返回第一个满足定制关系数据元素的位序的算法如下:
         1 int LocateElem(LinkList L,ElemType e, 
             int(*compare)(ElemType*,ElemType*)) 
         2 {//返回L中第1个与e满足关系compare()的元素的位序 
         3       int i=0; 
         4       LinkList p=L->next; //p指向开始结点 
         5       while(p!=NULL) { //若不是链尾继续 
         6           i++; //记录结点的位序 
         7           if(compare(&(p->data),&e)) return i; 
         8           p=p->next; //指向直接后继结点 
         9       } 
        10       return 0; //关系不存在返回0 
        11 }

►(3)插入结点 
    ►插入结点操作是指将一个新结点插入到已知的链表中。插入位置可能在头结点、尾结点或者链表中
    间,插入操作前需要定位插入元素的位置和动态分配产生新结点。 
    
    ►假设将新结点s插入到单链表的第i个结点位置上。方法是先在单链表中找到第i-1个结点p,在其后
    插入新结点s。
    1 int ListInsert(LinkList *L,int i,ElemType e) 
    2 { 	//在第i个位置之前插入元素e 
    3 		LinkList s,p=*L; //p指向头结点 
    4 		while(p!=NULL && i>1) { //寻找第i-1个结点 
    5 			p=p->next; //p指向直接后继结点 
    6 			i--; 
    7 		} 
    8 		if(p==NULL||i<1) return 0; //i值不合法返回假(0) 
    9 		s=new LNode; //创建新结点 
   10 		s->data=e; //插入L中 
   11 		s->next=p->next, p->next=s; //结点插入算法 
   12 		return 1; //操作成功返回真(1) 
   13 }

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

►(4)删除结点 
    ►结点删除操作是指将链表中的某个结点从链表中删除。删除位置可能在头结点、尾结点或者链表中
    间,删除操作后需要释放删除结点的内存空间。 
    
    ►将链表中第i个结点删去的方法是先在单链表中找到第i-1个结点p,再删除其后的结点。
    1 int ListDelete(LinkList *L,int i,ElemType *ep) 
    2 { 	//删除第i个结点,并由*ep返回其值 
    3 		LinkList p=NULL,q=*L; //q指向头结点 
    4 		while(q!=NULL && i>=1) { //直到第i个结点 
    5 			p=q; //p是q的前驱 
    6 			q=q->next; //q指向直接后继结点 
    7 			i--; 
    8 		} 
    9 		if(p==NULL||q==NULL) return 0;//i值不合法返回假(0) 
   10 		p->next=q->next; //结点删除算法 
   11 		if(ep!=NULL) *ep=q->data; //删除结点由*ep返回其值 
   12 		delete q; //释放结点 
   13 		return 1; //操作成功返回真(1) 
   14 }

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值