【1.3线性表】

本文详细介绍了线性表的定义、特征及其两种主要存储结构:顺序存储和链式存储。顺序存储利用数组实现,通过元素的位置关系进行访问;链式存储则通过指针链接元素,允许动态调整表长。对于这两种结构,文章列举了它们的基本操作,如初始化、销毁、获取元素、插入、删除等,并给出了相应的C语言实现。此外,还讨论了单链表、双向链表及循环链表的特点和操作。
摘要由CSDN通过智能技术生成

线性表

线性表的定义和特征

  1. 定义:线性表是具有相同特性的数据元素的一个有限序列
    在这里插入图片描述
  2. 特征:线性表中的数据元素最多有一个直接前趋和一个直接后继;线性表中的所有数据都具有相同特性;线性起点没有直接前趋,线性终点没有直接后继。

线性表的类型定义

  1. 抽象类型线性表的定义:

    ADT list{
    数据对象:D={$a_i$|$a_i$属于Elemset,(i=1,2,...,n,n>=0)}
    数据关系:R={<$a_{i-1},a_i$>|$a_{i-1},a_i$属于D,(i=1,2,...,n)}
    基本操作:
        (1) InitList(&L);
       			操作结果:构造一个空的线性表L
        (2)DestroyList(&L);
        		初始条件:线性表L存在
        		操作结果:销毁性表L
        (3)GetElem(L,i,&e); 
                初始条件:线性表L存在,1=<i=<ListLength(L)
        		操作结果:用e返回线性表L中第i个元素
        (4)ListLength(L);
                初始条件:线性表L存在,
        		操作结果:返回线性表中数据元素的个数
        (5)ClearList(&L);
        		初始条件:线性表L存在
        		操作结果:清空L中元素变成空表
        (6)ListEmpty(L);
        		初始条件:线性表L存在
        		操作结果:若线性表为空表,返回True;若线性表非空,返回False;
        (7)locateElem(L,e,compare());
        		初始条件:线性表L存在,compare()是数据元素判定函数
        		操作结果:返回L中第一个满足与e满足判定函数 的数据元素的位序。若这样的元素不存在则返回值为0。       
        (8)PriorElem(L,cur_e,&pre_e)
        		初始条件:线性表L存在
        		操作结果:若cur_e是数据元素且不是第一个,则用pre_e返回他的前驱;否则操作失败,pre_e无意义。    
        (9)NextElem(L,cur_e,&next_e)
        		初始条件:线性表L存在
        		操作结果:若cur_e是数据元素且不是最后一个,则用next_e返回他的后继;否则操作失败,pre_e无意义。   
        (10)ListInsert(&L,i,e);
                初始条件:线性表L存在,1<=i<=ListLength(L)+1
        		操作结果:在线性表L的第i个位置插入元素e,L的长度加一. 
        (11)ListDelete(&L,i,&e);
                初始条件:线性表L存在,1<=i<=ListLength(L)
        		操作结果:删除线性表L的第i个位置插入元素,L的长度减一,并用e返回其值。
        (12)ListTraverse(&L,visit());
                初始条件:线性表L存在
        		操作结果:依次遍历L中的每个元素调用visit()函数。
    }ADT list
    

线性表的顺序存储结构的表示和实现

顺序存储结构

  • 顺序存储定义:把逻辑上相邻的数据元素存储在物理上相邻的存储单元的存储结构。
    在这里插入图片描述
  • 线性表的第一个数据元素 a 1 a_1 a1的存储地址,乘坐线性表的起始地址又叫基地址
  • 假设线性表中每个数据元素需要占l个存储单元内,则第i+1个数据元素的存储位置和第i个数据元素的存储位置之间满足关系:
    LOC( a i + 1 a_{i+1} ai+1)=LOC( a i a_i ai)+l;

顺序表的定义

  • 顺序表的存储结构
    顺序表的特征同C语言中的数组,所以实现时可以用数组表示顺序表。不同的是,线性表表长可变,而数组长度不可变。
    解决方法:用一个变量表示顺序表的长度属性。
  • 线性表的定义:
  •  #define LIST_INIT_SIZE=100 //线性表存储空间的初始分配量
     typedef struct Sqlist{
         	ElemType elem[LIST_INIT_SIZE];
     		int length;
     		};
    

此处 ElemType为数据元素的类型,如果存储的为字母,那么即为“char”类型,如果为整数 即为int类型,根据自己的顺序表的数据元素自行定义。也可以在最前面进行宏定义,如**:#typedef char ElemType**。 如果存储的数据元素为复杂类型,那么可以对元素定义一个结构类型,以多项式为例,结构定义和顺序表的定义参考如下:
多项式:
P n ( x ) P_n(x) Pn(x)= p 1 ∗ x e 1 p_1*x^{e1} p1xe1+ p 2 ∗ x e 2 p_2*x^{e2} p2xe2+…+ p m ∗ x e m p_m*x^{em} pmxem
所以,线性表P=(( p 1 , e 1 p_1,e1 p1e1),( p 2 , e 2 p_2,e2 p2e2),…,( p m , e m p_m,em pmem));

	#define MAXSIZE 1000  //多项式可能达到的最大长度

多项式非零项的定义:

	typedef struct Polynomial{
		float p; //系数
		int e;	//指数
		}

顺序表的定义:

	typedef struct Sqlist{   
		Polymomial *elem	//存储空间当中的基地址
		int length;	//多项式中当前项的个数
		}		

顺序表的操作

函数结果状态代码:

	#define TRUE			1
	#define FALSE			0
	#define OK				1
	#define ERROR			0
	#define INFEASIBLE		-1
	#define OVETFLOW		-2
	
	typedef  int Status;
	typedef char ElemType;

线性表的初始化:

	Status InitList_Sq(Sqlist &L){
		L.elem=new ELemType[MAXSIZE];		//为线性表L分配空间
		if(!l.elem) 
			exit(OVERFLOW);		//存储空间分配失败
		L.length=0;				//空表长度为0
		return ok;
	}

销毁线性表:

	void DestoryList(SqList &L){
		if(L.elem)
			delete L.elem;		//释放
	}

清空线性表:

	void ClearList(SqList &L){
		if(L.elem)
			L.length=0;		//清空
	}

求线性表长度:

	void GetLength(SqList &L){
		return (L.length);
	}

判断线性表是否为空:

	void IsEmpty(SqList &L){
		if(L.length!=0)
			return 0;
		else
			return 1;
	}

顺序表的取值:

	int  GetElement(Sqlist L,int i,ElemType &e){
		if(i<1||i>L.length)
			return ERROR;
		else 
			e=L.elem[i-1];
			return OK;
	}

顺序表的查找 :

	int LocateElem(SqList L,Elemtype e){
		for(i=0;i<=L.length;i++)
			if(L.elem(i)==e)
				return i+1;
		return 0;
	}	

顺序表的插入 :

	void LocateElem(SqList &L, int i,ElemType e){
		if(i<1||i>L.length+1)
			return error;
		if(L.length==MAXSIZE);
				return error;   
		for(j=L.length-1;j<=i-1;j--)
				L.elem( j+1)==L.elem( j);		
				L.elem(i-1)=e;
				L.length++;
		return ok;
	}

顺序表的删除 :

	void LocateElem(SqList &L, int i){
		if(i<1||i>L.length+1)		//判断i值是否合法
			return error;
		if(L.length==0);
				return error;   
		for(j=i;j<=L.length-1;j++)
				L.elem( j-1)==L.elem( j);		
				L.length--;
		return ok;
	}

线性表的链式存储结构的表示和实现

链表的存储结构

  • 用一组物理位置任意的存储单元来存放线性表的数据元素。

  • 这组存储单元既可以是连续的,也可以是不连续的,设置是零散分布在内存当中的任意位置上。

  • 链表中元素的逻辑次序和物理次序不一定相同。

  • **在存储每一个元素的同时,存储下一个元素的地址。由数据域和指针域组成。**记录下第一个元素的地址就可以依次找到下面的 元素。
    在这里插入图片描述
    单链表、双链表、循环列表

    • 单链表结点只有一个指针域,指向下一个元素的地址;
    • 双链表有两个指针域,分别指向他的前驱和后继元素地址;
    • 循环链表只有一个指针域,尾结点的指针指向头结点的地址。

    头指针、头结点、首元结点
    在这里插入图片描述

    • 头指针:指向链表第一个结点的指针。
    • 头结点:头指针的指向对象,头结点的指针域指向首元结点。
    • 首元结点:是在链表中存储第一个数据元素a1的结点。

    空表的表示分类

    • 不带头节点:头指针为空。
    • 带头结点:头结点的指针域为空。

    头结点的好处

    • 便于首元结点的处理
    • 便于空表和非空表的处理。

    头结点的数据域存储内容

    • 可以为空,也可以存放线性表长度等附加信息。但,此结点不能计算入链表的长度。

    链表的存储方式:

    • 顺序表——>顺序存储——>(元素)随机存取
    • 链表——>随机存储——>(元素)顺序存取

链表的定义和表示

单链表

单链表的定义

以下所讲内容都是基于带头结点的单链表展开

  • 单链表的存储结构

在这里插入图片描述
在这里插入图片描述
单链表由表头唯一确定

  • 单链表的定义

    • 结点的声明

       typedef  struct Lnode{		//声明结点的类型和指向结点的指针的类型
       	ElemType data;		//结点的数据域
       	Struct Lnode *next;		//结点的指针域 ,嵌套定义,指针指向和自己一个类型的结点
       }Lnode,*Linklist;		//linklist为指向这种结构体Lnode的指针类型
      
    • 定义链表:

       linklist L;
      
    • 定义结点指针P:

       Lnode *p;等价于linklist p;
      

    例如,存储学生学号、姓名、成绩的单链表定义如下:

     typedef Struct Student{
     	char num[8];		//数据域,存放学号
     	char name[8];		//数据域,存放姓名
     	int score;		//数据域,存放成绩
     	struct student *next;		//指针域
     } Lnode,*linklist;
    

    换种方法,将数据域定义好后,在定义链表,定义方式如下

     typedef Struct ElemType{		//定义数据域
     	char num[8];		//数据域,存放学号
     	char name[8];		//数据域,存放姓名
     	int score;		//数据域,存放成绩
     } ElemType;
    
     typedef Struct Student{		//定义结点
     	ElemType  data;
     	struct student *next;		//指针域
     } Lnode,*linklist;
    
单链表上的操作

单链表的初始化:

	Status InitList_Sq(Linklist &L){
		L=new Lnode;	//L=(linklist) malloc(sizeof(Lnode));//向空间申请一块Lnode大小的存储空间
		L->next==null;
		return ok;
	}

判断单链表是否为空:

	Void  IsEmpty(Linklist L)
	{
		if(L->next)
			return 0;
		else
		    return 1;
	}

单链表的销毁:

	Void DestoryList(Linklist &L)
	{
		 Lnode *p;
		 while(L){
			p=L;
			L=L->next;
			delete p;
		 }
		 return OK;
	}

单链表的清空:

	Void ClearList(Linklist &L)
	{
		 Lnode *p,*q;
		 p=L->next;		//头结点的指针域实为首元结点的地址
		 q=p->next;		//首元结点的指针域,指向下一个结点
		 while(p)
		 {
		    q=p->next;		//首元结点的指针域,指向下一个结点
			delete p;		//释放内存
			p=q	;		//移动到下一个结点
		 }
		 L->next==null;
		 return OK;
	}

求单链表的表长:

	int  GeListLength(linklist L,int i)
	{
		Lnode *p;
		p=L->next;
		i=0;
		while(p)
		{
			i++;
			p=p->next;	
		}
		return i;	
	}

取单链表的第i个元素:

	char  GetI(linklist L,int i,char[8] &data)
	{
	 
		Lnode *p;
		p=L->next;
		for( j=1;j>i;j++)
		{
			p=p->next;
			if(!p||j<i)		//遍历到最后一个结点或者第i个元素不存在
				return error;	
			data=p->data;	
		}
		return OK;

	}

单链表的按值查找

  • 返回结果为地址

     Lnode  *LocateElem(linklist L,char[8] e)
     {
     	Lnode *p;
     	p=L->next;
     	while(p&&p->data!=e)		 //遍历到最后一个结点或者第i个元素不存在               
     			p=p->next;	
     	return p;	
    
  • 返回结果为序号

      char  GetI(linklist L,char[8] e)
      {
      	Lnode *p;
      	p=L->next;
      	int j=1;
      	while(p&&p->data!=e)
      	{	
      		p=p->next;
      		j++;
      	}
      	if(!p)
      		return j;	
      	else
      		return 0;
      }
    

单链表的插入

	Status ListInsert(linklist &L,char[8] e,int i)
	{
		Lnode *p,*s;
		p=L;
		int j=0;
		while(p&&j<i-1)
		{	
			p=p->next;
			++j;	
		}
		if(!p||j>i-1)		//i大于表长加1或小于1,非法位置
			return error;
		s->next=p->next;
		s->data=e;
		p->next=s ;
		return ok; 
	}	

单链表的删除

 Status ListInsert(linklist &L,char[8] &e,int i)
	{
		Lnode *p,*q;
		p=L;
		int j=0;
		while(p&&j<i-1)
		{	
			p=p->next;
			++j;	
		}
		if(!(p->next)||j>i-1)		//i大于表长加1或小于1,非法位置
			return error;
		q=p->next;		//临时保存被删除结点的地址,以备释放
		p->next=q->next		//更改前驱结点的指针域
		e=q->data;		//保存删除结点的数据域
		delete q;		//释放删除结点的空间
		return  OK; 
	}	

单链表的建立

  • 头插法——建立链表,元素插入到链表的表头部

     Void CreatList(linklist &L,int n)
     {
         L=new Lnode;
         L-next=null;
     	for(i=i>0;--i)
     	{
     		Lnode *p; 
     		cin>>p->data;
     		p->next=L->next;
     		L->next=p;
     	}
     }	
    
  • 尾插法——建立链表,元素插入到链表的尾部

     Void CreatListrt(linklist &L,int n)
     {
         Lnode *r
         L=new Lnode;
         L-next=null;
         
     	for(i=0i<n;++i)
     	{
     		Lnode *p; 
     		cin>>p->data;
     		p->next=null;
     		L->next=p;
     		r->next=p;
     		r=p;
     		
     	}
     }
    

循环链表

循环链表是一种尾结点指针域指向头结点的链表,整个链表构成一个环。
在这里插入图片描述
优点:从链表中任何一个结点出发,都可以找到链表中的其他结点。
注意:由于循环链表当中不存在null指针,因此,遍历链表时,停止条件为:指针域为头结点
对比

单链表循环链表
循环条件p!=nullp!=L
p->next !=nullp->next !=L

注意:通常情况下,表的操作在表的首尾进行。使用尾指针对循环链表进行表示:

	首元结点:R->next->next
	尾结点:R

在这里插入图片描述

带尾指针的循环链表的合并

在这里插入图片描述
步骤

  1. 保存A链表的头指针,p=Ta->next;

  2. A链表的尾指针指向B链表的首元结点,Ta->next=Tb->next->next;

  3. 释放Tb的头结点 delete Tb->next;

  4. B链表的尾指针指向A链表的头结点Tb->next=p;

    Linklist connect(LInklist Ta,Linklist Tb)
    {
    	Lnode*p;		//假设;两个链表非空
    	p=Ta->next;		//保存Ta的头指针
    	q=Tb->next;
    	Ta->next=q->next;  // A链表的尾指针指向B链表的首元结点
    	delete q;		// 释放Tb的头结点
    	Tb->next=p;			//B链表的尾指针指向A链表的头结点
    	return Tb
    }
    

双向链表

  • 在单链表和循环链表中,我们只能找到一个结点的后继结点,而忽略了前驱结点,因此当我们想要研究前驱结点时,就需要引入双向链表。
  • 在双向链表中存在三个域,前驱指针域、后继指针域、数据域
    在这里插入图片描述
双向链表的定义
	typedef  struct  DuLnode
	{		//声明结点的类型和指向结点的指针的类型
			ElemType data;		//结点的数据域
			Struct DuLnode *prior,*next;		//结点的指针域 ,嵌套定义,指针指向和自己一个类型的结点
	}DuLnode,*Linklist;		//linklist为指向这种结构体Lnode的指针类型

在这里插入图片描述

双向循环链表
  • 头结点的前驱指针指向尾结点
  • 尾结点的后继指针指向头结点
  • 空表的前驱指针和后继指针都指向本身

在这里插入图片描述
在这里插入图片描述
注意:在双向链表中的某些操作,如:求链表长度、获得链表中的结点,只涉及一个方向的指针,因此其操作同单链表;但在删除、插入两个操作时,则需要同时修改两个方向的指针。

双向链表的插入

在这里插入图片描述

序号含义:

  1. X的前驱指针指向B的前驱结点,即s->prior=p->prior;

  2. B的前驱结点的后继指针指向X结点,即p->prior->next=s;

  3. X结点的后继指针指向B结点,即s->next=p;

  4. B结点的前驱指针指向X结点,即p->prior=s;

    void ListInsert_Dul(Dulinklist &L,int i,ElemType e)
    {
    	if(!(p=GetElemP_Dul(L,i))) 		//找到第i个元素,并让指针p指向它
    		return error;		//如果i不存在,指向非法地址,报错。
    	DuLNode *s;
    	s->data=e;
    	s->prior=p->prior;
    	p->prior->next=s;
    	s->next=p;
    	p->prior=s;
    	return ok;
    }
    
双向链表的删除

在这里插入图片描述

序号含义:

  • A结点的后继指针指向B的后继结点C,即p->prior->next=p->next;

  • C结点的前驱是A结点,即p->next->prior=p->prior;

     void ListInsert_Dul(Dulinklist &L,int i)
     {
     	if(!(p=GetElemP_Dul(L,i))) 		//找到第i个元素,并让指针p指向它
     		return error;		//指向非法地址,报错。
     	p->prior->next=p->next;
     	p->next->prior=p->prior;
     	delete p;
     	return ok;
     }
    

三种链表时间效率的对比

在这里插入图片描述

顺序表和链表的对比

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值