数据结构 ②线性表(数组与链表)

talking:这里会先介绍线性表,一种最简单的储存方式 。


一、线性表
1. 定义:零个或多个元素的有限序列;

一对一的关系构成线性表;

即有头有尾,前后相接,只有一条链;

在较复杂的线性表中,一个数据元素通常由多个数据项组成,这里的线性表一般建立在结构体的基础上

2. 重要属性:
  • 直接前继元素
  • 直接后继元素
  • 线性表的长度n:线性表元素的个数
  • 空表(n=0)
3.储存方式:
  • 顺序储存结构:数组

  • 链式储存结构:链表

这两种都只的是在内存中的物理存储方式;

4.抽象数据类型
  • 数据类型:性质相同的值的集合以及定义在这个集合上的操作的总称;
二、数组(线性表的顺序储存结构)

此处强调数据结构的线性表,故只介绍一维数组。

1.概述:
  • 定义:用一段地址连续的存储单元依次存储线性表的数据元素;

  • 特点:在内存中占据连续的储存空间;在插入等算法中需移动大量数据,效率较低

  • 三个必要属性:

    • 储存空间的起始位置
    • 线性表的最大存储容量
    • 线性表的当前长度
  • 线性表长度<=数组长度

  • 存取时间性能为O(1),为随机存储结构

2.算法:创建、读取、插入、删除

记得对数组是否为空/满进行检验(要不检验空要不检验满,依该函数实际而定,例如:读取,删除时检验其是否为空,插入时检验其是否为满)

可以注意算法对数据元素的位置处在表尾的处理
以插入函数为例:

//初始化线性表
typedef struct{
	int data[maxsize];      //假装最多能放下maxsize个元素(数组长度)
	int length;    			//线性表长度,表示当前已经使用的长度
}list;
//然后假装线性表里面存放了数据
//插入函数:现将e插入到第i个元素的位置
int listInsert(list *l,int i,int e){  //l为数组首地址
	int j;
	if(l->length==maxsize){    //注意,这个地方不能直接写length,此时表示线性表已满时
		return 0;
	}
	if(i>l->length-1 || i<1){   //i不在范围内时
		return 0;
	}
	if(i<l->length-1){     //i不在末尾时
		for(j=l->length-1;j>=i;j--){
			l->data[j+1]=l->data[j];
		}
	}
	l->data[i]=e;    //把这个值赋值给第i个元素
	l->length++;
	return 1;
}

可以看到该算法重复度很少,效率较高。


三、链表(线性表的链式储存结构)
1.概述:
  • 定义:在内存中的储存位置并不连续,靠指针的作用将其一个一个串起来,形成一个链式结构;

  • 特点:在内存中不占据连续的储存空间;整体效率较高,插入等算法中只需要简单的修改指针域的值;

  • 属性:

    • 指针域:存放指针的区域,可以有一个两个或者多个
    • 数据域:存放数据的区域
    • 结点:指针域和数据域共同构成一个结点,很多结点相连构成一个链表
    • 头结点:在第一个结点之前,其指针域里的指针指向第一个结点,对于链表来说它不是必须存在的。
    • 头指针:头指针不是头结点里面的指针!如果头结点存在,则指向头结点;如果没有头结点,则指向第一个结点,对于链表来说它必须存在。
2.单链表
  • 简单来说,其特点为:尾结点指向NULL或^,指针域中只有指向下一个结点的指针;

  • 算法:读取、插入、删除、整表创建、整表删除;

    需要注意的地方:

    • 单链表的插入时,在所有检验语句之后,即判断确定可以插入时再为新结点申请内存空间(使用malloc()函数);
    • 单链表的删除中效率更高的方法是以要删除的前一个结点为主,用p->next->next指向要删除的后一个结点(假设p是要删除的结点);
    • 删除时注意删除语句的顺序,记得用free()让系统收回一个结点,释放内存;
    • 整表创建包括头插法、尾插法,使用malloc()函数;
    • 整表删除时记得对已释放的结点的指针域的替代与保存,以便下一个结点的删除
//单链表的整表删除,初始条件:线性表l已存在且有头节点;
int Clearlist (linklist *l){   //l为头指针;
	linklist p,q;
	p=(*l)->next;   //p指向第一个结点
	while(p!=NULL){
		q=p->next;     //用q暂时存储下一个结点,防止在释放p的空间后找不到下一个结点
		free(p);
		p=q;
	}
	(*l)->next=NULL;
	return 1;
}
  • 在实际编程时,每个块都要记得对链表是否为空/满进行检验(要不检验空要不检验满,依该函数实际而定,例如:读取,删除时检验其是否为空,插入时检验其是否为满)
  • 注意检验语句的先后顺序,尽量使其效率最高,时间复杂度最低。
  • 要经常检查头指针是否为NULL,避免其初始化失败或者传值失败。

这里有自己做的一个 家庭财务管理系统 的实例,当时没有学数据结构,代码的层次等也不是很清晰,没有使用free()语句,但是对于链表的实际应用也有参考意义。
地址如下:https://blog.csdn.net/qq_44263261/article/details/95222343

3.静态链表(数组和链表的转化)
  • 静态链表实际是用数组来描述链表,也被称为游标实现法

  • 也有其类似于单链表的插入、删除等算法,实际应用中基本不用,但可以以之拓展链表的应用,更好的理解链表的算法。

  • 其算法最关键的部分在于

    • 将数组每个元素像结点那样分成两个部分,储存数据和下一个元素的地址;
    • 将已用的部分和未用的部分分别当作两个链表;
4.循环链表
  • 最后一个结点的指针域存放的是第一个结点的地址,相当于将其首尾相连
  • 小技巧:如果第一个结点和尾结点的地址都需要知道,可以将头指针指向尾结点,可以大大增加效率;
5.双向链表
  • 指针域有两个指针,分别指向前一个结点和后一个结点
  • 用空间来换取时间
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值