zoukankan      html  css  js  c++  java
  • 2.0 线性表


    title: 数据结构 | 线性表
    date: 2019-12-03 17:40:48
    tags: 数据结构


    线性表的顺序和链式结构
    线性链表、循环链表、双向链表

    主要内容

    线性结构特点
    在非空有限集内,存在唯一始末元素、除头全有唯一前驱,除尾全有唯一后继。

    2.1 线性表的类型定义
    2.2 线性表的顺序表示与实现
    2.3 线性表的链式表示与实现
    2.3.1 线性链表
    2.3.2 循环链表
    2.3.3 双向链表

    线性表的逻辑结构

    定义

    一个线性表是n个数据元素的有限序列

    特点

    1.线性表中所有元素的性质相同。

    2.除第一个和最后一个数据元素之外,其它数据元素有且仅有一个前驱和一个后继。第一个数据元素无前驱,最后一个数据元素无后继。

    3.数据元素在表中的位置只取决于它自身的序号

    ADT

    ADT List{
    	数据对象:D={ai|ai∈ElemSet,i=1,2,…,n,n≥0}
    	数据关系:R={<ai-1,ai>|ai,ai-1∈D,1=2,…,n}
    	基本操作:
    		InitList( &L);
    		DestroyList(&L);
    		ClearList(&L);
    		ListEmpty(L);
    		ListLength(L);
    		GetElement(L,i,&e);
    		LocateElement(L,e,compare( ))
    		PriorElement(L,cur_e,&pre_e)
    		NextElement(L,cur_e,&next_e)
    		ListInsert(&L,i,e);
    		ListDelete(&L,i,&e);
    		ListTraverse(L,visit( ))
    }ADT List
    
    

    例题

    线性表合并

    假设有两个集合A和B分别用两个线性表LA和LB表示,现要求一个新的集合A=A∪B。

    void union( List &La, List Lb)
    {
        // 将所有在线性表Lb中但不在La中的数据元素插入到La中
    	La_Len = ListLength( La ); // 求线性表的长度
    	Lb_Len = ListLength( Lb );
    	for( i = 1; i <= Lb_Len; i++)
    	{
    	     GetElem( Lb, i, e); // 取Lb中第i个数据元素赋给e
    	     if( !LocateElem( La, e, equal))
    	         ListInsert( La, ++La_Len, e); 
                 // La中不存在和 e 相同的数据元素,则插入之
           }
    } // union
    

    时间复杂度:
    O(ListLength( La ) * ListLength( Lb ))

    非递减线性表La,Lb的合并

    void MergeList( List La, List Lb, List &Lc )
    {
    	InitList( Lc );
    	i = j = 1; // i和j分别是La和Lb的序号
    	k = 0;     //k是Lc的序号
    	La_Len = ListLength( La );
    	Lb_Len = ListLength( Lb );
    	while((i <= La_Len) && (j <= Lb_Len))
        {
            GetElem( La, i, ai ); 
            GetElem( Lb, j, bj );
        if( ai < = bj ) {
            ListInsert( Lc, ++k, ai); ++i; 
        	}
    	else {
            ListInsert( Lc, ++k, bj ); ++j; 
            }
         }
    	while( i <= La_Len ){//若La非空,把La剩余的数据元素插入到Lc中
    		GetElem( La, i++, ai );    
    		ListInsert( Lc, ++k, ai );
    	}
    	while( j <= Lb_Len ){//若Lb非空,把La剩余的数据元素插入到Lc中
    		GetElem( Lb, j++, bj );      
    		ListInsert( Lc, ++k, bj );
    	}
    }//MergeList
    

    时间复杂度:
    O(ListLength(La ) + ListLength( Lb))

    线性表的顺序存储结构

    顺序表:用一组地址连续的存储单元存放一个线性表

    • 元素地址计算方法
      LOC(ai)=LOC(a1)+(i-1)*L
      L—一个元素占用的存储单元个数
      LOC(ai)—线性表第i个元素的地址
    • 特点
      实现逻辑上相邻—物理地址相邻
      实现随机存取

    顺序表的类型定义

    #define LIST_INIT_SIZE 100  // 线性表存储空间的初始分配量
    #define LISTINCREMENT 10   // 线性表存储空间的分配增量
    typedef  struct{
    	ElemType *elem;//存储空间基址
    	int length;   //当前长度
    	int listsize //当前分配的存储容量(以sizeof(ElemType)为单位)
    }Sqlist;
    

    顺序表的重要操作

    初始化顺序表

    Status InitList_Sq(SqList &L)
    { //构造一个空的顺序表L 
    	L.elem=(ElemType*)malloc(LIST_INIT_SIZE*sizeof(ElemType));
    	if (! L.elem) 
    		exit(OVERFLOW);  //存储分配失败
    	L.length=0;  //空表长度为0
    	L.listsize=LIST_INIT_SIZE; //初始存储容量
    	Return OK;
    }//InitList_Sq                  
    

    顺序表的插入操作

    思路

    1.输入是否有效?
    2.当前表是否已经满?
    3.移动 i 后的元素
    4.插入元素
    5.表长增1

    代码

    Status ListInsert_Sq(Sqlist &L, int i, ElemType e)      
    {  // 在顺序线性表L的第i个位置之前插入新的元素e
       // i的合法值为1<=i<=ListLength_Sq(L) + 1
       if(i<1 || i>L.length+1) 
       	return ERROR;   //i值不合法
       if(L.length>=L.listsize){ // 当前存储空间已满,增加分配 
       		newbase=(ElemType*)realloc(L.elem,(L.listsize+LISTINCREMENT)*sizeof(ElemType));
             if(!newbase)
             	exit(OVERFLOW); // 存储分配失败
             L.elem=newbase;
             L.listsize+=LISTINCREMENT; // 增加存储容量
       }
       q=&(L.elem[i-1]);    // q为插入位置
       for(p=&(L.elem[L.length-1]); p>=q; --p)
            *(p+1)=*p;   // 插入位置及之后的元素后移
       *q=e;           // 插入e
       ++L.length;    // 表长增1
       return OK;
    }                                                                          
    

    顺序表的删除操作

    思路

    1.输入是否有效?
    2.删除(前移元素)
    3.表长减1

    代码

    Status ListDelete_Sq(SqList &L,int i,ElemType &e){
    //在顺序线性表L中删除第.i个元素,并用e返回其值       
    //i的合法值为  1≤i≤L.length
    	if((i<1)||(i>L.Length))
    		return ERROR;	// i值不合法或表空       
    	p=&(L.elem[i-1]);   //p为被删除元素的位置      
    	e=*p;             // 被删除元素的值赋给e       
    	q=L.elem+L.length-1; // 表尾元素的位置       
    	for (++p; p<=q;++p)  
    		*(p-1)=*p;  //被删除元素之后的元素前移       
    	--L.length; //表长减1      
    	return OK;
    }//ListDelete_Sq                                       
    

    顺序表的查找操作

    int LocateElem(SqList L,ElemType e, Status(*compare)(ElemType,ElemType)){
        ElemType *p;
        int i=1; // i的初值为第1个元素的位序
        p=L.elem; // p的初值为第1个元素的存储位置
        while(i<=L.length&&!compare(*p++,e))
            ++i;
        if(i<=L.length)
           return i;
        else
           return 0;
     }
    

    顺序表的遍历(函数指针使用说明书)

    • 初始条件:
      顺序线性表L已存在
    • 操作结果:
      依次对L的每个数据元素调用函数vi()。一旦vi()失败,则操作失败。
      若在vi()的形参加'&',表明可通过调用vi()改变元素的值。
    Status ListTraverse(SqList ,void(*vi)(ElemType&)){
    	ElemType *p;
    	int i;
    	p=L.elem;
    	for(i=1;i<=L.length;i++)
    		vi(*p++);
    	return OK;
    }
    

    顺序表的一般操作

    销毁顺序表

    Status DestroyList_Sq ( SqList &L) {      
    	if (!L.elem) 
    		return ERROR;  // 若表L不存在      
    	free (L.elem);	   // 若表L已存在,回收动态分配的存储空间
    	L.elem = null;      
    	L.length = 0;      
    	L.Listsize = 0;      
    	return OK;
    }// DetroyList_Sq
    

    置空线性表

     Status ClearList_Sq ( SqList &L) {      
    	if (!L.elem) 
    		return ERROR; // 若表L不存在      
    	L.length = 0;	//若表L已存在,将L置空      
    	return OK;
    }// ClearList_Sq
    

    判断空表

    Status ListEmpty(SqList L)
     { // 初始条件:顺序线性表L已存在。
       //操作结果:若L为空表,则返回TRUE,否则返回FALSE
    	if(L.length==0)
    		return TRUE;
    	else
    		return FALSE;
     }
    

    求表长

    int ListLength(SqList L){ 
    	//初始条件:顺序线性表L已存在。
    	//操作结果:返回L中数据元素个数
    	return L.length;
    }
    

    取元素操作

    Status GetElem_Sq ( SqList L, int i, ElemType &e ) {      
    	if((i< 1)||(i>L.length)) 
    		return ERROR;  // i 非法      
    	e=L.elem[i-1]; //将顺序表中第i 个元素赋值给 e      
    	return OK;
    }// GetElem_Sq
    

    顺序存储结构的优缺点

    优点

    • 逻辑相邻,物理相邻
    • 可随机存取任一元素
    • 存储空间使用紧凑

    缺点

    • 插入、删除操作需要移动大量的元素
    • 预先分配空间需按最大空间分配,利用不充分
    • 表容量难以扩充

    线性表的链式存储结构

    特点

    • 用一组任意的存储单元存储线性表的数据元素
    • 利用指针实现了用不相邻的存储单元存放逻辑上相邻的元素
    • 每个数据元素ai,除存储本身信息外,还需存储其直接后继的信息
    • 结点{
      数据域:元素本身信息
      指针域:指示直接后继的存储位置
      }

    线性链表的定义

    结点中只含一个指针域的链表叫线性链表,也叫单链表。

    typedef struct LNode {
         ElemType data;
         struct LNode  *next;
    }Lnode,*LinkList;
    

    LNode* p 和 LinkList p
    意思一样,都是建立一个Lnode型的单链表
    定义出来的都是1个Lnode型的指针变量,通常用他指向头结点
    特别地,注意
    LinkList P,Q; /P,Q都是指针 /
    LNode P,Q;/只有P是指针* /

    • 头结点
      在单链表第一个结点前附设一个结点叫头结点
      头结点指针域为空表示线性表为空

    链表的重要操作

    初始化链表

    Status InitList_L (LinkList &L) {
    	L = (LinkList)malloc(sizeof(LNode));     
    	if (!L)
    		exit(OVERFLOW);    
    	L->next = null;      
    	Return OK;
    }// InitList_L 
    

    链表的按值查找

    Status LocateNode_L(LinkList L,Elemtype key,LNode &e){
    	p=L–>next;
    	while( p && p–>data!=key)
    		p=p–>next;
    	if(!p) 
    		return ERROR;
    	e=p;
    	return OK;
    }   
    

    链表的插入操作

    思路

    1.寻找第i-1个结点:
    顺指针向后查找,直到p指向第i-1个元素或p->next为空
    2.分配新空间
    3.插入节点

    代码

    Status ListInsert_L(LinkList &L, int i, ElemType e){
     //在带头结点的线性链表L中第i元素结点之前插入元素e
    	p=L; j=0
    	while (p&&j<i-1){
    		p=p->next; 
    		++j;
    	}//☆寻找第i-1个元素结点
    	if(!p||j>i-1)
    		return ERROR; // i小于1  则 j>i-1
    	                  // i大于表长+1 则p为空         
    	s=(LinkList)malloc(sizeof(LNode)); //分配新结点
    	s->data=e;
    	s->next=p->next; p->next=s;   //插入新结点
    	return OK;
    }//LinstInsert_L
    

    链表的删除操作

    思路

    1.寻找第i-1个结点:
    顺指针向后查找,直到p指向第i-1个元素或p->next为空
    2.删除结点(修改其后继指针)
    3.回收(释放)结点空间

    代码

    Status ListDelete_L(LinkList &L, int i, ElemType &e){
    	//在带头结点的单链线性表L中,删除第i个元素,并由e返回其值
    	p=L; j=0;
    	while (p->next&&j<i-1){  //寻找第i-1个结点
    		p=p->next; ++j;
    	}
    	if(!(p->next)||j>i-1)
    		return ERROR; //  表中无第i个结点(i不合法)
    	                  //  i<1 则 j>i-1
    	                  //  i>表长 则 p->next为空
    	q=p->next;p->next=q->next;   //删除结点
    	e =q->data; free(q);   //  释放结点空间
    	return OK;
    }//LinstDelete_L                    
    

    链表的一般操作

    取元素操作

    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                       
    

    头插法建立单链表

    思路

    ① 建立新节点
    ② 向新节点中添入内容
    ③ 使新节点指向链头(第一个元素)
    ④ 改变头指针,指向新节点

    代码

    void CreateList_L(LinkList &L, int n) {
    	//逆序输入n个元素的值,建立带表头结点的线性链表
    	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 CreateList_L(LinkList &L, int n) {
    	//输入n个元素的值,建立带表头结点的线性链表
    	L=(LinkList)malloc(sizeof (LNode));
    	L->next=NULL; //先建立一个带头结点的单链表
    	r=L;
    	for (i=1; i<=n;i++){
    		p=(LinkList)malloc(sizeof(LNode));//生成新结点
    		scanf(&p->data);//输入元素值
    		p->next=NULL;
    		r->next=p;
    		r=p; 
    	}
    }//CreateList_L
    

    归并2个有序链表

    void MergeList_L(LinkList &La, LinkList &Lb, LinkList &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_L                
    

    单链表结构的优缺点

    优点

    • 动态结构,整个存储空间为多个链表共用
    • 不需预先分配空间

    缺点

    • 指针占用额外存储空间
    • 不能随机存取,查找速度慢

    循环链表

    • 在单链表中,将终端结点的指针域NULL改为指向表头结点的或开始结点,就得到了单链形式的循环链表,并简单称为单循环链表。
    • 为了使空表和非空表的处理一致,循环链表中也可设置一个头结点。
    • 由于循环链表中没有NULL指针,故涉及遍历操作时,其终止条件就不再像非循环链表那样判断p或p—>next是否为空,而是判断它们是否等于头指针

    双向链表

    结构定义

    typedef struct DulNode {
         ElemType   data;
         struct DuLNode *prior;
         struct DuLNode *next;
    }DuLNode,  *DuLinkList;
    

    插入结点程序

    Status ListInsert_DuL(DuLinklist L, int i, ElemType e)
    { 
       DuLinklist s,p;
       if (!(p=GetElemP_DuL(L,i))) 
          return ERROR;      // 在L中确定第i个元素的位置指针p
       if(!(s = (DuLinklist)malloc(sizeof(DuLNode)))) return ERROR;
       s->data = e;                // 构造数据为e的结点s
       s->prior = p->prior;  p-> prior ->next = s;
       s->next = p;        p->prior = s;
       return OK;
    }  // ListInsert_DuL
    

    删除结点程序

    Status ListDelete_DuL(DuLinklist L, int i, ElemType &e)
    {
       DuLinklist p;
       if (!(p=GetElemP_DuL(L,i))) 
            return ERROR;    // 在L中确定第i个元素的位置指针p
       e = p->data;                // 删除的结点的值存入e
       p-> prior ->next = p->next;
       p->next->prior = p->prior;
       free(p);
       return OK;
    }  // ListDelete_DuL
    

    带头结点的线性链表类型

    具有实用意义的线性链表

     typedef struct LNode // 结点类型
     {
          ElemType data;
          LNode *next;
     }*Link,*Position;
    
     struct LinkList // 链表类型
     {
          Link head,tail; // 分别指向线性链表中的头结点和最后一个结点
          int len;      // 指示线性链表中数据元素的个数
     };                                                
    
    
  • 相关阅读:
    Boost线程库学习笔记
    sizeof运算符
    用法char ch=getchar()正确性详解
    C语言中的缓冲输出
    算术运算的溢出行为 and 一个数内存中表示1的个数
    ARP、RARP、ICMP、ping
    http和https协议
    关于宋词频率统计(R语言)
    Backbone.js API中文文档
    腾讯小Q书桌图标怎么实现的啊?
  • 原文地址:https://www.cnblogs.com/nightland/p/13504417.html
Copyright © 2011-2022 走看看