数据结构笔记 | 第三章 | 线性表

Chapter 3 线性表

  • 是什么:零个或多个数据元素的有限序列。(序列表明其有顺序,n=0成为空表)
  • 在复杂线性表中,一个数据元素可由多个数据项组成。

线性表的抽象数据类型

ADT 线性表(List)
Data 
	线性表的数据对象集合为{a1,a2,...,an},每个元素的类型均为DataType。其中,除地一个元素外,
每个元素只有有且只有一个前驱元素,除了最后一个元素外,每个元素有且只有一个后继元素。数据元素
之间是一对一的关系。
Operation 
	InitList(*L): 		初始化操作,建立一个空的线性表L。
	ListEmpty(L):		若线性表为空,返回true,反则返回false。
	ClearList(*L):		将线性表清空。
	GetElem(L,i,*e): 	将线性表L中第i个位置元素值返回给e。
	LocateElem(L,e):	在线性表L中查找与给定值e相等的元素,如果查找成功,返回该元素在表中
						序号表示成功;否则,返回0表示失败。
	ListInsert(*L,i,e):   在线性表L中的第i个位置插入新元素e。
	ListDelete(*L,i,*e):  删除线性表L中第i个位置元素,并用e返回其值。
	ListLength(L): 		  返回线性表L的元素个数。	
e.g.
/*实现:两个线性表集合A和B的并集操作,
       La表示集合A,Lb表示集合B,即,将所有在线性表Lb中但不在La中的数据元素插入到La中 */
/*方法:循环集合B中的每个元素,判断当前元素是否在A中,若不存在,插入到A中即可*/
void unionL(List *La,List Lb)
{
	int La_len,Lb_len;
	ElemType e;					/*声明与La和Lb相同的数据元素e*/
	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))	/*La中不存在和e相同的数据元素*/
			ListInsert(La, ++La_len,e);  /*插入*/	
	}
}

线性表的顺序存储结构

  • 是什么: 用一段地址连续的存储单元依次存储线性表的数据元素。
  • 如何存:用一维数组。找到第一个位置(存储空间的起始位置),估计线性表最大存储容量,数组的长度就是这个最大存储容量。
#define MAXSIZE 20
typedef int ElemType;
typedef struct
{
	ElemType data[MAXSIZE];		/*数组存储数据元素,最大值为MAXSIZE*/
	int length;					/*线性表当前长度*/
}SqList;
  • 线性表的长度是线性表中数据元素的个数,随插入删除变化,任意时刻小于等于数组长度。
  • 地址计算方法:LOC(ai)=LOC(a1)+(i-1)c 。由公式可计算线性表中任意位置地址,存取元素时间性能为O(1),具有这一特点的结构叫随机存取结构
  • 获得元素操作
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int Status;
/*初始条件:顺序线性表L已存在,1<=i<=ListLength(L)*/
/*操作结果:用e返回L中第i个数据元素的值*/
/*实现:i在数组下标范围内,就把数组第i-1小标的值返回*/
Status GetElem(SqList L,int i,ElemType *e)
{
    if(L.length==0 || i<1 || i>L.length)
        return ERROR;
    *e=L.data[i-1];
    return OK;
}
  • 插入操作
/*初始条件:顺序线性表L已存在,1<=i<=ListLength(L) */
/*操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1*/
/*实现:从最后一个元素向前遍历到第i个,分别后移1位,再把元素插入i处,表长加1*/
Status ListInsert(SqList *L,int i,ElemType e)
{
    int k;
    if (L->length==MAXSIZE)    /*顺序线性表已满*/
        return ERROR;
    if(i<1 || i>L->length+1)   /*当i不在范围内时*/
        return ERROR;
    if(i<=L->length)    /*若插入数据位置不在表尾*/
    {
    	for(k=L->length-1;k>=i-1;k--) /*将插入位置后的数据元素都向后移动一位*/
            L->data[k+1]=L->data[k];
    }
    L->data[i-1]=e;
    L->length++;
    return OK;
}
  • 删除操作
/*初始条件:顺序线性表L已存在,1<=i<=ListLength(L) */
/*操作结果:删除L中的第i个数据元素,并用e返回其值,L的长度减1*/
/*实现:从删除元素位置开始遍历到最后一个元素位置,分别将它们都前移一个位置,表长减1*/
Status ListDelete(SqList *L,int i,ElemType *e)
{
    int k;
    if (L->length==0)          /*线性表为空*/
        return ERROR;
    if(i<1 || i>L->length+1)   /*删除位置不正确*/
        return ERROR;
    *e=L->data[i-1];
    if(i<L->length)    /*如果删除不是最后位置*/
    {
        for(k=i;k<L->length;k++) /*将删除位置后继元素前移*/
            L->data[k-1]=L->data[k];
    }
    L->length--;
    return OK;
}
  • 线性表读取元素时间复杂度为O(1),插入删除操作,时间复杂度为O(n)。
  • 线性表顺序存储结构的优点:无须为表示表中元素之间的逻辑关系而增加额外的存储空间;可以快速地存取表中任一位置的元素。
  • 线性表顺序存储结构的缺点:插入和删除操作需要移动大量元素;当线性表长度变化较大时,难以确定存储空间的容量;载程存储空间的“碎片”。

线性表的链式存储结构

  • 是什么:每个结点包含数据域(存储自己的信息)和指针域(指向后继的存储位置),n个结点链结成一个链表。
  • 怎么存:单链表用结构体指针。
	typedef struct Node
	{
    	ElemType data;
    	struct Node *next;
	}Node;
	typedef struct Node *LinkList;  /*定义LinkList*/
  • 头指针:链表指向第一个结点的指针。若有头结点就是指向头结点的指针。常用头指针冠以链表的名字。无论链表是否为空,头指针均不为空。
  • 头结点:单链表的第一个结点前附设一个结点,为了操作的统一和方便设立的。数据域一般无意义或者放链表长度。
  • 单链表的读取操作:
    /*初始条件:顺序线性表L已存在,1<=i<=ListLength(L)*/
	/*操作结果:用e返回L中第i个数据元素的值*/
	/*实现:j<i是就让p指针后移,不断指向下一个结点。若到p为空说明不存在,否则查找成功,返回p的数据*/
	Status GetElem(LinkList L,int i,ElemType *e)
	{
    	int j;
        LinkList p;        /*声明一指针p*/
        p = L->next;       /*让p指向链表L的第一个结点*/
        j = 1;             /*j为计数器*/
        while(p && j<i)    /*p不为空或者计数器j还没有等于i时,循环继续*/
        {
            p = p->next;   /*让p指向下一个结点*/
            ++j
        }
        if(!p || j>i)
            return ERROR;  /*第i个元素不存在*/
        *e = p->data;      /*取第i个元素的数据*/
        return OK;
	}
	//最坏情况时间复杂度为O(n)
	//由于单链表结构没有定义表长,所以不知要循环多少次,因此不能用for控制循环。
	//其主要核心思想是“工作指针后移”。
  • 单链表的插入操作
                                    在这里插入图片描述
    /*初始条件:顺序线性表L已存在,1<=i<=ListLength(L)*/
	/*操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1*/
	/*实现:遍历到i后用标准插入语句*/
	Status ListInsert (LinkList *L,int i,ElemType e)
	{
    	int j;
        LinkList p,s;       
        p = *L;      
        j = 1;             
        while(p && j<i)    /*寻找第i个结点*/
        {
            p = p->next;  
            ++j
        }
        if(!p || j>i)
            return ERROR;  /*第i个元素不存在*/
        s = (LinkList)malloc(sizeof(Node)); /*生成新结点(C标准函数)*/
        s->data=e;
        s->next=p->next;   /*将p的后继结点赋值给s的后继*/
        p->next=s;         /*将s赋值给p的后继*/
        return OK;
	}
  • 单链表的删除操作
                            在这里插入图片描述
其实直接就一步  p->next=p->next->next;
用q来取代p->next,就是 q=p->next;  p->next=q->next;
    /*初始条件:顺序线性表L已存在,1<=i<=ListLength(L)*/
	/*操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1*/
	/*实现:遍历到i后用标准删除语句*/
	Status ListDelete (LinkList *L,int i,ElemType *e)
	{
    	int j;
        LinkList p,q;       
        p = *L;      
        j = 1;             
        while(p && j<i)    /*寻找第i个结点*/
        {
            p = p->next;  
            ++j
        }
        if(!p || j>i)
            return ERROR;  /*第i个元素不存在*/
        q = p->next;
        p->next = q->next;
        *e=q->data;
        free(q);
        return OK;
	}
  • 单链表的整表创建操作:
	/*操作结果:随机产生n个元素的值,建立带头结点的单链线性表L*/
	/*实现:(头插法)建立头结点,循环n次在头结点与前一新结点间用标准语句插入*/
	void CreateListHead (LinkList *L,int n)
	{
        LinkList p;       
        int i;
        srand(time(0));    /*初始化随机数种子*/   
        *L = (LinkList)malloc(sizeof(Node));
        (*L)->next = NULL;  /*先建立一个带头结点的单链表*/
        for(i=0; i<n; i++)
        {
            p = (LinkList)malloc(sizeof(Node)); /*生成新结点*/
            p->data = rand()%100+1; /*随机生成100以内的数字*/
            p->next = (*L)->next;
            (*L)->next = p;         /*插入到表头*/
        }
	}
	/*操作结果:随机产生n个元素的值,建立带头结点的单链线性表L*/
	/*实现:(尾插法)循环n次每次在表尾插入新结点*/
	void CreateListHead (LinkList *L,int n)
	{
        LinkList p,r;       
        int i;
        srand(time(0));    /*初始化随机数种子*/   
        *L = (LinkList)malloc(sizeof(Node));  /*为整个线性表*/
        r=*L;  /*r为指向尾部的结点*/
        for(i=0; i<n; i++)
        {
            p = (Node *)malloc(sizeof(Node)); /*生成新结点*/
            p->data = rand()%100+1; /*随机生成100以内的数字*/
            r->next = p;            /*将表尾终端结点的指针指向新结点*/
            r = p;                  /*将当前的新结点定义为表尾终端结点*/
        }
        r->next = NULL;             /*表示当前链表结束*/
	}
	//r随着循环不断地变化,而L则是随着循环增长为一个多结点的链表
  • 单链表的整表删除
	/*初始条件:顺序线性表L已存在,操作结果:将L重置为空表*/
	/*实现:(尾插法)循环n次每次在表尾插入新结点*/
	Status ClearList(LinkList *L)
    {
        LinkList p,q;
        p=(*L)->next;      /*p指向第一个结点*/
        while (p)          /*没到表尾*/
        {
            q=p->next;     /*先记录好下一个结点,以免删了以后找不到下一个了*/
            free(p);
            p=q;
        }
        (*L)->next=null;   /*头结点指针域为空*/
        return OK;
        
    }
  • 单链表结构与顺序结构对比
    ①存储分配方式:顺序结构用连续存储单元,链式结构是一组任意存储单元。
    ②时间性能:顺序结构查找为O(1),插入删除为O(n);单链表查找为O(n),在找出某位置的指针后插入删除仅为O(1)。
  • 频繁查找少并插入删除,用顺序结构;频繁插入删除用单链表。元素个数变化大或者根本不知道大小,用单链表。
其他链表结构
静态链表
  • 是什么:用数组代替指针,描述单链表。
  • 怎么搞:两个数据域,data存放数据元素,cur相当于单链表中的next指针,存放该元素的后继在数组中的下标。通常对数组第一个最后一个元素特殊处理。把未被使用的数组元素称为备用链表。数组第一个元素的cur用来存放备用链表第一个节点的下标。数组最后一个元素的cur用来存放第一个插入元素的下标,相当于单链表中的头结点作用。
	/*线性表的静态链表存储结构*/
	/*通常把数组建立得大一些,以便插入时不至于溢出*/
	#define MAXSIZE 1000
	typedef struct
	{
    	ElemType data;
    	int cur;        /*游标(Cursor),为0时表示无指向*/
	}Component,StaticLinkList[MAXSIZE];
  • 静态链表的初始化
    /*将一维数组space中各分量链成一备用链表*/
    /*space[0].cur为头指针,"0"表示空指针*/
    Status InitList(StaticLinkList space)
    {
        int i;
        for (i=0; i<MAXSIZE-1; i++)
            space[i].cur = i+1;
        space[MAXSIZE-1].cur = 0;   /*目前静态链表为空,最后一个元素的cur为0*/
        return OK;
    }
  • 静态链表的插入操作
    /*因为是数组,自己实现malloc函数*/
    /*为了辨明数组中哪些分量未被使用,可将所有未被使用以及被删除的分量用游标链成一个备用的链表,
      每当进行插入时,便可以从备用链表上取得第一个结点作为待插入的新结点。*/
    /*若备用空间链表非空,则返回分配的节点下表,否则返回0*/
    int Malloc_SLL(StaticLinkList space)
    {
        int i = space[0].cur;   /*当前数组第一元素的cur值,
                                就是要返回的第一个备用空闲的下标*/
        if(space[0].cur)
            space[0].cur = space[i].cur; /*由于要拿出一个分量来使用了,
            								所以把它的下一个分量作为备用*/
        return i;
    }
    
    /* 在L中第i个元素之前插入新的数据元素e */
    Status ListInsert(StaticLinkList L, int i, ElemType e)
    {
        int j, k, l;
        k = MAX_SIZE-1;     /*注意k首先是最后一个元素的下标*/
        if (i<1 || i > ListLength(L) + 1)
            return ERROR;
        j = Malloc_SLL(L);  /*获得空闲分量的下标*/
        if (j)
        {
            L[j].data = e;  /*将数据赋值给此分量的data*/
            for(l = 1; l <= i - 1; l++) /*找到第i个元素之前的位置*/
                k=L[k].cur;
            L[j].cur = L[k].cur; /*相当于s->next=p->next;*/
            L[k].cur = j;        /*相当于p->next=s;  */
            retunr OK;
        }
        return ERROR;
    }
  • 静态链表的删除操作
    /*因为是数组,自己实现free函数*/
    /*将下标为k的空闲结点回收到备用链表*/
    void Free_SSL (StaticLinkList space, int k)
    {                                   /*把空闲点加入到备用链表,其实是相当于插入*/
        space[k].cur = space[0].cur;    /*s->next=p->next;*/ 
        space[0].cur = k;               /*p->next=s*/
    }

    /* 删除在L中第i个数据元素e */
    Status ListDelete(StaticLinkList L, int i)
    {
        int j, k;
        if(i<1 || i> ListLength(L) )
            return ERROR;
        k = MAX_SIZE - 1;
        for(j = l; j<= i-1; j++)
            k=L[k].cur;         /*k变为第i-1个,要删除第i个*/
        j = L[k].cur;           /*相当于q=p->next*/
        L[k].cur = L[j].cur;    /*相当于p->next=q->next*/
        Free_SSL(L, j);
        return OK;
    }
    
  • 静态链表的长度
    /*初始条件:静态链表L已存在。操作结果:返回L中数据元素的个数*/
    int ListLength(StaticLinkList L)
    {
        int j=0;
        int i=L[MAXSIZE-1].cur;
        while (i)
        {
            i=L[i].cur;
            j++;
        }
        return j;
    }
  • 静态链表的优缺点
    ①有点:插入删除只需要修改游标 ②缺点:没有解决连续存储分配带来的表长难以确定的问题,失去了顺序存储结构随机存取的特性。
循环链表
  • 是什么:单链表的尾指针指向头结点,形成环。
  • 干什么:解决从一个结点出发,可以访问到链表的全部结点。
  • 与单链表的差异在循环的判断,原来是判断p->next是否为空,现在是p->next是否为头结点。
  • 有头指针时,访问第一个结点为O(1),想访问最后一个结点也为O(1),就改用尾指针rear,此时头指针为rear->next,rear->next->next; 就为头结点,时间为O(1)。
  • 有尾指针合并链表非常简单:
    在这里插入图片描述
     p=rearA->next;
     rearA->next=rearB->next->next;
     q=rearB->next;
     rearB->next=p;
     free(q);

在这里插入图片描述

双向链表
  • 是什么:单链表的每个节点中,再设一个指向前驱的指针。
  • 存储结构
    typedef struct  DulNode
    {
        ElemType data;
        struct DuLNode *prior;
        struct DuLNode *next;
    }DulNode, *DuLinkList;    
  • p->next->prior = p = p->prior->next
  • 单链表可以有循环链表,双向链表也可以有循环链表。
    双向链表的循环带头结点的空链表如图
    在这里插入图片描述
    非空的循环的带头结点的双向链表如图在这里插入图片描述
  • 双向链表可反向遍历查找数据,但在插入和删除时,需要更改两个指针变量。注意顺序。
    插入
    在这里插入图片描述
	s->prior = p;
	s->next = p->next;
	p->next->prior =s;
	p->next = s;

删除在这里插入图片描述

	p->prior->next=p->next;
	p->next->prior=p->prior;
	free(p);

总结

  • 首先定义线性表,给出基本操作。
  • 线性表两大结构。由顺序结构插入删除不方便引出链式存储结构,又介绍了链式存储结构的不同形式:单链表、循环链表、双向链表。还有不用指针的静态链表。在这里插入图片描述
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值