4.15k likes | 4.41k Views
第 2 章. 数据结构. 第 2 章目标. 线性表及其两种存储方式 受限的线性结构的处理 二叉树的概念及特点 二叉树的遍历及存储 二叉树的应用 图形的概念及存储方法 图形的遍历方法 图的最小生成树问题 图形的应用 几种数据查找方法 几种数据排序方法. 基于工作过程的教学任务. 利用线性处理方法实现图书基本信息管理解决方案 利用树形处理方法实现数据编码解决方案 利用图形处理方法实现最短路径解决方案 信息查找解决方案的实现 数据排序解决方案的实现. 数据结构简介. 解决非数值计算问题,关键已不再是数学分析和计算方法,而是要设计出合适的 数据结构 例如
E N D
第2章 数据结构
第2章目标 • 线性表及其两种存储方式 • 受限的线性结构的处理 • 二叉树的概念及特点 • 二叉树的遍历及存储 • 二叉树的应用 • 图形的概念及存储方法 • 图形的遍历方法 • 图的最小生成树问题 • 图形的应用 • 几种数据查找方法 • 几种数据排序方法
基于工作过程的教学任务 • 利用线性处理方法实现图书基本信息管理解决方案 • 利用树形处理方法实现数据编码解决方案 • 利用图形处理方法实现最短路径解决方案 • 信息查找解决方案的实现 • 数据排序解决方案的实现
数据结构简介 • 解决非数值计算问题,关键已不再是数学分析和计算方法,而是要设计出合适的数据结构 • 例如 • 图书信息检索 • 人机对弈问题 • 城市通信布线等
有关基本概念和术语2-1 • 数据是信息的载体,是对客观事物的符号表示,是所有能输入到计算机中并被计算机识别、存贮和处理的符号的集合,是计算机程序使用、加工的原料和输出的结果 • 数据元素是数据的基本单位 • 数据对象是具有相同性质的数据元素的集合 • 数据项/字段/域是数据元素的详细描述,是数据处理中不可分割的最小单位
有关基本概念和术语2-2 • 数据结构是指组成数据的元素之间的结构关系 • 从数据结构的概念中可知,一个数据结构由两个要素组成:数据元素的集合和关系的集合 • 数据结构的形式定义为:数据结构是一个二元组 • D.S=(D, R) • D是数据元素的有限集; • R是D上关系的有限集; • 表示关系的基本单位是有序对(x,y)或x,y
1 2 5 3 4 7 8 6 9 示例 • linear=(D, R) • D={1,2,3,4,5,6,7,8,9} • R={<1,2>,<2,3>,<3,4>,<4,5>,<5,6>, <6,7>, <7,8>,<8,9>} • 逻辑图为:
示例 • tree=(D, R) • D={a,b,c,d,e,f,g,h,i,j,k,l} • R={(a,b),(a,c),(a,d),(b,e),(b,f),(b,g),(c,h),(c,i),(c,j),(d,k),(d,l)} • 逻辑图为:
数据结构分类 • 数据结构是将数据存储到计算机中的一种方式,以便有效使用这些数据 • 经过精心设计的每种数据结构都有一些独特的属性,使其适用于解决各种不同的问题 数据结构的类型 集合 逻辑结构 线性结构 树 物理结构 图 顺序存储结构 链式存储结构
逻辑数据结构图例 • 线形结构 元素关系为“一对一” • 树形结构 元素关系为“一对多” • 图状结构 元素关系为“多对多” • 集合结构 元素关系为“同属于“
数据类型 • 数据类型是指程序设计语言中允许的变量类型 • 程序中出现的每一个变量都必须与一个且仅与一个数据类型相联系,它规定了两方面内容 • 该变量可以设定的值的集合 • 这个集合上的一组运算 • 数据类型分为两类 • 基本数据类型,如整型、实型、布尔型 • 结构数据类型,如数组、结构体,他们的值可再分
测验 • 列举数据结构的逻辑结构分类 • 列举数据结构的物理结构分类 • 画出下列二元组对应的图形,并判断其所属逻辑结构的类型 • B=(D, R) • D={d1, d2, d3, d4, d5} • R={(d5, d4), (d4, d3), (d3, d1), (d1, d2)}
图书基本信息管理解决方案 • 图书基本信息的存储、插入、删除和修改是其信息管理的主要内容,采用合理的存储方式会有利于其他处理操作的执行,对每种管理操作则应采用科学的算法,以期节省时间和内存资源。 • 图书信息管理可采用线性结构的处理方式,主要进行插入、删除、查找和排序。线性结构在计算机中有两种存储形式:顺序存储和链式存储。
线性表 • 线性表是由n(n>=0)个数据元素(结点)a1,a2,a3, ……an组成的有限序列 • 其中 • n为数据元素的个数,也称为表的长度 • 当n=0 时,称为空表 • 非空的线性表(n>0) ,记作:(a1,a2,a3, ……an),ai是不可再分的基本单位,称为原子项
线性表的逻辑特性 • 有且仅有一个开始结点a1(无直接前趋) • 有且仅有一个终端结点an(无直接后继) • 其余的结点ai都有且仅有一个直接前趋ai-1和一个直接后继ai+1 • ai是属于某个数据对象的元素,它可以是一个数字、一个字母或一个记录
示例 • 一年有4季(春,夏,秋,冬) • 人民币面值构成线性表(1分,2分,5分,1角,2角,5角,……)
补充内容:数组 • 数组 • 数组不是一种数据类型 • 数组是一组相同类型的变量集合 • 数组名称相同,用不同下标表示不同元素 • 数组必须先声明后使用 • 声明时给出数组名、类型、维数和数组大小
数组定义 • 数组:用于保存一批相同类型的数据,属构造类型 • 数组名:一批同类型数据共有的名字 • 下标:数组中的各个数据用下标来区分
一维数组 • 一维数组的一般形式 : 类型说明符 数组名[常量表达式] 如:float score[30] • float 是类型说明符,定义了该数组中元素的类型 • score 是数组名,是这一组数据共有的的名字 • 30是常量表达式,表示数组的长度,即数组元素的个数 也就是说:数组score 可以保存30个float型的数据 • 注意:定义数组时长度必须是确定的值 例如 : int aa[ ]; (error) int n; float xs[n]; (error)
a[0] a[1] a[2] a[3] a[4]……a[49] 一维数组 • 数组元素的引用: 数组名[下标] 如:a[i]是数组 a 中下标为 i 的数组元素; a[0]是下标为 0 的数组元素 • C语言的规定:数组元素的下标从0开始 如数组a[50]包含50个数组元素,分别是:a[0] a[1] a[2] …… a[49],即不存在数组元素a[50] • 若使用a[50]是危险的,为什么?
一维数组 • 一维数组的存储格式:每个数组占用一片连续的存储单元,按下标次序依次存放各元素 如 int data[10]; 数组data占4*10=40个字节 • 数组初始化: • 定义数组时对数组元素赋予初值;如: int a[6]={0,1,2,3,4,5}; • 对全部元素赋初值时,可不指定数组长度;如: int a[ ]={0,1,2,3,4,5}; • 若只给部分元素赋初值,长度不能省略;如: int a[6]={0,1,2};
例: 输入50个整数按逆序输出 #include <stdio.h> main() {int a[50], i; /* 定义数组a*/ for (i=0; i<50; i++) /* 从键盘输入50个整数 */ scanf("%d", &a[i]); for (i=49; i>=0; i--) /* 逆序输出50个整数 */ printf("%4d", a[i]); }
结构类型4-1 • 结构类型是若干个类型相同或不同数据项的集合。定义一个结构类型的一般形式为: Struct 结构类型名 { 数据类型 数据项1; 数据类型 数据项2; …… 数据类型 数据项n; }
结构类型4-2 • 结构变量的定义: • 间接定义法:先定义结构类型,再定义结构变量 Struct 结构类型名 变量名; • 直接定义法:在定义结构类型的同时,定义结构变量 Struct [结构类型名] { 数据类型 数据项1; 数据类型 数据项2; …… 数据类型 数据项n; }变量表;
结构类型4-3 • 结构变量的引用: • 普通变量: 结构变量名.成员名 • 指针变量: (* 结构指针变量名).成员名 结构指针变量名->成员名
结构类型4-4 Struct student { char no[15]; char name[15]; int age; }stu1, *stu2; Stu1.age = 18; Stu2->age = 20; (* stu2).age = 20; Struct student stu3;
内容 元素序号 存储地址 1 A1 L 2 A2 L+1 … … … i Ai L+(i-1) … … … n An L+(n-1) 顺序表2-1 • 在内存中用一块地址连续的存储空间顺序存放线性表的各元素,用这种形式存储的线性表称为顺序表 • 若一个数据元素仅占一个存储单元,则其存储方式见图,则第i个数据元素的地址为: LOC(ai)=LOC(a1)+(i-1)
顺序表2-2 • 若每个数据元素占用L个存储单元,则 • 第i 个数据元素的存储位置为: LOC(ai)= LOC (a1)+(i-1)*L • LOC (a1)称为基地址(第一个数据元素的存储位置) • 显然,数据元素在顺序表中物理位置取决于数据元素在线性表中的逻辑位置 • 可得出顺序表的特点 • 逻辑位置相邻,其物理位置也相邻
顺序表的特点 • 为线性表提供的空间必须是连续的 • 元素的逻辑次序与物理次序是一致的 • 每个结点和其后继存放在相隔一定距离的位置上 • 内存地址可以用一个简单直观的公式表示 • 两个相邻元素间不能有空闲位置 • 存取表中任何一个结点所使用的时间是相同的
A a1 a2 a3 a4 a5 a6 a7 0 1 2 3 4 5 6 顺序表的定义 • 用一组连续的存储单元(地址连续)依次存放线性表各数据元素 #define maxlen 8 Typedef struct{ elementtype data[maxlen]; int last; }seqlist; 定义:seqlist L, *L1; 引用:L.last=-1; L1->last=-1; (* L1).last=-1;
顺序表的插入操作 x x • 在第i(1 <= i <= n+1)个元素之前插入一个新的数据元素 x • 使长度为n的线性表变为长度为n+1的线性表 • 从表尾开始到i个依次向后移动一个位置(共需移动n-i+1个数据元素) • 最后将x存入插入位置中,表长n增1,插入成功 • 顺序表插入算法平均约需移动一半结点 a1 a2 …… ai-1 ai …… an
顺序表插入操作分析 • 顺序存储方式在高级语言中是使用数组来表示的 • 数组所占用的存储空间是一次性分配的,不能再次进行更改 • 插入元素时要考虑“溢出”问题
顺序表插入算法描述 注意位置与下标的关系 void insert_SeqList(seqlist *L, elementtype x, int i) { int j; if (L->last == maxlen-1) error(“溢出,不能插入”); else if (i<1 || i>L->last+2) error(“插入范围错误”); else { for (j=L->last; j>=i-1; j--) L->data[j+1] = L->data[j]; L->data[i-1] = x; L->last ++; //修改表的长度 } }
顺序表的删除操作 i • 将线性表的第i(1<=i<=n)个结点删除 • 使长度为n的线性表变为长度为n-1的线性表 • 则将表中结点ai+1,ai+2,…,an依次向前移动一个位置(共需移动n-i个数据元素) • 最后将表长n减1,删除成功 • 顺序表删除算法平均约需移动一半结点 a1 a2 …… ai-1 ai ai+1 …… an
顺序表删除操作分析 • 若i=n,只需删除终端结点,不用移动结点 • 若表长度n<=0或删除位置不适当,则输出错误信息,并返回-1 • 当1<=i<n时 • 则将表中结点ai+1,ai+2,…,an依次向前移动一个位置(共需移动n-i个数据元素) • 最后表长n减1,删除成功,函数返回值为0
顺序表删除算法描述 void delete_SeqList(seqlist *L, int i) { int j; if (L->last <0) error(“溢出,不能插入”); if (i>L->last+1 || i<1) error(“删除位置错误”); else { for (j=i; j<=L->last; j++) L->data[j-1] = L->data[j]; L->last --; //修改表的长度 } }
顺序表的优缺点 • 优点 • 无需为表示结点间的逻辑关系而增加额外的存储空间 • 可以方便地随机存储表中的任一结点 • 缺点 • 插入和删除平均须移动一半结点 • 存储分配只能预先进行(静态) • 过大 浪费 • 过小 溢出
顺序表的按值查找操作 • 在顺序表L中查找值为x的元素 • 从第一个元素(i=0)开始,依次与x比较 • 直到找到或i到表尾为止 • 找到返回元素序号,找不到则返回0
顺序表按值查找算法描述 int Location_SeqList(seqlist *L, elementtype x) { int i; i = 0; while ( i <= L->last && L->data[i]! = x) i++; if( i > L->last) return –1; else return i; } 返回的为下标号
链表 • 用一组任意的存储单元(可以是无序的)存放线性表的数据元素 • 无序,可零散地分布在内存中的任何位置上 • 链表的特点 • 链表中结点的逻辑次序和物理次序不一定相同,即逻辑上相邻未必在物理上相邻 • 结点之间的相对位置由链表中的指针域指示,而结点在存储器中的存储位置是随意的
Head 数据域 指针域 a1 a2 an^ … 链表结点的组成 • 数据域----表示数据元素自身值 • 指针域(链域) ----表示与其它结点关系 • 通过链域,可将n个结点按其逻辑顺序链接在一起(无论其物理次序如何)
Head a1 a2 an^ … 单链表 • 每个结点只有一个链域 • 开始结点----(无前趋)用头指针指向 • 最后结点----(无后继)指针为空,用^或null表示 • 其它结点----由其前趋的指针域指向 • 空表----头指针为空的链表
Data next • 指针p与指向的结点关系示意图 p 结点 (*p) • 说明: • p---指向链表中某一结点的指针 • *p---表示 由指针p所指向的结点 • p->data----表示由p所指向结点的数据域 • p->next----表示由p所指向结点的指针域
P head->data head->next 思考:说出下列语句的意义 定义: NODE *p, *q; 1. q = p; 2. q = p -> next; 3. p = p -> next; 4. p = p -> next -> next; 5. p -> data = x; 6. q -> next = p -> next;
链表的总结 • 验证一个结点是否是表尾,只须考察该结点的next域值是否为NULL(空指针) • 在链表中,删除一个结点或插入一个结点,只需改变一个或两个相关结点的指针,不对其它结点产生影响 • 由于链式结构的“结点”是动态产生的,只要内存有足够的空间,就可以存储任意长度的线性表,一般不会产生溢出 • 相关操作 • 申请空间 • 动态回收空间
相关函数 • 申请空间 • P=(NODE*)malloc(sizeof(NODE)); • 对指针p赋值使其指向某一结点(按需生成一个NODE结点类型的新结点) • 其中: • (NODE*)----进行类型转换 • Sizeof(NODE) ----求结点需占用的字节数 • Malloc(size) ----在内存中分配size个连续可用字节的空间 • 释放空间 • Free(p); ----系统回收p结点(动态)
链表的初始化 Void initial_List(node *L) { L = (node *) malloc (sizeof(node)); L -> next = null; } /*end*/
L a3^ a1 a2 P 思考:1. 回忆顺序表中如何求得表中元素个数? 2. 链表长度如何获取?
链表的长度求取 int Length_LinkList(node L) { int n=0; node *p=L->next; while(p!=Null){ • p=p->next; • n++; • } return n; }