数据结构基本概念与线性表操作

基本概念和术语

  • 数据结构是研究非数值计算的程序设计问题中计算机的操作对象以及它们之间的关系和操作。
  • 数据:所有能输入到计算机中的描述客观事物的符号。
  • 数据元素(Data Element) / 结点(Node) / 记录(Record):数据的基本单位
  • 数据项(Data Item) / 域(Field):组成数据元素的有独立含义的、不可分割的最小单位
  • 数据对象(Data Object):性质相同的的数据元素的集合,是数据的一个子集
  • 数据 > 数据对象 > 数据元素/结点/记录 > 数据项/域
  • 数据结构研究3个方面:逻辑结构 存储结构 运算
  • 算法的定义:为了解决某类问题而规定的一个有限长的操作序列
  • 算法的特性:1.0个或多个输入 2.1个或多个输出 3.确定性 4.有穷性 5.有效性
  • 算法的评价:1.正确性 2.可读性 3.健壮性 4.高效性
  • 算法效率的度量:1.事后统计(实测法) 2.事前分析估计(分析估算法)
  • 抽象数据类型:是用户定义的包括数据对象,数据对象上关系的集合,数据对象的基本操作的集合。
    ADT =DSPADT抽象数据类型名{
    	数据对象:<数据对象的定义>
    	数据关系:<数据关系的定义>
    	基本操作 :<基本操作的定义>
    } ADT抽象数据类型名
    
  • 顺序存储结构是借助元素在存储器中的相对位置来表示数据元素之间的逻辑关系,通常借助程学设计语言中的数组类型来描述。
  • 顺序存储结构要求所有的元素依次存放再一段连续的存储空间中,而链式存储结构无需占用一整块的存储空间,但为了表示结点之间的关系,需要给每个结点附加指针域,用于存放后继元素的存储地址。所有链式存储结构通常借助程序设计语言的指针类型来描述。
    #define OK 1
    #define ERROR 0
    #define OVERFLOW -2
    typedef int Status;
    

线性表

从逻辑结构上看,线性表是一种是由n个同属于一个数据对象的数据元素的有序序列;要实现线性表存储,可以是顺序存储(顺序表)或链式存储(链表);无论是顺序表还是链表,我们不仅要研究其存储结构定义本身,还要研究各种运算实现。
线性表中的数据元素类型可以为简单类型,也可以为复杂类型
许多实际应用问题所涉及的基本操作有很大的相似性,不应该为每个具体应用单独编写一个程序
从具体应用中抽象出共性的逻辑结构和基本操作(抽象数据类型),然后实现其存储结构和基本操作

顺序表

线性表的顺序存储表示又称为顺序存储结构或顺序映像。逻辑上相邻的,物理存储单元也相邻。用一组地址连续的存储单元依次存储线性表的元素,可以通过数组V[n]来实现。基本操作有取值、查找、插入、删除。

  • 线性表的顺序表示和实现
    #define MAXSIZE 100
    /*-----顺序表的类型定义-----*/
    typedef struct
    {
    	ElemType *elem;	//指向数据元素的基地址 
    	int length;		//线性表当前长度 
    }SqList;
    
  • 顺序表初始化
    /*-----初始化线性表 用引用-----*/
    Status InitList_Sq(SqList &L)
    {
    	L.elem = new ElemType[MAXSIZE]; //为顺序表分配空间
    	if(!L.elem) exit(OVERFLOW); //存储分配失败
    	L.length = 0;	//空表长度为0
    	return OK;
    } 
    
    /*-----初始化线性表 用指针-----*/
    Status InitList_Sq(SqList* L)
    {
    	L->elem = new ElemType[MAXSIZE]; //为顺序表分配空间
    	if(!L->elem) exit(OVERFLOW); //存储分配失败
    	L->length = 0; //空表长度为0
    	return OK; 
    }
    
  • 销毁顺序表
    /*-----销毁顺序表-----*/ 
    void DestroyList(SqList &L)
    {
    	//释放存储空间 
    	if(L.elem) delete[] L.elem; 
    } 
    
  • 清空顺序表
    /*-----清空顺序表-----*/ 
    void ClearList(SqList &L)
    {
    	//将顺序表长度置为0 
    	L.length = 0;
    } 
    
  • 求顺序表的长度
    /*-----求顺序表的长度-----*/ 
    int GetLength(SqList L)
    {
    	return L.length;
    } 
    
  • 判空
    /*-----判断顺序表L是否为空-----*/ 
    int IsEmpty(SqList L)
    {
    	if(L.Length == 0) return 1;
    	else return 0;
    } 
    
  • 取值 顺序表的存取是随机存取
    /*-----取值-----*/ 
    int GetElem(SqList L, int i, ElemType &e)
    {
    	//获取线性表L中某个数据元素的内容
    	if(i<1||i>L.length) 
    		return ERROR; //i的值不合理,就返回ERROR
    	e = L.elem[i-1];
    	return OK; 
    } 
    
  • 查找
    /*-----查找-----*/ 
    int LocateElem(SqList L, ElemType e)
    {
    	//在线性表L中查找值为e的数据元素 
    	for(i=0; i<L.length; i++)
    		if(L.elem[i] == e) return i+1;//找到了就返回是第几个元素 
    	return 0;//遍历线性表却没找到,返回0 
    } 
    
    平均查找长度ASL = (n+1)/2
    顺序表查找算法平均时间复杂度为O(n)
  • 插入
    /*-----顺序表插入算法-----*/ 
    Status ListInsert_Sq(SqList &L, int i, ElemType e)
    {
    	if(i<1 || i>L.length+1) return ERROR; //i不合法
    	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;
    }
    
    顺序表插入算法平均时间复杂度为O(n)
  • 删除
    /*-----顺序表删除算法-----*/ 
    Status ListDelete_Sq(SqList &L, int i)
    {
    	if((i<1) || (i>L.length)) return ERROR; //i不合法
    	for(j=i; j<=L.length-1; j++)
    		L.elem[j-1] = L.elem[j]; //被删元素之后的元素前移 
    	--L.length;	//表长减1 
    	return OK; 
    } 
    
    顺序表删除算法的平均时间复杂度为O(n)
  • 总结
    1.顺序表逻辑结构与存储结构一致
    2.顺序表的优点:存储密度大;可以随机存取表中任一元素
    3.顺序表的缺点:插入、删除某一元素时要移动大量元素;浪费存储空间;属于静态存储,数据元素个数不能自由扩充

链表

链式存储,逻辑上相邻的数据元素物理上未必相邻。线性表的链式表示又称为非顺序映像或链式映像
结点:由数据域和指针域组成
单链表:结点只有一个指针域
双链表:结点有两个指针域的链表
循环链表:首尾相接
头指针:指向链表第一个结点的指针
首结点:链表中存储第一个数据元素的结点
头结点:位于首结点之前,数据域内只放空表表中或表长等信息,指针域指向首结点
链表的优点:数据元素可以自由扩充;插入删除等无需移动数据只需修改指针效率高
链表的缺点:存储密度小;存取效率不高,必须采用顺序存取

单链表
  • 单链表的存储结构定义

    /*-----单链表的存储结构定义-----*/
    typedef struct LNode
    {
    	ElemType data;	//数据域
    	struct LNode *next; //指针域 
    }LNode, *LinkList; 
    
  • 单链表初始化

    /*-----单链表初始化-----*/
    Status InitList_L(LinkList &L)
    {
    	L = new LNode;	//生成新结点 
    	L->next = NULL;	//指针域置空 
    	return OK;
    }
    
  • 销毁单链表

    /*-----销毁单链表-----*/
    Status DestoryList_L(LinkList &L)
    {
    	LinkList p;	//辅助指针 
    	while (L)	//当L为到达表尾 
    	{
    		p = L;			//辅助指向待释放结点 
    		L = L->next;	//指向下一结点 
    		delete p;		//释放当前结点 
    	}
    	return OK;
    } 
    
  • 清空单链表

    	/*-----清空单链表-----*/
    	Status ClearList(LinkList &L)
    	{
    		//将L重置为空表 
    		LinkList p, q;
    		p = L->next;	//p指向首结点 
    		while (p)		//p非空 即未到达表尾 执行循环 释放结点 
    		{
    			q = p->next;	//将后继结点地址拷贝一份 
    			delete p;		//释放当前结点 
    			p = q;			//工作指针指向后继结点 
    		 } 
    	}
    
  • 求表长

    /*-----求表长-----*/
    int ListLength_L(LinkList L)
    {
    	//返回L中数据元素/结点个数
    	LinkList p;		//工作指针
    	p = L->next;	//指向首结点
    	int i = 0;		//计数器 
    	while (p) 
    	{
    		i++;			//计数器增1 
    		p = p->next;	//指向后继结点 
    	}
    	return i;
    } 
    
  • 判空

    /*-----链表判空-----*/ 
    int ListEmpty(LinkList L)
    {
    	//若L是空表 返回1 否则返回0 
    	if(L->next)
    		return 0; 
    	else 
    		return 1;
    } 
    
  • 查找
    1.从第一个结点起,依次当前结点数据域与e的值
    2.如果找到一个结点数据域与e相等,则返回其在链表中的位置或地址
    3.如果查遍整个链表都没有找到数据域的值与e相等的结点,则返回0或NULL
    算法时间复杂度为O(n)

    /*-----查找值为e的元素-----*/
    LinkList LocateElem_L(LinkList L, ElemType e)
    {
    	//返回L中值为e的元素的地址,查找失败返回NULL
    	LinkList p = L->next;	//工作指针p指向首结点 
    	while(p && p->data!=e)	//未到达表尾且未找到待查元素 执行循环 
    		p = p->next;		//指向后继结点 
    	return p; 
    } 
    
    int LocateElem_L(LinkList L, ElemType e)
    {
    	//返回L中值为e的元素序号,查找失败返回0
    	LinkList p = L->next; //工作指针p指向首结点
    	int j = 1;
    	while (p && p->data!=e) //未到达表尾且未找到待查元素 执行循环
    	{	
    		p = p->next;	//工作指针p后移一位 
    		j++;			//位置序号后移增1 
    	} 
    	if (p)	//p非空 即跳出循环的原因是p->data==e为真(即p->data!=e为假)
    		return j;	//返回元素序号 
    	else //跳出循环的原因是p=null 即到达表尾仍未找到待查元素 
    		return 0;	//查找失败返回0 
    }
    
  • 插入
    1.找到插入位置p
    2.生成新结点*s
    3.将新结点数据域置为e
    4.新结点指针域指向第i个结点ai
    5.令结点*p的指针域指向新结点*s
    算法时间复杂度为O(1)

    /*-----在L中的第i个元素之前插入e-----*/
    Status ListInsert_L(LinkList &L, int i, ElemType e)
    {
    	LinkList p = L; //工作指针初始化 
    	int j = 0;		//位置序号标记初始化 
    	while (p && j < i-1) //当p未到达表尾或j未到达i-1(插入位置)
    	{
    		p=p->next;	//工作指针后移 
    		++j;		//位置序号后移 更新同步 
    	}  
    	if (!p || j>i-1)
    		return ERROR; //i大于表长+1或者小于1 
    	
    	s = new LNode;		//生成新节点 
    	s-data = e;			//数据域置为e 
    	s->next = p->next;	//新结点指向第i个节点 
    	p-next = s;			//前驱结点指向新结点 
    	return OK;
    }
    
  • 删除
    1.找到第i结点的前驱i-1结点*p,则p->next为待删除结点的地址
    2.临时保存结点i的地址在q中以备释放
    3.令p->next指向i+1结点
    4.将第i结点的值保留在e中
    5.释放第i结点的空间
    算法时间复杂度为O(1)

    /*-----删除第i个结点-----*/  
    Status ListDelete_L(LinkList &L, int i, ElemType &e)
    {
    	LinkList p = L; 
    	int j = 0;
    	while (p->next && j < i - 1) //寻找第i个结点 
    	{
    		p=p->next;
    		++j
    	}
    	if(!(p->next) || j > i - 1)	//删除位置不合理 
    		return ERROR; 
    	q = p-next;			//临时保存被删结点的地址以备释放 
    	p-next = q-next;	//前驱结点跳过待删除结点而指向指向后继结点 
    	e = q->data;		//保存待删除结点的数据域
    	delete q; 			//释放删除节点空间
    	return OK; 
    }
    
  • 前插法建立单链表
    1.从空表开始,读入数据
    2.生成新结点
    3.将读入数据存入新节点数据域中
    4.将该节点插入链表前端

    /*-----前插法建立单链表-----*/ 
    void CreateList_F(LinkList &L, int n)
    {
    	L = new LNode;		//生成头结点 
    	L->next = NULL;		//空表 
    	for(int i = n; i > 0; --i)	//循环读入数据 
    	{
    		LinkList p = new LNode;		//生成新结点 
    		cin >> p->data;		//输入元素值 
    		p->next = L->next;	//新首结点 
    		L->next = p;		//头指针指向新结点 
    	}
    } 
    
  • 尾插法建立单链表

    /*-----尾插法建立单链表-----*/  
    void CreateList_L(LinkList &L, int n)
    {
    	L = new LNode;	//生成头结点
    	L->next = NULL;	//空表
    	LinkList r = L; //尾指针指向头结点
    	for(int i = 0; i < n; ++i)
    	{
    		LinkList p = new LNode;	//生成新结点 
    		cin >> p->data;	//输入元素值 
    		p->next = NULL;	//新结点*p为更新后的表尾结点 
    		r->next = p;	//尾结点后继更新为 新结点*p 
    		r = p;  		//r指向尾结点 
    	} 
    }
    
循环链表
双链表

线性表的合并

有序表的合并

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值