4.8k likes | 4.96k Views
æ•°æ®ç»“æž„. 山东教育å¦é™¢è®¡ç®—机系 æŽå›½ 2007 å¹´ 7 月. 目录. 第 1 ç« ç»ªè®º 第 2 å•å…ƒ : 线性表 æ ˆä¸Žé˜Ÿåˆ— ä¸²ã€æ•°ç»„与广义表 第 3 å•å…ƒï¼šæ ‘ 第 4 å•元:图 第 5 å•元:查找 排åº. å¸¸è§æ•°æ®ç»“æž„ï¼Œä¸»è¦ ä»‹ç»åŸºæœ¬å®šä¹‰ã€æž„æˆæ•°æ®ç»“æž„çš„å…ƒç´ ä¹‹é—´çš„å…³ç³» æ•°æ®åœ¨è®¡ç®—机的å˜å‚¨åŠè¯¥ç§æ•°æ®ç»“构的应用. 基于上述数æ®ç»“构的最基本æ“作. 第 1 ç« ç»ªè®º. 1.1 什么是数æ®ç»“æž„ 一ã€åˆ©ç”¨è®¡ç®—机解决实际问题的æ¥éª¤ï¼š
E N D
数据结构 山东教育学院计算机系 李国 2007年7月
目录 • 第1章 绪论 • 第2单元: 线性表 • 栈与队列 • 串、数组与广义表 • 第3单元:树 • 第4单元:图 • 第5单元:查找 • 排序 常见数据结构,主要 介绍基本定义、构成数据结构的元素之间的关系 数据在计算机的存储及该种数据结构的应用 基于上述数据结构的最基本操作
第1章 绪论 • 1.1什么是数据结构 • 一、利用计算机解决实际问题的步骤: • 具体问题 数学模型 算法(求解步骤) 数据结构重点研究: 根据数学模型如何 形成算法 程序 程序设计 语言环节 上机调试
二、数据结构的定义 线性表 • 例1:图书书目管理检索系统自动化问题
…….. …….. …... …... …... …... 例2 人机对奕问题 树
例3:连通N个城市并且代价最小 图 B 30 20 A 45 C 70 60 50 D 40 40 C
数据结构定义: • 是一门研究非数值计算的程序设计问题中计算机的操作对象以及它们之间的关系和操作等等的学科
1.2 基本概念和术语 • 1、数据(data)—所有能输入到计算机中去的描述客观事物的符号 • 2、数据元素(data element)—数据的基本单位,也称节点(node)或记录(record) • 3、数据项(data item)—有独立含义的数据最小单位,也称域(field) • 4、数据对象(data object)—性质相同的数据元素的集合 • 5、数据结构(data structure)—数据元素和数据元素关系的集合
根据数据元素间关系的基本特性,有四种基本数据结构:根据数据元素间关系的基本特性,有四种基本数据结构: • 1、(集合)——数据元素间除“同属于一个集合”外,无其它关系 • 2、线性结构——一个对一个,如线性表、栈、队列 • 3、树形结构——一个对多个,如树 • 4、图状结构——多个对多个,如图
6、数据的逻辑结构—只抽象反映数据元素的逻辑关系6、数据的逻辑结构—只抽象反映数据元素的逻辑关系 • 7、数据的存储(物理)结构—数据的逻辑结构在计算机存储器 • 存储结构分为: • 顺序存储结构——借助元素在存储器中的相对位置来表示数据元素间的逻辑关系 • 链式存储结构——借助指示元素存储地址的指针表示数据元素间的逻辑关系 • 数据的逻辑结构与存储结构密切相关 • 算法设计 逻辑结构 • 算法实现 存储结构
存储地址 存储内容 元素1 Lo 元素2 Lo+m …….. 顺序存储 元素i Lo+(i-1)*m …….. 元素n Lo+(n-1)*m Loc(元素i)=Lo+(i-1)*m
h 链式存储 1345 h 元素2 1536 元素3 1346 元素4 ∧ 元素1 1400
线性表 数据结构的三个方面: 栈 线性结构 队 数据的逻辑结构 树形结构 非线性结构 图形结构 顺序存储 数据的存储结构 链式存储 数据的运算:检索、排序、插入、删除、修改等
8、数据类型—高级语言中指数据的取值范围及其上可进行的操作的总称8、数据类型—高级语言中指数据的取值范围及其上可进行的操作的总称 例:C语言中,提供int, char, float, double等基本数据类型,数组、结构体、共用体、枚举等,还有指针、空(void)类型等。用户也可用typedef 自己定义数据类型 typedef struct { int num; char name[20]; float score; }STUDENT; STUDENT stu1,stu2, *p;
1 .3抽象数据类型的表示与实现 • 利用C语言描述,注意以下几个约定: • 1、为了清楚表达算法的返回结果是真、假、正常、不合理数据或溢出等状态,本教材采取以下宏定义,目的使返回更明确。 • #define true 1 • #define false 0 • #define ok 1 • #define infeasible -1 • #define overflow -2 • typedef int status; /*数据类型名的取别名
2、为了表示数据元素更具有一般性,教材采取约定ELEMTYPE作为数据元素的类型,将来上机处理数据时根据需要转换成C语言可接受的整型、实型或字符类型等。2、为了表示数据元素更具有一般性,教材采取约定ELEMTYPE作为数据元素的类型,将来上机处理数据时根据需要转换成C语言可接受的整型、实型或字符类型等。 • 3、为了便于算法描述,除了值传送方式外,增添了C++语言的引用的参数传递方式,在形参表中,以&打头的参数为引用参数。 • 4、其他具体语句格式基本类似于C语言,但有所扩充,表达更方便,但有些不符合C语言语法规则,转换为上机调试程序时候需要略微做修改。
1 .4算法和算法分析 • 一、算法:(algorithm)解决某一特定问题的具体步骤的描述,是指令的有限序列。 • 特性: • 1、有穷性:有穷步之后结束 • 2、确定性:每一条指令都确定,不可有二义性 • 3、可行性:每个算法是能行的,每一步骤都是可以实现的。 • 4、输入:0或多个输入 • 5、输出:至少有1个输出。
二、算法的评价—衡量算法优劣的标准 • 1、正确性(correctness) • 2、可读性(readability) • 3、健壮性(robustness) • 4、效率与低存储量 • 三、算法效率的度量 • 1、时间复杂度:算法中基本操作重复执行的次数是问题规模n的某个函数f(n),基本操作重复执行的次数的阶数 T(n)=o(f(n)) 称为算法的时间复杂度。 • (1) {++x;s=0;} O(1) • (2) for(i=1;i<=n;++i) {++x;s+=x;} O(n) • (3) for(j=1;j<=n;++j) • for(k=1;k<=n;++k) {++x;s+=x;} O(n2) • 另外一些时间复杂度表示如 O(n3) O(logn) O(2n)
例:NXN矩阵相乘 for(i=1;i<=n;i++) for(j=1;j<=n;j++) {c[i][j]=0; for(k=1;k<=n;k++) c[i][j]=c[i][j]+a[i][k]*b[k][j]; } 算法的时间复杂度为: O(n3)
例:冒泡排序算法: • Void bubbe_sort(int a[],int n) { • for(i=n-1,change=true;i>=1&&change;--i) • { change=false; • for(j=0;j<i;++j) • if (a[j]>a[j+1]) {a[j]<->a[j+1]; • change=true • } • } • } • 算法时间复杂度:O(n2)。 • 请大家考虑,change在此起什么作用?
2、空间复杂度:s(n)=o(f(n)),即算法在执行过程用到的辅助空间越少越好。2、空间复杂度:s(n)=o(f(n)),即算法在执行过程用到的辅助空间越少越好。
第二章 线性表 线性结构特点:在数据元素的非空有限集中存在唯一的一个被称作“第一个”的数据元素,存在唯一的一个被称作“最后一个”的数据元素;除第一个外,集合中的每个数据元素均只有一个前驱,除最后一个外,集合中的每个数据元素均只有一个后继。 • 主要内容: • 2.1 线性表的定义及逻辑结构 • 2.2 线性表的顺序表示及实现 • 2.3 线性表的链式表示及实现 • 2.4 线性表的基本应用
2.1 线性表的定义及逻辑结构 • 一、线性表定义 • 1、定义:一个线性表是n个数据元素的有限序列。一般记作(a1,a2…ai-1,ai,ai+1,…an)。 • 如: (A,B,C,D,E,F) • (6,12,15,21,89,100) • 数据库中每一条记录构成线性表中的每一个元素。 • 2、特征:元素个数n—表长度,n=0空表
当1<i<n时,ai的直接前驱是ai-1,a1无直接前驱ai的直接后继是ai+1,an无直接后继。当1<i<n时,ai的直接前驱是ai-1,a1无直接前驱ai的直接后继是ai+1,an无直接后继。 • 通常用〈ai,ai+1〉表示两个元素之间的关系, ai称为ai+1的前驱, ai+1称为ai的后继。 • 二、线性表的基本操作 • 1、初始化空表:initlist(&L) • 2、清空线性表:clearList(L) • 3、判空线性表:ListEmpty(L) • 4、求长度:lengthList(L) • 5、取元素:GetElem(L,i,&e) • 6、查找元素:LocateElem(L,e)
7、求前驱、后继函数:PriorElem(L,cur_e,&pre_e) • NextElem(L,cur_e,&next_e) • 8、插入元素:ListInsert(&L,i,e)在第i个元素前面插入元素e,注意元素的移动。 • 9、删除元素:ListDelete(&L,i,e)删除第i个元素,大量元素的向前移动。 • 熟悉各个函数的基本功能,学习这些函数的目的是为了将来应用到线性表数据结构的时候,采用这些基本函数去组合实现不同的需求。 • 在这里,只是给出函数的基本功能,只有学习了线性表的存储结构,才能将这些函数真正实现。
例2-1 利用两个线性表LA和LB分别表示两个集合A和B,现要求一个新的集合A=A∪B。 • void union(List &La,List Lb) { • La-len=listlength(La); • Lb-len=listlength(Lb); • for(I=1;I<=lb-len;I++) { • getelem(lb,I,e); • if(!locateelem(la,e,equal)) • listinsert(la,++la-en,e) • } • }
例2-2 巳知线性表LA和线性表LB中的数据元素按值非递减有序排列,现要求将LA和LB归并为一个新的线性表LC,且LC中的元素仍按值非递减有序排列。 • 此问题的算法如下: • void mergelist(list la,list lb,list &lc) • initlist(lc); • I=j=1;k=0; • la-len=listlength(la); • lb-len=listlength(lb); • while((I<=la-len)&&(j<=lb-len)){
getelem(la,I,ai); getelem(lb,j,bj); • if(ai<=bj){listinsert(lc,++k,ai);++I;} • else{listinsert(lc,++k,bj);++j;} • } • while(I<=la-len){ • getelem((la,I++,ai);listinsert(lc,++k,ai); • } • while(j<=lb-len){ • getelem((lb,j++,bj);listinsert(lc,++k,bi); • } • }
2.2 线性表的顺序表示及实现 • 一、线性表的顺序存储 • 1、存储定义:用一组地址连续的存储单元存放一个线性表。 • 2、元素地址计算方法: • LOC(ai)=LOC(a1)+(i-1)*L • LOC(ai+1)=LOC(ai)+L • 其中:L: 一个元素占用的存储单元个数 • LOC(ai):线性表第i个元素的地址 • 3、特点:实现逻辑上相邻—物理地址相邻 • 4、实现:可用C语言的一维数组实现
V数组下标 内存 元素序号 0 a1 1 1 a2 2 n-1 an n M-1 • 二、顺序存储的计算机内存储
1、存储定义 • #define List_Init_Size 100 //初始分配存储容量 • #define LISTINCREMENT 10 //初始容量满后每次增加的分配增量 • Typedef struct { • Elemtype *elem; //连续存储空间的首地址或一维数组名 • Int length; //当前线性表的实际长度 • Int listsize; //目前连续空间的实际存储元素的容量 • }Sqlist; • 说明:本定义是采取的指针定义连续存储空间的首地址elem及实际能存储元素的个数listsize,并记录出目前线性表的实际个数length,并实际提出若超出实际存储空间后可以增添新的空间能力。
此定义形式应该等价于 • Typedef struct { • Elemtype elem[]; • Int length; • Int listsize; • }SqList; • 或有些教材干脆: • #define Max 100 • Typedef struct { • Elemtype elem[Max]; //一维数组最多能存100个元素 • Int length; //目前线性表的实际长度 • }SqList;
由以上定义:在定义了结构体后同时取别名为SqList,因此,以后用SqList就表示线性表的顺序存储结构的数据类型名。由以上定义:在定义了结构体后同时取别名为SqList,因此,以后用SqList就表示线性表的顺序存储结构的数据类型名。 • SqList L; • L 就可表示一个线性表: • L.elem:该线性表的存储起始地址; • L.length:该线性表的目前长度; • L.listsize: 该线性表的实际存储元素的能力,初始默认设置就是100,以后随着元素的增加,每次增加10个存储容量。
三、依据存储结构线性表基本操作的实现 • 1、线性表的初始化 initlist(&L) • status InitList_sq(SqList &L) • { L.elem=(ElemType*)malloc(List_Init_Size*sizeof(ElemType)); • if (! L.elem) exit(OVERFLOW); • L.length=0; • L.listsize=List_Init_Size; • Retuen OK; • } • Malloc()为动态分配函数。
2、插入ListInsert(&L,i,e) • 线性表的插入是指在第i(1i n+1)个元素之前插入一个新的数据元素x,使长度为n的线性表 • (a1,a2…ai-1,ai,ai+1,…an)变成长度为n+1的线性表(a1,a2…ai-1,x,ai,ai+1,…an),需将第i至第n共(n-i+1)个元素后移 • (1)判断i是否越界 • (2)判断存储空间是否已经满,需要增添新空间否? • (3)从第n个元素到第i个元素顺序后移1个位置 • (4)将x存放到第i个元素的位置上,并使线性表长度加1。
V数组下标 V数组下标 内存 元素序号 元素序号 内存 0 0 a1 a1 1 1 a2 2 2 a2 1 1 i-1 i-1 ai i i ai i i ai+1 i+1 i+1 ai+1 an n n an-1 n-1 n-1 n+1 n+1 n n an
Status ListInsert_Sq(SqList &L, int i, ElemType e) { // 算法2.4 • // 在顺序线性表L的第i个元素之前插入新的元素e, • // i的合法值为1≤i≤ListLength_Sq(L)+1 • ElemType *p; • if (i < 1 || i > L.length+1) return ERROR; // i值不合法 • if (L.length >= L.listsize) { // 当前存储空间已满,增加容量 • ElemType *newbase = (ElemType *)realloc(L.elem, • (L.listsize+LISTINCREMENT)*sizeof (ElemType)); • if (!newbase) return ERROR; // 存储分配失败 • L.elem = newbase; // 新基址 • L.listsize += LISTINCREMENT; // 增加存储容量 • } • ElemType *q = &(L.elem[i-1]); // q为插入位置 • for (p = &(L.elem[L.length-1]); p>=q; --p) *(p+1) = *p; • // 插入位置及之后的元素右移 • *q = e; // 插入e • ++L.length; // 表长增1 • return OK; • } // ListInsert_Sq
算法时间复杂度T(n); • 设Pi是在第i个元素之前插入一个元素的概率,则在长度为n的线性表中插入一个元素时,所需移动的元素次数的平均次数为:
3、删除ListDelete(&L,i,e) • 线性表的删除是指将第i(1i n)个元素删除,使长度为n的线性表(a1,a2…ai-1,ai,ai+1,…an)变成长度为n-1的线性表(a1,a2…ai-1,ai+1,…an),需将第i+1至第n共(n-i)个元素前移。 • (1)i是否合法 • (2)将第i个元素的值赋给e • (3)从第i+1个元素到第n个元素开始依次前移动,并使线性表长度减1。
V数组下标 内存 V数组下标 元素序号 元素序号 内存 0 0 a1 a1 1 1 a2 2 2 a2 1 1 i-1 i-1 ai+1 ai i i i i ai+1 ai+2 i+1 i+1 an n n-1 an n-1 n-2 n n+1 n n-1
Status ListDelete_Sq(SqList &L, int i, ElemType &e) { // 算法2.5 • // 在顺序线性表L中删除第i个元素,并用e返回其值。 • // i的合法值为1≤i≤ListLength_Sq(L)。 • ElemType *p, *q; • if (i<1 || i>L.length) return ERROR; // i值不合法 • p = &(L.elem[i-1]); // p为被删除元素的位置 • e = *p; // 被删除元素的值赋给e • q = L.elem+L.length-1; // 表尾元素的位置 • for (++p; p<=q; ++p) *(p-1) = *p; // 被删除元素之后的元素左移 • --L.length; // 表长减1 • return OK; • } // ListDelete_Sq
算法评价: • 设Qi是删除第i个元素的概率,则在长度为n的线性表中删除一个元素所需移动的元素次数的平均次数为:
4、查找元素LocateElem(L,e) • Int locateElem_sq(sqlist L,ElemType e) • {i=1; • p=L.elem; • while (i<=L.length&&*p!=e) • ++I; • if (i<=L.length) return I; • else return 0; • }
void MergeList_Sq(SqList La, SqList Lb, SqList &Lc) { // 算法2.7 // 已知顺序线性表La和Lb的元素按值非递减排列。 // 归并La和Lb得到新的顺序线性表Lc,Lc的元素也按值非递减排列。 ElemType *pa,*pb,*pc,*pa_last,*pb_last; pa = La.elem; pb = Lb.elem; Lc.listsize = Lc.length = La.length+Lb.length; pc = Lc.elem = (ElemType *)malloc(Lc.listsize*sizeof(ElemType)); if (!Lc.elem) exit(OVERFLOW); // 存储分配失败 pa_last = La.elem+La.length-1; pb_last = Lb.elem+Lb.length-1; while (pa <= pa_last && pb <= pb_last) { // 归并 if (*pa <= *pb) *pc++ = *pa++; else *pc++ = *pb++; } while (pa <= pa_last) *pc++ = *pa++; // 插入La的剩余元素 while (pb <= pb_last) *pc++ = *pb++; // 插入Lb的剩余元素 } // MergeList • 例题算法2-7
2.3 线性表的链式表示及实现 • 顺序表的缺点: • (1)插入、删除操作需要移动大量的元素 • (2)预先分配空间需按最大空间分配,利用不充分 • (3)表容量扩充代价高 • 线性链表的引入: • 1、用一组任意的存储单元存储线性表的数据元素 • 2、利用指针实现了用不相邻的存储单元存放逻辑上相邻的元素 • 3、每个数据元素ai,除存储本身信息外,还需存储其直接后继的信息 • 4、结点:数据域:元素本身信息; • 指针域:指示直接后继的存储位置
H 存储地址 数据域 指针域 例 线性表 (ZHAO,QIAN,SUN,LI,ZHOU,WU,ZHENG,WANG) LI ZHAO SUN QIAN 1 LI 7 QIAN 13 WANG ZHOU ZHENG WU ^ SUN 头指针 43 19 H WANG 25 31 13 WU 31 ZHAO 1 37 ZHENG 结点 NULL 43 ZHOU 数据域 指针域 37 7 19 25
一、线性链表的定义 • 1、定义:结点中只含一个指针域的链表线性链表,也叫单链表。 • 2、线性链表的计算机内实现: • typedef struct LNode { • Elemtype data; • struct LNode *next; • }LNode,*LinkList; • 说明:(1)先定义两个域,数据域data;指针域是一个指向结点自身类型的指针变量next,从而实现结点之间的连接。 • (2)整个数据类型取别名为LNode,并以为定义指针数据类型LinkList,以后说明变量属于LinkList,即定义一个线性链表。
头结点 …... L an ^ a1 a2 L 空表 ^ • 如:LinkList p; • 则P为指针变量,p->data是指针所指元素的数值, p->next为指针变量,其又指向下一个结点。 • 头结点:在单链表第一个结点前附设一个结点叫头结点。指向头结点的指针称为头指针。
二、链表的基本操作算法实现: • 1、取元素算法2-8 • status GetElem(LinkList L,int I,ElemType &e) • p=L->next;j=1; • while(p&&j<i) { • p=p->next;++j; • } • if (!p||j>i) return ERROR; • e=p->data; • return OK; • } • 学会指针的向后移动,在次,每移动1个位置,j++计 • 数。
p b a 2 1 s x • 2、插入元素算法2-9 Status ListInsert_L(LinkList &L, int i, ElemType e) { // 算法2.9 // 在带头结点的单链线性表L的第i个元素之前插入元素e LinkList p,s; p = L; int j = 0; while (p && j < i-1) { // 寻找第i-1个结点 p = p->next; ++j; } if (!p || j > i-1) return ERROR; // i小于1或者大于表长 s = (LinkList)malloc(sizeof(LNode)); // 生成新结点 s->data = e; s->next = p->next; // 插入L中 p->next = s; return OK; } // LinstInsert_L