第一章 绪 论
1.1 基 本 概 念 和 术 语
-
数据:是对客观事物的符号表示,在计算机科学中是指所有能输入到计算机中并被计算机程序处理的符号的总称。
-
数据元素:是数据的基本单位,在计算机程序中通常作为一个整体进行考虑和处理。
-
数据对象:是性质形同的数据元素的集合,是数据的一个子集
-
数据结构:是相互之间存在一种或多种特定关系的数据元素的集合
- 常见的数据结构:
- 线性结构:一对一
- 树形结构:一对多
- 图状结构:对对多
- 数据结构的形式定义:
Data_Structure = (D,S)
- 常见的数据结构:
-
物理结构(存储结构):数据结构在计算机中的表示成为数据的物理结构
-
顺序映像:特点是借助元素在存储器中的相对位置来表示数据元素之间的逻辑关系
-
非顺序映像:非顺序映像的特点是借助指示元素存储地址的指针来表示数据元素之间的逻辑关系
-
数据类型:是一组性质相同的值得集合以及定义于这个值集合上得到一组操作的总称。
值得集合+值集合上的一组操作
-
数据类型的作用:
- 约束变量的内存空间
- 约束变量或常量的取值范围
- 约束变量或常量的操作
-
抽象数据类型:是指一个数学模型以及定义在该模型上的一组操作
-
常见的抽象数据类型:
- 原子类型
- 固定聚合类型
- 可变聚合类型
-
抽象数据类型的定义:
ADT 抽象数据类型名{ 数据对象:<数据对象的定义> 数据关系:<数据关系的定义> 基本操作:<基本操作的定义> }ADT 抽象数据类型名
-
-
多形数据类型:值的成分不确定的数据类型
1.2 抽 象 数 据 类 型 的 表 示 与 实 现
- 预定义常量和类型
typedef
:数据结构的表示(存储结构),ElemType
:数据元素类型- 基本操作的算法:
函数类型 函数名(函数参数){
//算法说明
语句序列
}//函数名
- 赋值语句
- 选择语句
- 循环语句
- 结束语句
综上,抽象数据类型的表示可以为:
typedef int ElemType;
typedef ElemType *Compare; //动态顺序存储结构
//初始化二元组
InitCom(Compare &C,ElemTyoe ee1,ElemType ee2)
第二章 线 性 表
2.1 线 性 表 类 型 定 义
- 线性表:一个线性表是n个数据元素的有限序列,线性表中的数据元素可以是各种各样的,但同一线性表中的元素必定具有相同特性,线性表中的数据元素之间存在着序偶关系
- 记录:由若干个数据项组成的数据元素
- 文件:含有大量记录的线性表
2.1.1 线 性 表 的 A D T 定 义
ADT List{
数据对象:D = {ai | ai ∈El emSet, i=1,2,...,n, n≥0 }
数据关系:R1 = { <ai-1 ,ai >|ai-1 ,ai∈D, i=2,...,n }
基本操作:
结构初始化操作
结构销毁操作
引用型操作2
加工型操作
}ADT List
2.2 线 性 表 的 顺 序 表 示 和 实 现
2.2.1 线 性 表 的 顺 序 存 储 结 构
- 线性表的顺序表示指的是用一组地址连续的存储单元依次存储线性表的数据元素,称作线性表的顺序存储结构或顺序映像
- 用这种方法存储的线性表称作顺序表
- 特点:
以物理地址相邻表示逻辑关系;任一元素均可随机存取
//线性表的动态分配顺序存储结构
#define LINK_INIT_SIZE 100 //线性表存储空间的初始分配量
#define LISTINCREMENT 10 // 线性表存储空间的分配增量
typedef struct {
ElemType *elem; //存储空间基址
int Length; //当前长度
int listsize //当前分配的存储容量(以sizeof(ElemType)为单位)
}SqList;
//线性表的顺序表示初始化
bool initList(SqList *L){
//构造一个空的线性表
L->elem = (ElemType *) malloc(LIST_INIT_SIZE*sizeof(ElemType)); //分配存储空间
if(L->elem ==NULL){
exit(VOERFLOW); //分配存储空间失败
}
L->length = 0; //空链表长度为0
L->initlist = LINK_INIT_SIZE; //初始存储容量
return OK;
}
2.2.2线 性 表 顺 序 存 储 的 插 入 和 删 除 操 作
线性表的插入操作:
-
算法思路:
先判断插入位置i的值是否合法
判断存储空间是否够用
确定插入位置
插入位置及之后的元素后移
插入元素
线性表长度+1
-
代码实现:
-
//在第i个位置之前插入新元素e Status ListInsert_Sq(SqList &L, int i, ElemType e){ //判断插入位置是否法 if(i<1||i>L.lenght+1){ return ERROP; } //判断存储空间是否够用 if(L.lenght>=L.listlize){ newbase = (ElemType *)realloc(L.elem,(L.listsize+LISTINCREMENT)*sizeof(ElemType)); if(newbase==NULL){ exit(OVERFLOW); } L.newbase = L.elem; L.listsize += LISTINCREMENT; } //确定插入位置 q = &(L.elem[i-1]); //插入位置及之后的元素后移 for(p = L.elem[lenght-1]; p>=q; --p){ *(p+1) = *p; } //插入元素 *q = e; //线性表长度+1 ++L.lenght; return OK }
-
-
插入算法的时间复杂度分析
- 问题规模是表的长度,设它的值为n(没插入元素之前的长度)
- 算法的时间主要花在向后移动元素的for循环语句上,循环次数为(n-i)+1 ,i为插入位置
- 当初入位置在表尾(i=n+1)时,不需要移动任何元素,这是最好情况,时间复杂度为T(n) = O(1)
- 当插入位置在表头(i = 1)时,这是最坏情况,时间复杂度为T(n) = O(n)
线性表的删除操作:
-
算法思想:
判断要删除的位置i是否合法
找到要删除的位置
将被删除的元素赋值给e
确定表尾位置
插入位置i之后的元素前移
线性表长度-1
-
代码实现:
-
Status ListDelete_Sq(SqList &L. int i, ElemType e){ //判断要插入的位置是否合法 if(i<1||i>L.lenght){ return ERROR; //删除位置不合法 } p = &L.elem[i-1]; //找到要删除的位置 e = *p; //将被删除的元素赋值给e q = L.elem+L.lenght-1; //确定表尾位置 //插入位置i之后的元素前移 for(++p; p<=q; ++p){ *(p-1) = *p; } //线性表长度-1 --L.lenght; return OK; }
-
-
删除算法分析:
- 问题规模:是表的长度,设它的长度为n(没有进行删除操作之前的线性表长度)
- 算法的主要是时间花费在向前移动元素的for循环上,循环次数为(n-i)
- 当删除的位置在表尾(i = n )时,时间复杂度尾O(1)
- 当删除的位置在表头(i = 1)时,时间复杂度为O(n)
2.3 线 性 表 的 链 式 表 示 和 实 现
2.3.1 线 性 链 表
- 线性表的链式存储结构是用一组任意的存储单元存储线性表的数据元素(这组存储单元可以使连续的,可以是不连续的,甚至可以是零散的)
- 结点:数据域+指针域
- 数据域:存储数据元素信息的域
- 指针域:存储直接后继存储位置的域
- 头指针:指示链表中第一个结点的存储位置
- 头结点:在单链表的第一个结点之前人为附设一个结点,头结点的数据域不放任何数据,指针域存放第一个结点的地址,则此时头指针存放的是头结点的地址
- 数据元素之间的逻辑关系是由结点中的指针指示的
//单链表的表示
typedef struct Lnode{
ElemType data; //数据元素类型
struct Lnode *next; //指示结点地址的指针
}Lnode/*结构体类型*/,*LinkList/*指向Lnode结构体类型的指针
2.3.2 单 链 表 的 基 本 操 作
查找运算:
- 算法思想:
初始化
查找
- 代码实现
-
Status GetElem_L(LinkList L,int i,ElemType &e){ //初始化,p指向第一个结点,j为计数器 p = L->next,j=1; //顺序查找 while(p!=NULL && j<i){ p = p->next; ++j; } //第i个元素不存在 if(!p || j>i){ return ERROR; } e = p->data; return OK; } //算法的时间复杂度为O(n)
-
插入操作:
-
算法思想:
寻找到要插入的位置
判断插入位置是否合法
生成新的结点
实现插入操作
-
代码实现
-
Status ListInser_L(LinkList &L, int i,ElemType e){ //在第i个元素之前插入新的元素e p = L,j = 0; //查找插入位置 while(p&&j<i-1){ p = p->next; j++; } //判断插入位置是否合法 if(!p || j>i-1){ return ERROR; } //生成新的结点 s = (LinkList)malloc(sizeof(LNode)); //实现插入操作 s->data = e; s->next = p->next; p-next = s; return OK; }
-
-
删除操作:
-
算法思想:
找到要删除的位置
令p指向它的前趋
判断删除的位置是否合法
删除并释放结点
-
代码实现:
-
Status ListDelete_L(LinkList &l,int i,ElemType e){ //删除第i个位置的元素,并将其返回给e p = L, j = 0; //找到要删除的位置,,并将p指向它的前趋 while(p->next && j<i-1){ p = p->next; j++; } //判断删除的位置是否合法 if(!p->next && j>i-1){ return ERROR; } //删除并释放结点 q = p->next; p->next = q->next; e = q->data; free(q); return OK; }
-
2.3.3 线 性 表 的 静 态 单 链 表 存 储 存 储 结 构
#define MAXSIZE 1000
typedef struct{
ElemType data;
int cur;
}component,SLinkList[MAXSIZE];
2.3.4 循 环 链 表
- 循环链表是一种头尾相接的链表,特点是表中最后欧的一个结点的指针域指向头结点
- 由于循环链表中没有NULL指针,故涉及到遍历操作时,其终止条件是判断它们是否等于头指针
2.3.5 双 向 链 表
//双向链表的结构可定义如下:
typedef struct DuLNode{
Elemtype data;
struct DuLNode *prior,*next;
}DuLNode,*DuLinkList;
-
双向链表结构的对称性:p->prior->next = p->next->prior
-
双向链表的删除结点过程:
-
p->prior->next = p->next; p->next->prior = p->prior;
-
-
双向链表的插入结点过程
s->prior = p->prior; p->prior->next = s; s->next = p; p->prior = s;
//双向链表的结构可定义如下: typedef struct DuLNode{ Elemtype data; struct DuLNode *prior,*next; }DuLNode,*DuLinkList; ~~~
-
双向链表结构的对称性:
p->prior->next = p->next->prior
-
双向链表的删除结点过程:
p->prior->next = p->next;
p->next->prior = p->prior;
- 双向链表的插入结点过程
s->prior = p->prior;
p->prior->next = s;
s->next = p;
p->prior = s;
第三章: 栈和队列
3.1 栈
3.1 .1抽象数据类型栈的定义
- 栈:是限定仅在表尾进行插入或删除操作的线性表,表尾称为栈顶,表头称为栈底,对应有栈顶元素和栈底元素
3.1.2 顺序栈的表示和实现
#include <stdio.h>
#define STACK_INIT_SZIE 100 //栈存储空间的初始分配量
#define STACKINCREMENT 10 //栈存储空间的分配增量
typedef struct{
SelemType *base; //栈底指针,在栈构造之前和销毁之后,base的值为NULL
SelemType *top; //栈顶指针
int stacksize; //当前分配的栈的可分配的最大存储容量
}Sqstack;
3.1.3 顺序栈的基本操作
- 栈的初始化(构造一个空栈)
Status InitStack(Sqstack &S){
//为栈底指针开辟存储空间
S.base = (SelemType *)malloc(STACK_INIT_SIZE* sizeof(SelemType));
if(!S.base){ //存储分配失败
exit(OVERFLOW);
}
S.top = S.base; //初始化时栈为空栈,将栈顶指针指向栈底指针
S.stacksize = STACK_INIT_SIZE;
return OK;
}
- 栈的插入操作:
- 因为栈是一种后进先出的线性表,因此插入和删除的操作都在栈顶进行
Status Push(Sqstack &S,ElemType data){
//将data插入栈中作为新的栈顶元素
//先判断栈的空间是否足够,如果足够,则执行插入操作,如果不足够则要先进行追加存储空间,再执行插入操作
if(S.top -S.base >=S.stacksize){ //判断栈满则表示:栈顶-栈底 >= 栈当前最大存储容量
//追加空间
S.bsae = (SelemType *)realloc(S.base,(S.stacksize+SATCKINCREMENT)*sizeof(SelemType));
if(!S.base){
exit(OVERFLOW);
}
//追加空间后栈顶指针的位置以及栈的最大存储容量
S.top = S.base+S.stacksize;
S.stacksize = S.stacksize+STACKINCREMENT;
}
//将要插入的元素data插入栈中
*S.top++ = data;
return OK;
}
- 栈的删除操作:
Status Pop(Sqstack &S){
//先判断栈是否为空栈,如若不是空栈,则将当前栈顶元素删除
if(S.top == S.base){
return ERROR;
}
//将栈顶指针向下移动即可
S.top--;
return OK;
}
3.2 栈的应用举例
3.2.1 数制转换
//数制转换
#include <stdio.h>
#include <stdlib.h>
#define STACK_INIT_SIZE 20
#define STACKINCREMENT 5
typedef struct{
int *base;
int *top;
int stacksize;
}Sqstack;
//初始化一个空栈
int InitStack(Sqstack *S){
S->base = (int *)malloc(STACK_INIT_SIZE * sizeof(int));
if(!S->base){
printf("申请空间失败\n");
return 0;
}
S->top = S->base;
S->stacksize = STACK_INIT_SIZE;
return 1;
}
int Push(Sqstack *S, int data){
//判断栈是否满了,如果满了则要进行追加空间
if(S->top-S->base >= S->stacksize){
S->base = (int *)realloc(S->base,(S->stacksize+STACKINCREMENT));
if(!S->base){
printf("追加空间失败\n");
return 0;
}
//追加空间后栈顶位置和栈的最大存储容量
S->top = S->base+S->stacksize;
S->stacksize = S->stacksize + STACKINCREMENT;
}
//将数据data插入栈中
*S->top++ = data;
return 1;
}
int Pop(Sqstack *S, int *data){
//判断栈是否为空
if(S->top ==S->base){
printf("栈中没有任何数据\n");
return 0;
}
//将栈顶数据返回给data;
*S->top--;
*data = *S->top;
return 1;
}
int conversion(Sqstack *S,int number){
int data;
InitStack(S);
while(number){
//如果入栈时追加空间失败,停止操作
if(!Push(S,number%8)){
return 0;
}
number/=8;
}
//输出结果
while(S->base != S->top){
Pop(S,&data);
printf("%d",data);
}
puts("\n");
return 1;
}
int main(void){
Sqstack S;
int num;
printf("请输入要转化为八进制的十进制数:");
scanf("%d",&num);
printf("%d转化为八进制的值为:",num);
InitStack(&S);
conversion(&S,num);
return 0;
}
3.2.2 括号匹配问题
#include <stdio.h>
#include <stdlib.h>
#define STACK_INIT_SIZE 20
#define STACKINCREMENT 5
typedef struct{
char *base;
char *top;
int stacksize;
}Sqstack;
int InitStack(Sqstack *S){
S->base = (char *)malloc(STACK_INIT_SIZE*sizeof(char));
if(!S->base){
printf("申请空间失败\n");
return 0;
}
S->top = S->base;
S->stacksize = STACK_INIT_SIZE;
return 1;
}
int Push(Sqstack *S,char str){
if(S->top - S->base >= S->stacksize){
//判断栈空间是否已满,如果已满,则进行空间追加操作
S->base = (char *)realloc(S->base,(S->stacksize+STACKINCREMENT));
if(!S->base){
printf("追加空间失败\n");
return 0;
}
S->top = S->base + S->stacksize;
S->stacksize = S->stacksize+STACKINCREMENT;
}
*S->top++ = str;
return 1;
}
int Pop(Sqstack *S,char *str){
if(S->top ==S->base){
printf("空栈,栈中没有数据\n");
return 0;
}
*S->top--;
*str = *S->top;
return 1;
}
int matchBracket(Sqstack *S,char *str){
int i = 0;
char temp = NULL;
printf("检测结果为:");
while(str[i]){
if(str[i]=='{'||str[i]=='('||str[i]=='['){
Push(S,str[i]);
}
else if(str[i]=='}'||str[i]==')'||str[i]==']'){
if(S->top ==S->base){
printf("括号不匹配,第一个括号右括号\n");
return 0;
}
//比较右括号是否跟当前栈顶元素的括号是一对括号
if(*(S->top-1)+1 == str[i]||*(S->top-1) + 2 == str[i]){
Pop(S,&temp);
}
else{
printf("括号不匹配,左括号与右括号没有相对应\n");
return 0;
}
}
i++;
}
//如果栈中此时没有括号,则表明输入的括号字符串是相互匹配的
if(S->top ==S->base){
printf("括号匹配成功\n");
return 1;
}
//如果栈中此时还有括号,则表明栈中还有多余的左括号,输入的括号字符串不匹配
else{
printf("括号匹配失败,左括号过多\n");
return 0;
}
}
int main(void){
Sqstack S;
char str[20];
printf("请输入要检验的括号字符串:");
scanf("%s",str);
InitStack(&S);
matchBracket(&S,str);
return 0;
}
#### 3.2.2 括号匹配问题
~~~c
#include <stdio.h>
#include <stdlib.h>
#define STACK_INIT_SIZE 20
#define STACKINCREMENT 5
typedef struct{
char *base;
char *top;
int stacksize;
}Sqstack;
int InitStack(Sqstack *S){
S->base = (char *)malloc(STACK_INIT_SIZE*sizeof(char));
if(!S->base){
printf("申请空间失败\n");
return 0;
}
S->top = S->base;
S->stacksize = STACK_INIT_SIZE;
return 1;
}
int Push(Sqstack *S,char str){
if(S->top - S->base >= S->stacksize){
//判断栈空间是否已满,如果已满,则进行空间追加操作
S->base = (char *)realloc(S->base,(S->stacksize+STACKINCREMENT));
if(!S->base){
printf("追加空间失败\n");
return 0;
}
S->top = S->base + S->stacksize;
S->stacksize = S->stacksize+STACKINCREMENT;
}
*S->top++ = str;
return 1;
}
int Pop(Sqstack *S,char *str){
if(S->top ==S->base){
printf("空栈,栈中没有数据\n");
return 0;
}
*S->top--;
*str = *S->top;
return 1;
}
int matchBracket(Sqstack *S,char *str){
int i = 0;
char temp = NULL;
printf("检测结果为:");
while(str[i]){
if(str[i]=='{'||str[i]=='('||str[i]=='['){
Push(S,str[i]);
}
else if(str[i]=='}'||str[i]==')'||str[i]==']'){
if(S->top ==S->base){
printf("括号不匹配,第一个括号右括号\n");
return 0;
}
//比较右括号是否跟当前栈顶元素的括号是一对括号
if(*(S->top-1)+1 == str[i]||*(S->top-1) + 2 == str[i]){
Pop(S,&temp);
}
else{
printf("括号不匹配,左括号与右括号没有相对应\n");
return 0;
}
}
i++;
}
//如果栈中此时没有括号,则表明输入的括号字符串是相互匹配的
if(S->top ==S->base){
printf("括号匹配成功\n");
return 1;
}
//如果栈中此时还有括号,则表明栈中还有多余的左括号,输入的括号字符串不匹配
else{
printf("括号匹配失败,左括号过多\n");
return 0;
}
}
int main(void){
Sqstack S;
char str[20];
printf("请输入要检验的括号字符串:");
scanf("%s",str);
InitStack(&S);
matchBracket(&S,str);
return 0;
}
3.2.3 行编辑程序
#include <stdio.h>
#include <stdlib.h>
#define STACK_INIT_SIZE 20
#define STACKINCRENMENT 5
typedef struct{
char *base;
char *top;
int satcksize;
}Sqstack;
//初始化一个空栈
int InitStack(Sqstack *S){
S->base = (char *)malloc(STACK_INIT_SIZE*sizeof(char));
if(!S->base){
printf("申请空间失败\n");
return 0;
}
S->top = S->base;
S->satcksize = STACK_INIT_SIZE;
return 1;
}
//入栈
int Push(Sqstack *S,char ch){
//执行入栈操作前,需先判断栈空间是否足够
if(S->top - S->base >= S->satcksize){
S->base = (char *)realloc(S->base,(S->satcksize+STACKINCRENMENT));
if(!S->base){
printf("追加空间失败\n");
return 0;
}
S->top = S->base+STACKINCRENMENT;
S->satcksize+=STACKINCRENMENT;
}
*S->top++;
*S->top = ch;
return 1;
}
//出栈
int Pop(Sqstack *S,int count){
int j;
for(j = 0; j<count-1; j++){
if(S->top == S->base){
printf("出栈失败,栈是空栈\n");
return 0;
}
*S->top--;
}
return 1;
}
void LineEditor(Sqstack *S){
char str[100];
int i,count = 0;
printf("请输入数据:");
scanf("%s",str);
for(i = 0; str[i]; i++){
count+=sizeof(str[i]);
//遇到退格符
if(str[i] == '#'){
Pop(S,2);
count--;
}
//遇到退行符
else if(str[i] == '@'){
Pop(S,count);
}
else{
Push(S,str[i]);
}
}
//输出最后输入的结果
while(S->top!=S->base){
*S->base++;
printf("%c",*S->base);
}
puts("");
}
int main(void){
Sqstack S;
InitStack(&S);
LineEditor(&S);
return 0;
}
3.2.4 栈与递归的的实现(Hanoi塔问题)
//注意函数调用时,形式参数的值的变化
#include <stdio.h>
void move(char a,int number,c){
static int count = 0;
printf("%d.Move disk %c from %c \n",++count,n,a,c);
}
void Hanoi(int n,char a,char b,char c){
if(n == 1){
//将编号为 1 的盘子从 a 移动到 c
move(a,1,c);
}
else{
//将a上编号为1至n-1的盘子从 a 移动到 c,b作为辅助塔
Hanoi(n-1,a,b,c);
//将编号为 n 的盘子从a移动到c
move(a,n,c);
//将编号为 1 至 n-1 的盘子从 b 移动到 c,a作为辅助塔
Hanoi(n-1,b,a,c);
}
}
int main(void){
Hanoi(3,'A','B','C');
return 0;
}
3.3 队列
3.3.1 抽象数据类型队列的定义
- 队列是一种先进先出的线性表,只允许在队尾插入,队头删除
3.3.2 链队列—队列的链式表示和实现
-
链队列的含义:顾名思义,就是用链表表示的队列,也就是仅限在表头(队头)删除,表尾(队尾)插入的单链表
-
一个链队列由一个头指针和一个尾指针唯一确定
-
没有特殊说明的情况下,空队列的判定条件是头指针个尾指针都指向头结点
-
用C语言定义链队列结构如下:
-
typedef struct QNode{ QElemType data; struct QNode *next; }QNode,*QueuePtr; //队列指针指向的结点数据类型为QNode,所以,队列指针的数据类型为QueuePtr typedef struct{ QueuePtr front; //队头指针 QueuePtr rear; //队尾指针 }LinkQueue;
-
3.3.3 队列的基本操作在链队列中的实现
-
队列的初始化:
-
Status InitQueue(LinkQueue *Q){ //为队头指针和队尾指针开辟内存空间 Q->front = Q->rear = (QueuePtr) malloc(sizeof(QNode)); if(Q->front){ printf("开辟空间失败\n"); return OVERFLOW; } reruen OK; }
-
-
销毁队列:
-
Status DestroyQueue(LinkQueue *Q){ while(Q->front){ //当队头指针不为空 //完成下列三行语句后,当前Q->front结点将被释放 Q->rear = Q->front->next; //将队尾指针置为队头指针下一位 free(Q->front); //释放队头指针 Q->front = Q->rear; //重新为队头指针赋值 } return OK; }
-
-
插入操作在队列中的实现:
-
Status EnQueue(LinkQueue *Q,QElemType x){ //将元素x插入对列中作为队列Q的新的队尾元素 QueuePtr p = (QueuePtr)malloc(sizeof(QNode)); //p作为存放元素x的数据域和指针域的结点 if(p){ printf("申请空间失败\n"); return VOERFLOW; } p->data = e; p->next = NULL; //因为插入队尾,所以将p的指针域置空 Q->rear->next = p; Q->rear = p; //队尾指针后移 return OK; }
-
-
删除操作在队列中的实现:
-
Status DeQueue(LinkQueue *Q){ if(Q->front == Q->rear){ printf("队列为空队列,出队列失败\n"); return ERROR; } QueuePtr p = Q->front->next; Q->front->next = p ->next; if(Q->rear == p){ Q->rear = Q->front; } free(p); return OK; }
-