Data-Structure 之一
第1章 概论
数据结构讨论的是数据的逻辑结构、存储方式以及相关操作的实现等问题。本章讲述数据结构的基本概念及相关术语,介绍数据结构、数据类型和抽象数据类型之间的联系,介绍了算法的特点及算法的时间与空间复杂性。
1.1数据结构
1.1.1数据结构
随着计算机软、硬件的发展,计算机的应用范围在不断扩大,计算机所处理的数据的数量也在不断扩大,计算机所处理的数据已不再是单纯的数值数据,而更多的是非数值数据。
需要处理的数据并不是杂乱无章的,它们一定有内在的联系,只有弄清楚它们之间的本质的联系,才能使用计算机对大量的数据进行有效的处理。
使用计算机处理用户信息表中的数据时,必须弄清楚下面3个问题:
1) 数据的逻辑结构(各结点的关系)
2) 数据的存储结构(存入计算机中的结构)
3) 数据的运算集合(相关运算,如删除、添加等)
数据结构就是指按一定的逻辑结构组成的一批数据,使用某种存储结构将这批数据存储于计算机中,并在这些数据上定义了一个运算集合。
1.1.2数据的逻辑结构
例如,有5个人,分别记为a,b,c,d,e,其中a是b的父亲,b是c的父亲,c是d的父亲,d是e的父亲。
如果只讨论他们之间所存在的父子关系,则可以用二元组形式化地予以表达:B=(K,R)
其中:K={a,b,c,d,e} R={r} r={<a, b>,<b,c>, <c, d>,<d,e>}
1.1.3数据的存储结构
对于一个数据结构B=(K,R),必须建立从结点集合到计算机某个存储区域M的一个映象,这个映象要直接或间接地表达结点之间的关系R。数据在计算机中的存储方式称为数据的存储结构。
数据的存储结构主要有4种:
1) 顺序存储
顺序存储通常用于存储具有线性结构的数据。将逻辑上相邻的结点存储在连续存储区域M的相邻的存储单元中,使得逻辑相邻的结点一定是物理位置相邻。
2) 链式存储
链式存储方式是给每个结点附加一个指针段,一个结点的指针所指的是该结点的后继的存储地址,因为一个结点可能有多个后继,所以指针段可以是一个指针,也可以是一个多个指针。
3) 索引存储
在线性结构中,设开始结点的索引号为1,其它结点的索引号等于其前继结点的索引号加1,则每一个结点都有唯一的索引号,索引号就是根据结点的索引号确定该结点的存储地址。
4) 散列存储
散列存储的思想是构造一个从集合K到存储区域M的一个函数h,该函数的定义域为K,值域为M,K中的每个结点ki在计算机中的存储地址由h(ki)确定。
1.1.4数据的运算集合
对于一批数据,数据的运算是定义在数据的逻辑结构之上的,而运算的具体实现就依赖于数据的存储结构。
数据的运算集合要视情况而定,一般而言,数据的运算包括插入、删除、检索、输出、排序等。
1.2数据类型和抽象数据类型
在程序设计中,数据和运算是两个不可缺少的因素。所有的程序设计活动都是围绕着数据和其上的相关运算而进行的。从机器指令、汇编语言中的数据没有类型的概念,到现在的面向对象程序设计语言中抽象数据类型概念的出现,程序设计中的数据经历了一次次抽象,数据的抽象经历了三个发展阶段:
Ø从无类型的二进制数到基本数据类型的产生
Ø从基本数据类型到用户自定义类型的产生
Ø从用户自定义类型到抽象数据类型的出现
1.2.1数据类型
数据类型(或简称类型)反映了数据的取值范围以及对这类数据可以施加的运算。
1.2.2数据结构
数据结构包括三个方面的内容:一组数据中各数据之间的逻辑关系;这组数据在计算机中的存储方式;对这组数据所能施加的运算的集合。数据结构是数据存在的形式。所有的数据都是按照数据结构进行分类的。简单数据类型对应于简单的数据结构;构造数据类型对应于复杂的数据结构。
1.2.3抽象数据类型
抽象数据类型是与表示无关的数据类型,是一个数据模型及定义在该模型上的一组运算。对一个抽象数据类型进行定义时,必须给出它的名字及各运算的运算符名,即函数名,并且规定这些函数的参数性质。一旦定义了一个抽象数据类型及具体实现,程序设计中就可以像使用基本数据类型那样,十分方便地使用抽象数据类型。
1.2.4抽象数据类型的描述和实现
抽象数据类型的描述包括给出抽象数据类型的名称、数据的集合、数据之间的关系和操作的集合等方面的描述。抽象数据类型的设计者根据这些描述给出操作的具体实现,抽象数据类型的使用者依据这些描述使用抽象数据类型。
抽象数据类型描述的一般形式如下:
ADT 抽象数据类型名称 { 数据对象: …… 数据关系: …… 操作集合: 操作名1: …… …… 操作名n: }ADT抽象数据类型名称
1.3 算法和算法分析
1.3.1算法
为了求解某问题,必须给出一系列的运算规则,这一系列的运算规则是有限的,表达了求解问题方法和步骤,这就是一个算法。
算法具有五个基本特征:
①有穷性,算法的执行必须在有限步内结束。
②确定性,算法的每一个步骤必须是确定的无二义性的。
③输入, 算法可以有0个或多个输入。
④输出, 算法一定有输出结果。
⑤可行性,算法中的运算都必须是可以实现的。
算法具有有穷性,程序不需要具备有穷性。一般的程序都会在有限时间内终止,但有的程序却可以不在有限时间内终止,如一个操作系统在正常情况下是永远都不会终止的。
1.3.2算法的时间和空间复杂性
一个算法的优劣主要从算法的执行时间和所需要占用的存储空间两个方面衡量,算法执行时间的度量不是采用算法执行的绝对时间来计算的,因为一个算法在不同的机器上执行所花的时间不一样,在不同时刻也会由于计算机资源占用情况的不同,使得算法在同一台计算机上执行的时间也不一样,所以对于算法的时间复杂性,采用算法执行过程中其基本操作的执行次数,称为计算量来度量。
算法中基本操作的执行次数一般是与问题规模有关的,对于结点个数为n的数据处理问题,用T(n)表示算法基本操作的执行次数。
在评价算法的时间复杂性时,不考虑两算法执行次数之间的细小区别,而只关心算法的本质差别。为此,引入一个所谓的O() 记号,则T1(n)=2n=O(n),T2(n)=n+1=O(n)。
一个函数f(n)是O(g(n))的,则一定存在正常数c和m,使对所有的n>m,都满足f(n)<c*g(n)。
算法的时间复杂性不仅和问题的规模大小有关,还与问题数据的初始状态有关。
这样就有了算法在最好、最坏以及在平均状态下的时间复杂性的概念。
①算法在最好情况下的时间复杂性是指算法计算量的最小值。
②算法在最坏情况下的时间复杂性是指算法计算量的最大值。
③算法的平均情况下的时间复杂性是指算法在所有可能的情况下的计算量经过加权计算出的平均值。
第2章 线性表及其顺序存储
顺序表:
顺序表类型的描述如下:
ADT sequence_list{ 数据集合K:K={k1, k2,…, kn},n≥0,K中的元素是datatype类型 数据关系R:R={r},r={ <ki, ki+1>| i=1,2,…,n-1} 操作集合: (1) void init_sequence_list(sequence_list *slt) 顺序表的初始化------置空表 (2) void insert_sequence_list(sequence_list *slt,datatype x) 后部插入值为x结点 (3) void print_sequence_list(sequence_list slt) 打印顺序表的各结点值 (4) int is_empty_sequence_list(sequence_list slt) 判断顺序表是否为空 (5) int find_num_sequence_list(sequence_list slt,datatype x) 查找值为x结点位置 (6) int get_data_pos(sequence_list slt,int i) 取得顺序表中第i个结点的值 (7) void insert_pos_sequence_list(sequence_list *slt,int position,datatype x) (8) void delete_pos_sequence_list(sequence_list *slt,int position) } ADT sequence_list;
// sequlist.h 头文件 #define MAXSIZE 100 typedef int datatype; typedef struct{ datatype a[MAXSIZE]; int size; }sequence_list;
// seqlinit.c 初始化--置空表 void init_sequence_list(sequence_list *slt) { slt->size = 0; }
// seqlinse.c 后部插入值为x结点 void insert_sequence_list(sequence_list *slt, datatype x) { if(slt->size==MAXSIZE) { printf("sequence_list is full!"); exit(1); } slt->size=slt->size+1; slt->a[slt->size]=x; }
// seqlprin.c 打印各结点值 void print_sequence_list(sequence_list slt) { int i; if(!slt.size) printf("the sequence_list is empty!\n"); else for(i=0; i<slt.size; i++) printf("%5d", slt.a[i]); }
// seqlempt.c 判断顺序表是否为空 int is_empty_sequence_list(sequence_list slt) { return(slt.size==0?0:1); }
// seqlfind.c 查找表中值为x的结点位置 int find_num_sequence_list(sequence_list slt, datatype x) { int i = 0; while(slt.a[i]!=x&&i<slt.size) i++; return(i<slt.size?i:-1); }
// seqlget.c 取得顺序表中第i个结点的值 int get_data_pos(sequence_list slt, int i) { if(i<0||i>=slt.size) { printf("wrong node!\n"); exit(1); } else return slt.a[i]; }
// 在顺序表的position位置插入值为x的结点 void insert_pos_sequence_list(sequence_list *slt, int position, datatype x) { int i; if(slt->size==MAXSIZE) { printf("the sequence_list is full!can't insert!\n"); exit(1); } if(position<0||position>slt->size) { printf("wrong position!\n"); exit(1); } for(i=slt->size; i>position; i--) slt->a[i]=slt->a[i-1]; slt->a[position] = x; slt->size++; }
// 删除顺序表中第position位置的结点 void delet_pos_sequence_list(sequence_list *slt, int position) { int i; if(slt->size==0) { printf("the sequence_list is empty!\n"); exit(1); } if(position<0||position>=slt->size) { printf("wrong position!\n"); exit(1); } for(i=position; i<slt->size-1; i--) slt->a[i] = slt->a[i+1]; slt->size--; }
顺序栈及其实现:
栈类型的描述如下:
ADT sequence_stack { 数据集合K:K={k1, k2,…, kn},n≥0,K中的元素是datatype类型 数据关系R:R={r} r={ <ki, ki+1>| i=1,2,…,n-1} 操作集合: (1) void init_sequence_stack(sequence_stack *st) (顺序存储)初始化 (2) int is_empty_stack(sequence_stack st) 判断栈(顺序存储)是否为空 (3) void print_sequence_stack(sequence_stack st) 打印栈(顺序存储)的结点值 (4) datatype get_top(sequence_stack st) 取得栈顶(顺序存储)结点值 (5) void push(sequence_stack *st,datatype x) 栈(顺序存储)的插入操作 (6) void pop(sequence_stack *st) 栈(顺序存储)的删除操作 } ADT sequence_stack
设定一个足够大的一维数组存储栈,数组中下标为0的元素就是栈底,对于栈顶,可以设一个指针top指示它。
设定top所指的位置是下一个将要插入的结点的存储位置,这样,当top=0时就表示是一个空的栈。
// seqstack.h #define MAXSIZE 100 typedef int datatype; typedef struct{ datatype a[MAXSIZE]; int top; }sequence_stack;
// seqsinit.c void init_sequence_stack(sequence_stack *st) { st->top = 0; }
// seqsempt.c int is_empty_stack(sequence_stack st) { return(st.top?0:1); }
// seqsfirs.c 取得栈顶结点值 datatype get_top(sequence_stack st) { if(is_empty_stack(st)) { printf("the stack is empty!\n"); exit(); } else return st.a[st.top-1]; }
// seqspush.c 栈的插入操作 void push(sequence_stack *st, datatype x) { if(st->top==MAXSIZE) { printf("the sequence stack if full!\n"); exit(1); } st->a[st->top] = x; st->top++; }
// seqspop.c 栈的删除操作 void pop(sequence_stack *st) { if(st->top==0) { printf("the seqence stack is empty!\n"); exit(1); } st->top--; }
栈的应用——括号匹配
设一个表达式中可以包含三种括号:小括号、中括号和大括号,各种括号之间允许任意嵌套,如小括号内可以嵌套中括号、大括号,但是不能交叉。
int match_kuohao(char c[]) { int i=0; sequence_stack s; init_sequence_stack(&s); while(c[i]!='#') { switch(c[i]) { case '{': case '[': case '(':push(&s,c[i]); break; case '}':if(!is_empty_sequence_stack(s)&&get_top(s)=='{') { pop(&s); break; } else return 0; case ']':if(!is_empty_sequence_stack(s)&&get_top(s)=='[') { pop(&s); break; } else return 0; case ')':if(!is_empty_sequence_stack(s)&&get_top(s)=='(') { pop(&s); break; } else return 0; } i++; } return (is_empty_sequence_stack(s));/*栈空则匹配,否则不匹配*/ }
队列
队列的插入和删除操作分别在表的两端进行。插入的那一端称为队尾,删除的那一端称为队首。队列的插入操作和删除操作也分别简称进队和出队。
队列类型的描述如下:
ADT sequence_queue { 数据集合K:K={k1, k2,…, kn},n≥0,K中的元素是datatype类型 数据关系R:R={r} r={ <ki, ki+1>| i=1,2,…,n-1} 操作集合: (1) void init_sequence_queue(sequence_queue *sq) 队列(顺序存储)初始化 (2) int is_empty_sequence_queue(sequence_queue sq) 判断队列(顺序存储)是否为空 (3) void print_sequence_queue(sequence_queue sq) 打印队列(顺序存储)的结点值 (4) datatype get_first(sequence_queue sq) 取得队列(顺序存储)的队首结点值 (5) void insert_sequence_queue (sequence_queue *sq,datatype x) 队列(顺序存储)插入操作 (6) void delete_sequence_queue(sequence_queue *sq) 队列(顺序存储)的删除操作 }ADT sequence_queue;
// seqqueue.h #define MAXSIZE 100 typedef int datatype; typedef struct{ datatype a[MAXSIZE]; int front; int rear; }sequence_queue;
// seqqinit.c void init_sequence_queue(sequence_queue *sq) { sq->front = sq->rear = 0; }
// seqqempt.c int is_empty_sequence_queue(sequence_queue sq) { return(sq.front==sq.rear?1:0); }
// seqqprin.c void print_sequence_quence(sequence_queue sq) { int i; if(is_empty_sequence_queue(sq)) { printf("the sequence_queue is empty!"); } else { for(i=sq.front; i<sq.rear; i++) printf("%5d", sq.a[i]); } }
// seqqinse.c void insert_sequence_queue(sequence_queue *sq, datatype x) { int i; if(sq->rear==MAXSIZE) { printf("the sequence_queue is full!"); exit(1); } sq->a[sq->rear] = x; sq->rear = sq->rear + 1; }
// seqqdele.c void delete_sequence_queue(sequence_queue *sq) { if(sq->front==rq->rear) { printf("the sequence_queue is empty!can't delete!\n"); exit(1); } sq->front++; }
循环队列:
循环队列满的条件是:(rear+1)%MAXSIZE=front
循环队列空的条件是:rear=front
// secqinst.c void insert_sequence_cqueue(sequence_queue *sq,datatype x) { int i; if((sq->rear+1)%MAXSIZE==sq->front) { printf("the sequence_queue is full! can’t insert!"); exit(1); } sq->a[sq->rear]=x; sq->rear=(sq->rear+1)%MAXSIZE; }
// secqdele.c void delete_sequence_cqueue(sequence_queue *sq) { if(sq->front==sq->rear) { printf("the sequence_queue is empty!can't delete!\n"); exit(1); } sq->front=(sq->front+1)%MAXSIZE; }