赞
踩
程序=数据结构+算法
非线性结构
用计算机解题一个问题的步骤
数据结构是一门研究非数值计算的程序设计中计算机的操作对象以及它们之间的关系和操作的学科
数据(Data)
是能输入计算机且能被计算机处理的各种符号的集合
数值型的数据:整数、实数等
非数值型的数据:文字、图像、图形、声音等
数据元素(Data Element)
数据项(Data Item)
数据对象(Data Object)
数据结构(Data Structure)
a. 数据元素之间的逻辑关系,称为逻辑结构
b. 数据元素及其关系在计算机内存中的表示(映像),称为物理结构或存储结构
关系:
有且仅有一个开始和一个终端结点,并且所有结点都最多只有一个直接前驱和一个直接后继。
例如:线性表、栈、队列、串
一个结点可能有多个直接前驱和直接后继
例如:树、图
(Data Type)
定义:数据类型是一组性质相同的值的集合以及定义于这个值集合上的一组操作的总称
(Abstract Data Type,ADT)
是指一个数学模型以及定义在此数学模型上的一组操作。
抽象数据类型可用(D,S,P)三元组表示:D是数据对象,S是D上的关系集,P是对D的基本操作集
一个抽象数据类型的定义格式如下:
ADT 抽象数据类型名{
数据对象:<数据对象的定义> //伪代码
数据关系:<数据关系的定义> //伪代码
基本操作:<基本操作的定义> //基本操作名(参数表)、初始条件、操作结果
}ADT 抽象数据类型名
参数表:赋值参数,只为操作提供输入值
引用参数,以&打头,除可提供输入值外,还将返回操作结果
初始条件:操作执行之前数据结构和参数应满足的条件
操作结果:操作正常完成之后,数据结构的变化状况和应返回的结果
例如:Circle的定义
ADT Cirle{
数据对象:D={r,x,y|r,x,y均为实数}
数据关系:R={<r,x,y>|r是半径,<x,y>是圆心坐标}
基本操作:
Circle(&C,r,x,y)
操作结果:构造一个圆
double Area(C)
初始条件:圆已存在。
操作结果:计算面积。
double Circumference(C)
初始条件:圆已存在。
操作结果:计算周长。
}ADT Circle
复数的定义
ADT Complex{
D={r1,r2|r1,r2都是实数}
S={<r1,r2>|r1是实部,r2是虚部}
assign(&C,v1,v2)
初始条件:空的复数C已存在
操作结果:构造复数C,r1,r2分别被赋以参数v1,v2的值
destroy(&C)
初始条件:复数C已存在
操作结果:复数C被销毁
}ADT Complex
复数的实现
typedef struct{ float realpart; /*实部*/ float imagpart; /*虚部*/ }Complex /*定义复数抽象类型*/ //函数声明 void assign(Complex *A,float real,float imag); /*赋值*/ void add(Complex *A,flaot real,float imag); /*A+B*/ void minus(Complex *A,flaot real,float imag); /*A-B*/ void multiply(Complex *A,flaot real,float imag); /*A*B*/ void divide(Complex *A,flaot real,float imag); /*A/B*/ //函数定义 Void assign(Complex *A,float real,float imag){ A->realpart=real; /*实部赋值*/ A->imagpart=imag; /*虚部赋值*/ } void add(Complex *c,Complex A,Complex B){ /*c=A+B*/ c->relpart=A.realpart+B.realpart; /*实部相加*/ c->imagpart=A.imagpart+B.imagpart; /*虚部相加*/ }
算法的定义
对特定问题求解方法和步骤的一种描述,它是指令的有限序列。
算法的描述
自然语言:英语、中文
流程图:传统流程图、NS流程图
伪代码:类语言:类C语言
程序代码:C语言程序、JAVA语言程序
算法与程序
算法是解决问题的一种方法或一个过程,考虑如何将输入转换成输出,一个问题可以有多种算法。
程序是用某种程序设计语言对算法的具体实现。
程序=数据结构+算法
算法特性
有穷性、确定性、可行性、输入、输出
算法设计的要求
正确性(Correctness)、可读性(Readability)、健壮性(Robustness)、高效性(Efficiency)
算法的效率
时间效率和空间效率有时候是矛盾的。
算法时间效率的度量
算法运行时间=一个简单操作所需的时间*简单操作次数
for(i=1;i<=n;i++) //n+1次
for(j=1;j<=n;j++) //n(n+1)次
c[i][j]=0; //n*n次
for(k=0;k<n;k++) //n*n*(n+1)次
c[i][j]=c[i][j]+a[i][k]*b[k][j]; //n*n*n次
算法的渐进时间复杂度
T(n)=O(f(n)) O是数量级的符号
简称时间复杂度。
方法:
忽略所有低次幂项和最高次幂系数
找出语句频度最大的那条语句最为基本语句
计算基本语句的频度得到问题规模n的某个函数f(n)
取其数量级用符号“O”表示
//分析以下程序段的时间复杂度
i=1;
while(i<=n)
i=i*2;
//若循环执行1次:i=1*2=2
//若循环执行2次:i=2*2
//若循环执行3次:i=2*2*2
//若循环执行x次:i=2^x
//因为i<=n,所以2^x<=n,所以x<=log2n,所以T(n)=O(log2n)=O(lgh)
最坏时间复杂度、平均时间复杂度、最好时间复杂度
时间复杂度T(n)按数量级递增顺序为:
常数阶 | 对数阶 | 线性阶 | 线性对数阶 | 平方阶 | 立方阶 | … | K次方阶 | 指数阶 |
---|---|---|---|---|---|---|---|---|
O(1) | O(log2n) | O(n) | O(nlog2n) | O(n^2) | O(n^3) | O(n^k) | O(2^n) |
渐进空间复杂度
算法所需存储空间的度量
记作:S(n)=O(f(n)) n为问题的规模(或大小)
顺序存储结构存在的问题:
基本操作 | 功能 | 操作结果 |
---|---|---|
InitList(&L) | 初始化 | 构建一个空的线性表L |
DestroyList(&L) | 销毁 | 销毁线性表L |
ClearList(&L) | 清除 | 将线性表L重置为空表 |
ListEmpty(L) | 判断是否为空 | 若线性表L为空表,则返回TRUE,否则返回FALSE |
ListLength(L) | 求长度 | 返回线性表L中的数据元素个数 |
GetElem(L,i,&e) | 获取元素 | 用e返回线性表L中第i个数据元素的值(1<=i<=ListLength(L)) |
LocateElem(L,e,compare()) | 查找搜索 | 返回L中第一个与e满足compare()的数据元素的位序。元素不存在则返回值为0 |
PriorElem(L,cur_e,&pre_e) | 求前驱 | cur_e不是第一个数据元素 |
NextElem(L,cur_e,&next_e) | 求后继 | cur_e不是第最后个数据元素 |
ListInsert(&L,i,e) | 插入 | 在L的第i个位置之前插入新的数据元素e,L的长度加一(1<=i<=ListLength(L)+1) |
ListDelete(&L,i,&e) | 删除 | 删除L的第i个数据元素,并用e返回其值,L的长度减一(1<=i<=ListLength(L)) |
ListTraverse(&L,visited()) | 遍历 | 依次对线性表中每个元素调用visited() |
线性表的顺序表示又称为顺序存储结构或顺序映像
顺序存储定义:把逻辑上相邻的数据元素存储在物理上相邻的存储单元中的存储结构
依次存储,地址连续——中间没有空出存储单元,是一个典型的线性表顺序存储结构。
地址不连续——中间存在空的存储单元,不是一个线性表顺序存储结构。
LOC(ai+1)=LOC(ai)+l
所有数据元素的存储位置均可由第一个数据元素的存储位置得到:
LOC(ai)=LOC(a1)+(i-1)*l
表示
顺序表(元素)>地址连续、依次存放、随机存取、类型相同
数组(元素)>用一维数组表示顺序表
线性表长可变,数组长度不可动态定义
#define LIST_INIT SIZE 100 //线性表存储空间的初始分配量
typedef int ElemType;
typedef struct{
ElemType elem[LIST INIT SIZE];
int length; //当前长度
}SqList;
多项式的顺序存储结构类型定义:
```c++
#define MAXSIZE 1000 //多项式可能达到的最大长度
typedef struct{
float p; //系数
int w; //指数
}Ploynomial;
typedef struct{
Polynomial *elem //存储空间的基地址
int length; //多项式中当前项的个数
}SqList; //多项式的顺序存储结构类型为SqList
图书表的顺序存储结构类型定义:
#define MAXSIZE 10000 //图书表可能达到的最大长度
typedef struct{ //图书信息定义
char no[20]; //图书ISBN
char name[50]; //图书名字
float price; //图书价格
}Book;
typedef struct{
Book *elem; //存储空间的基地址
int length; //图书表中当前图书个数
}SqList; //图书表的顺序存储结构类型为SqList
顺序表(Sequence List)
逻辑位序(0开始)和物理位序相差1
//数组动态分配
typedef struct{
ElemType *elem;
int length;
}SqList;
L.elem=(ElemType*)malloc(sizeof(ElemType)*MAXSIZE);
//malloc(m)函数,开辟m字节长度的地址空间,并返回这段空间的首地址
//sizeof(x)运算,计算变量x的长度
//free(p)函数,释放指针p所指变量的存储空间,即彻底删除一个变量
//需要加载头文件:<stdlib.h>
```c++
//数组静态分配
#define MAXSIZE 100
typedef struct{
ElemType elem [MAXSIZE];
int length;
}SqList; //定义顺序表类型
SqList L; //定义变量L,L是SqList这种类型的,L是个顺序表
//引用成员L.elem、L.length
SqList *L //引用成员L->elem、L->length
typedef char ElemType;
typedef int ElemType;
预定义常量和类型:
//函数结果状态代码
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
//Status 是函数的类型,其值是函数结果状态代码
typedef int Status;
typedef char ElemType;
线性表L的初始化
Status InitList_Sq(SqList &L){ //构造一个空的顺序表L
L.elem=new ElemType[MAXSIZE]; //为顺序表分配空间c++
if(!L.elem) exit(OVERFLOW); //存储分配失败
L.length=0; //空表长度为0
return OK;
}
销毁线性表
void DestroyList(SqList &L){
if(L.elem)delete L.elem; //释放存储空间c++
}
清空线性表L
void ClearList(SqList &L){
L.length=0; //将线性表的长度置为0
}
求线性表L的长度
int GetLength(SqList){
return (L.length);
}
判断线性表L是否为空
int IsEmpty(SqList L){
if(L.length==0) return 1;
else return 0;
}
顺序表的取值
根据位置i获取相应位置数据元素的内容
int GetElem(SqList L,int i,ElemType &e){
if(i<1||i>L.length)return ERROR; //判断i值是否合理,若不合理返回error
e=L.elem[i-1]; //第i-1的单元存储着第i个数据
return OK;
}
顺序表的查找
int LocateElem(SqList L,ElemType e){
//在线性表L中查找值为e的数据元素,返回其序号(是第几个元素)
for(i=0;i<L.length;i++)
if(L.elem[i]==e) return i+1; //查找成功,返回序号
return 0; //查找失败,返回0
}
平均查找长度ASL(Average Search Length)
顺序表的插入
Status ListInsert_Sq(SqList &L,int i,ElemType e){
if(i<1||i>L.length+1) return ERROR; //i值不合法
if(L.length==MAXSIZE) retrun ERROR; //当前存储空间已满
for(j=L.length-1;j>=i-1;j--)
L.elem[j+1]=L.elem[j]; //插入位置及之后的元素后移
L.elem[i-1]=e; //将新元素e放入第i个位置
L.length++; //表长增1
return OK;
}
顺序表的删除
Status ListDelete_Sq(SqList &L,int i){
if((i<1)||(i>L.length)) return ERROR;
for(j=i;j<=L.length-1;j++)
L.elem[j-1]=L.elem[j];
L.length--;
return OK;
}
顺序表的操作算法分析
时间复杂度
查找、插入、删除算法的平均时间复杂度为O(n)
空间复杂度
S(n)=O(1) (没有占用辅助空间)
优点
(1)存储密度大
(2)可以随机存取表中任一元素
缺点
(1)在插入、删除某一元素时,需要移动大量元素
(2)存储空间不灵活,浪费存储空间
(3)属于静态存储形式,数据元素的个数不能自由扩充
链式存储结构:结点在存储器中的位置时任意的,即逻辑上相邻的数据元素在物理上不一定相邻
线性表的链式表示又称为非顺序映像或链式映像
用一组物理位置任意的存储单元来存放线性表的数据元素
这组存储单元既可以是连续的,也可以是不连续的
链表中元素的逻辑次序和物理次序不一定相同
单链表是由头指针唯一确定,因此单链表可以用头指针的名字来命名
各结点有两个域组成:
数据域:存储袁术数值数据
指针域:存储直接后继结点的存储位置
相关术语:
1.结点:数据元素的存储映像。由数据域和指针域两部分组成
2.链表:n个结点由指针链组成一个链表,它是线性表的链式存储映像,称为线性表的链式存储结构
分类:
1.单链表:结点只有一个指针域的链表,称为单链表或线性链表
2.双链表:结点有两个指针域的链表
3.循环链表:首尾相接的链表
空表:
无头结点是,头指针为空时表示空表
有头结点时,当头结点的指针域为空时表示空表
特点:
1.结点在存储器中的位置是任意的,即逻辑上相邻的数据元素在物理上不一定相邻
2.访问时只能通过头指针进入链表,并通过每个结点的指针域依次向后顺序扫描其余结点,所以寻找第一个结点和最后一个结点所花费的时间不等(顺序存取法)
typedef struct Lnode{ //生命结点的类型和指向结点的指针类型
ElemType data; //结点的数据域
struct Lnode *next; //结点的指针域
}Lnode,*LinkList; //LinkList为指向结构体Lnode的指针类型
LinkList L; //定义链表L:
Lnode *p;
LinkList p; //定义结点指针p:
typedef Struct student{
char num[8];
char name[8];
int score;
struct student *next;
}Lnode,*LinkList;
typedef Struct{
char num[8];
char name[8];
int score;
}ElemType;
typedef struct Lnode{
ElemType data;
struct Lnode *next;
}Lnode,*LinkList;
单链表的初始化
即构造一个空表
Status InitList L(LinkList &L){
L=new LNode; //或L=(LinkList)malloc(sizeof(LNode));
L->next=NULL;
return OK;
}
判断链表是否为空
int ListEmpty(LinkList){ //若L为空表,则返回1,否则返回0
if(L->next) //非空
return 0;
else
return 1;
}
单链表的销毁
Status DestroyList_L(LinkList &L){
Lnode *p; //或LinkList p;
while(L){
p=L,
L=L->next;
delete p;
}
return OK;
}
清空单链表
Status ClearList(LinkList &L){ //将L重置为空表
Lnode *p,*q; //或LinkList p,q;
p=L->next;
while(p){ //没到表尾
q=p->next;
delete p;
p=q;
}
L->next=NULL; //头结点指针域为空
return OK;
}
求单链表的表长
int ListLengt_L(LinkList L){ //返回L中数据元素个数
LinkList p;
p=L->next; //p指向第一个结点
i=0;
while(p){ //遍历单链表,统计结点数
i++;
p=p->next;
}
return i;
}
取值
//获取线性表中的某个数据元素的内容,通过变量e返回
Status GetElem_L(LinkList L,int i,ElemType &e){
p=L->next;
j=1; //初始化
while(p&&j<i){ //向后扫描,知道p指向第i个元素或p为空
p=p-next;++j;
}
if(!p||j>i)return ERROR; //第i个元素不存在
e=p->data;
return OK;
}//GetElem_L
查找
//1. Lnode *LocateElem_L(LinkList L,Elemtype e){ //在线性表L中查找值为e的数据元素 //找到,则返回L中值为e的数据元素的地址,查找失败返回NULL p=L->next; while(p&&p->data!=e) p=p->next; return p; } //2. int LocateElem_L(LinkList L,Elemtype e){ //在线性表L中查找值为e的数据元素的位置序号 //返回L中值为e的数据元素的位置序号,查找失败返回0 p=L-next; j=1; while(p&&p->data!=e) { p=p->next;j++; } if(p) return j; else return 0; }
插入
Status ListInsert_L(LinkList &L,int i,ElemType e){
p=L;
j=0;
while(p&&j<i-1){
p=p->next;++j; //寻找第i-1个结点,p指向i-1结点
}
if(!p||j>i-1)return ERROR; //i大于表长+1或者小于1,插入位置非法
s=new LNode; //生成新结点s,将结点s的数据域置为e
s->data=e; //将结点s插入L中
s-next=p-next;
p-next=s;
return OK;
}//ListInsert_L
删除
//将线性表L中第i个数据元素删除
Status ListDelete_L(LinkList &L,int i,ElemType &e){
p=L;
j=0;
while(p-next&&j<i-1){
p=p-next;++j; //寻找第i个结点,并令p指向其前驱
}
if(!(p->next)||j>i-1)return ERROR; //删除位置不合理
q=p->next; //临时保存被删结点的地址以备释放
p->next=q->next; //改变删除结点前驱结点的指针域
e=q->data; //保存删除结点的数据域
delete q; //释放删除结点的空间
return OK;
}//ListDelete_L
单链表的建立
void CreateList_H(LinkList &L,int n){
L=new LNnode;
L->next=NULL; //先建立一个带头结点的单链表
for(i=n;i>0;--i){
p=new LNode; //生产新结点
//p=(LNode*)malloc(sizeof(LNode));
scanf(&p->data); //输入元素值
p->next=L-next; //插入到表头
L->next=p;
}
}//CreateList_H
//算法的时间复杂度是O(n)
void CreateList_R(LinkList &L,int n){
L=new LNode;
L->next=NULL;
r=L; //尾指针r指向头结点
for(i=0;i<n;++i){
p=(LNode*)malloc(sizeof(LNode));;
scanf(&p->data);
p->next=NULL;
r->next=p;
r=p;
}
}//CreateList_R
//算法的时间复杂度是O(n)
查找
因线性链表只能顺序存取,即在查找时要从头指针找起,查找时间复杂度为O(n)
插入和删除
因线性表不需要移动元素,只要修改指针,一般情况下时间复杂度为O(1)
头尾相接的链表——表中最后一个结点的指针域指向头结点,整个链表形成一个环
优点:从表中任一结点出发均可找到表中其他结点
循环条件:
p != L
p->next != L
带尾指针循环链表的合并:
LinkList Connect(LinkList Ta,LinkList Tb){ //假设Ta、Tb都是非空的单循环链表
p=Ta->next; //p存表头结点
Ta->next=Tb->next-next; //Tb表头连结Ta表尾
delete Tb->next; //释放Tb表头结点
Tb->next=p; //修改指针
return Tb;
}
在单链表的每个结点里再增加一个指向其直接前驱的指针域prior,形成两个方向不同的链
双向循环链表
头结点的前驱指针指向链表的最后一个结点
最后一个结点的后继指针指向头结点
双向链表的对称性
p->prior->next = p = p->next->prior
双链表结构定义:
typedef struct DuLNode{
Elemtype data;
struct DuLNode *prior,*next;
}DuLNode,*DuLinkList;
双链表的插入
void ListInsert_DuL(DuLinkList &L,Int i,ElemType e){
//在带头结点的双向循环链表L中第i个位置之前插入元素e
if(!(p=GetElemP_Dul(L,i))) return ERROR;
s=new DulNode;
s->data=e;
s->prior=p->prior;
p->prior->next=s;
s->next=p;
p->prior=s;
return OK;
}//ListInsert_DuL
双向链表的删除
void ListDelete_DuL(DuLink &L,Int i,ElemType &e){
if(!(p=GetElemP_DuL(L,i))) return ERROR;
e=p->data;
p->prior->next=p->next;
p->next->prior=p->prior;
free(p);
return OK;
}//ListDelete_DuL
优点
缺点
存储密度小,每个结点的指针域需额外占用存储空间
(存储密度=结点数据本身占用的空间/结点占用的空间总量)
存储密度越大,存储空间的利用率就越高。顺序表存储密度为1(100%),链表的存储密度小于1
链式存储结构是非随机存取结构,对任一结点的操作都要从头指针依指针链查找到该结点,增加了算法的复杂度
顺序表 | 链表 | |
---|---|---|
存储空间 | 预先分配,会导致空间闲置或溢出现象 | 动态分配 |
存储密度 | 不用为表示结点间的逻辑关系而增加额外的存储开销,存储密度=1 | 需要借助指针来体现元素间的逻辑关系,存储密度小于1 |
存取元素 | 随机存取,按位置访问元素的时间复杂度为O(1) | 顺序存取,按位置访问元素的时间复杂度为O(n) |
插入、删除 | 时间复杂度为O(n) | 不需移动元素,时间复杂度为O(1) |
适用情况 | 1. 表变化不大,且能事先确定变化的范围2. 很少进行插入或删除操作,经常按元素位置序号访问数据元素 | 1. 长度变化较大2. 频繁进行插入或删除操作 |
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))
ListInsert(&La,++La_len,e);
}
}
void MergeList_Sq(SqList LA,SqList LB,SqList &LC){
pa=LA.elem;
pb=LB.elem; //指针pa和pb的处置分别指向两个表的第一个元素
LC.length=LA.length+LB.length; //新表长度为待合并两表的长度之和
LC.elem=new ElemType[LC.length];//为合并后的表分配一个数组空间
pc=LC.elem; //指针pc指向新表的第一个元素
pa_last=LA.elem+LA.length-1; //指针pa_last指向LA表的最后一个元素
pb_last=LB.elem+LB.length-1; //指针pb_last指向LB表的最后一个元素
while(pa<=pa_last && pb<=pb_last){ //两个表都非空
if(*pa<=*pb) *pc++=*pa++; //依次“摘要”两表中值较小的结点
else *pc++=*pb++;
}
while(pa<=pa_last) *pc++=*pa++;//LB表已到达表尾,将LA中剩余元素加入LC
while(pb<=pb_last) *pc++=*pb++;//LA表已到达表尾,将LB中剩余元素加入LC
}//MergeList_Sq
void MergeList_L(LinkList &La,LinkList &Lb,LinkList &Lc){ pa=La->next; pb=Lb->next; pc=Lc=La; //用La的头结点作为Lc的头结点 while(pa&&pb){ if(pa->data <= pb->data){ pc->next=pa; pc=pa; pa=pa->next; } else{ pc->next=pb; pc=pb; pb=pb->next; } } pc->next=pa?pa:pb; //插入剩余段 delete Lb; //释放Lb的头结点 }
void CreatePolyn(Polynomial &P,int n){ //输入m项的系数和指数,建立表示多项式的有序链表p P=new PNode; p->next=NULL; //先建立一个带头结点的单链表 for(i=2;i<=n;++i){ //依次输入n个非零项 s=new PNode; //生成新结点 cin>>s->code>>s->expn; //输入系数和指数 pre=P; //pre用于保存q的前驱,值为头结点 q=P->next; //q初始化,指向首元结点 while(q&&q->expn<s->expn){ //找到第一个大于输入项指数的项*q pre=q; q=q->next; } s->next=q; pre->next=s; } }
struct Book{
char id[20];
char name[50];
int price;
};
typedef struct{
Book *elem;
int length;
}SqList;
typedef struct LNode{
Book data;
struct LNode *next;
}LNode,*LinkList;
栈和队列是限定插入和删除只能在表的“端点“进行的线性表(操作受限)
操作 | 描述 |
---|---|
InitStack(&S) | 初始化操作 |
DestoryStack(&S) | 销毁栈操作 |
StackEmpty(S) | 判定S是否为空栈 |
StackLength(S) | 求栈的长度 |
GetTop(S,&e) | 取栈顶元素 |
ClearStack(&S) | 栈置空操作 |
Push(&S,e) | 入栈操作 |
Pop(&S,&e) | 出栈操作 |
#define MAXSIZE 100
typedef struct{
SElemType *base; //栈底指针
SElemType *top; //栈顶指针
int stacksize; //栈可用最大容量
}SqStack;
Status InitStack(SqStack &S){ //构造一个空栈
S.base=new SElemType[MAXSIZE];
//S.base=(SElemType*)malloc(MAXSIZE*sizeof(SElemType));
if(!S.base)exit (OVERFLOW); //存储分配失败
S.top=S.base; //栈顶指针等于栈底指针
S.stacksize=MAXSIZE;
return OK;
}
Status StackEmpty(SqStack S){
if(S.top==S.base)
return TRUE;
else
return FALSE;
}
int StackLength(SqStack S){
return S.top-S.base;
}
Status ClearStack(SqStack S){
if(S.base)S.top=S.base;
return OK;
}
Status DestoryStack(SqStack &S){
if(S.base){
delete S.base;
S.stacksize=0;
S.base=S.top=NULL;
}
return OK;
}
Status Push(SqStack &S,SElemType e){
if(S.top-S.base==Stacksize)//栈满
return ERROR;
*S.top++=e;
return OK;
}
Status Pop(SqStack &S,SElemType &e){
if(S.top==S.base) return ERROR;
e=*--S.top;
return OK;
}
typedef struct StackNode{
SElemType data;
struct StackNode *next;
}StackNode,*LinkStack;
LinkStack S;
void InitStack(LinkStack &S){
//构造一个空栈,栈顶指针置为空
S=NULL;
return OK;
}
Status StackEmpty(LinkStack S){
if(S==NULL)return TRUE;
else return FALSE;
}
Status Push(LinkStack &S,SElemType e){
p=new StackNode;
p->data=e;
p->next=S;
S=p;
return OK;
}
Status Pop(LinkStack &S,SElemType &e){
if(S==NULL)return ERROR;
e=S->data;
p=S;
S=S->next;
delete p;
return OK;
}
SElemType GetTop(LinkStack S){
if(S!=NULL)
return S-data;
}
若一个对象部分地包含它自己,或用它自己给自己定义,则称这个对象是递归的;
若一个过程直接地或间接地调用自己,则称这个过程是递归的过程。
函数调用过程
嵌套调用:遵循后调用的先返回
递归工作栈——递归程序运行期间使用的数据存储区
long Fact(long n){
if(n==0)return 1;
else return n*Fact(n-1);
}
//两者等同
long Fact(long n){
t=1;
for(i=1;i<=n;i++)t=t*i;
return t;
}
队列(queue)是一种先进先出(First In First Out----FIFO)的线性表。
在表一端插入(表尾),在另一端(表头)删除
入队、出队
表达式的组成
操作数(operand):常数、变量
运算符(operator):算术运算符、关系运算符、逻辑运算符
界限符(delimiter):左右括弧和表达式结束符
操作 | 描述 |
---|---|
InitQueue(&Q) | 构造空队列Q |
DestoyQueue(&Q) | 销毁队列 |
ClearQueue(&Q) | 清空队列 |
QueueLength(Q) | 求队长 |
GetHead(Q,&e) | 返回对头元素 |
EnQueue(&Q,e) | 插入队尾元素 |
DeQueue(&Q,&e) | 删除对头元素 |
#define MAXSIZE 100
Typedef struct{
QElemType *base; //初始化的动态分配存储空间
int front; //头指针
int rear; //尾指针
}SqQueue;
Status InitQueue(SqQueue &Q){
Q.base=new QElemType[MAXQSIZE]; //分配数组空间
//Q.base=(QElemType*)malloc(MAXQSIZE*sizeof(QElemType));
if(!Q.base)exit(OVERFLOW); //存储分配失败
Q.front=Q.rear=0; //头指针尾指针置为0,队列为空
return OK;
}
int QueueLength(SqQueue Q){
return((Q.rear-Q.front+MAXQSIZE)%MAXQSIZE);
}
Status EnQueue(SqQueue &Q,QElemType e){
if((Q.rear+1)%MAXQSIZE==Q.front)return ERROR; //队满
Q.base[Q.rear]=e; //新元素加入队尾
Q.rear=(Q.rear+1)%MAXQSIZE; //队尾指针+1
return OK;
}
Status DeQueue(SqQueue &Q,QElemType &e){
if(Q.front==Q.rear)return ERROR; //队空
e=Q.base[Q.front]; //保存队头元素
Q.front=(Q.front+1)%MAXQSIZE; //队头指针+1
return OK;
}
QElemType GetHead(SqQueue Q){
if(Q.front!=Q.rear) //队列不为空
return Q.base[Q.front]; //返回队头指针元素的值,队头指针不变
}
#define MAXQSIZE 100 //最大队列长度
typedef struct Qnode{
QElemType data;
struct Qnode *next;
}QNode,*QueuePrt;
typedef struct{
QueuePrt front; //队头指针
QueuePrt rear; //队尾指针
}LinkQueue;
Status InitQueue(LinkQueue &Q){
Q.front=Q.rear=(QueuePrt)malloc(sizeof(QNode));
if(!Q.front)exit(OVERFLOW);
Q.front->next=NULL;
return OK;
}
Status DestroyQueue(LinkQueue &Q){
while(Q.front){
p=Q.front->next;
free(Q.front);
Q.front=p;
}
return OK;
}
Status EnQueue(LinkQueue &Q,QElemType e){
p=(QueuePtr)malloc(sizeof(QNode));
if(!p)exit(OVERFLOW);
p->data=e;
p->next=NULL;
Q.rear->next=p;
Q.rear=p;
return OK;
}
Status DeQueue(LinkQueue &Q,QElemType &e){
if(Q.front==Q.rear)return ERROR;
p=Q.front->next;
e=p->data;
Q.front->next=p->next;
if(Q.rear==p)Q.rear=Q.front;
delete p;
return OK;
}
Status GetHead(LinkQueue Q,QElemType &e){
if(Q.front==Q.rear)return ERROR;
e=Q.front->next->data;
return OK;
}
串(String)——零个或多个任意字符组成的有限序列
s=“a1a2…an”(n>=0)
空串——n=0
子串:串中任意个连续字符组成的子序列称为该串的子串
真子串:指不包含自身的所有子串
主串:包含字串的串相应地称为主串
字符位置:字符在序列中的序号为该字符在串中的位置
字串位置:字串第一个字符在主串中的位置
空格串:由一个或多个空格组成的串
串相等:当且仅当两个串的长度相等并且各个对应位置上的字符都相同时,这两个串才是相等的
所有空串是相等的
操作 | 描述 |
---|---|
StrAssign(&T,chars) | 串赋值 |
StrCompare(S,T) | 串比较 |
StrLength(S) | 求串长 |
Concat(&T,S1,S2) | 串连结 |
SubString(&Sub,S,pos,len) | 求子串 |
StrCopy(&T,S) | 串拷贝 |
StrEmpty(S) | 串判空 |
ClearString(&S) | 清空串 |
Index(S,T,pos) | 子串的位置 |
Replace(&S,T,V) | 串替换 |
StrInsert(&S,pos,T) | 子串插入 |
StrDelete(&S,pos,len) | 子串删除 |
DestroyString(&S) | 串销毁 |
#define MAXLEN 255
typedef struct{
char ch[MAXLEN+1]; //存储串的一维数组
int length; //串的当前长度
}SString;
#define CHUNKSIZE 90 //块的大小
typedef struct Chunk{
char ch[CHUNKSIZE];
struct Chunk *next;
}Chunk;
typedef struct{
Chunk *head,*tail; //串的头指针和尾指针
int curlen; //串的当前长度
}LString; //字符串的块链结构
亦称简单匹配算法,采用穷举法的思路
int Index_BF(SString S,SString T){
int i=1,j=1;
while(i<=S.length && j<=T.length){
if(s.ch[i]==t.ch[j]){++i;++j;} //主串和子串依次匹配下一个字符
else {i=i-j+2;j=1;} //主串、子串指针回溯重新开始下一次匹配
}
if(j>=T.length)return i-T.length; //返回匹配的第一个字符的下标
else return 0; //模式匹配不成功
}
时间复杂度:O(n*m) (n为主串长度,m为子串长度)
int Index_KMP(SString S,SString T,int pos){ i=pos,j=1; while(i<S.length && j<T.length){ if(j==0 || S.ch[i]==T.ch[j]){i++;j++;} else j=next[j]; //i不变,j后退 } if(j>T.length) return i-T.length; //匹配成功 else return 0; //匹配失败 } void get_next(SString T,int &next[]){ i=1;next[1]=0;j=0; while(i<T.length){ if(j==0||T.ch[i]==T.ch[j]){ ++i;++j; next[i]=j; } else j=next[j]; } } //改进 void get_nextval(SString T,int &nextval[]){ i=1,j=0; nextval[1]=0; while(i<T.length){ if(j==0||T.ch[i]==T.ch[j]){ ++i;++j; if(T.ch[i]!=T.ch[j])nextval[i]=j; else nextval[i]=nextval[j]; } else j=nextval[j]; } }
时间复杂度:O(n+m) (n为主串长度,m为子串长度)
数组:按一定格式排列起来的,具有相同类型的数据元素的集合
一维数组:若线性表中的数据元素为非结构的简单元素,则称为一维数组
一维数组的逻辑结构:线性结构。定长的线性表。
声明格式:数据类型 变量名称[长度];
在C语言中,一个二维数组类型也可以定义为一维数组类型(其分量类型为一维数组类型)即:
typedef elemtype array2[m] [n];
等价于:
typedef elemtype array1[n];
typedef array1 arrray2[m];
线性表结构是数组结构的一个特例,而数组结构又是线性表结构的扩展。
数组特点:结构固定——定义后,维数和维界不再改变。
基本操作:
操作 | 描述 |
---|---|
InitArray(&A,n,bound1,…boundn) | 构造数组A |
DestroyArray(&A) | 销毁数组A |
Value(A,&e,index1,…,indexn) | 取数组元素值 |
Assign(A,&e,index1,…,indexn) | 给数组元素赋值 |
LOC(i)=LOC(i-1)+L=a+i*L(LOC(0)=a)
LOC(i,j)=LOC(0,0)+(n*i+j)*L
两种顺序存储方式:
压缩存储:若多个数据元素的值都相同,则只分配一个元素值的存储空间,且零元素不占存储空间。
对称矩阵、三角矩阵、对角矩阵
稀疏矩阵:
三元组顺序表 i j v
十字链表法
又称列表Lists
LS=(a1,a2,…,an)
表头head(LS)=a1
表尾tail(LS)=(a2,…,an),表尾一定是一个表
广义表的长度定义为最外层所包含元素的个数
广义表的深度定义为该广义表展开后所含括号的重数
原子的深度为0,空表的深度为1
广义表可以是一个递归表,例如F=(a,F)=(a,(a,(a,…))),递归表的深度是无穷值,长度是有限值
广义表可以看成是线性表的推广,线性表是广义表的特例
广义表的结构灵活,在某种前提下,它可以兼容线性表、数组、树和有向图等各种常用的数据结构
基本运算:
功能 | 描述 |
---|---|
GetHead(L) | 求表头 |
GetTail(L) | 求表尾 |
功能 | 描述 |
---|---|
CreateBiTree(&T,definition) | 按definition构造二叉树T |
PreOrderTraverse(T) | 先序遍历T |
InOrderTraverse(T) | 中序遍历T |
PostOrderTraverse(T) | 后序遍历T |
性质1:在二叉树的第i层上至多有2^(i-1)个结点(i>=1)
第i层上至少有1个结点
性质2:深度为k的二叉树至多有2^k-1个结点(k>=1)
深度为k时至少有k个结点
性质3:对任何一棵二叉树T,如果其叶子树为n0,度为2的结点树为n2,则n0=n2+1
总边数B=结点数n - 1
B=n2 * 2 + n1 * 1
n=B+1=n22 + n11 + 1
n=n2+n1+n0
性质4:具有n个结点的完全二叉树的深度为|log2n|+1
|x|:称作x的低,表示不大于x的最大整数
表明了完全二叉树结点数n与完全二叉树深度k之间的关系 k=|log2n|+1
性质5:如果对一棵有n个结点的完全二叉树(深度为|log2n|+1)的结点按层序编号(从第一层到第|log2n|+1层,每层从左到右),则对任一结点i(1<=i<=n),有:
a. 如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则其双亲是结点|i/2|
b. 如果2i>n,则结点i为叶子结点,无左孩子;否则,其左孩子是结点2i
c. 如果2i+1>n,则结点i无右孩子;否则其右孩子是结点2i+1
实现:按满二叉树的结点层次编号,依次存放二叉树中的数据元素
#define MAXTSIZE 100
Typedef TElemType SqBiTree[MAXSTIZE]
SqBiTree bt;
缺点:最坏情况,深度为k的且只有k个结点的单支树需要长度2^k-1的一维数组
特点:结点间关系蕴含在其存储位置中
浪费空间,适于存满二叉树和完全二叉树
指向两个孩子
typedef struct BiNode{
TElemType data;
struct BiNode *lchild,*rchild; //左右孩子指针
}BiNode,*BiTree;
在n个结点的二叉链表中,有_________个空指针域:
必有2n个链域。除根结点外,每个结点有且仅有一个双亲,所以只会有n-1个结点的链域存放指针,指向非空子女结点。
空指针数目=2n-(n-1)=n+1
指向两个孩子和老父亲
typedef struct TriTNode{
TelemType data;
struct TriTNode *lchild,*parent,*rchild;
}TriTNode,*TriTree;
Status PreOrderTraverse(BiTree T){ if(T==NULL) return OK; //空二叉树 else{ visit(T); //访问根结点 PreOrderTraverse(T->lchild); //递归遍历左子树 PreOrderTraverse(T->rchild); //递归遍历右子树 } } void Pre(BiTtee *T){ if(T!=NULL){ printf("%d\t",T->data); pre(T->lchild); pre(T->rchild); } }
Status InOrderTraverse(BiTree T){
if(T==NULL)return OK; //空二叉树
else{
InOrderTraverse(T->lchild); //递归遍历左子树
visit(T); //访问根结点
InOrderTraverse(T->rchild); //递归遍历右子树
}
}
Status PostOrderTraverse(BiTree T){
if(T==NULL)return OK; //空二叉树
else{
PostOrderTraverse(T->lchild); //递归遍历左子树
PostOrderTraverse(T->rchild); //递归遍历右子树
visit(T); //访问根结点
}
}
递归遍历算法的时间效率:O(n) //每个结点只访问一次
空间效率:O(n) //栈占用的最大辅助空间
Status InOrderTraverse(BiTree T){ BiTree p; InitStack(S); p=T; while(p||!StackEmpty(S)){ if(p){ Push(S,p); p=p->lchild; } else{ Pop(S,q); printf("%c",q->data); p=q->rchild; } } return OK; }
typedef struct{ BTNode data[MaxSize]; //存放队中元素 int front,rear; //队头和队尾指针 }SqQueue; //顺序循环队列类型 void LevelOrder(BTNode *b){ BTNode *p; SqQueue *qu; InitQueue(qu); //初始化队列 enQueue(qu,b); //根结点指针进入队列 while(!QueueEmpty(qu)){ //队不为空,则循环 deQueue(qu,p); //出队结点p printf("%c",p->data); //访问结点p if(p->lchild!=NULL)enQueue(qu,p->lchild); //有左孩子是将其进队 if(p->rchild!=NULL)enQueue(qu,p->rchild); //有右孩子是将其进队 } }
按先序遍历序列建立二叉树的二叉链表
Status CreateBiTree(BiTree &T){
scanf(&ch);
if(ch=="#") T=NULL;
else{
if(!(T=(BiTNode *)malloc(sizeof(BiTNode))))
exit(OVERFLOW); //T=new BiTNode;
T->data=ch; //生成根结点
CreateBiTree(T->lchild); //构造左子树
CreateBiTree(T->rchild); //构造右子树
}
return OK;
}//CreateBiTree
int Copy(BiTree T,BiTree &NewT){
if(T==NULL){
NewT=NULL;
retrun 0;
}
else{
NewT=new BiTNode;
NewT->data=T->data;
Copy(T->lChild,NewT->lchild);
Copy(T->rChild,NewT->rchild);
}
}
int Depth(BiTree T){
if(T==NULL)return 0;
else{
m=Depth(T->lChild);
n=Depth(T->rChild);
if(m>n)return(m+1);
else return(n+1);
}
}
int NodeCount(BiTree T){
if(T==NULL)return 0;
else
return NodeCount(T->lchild)+NodeCount(T->rchild)+1;
}
int LeadCount(BiTree T){
if(T==NULL) return 0;
if(T->lchild==NULL && T->rchild==NULL) return 1;
else return LeafCount(T->lchild)+LeafCount(T->rchild);
}
利用某个结点的左孩子为空,则将空的左孩子指针域改为指向其前驱;
如果某结点的右孩子为空,则将空的右孩子指针域改为指向其后继;
——这种改变指向的指针称为"线索"
加上了线索的二叉树称为线索二叉树(Threaded Binary Tree)
对二叉树按某种遍历次序使其变为线索二叉树的过程叫线索化
为区分lchild和rchild指针到底是指向孩子的指针,还是指向前驱或者后继的指针,对二叉链表中每个结点增设两个标志域ltag和rtag,并约定:
Itag=0 lchild指向该结点的左孩子
ltag=1 lchild指向该结点的前驱
rtag=0 rchild指向该结点的右孩子
rtag=1 rchild指向该结点的后继
typedef struct BiThrNode{
int data;
int ltag,rtag;
struct BiThrNode *lchild,*rchild;
}BiThrNode,*BiThrTree;
增设了一个头结点:
ltag=0,lchild指向根结点,rtag=1,rchild指向遍历序列中最后一个结点
遍历序列中第一个结点的lchild域和最后一个结点的rc域都指向头结点
typedef struct PTNode{
TElemType data;
int parent; //双亲位置域
}PTNode;
#define MAX_TREE_SIZE 100
typedef struct{
PTNode nodes[MAX_TREE_SIZE];
int r,n; //根结点的位置和结点个数
}PTree;
//孩子结点结构 typedef struct CTNode{ int child; sturct CTNode *next; }*ChildPtr; //双亲结点结构 typedef struct{ TElemType data; ChildPtr firstchild; //孩子链表头指针 }CTBox; //树结构 typedef struct{ CTBox nodes[MAX_TREE_SIZE]; int n,r; //结点数和根结点的位置 }CTree;
又称二叉树表示法,二叉链表表示法
typedef struct CSNode{
ElemType data;
struct CSNode *firstchild,*nextsibling;
}CSNode,*CSTree;
A. 先根(次序)遍历:若树不空,则先访问根结点,然后依次先根遍历各棵子树
B. 后根(次序)遍历:若树不空,则先依次后根遍历各棵子树,然后访问根结点
C. 按层次遍历:若树不空,则自上而下自左至右访问树中每个结
A. 先序遍历
若森林不空,则
访问森林中第一棵树的子树森林
先序遍历森林中第一棵树的子树森林
遍历森林中(除第一棵树之外)其余树构成的森林
B. 中序遍历
贪心算法:构造哈夫曼树时首先选择权值小的叶子结点
哈夫曼树中权越大的叶子离根越近
哈夫曼算法:
哈夫曼树的结点的度数为0或2,没有度为1的结点
包含n个叶子结点的哈夫曼树中共有2n-1个结点
包含n棵树的森林要经过n-1次合并才能形成哈夫曼树,共产生n-1个新结点
//结构类型定义
typedef struct{
int weight;
int parent,lch,rch;
}HTNode,*HuffmanTree;
哈夫曼树中共有2n-1个结点,不使用0下标,数组大小为2n
void CreateHuffmanTree(HuffmanTree HT,int n){ if(n<=1)return 0; m=2*n-1; //数组共2n-1个元素 HT=new HTNode[m+1]; //0号单元未用,HT[m]表示根结点 for(i=1;i<=m;++i){ //将2n-1个元素的lch、rch、parent置为0 HT[i].lch=0; HT[i].rch=0; HT[i].parent=0; } for(i=1;i<=n;++i) ch>>HT[i].weight; //输入前n个元素的weight值 //初始化结束,下面开始建立哈夫曼树 for(i=n+1;i<=m;i++){ //合并产生n-1个结点——构造Huffman树 Select(HT,i-1,s1,s2); //在HT[k](1<=k<=i-1)中选择两个其双亲域为0,且权值最小的结点,并返回它们在 HT中的序号s1和s2 HT[s1].parent=i; HT[s2].parent=i; //表示从F中删除s1,s2 HT[i].lch=s1; HT[i].rch=s2; //s1,s2分别作为i的左右孩子 HT[i].weight=HT[s1].weight+HT[s2].weight; //i的权值为左右孩子权值之和 } }
关键:要设计长度不等的编码,则必须使任一字符的编码都不是另一个字符的编码的前缀——前缀编码
哈夫曼编码
统计字符集中每个字符在电文中出现的平均概率(概率越大,要求编码越短)
利用哈夫曼树的特点:权越大的叶子离根越近;将每个字符的概率值作为权值,构造哈夫曼树。则概率越大的结点,路径越短
在哈夫曼树的每个分支上标上0或1:
结点的左分支标0,右分支标1
把从根到每个叶子的路径上的标号连结起来,作为该叶子代表的字符的编码
为什么哈夫曼编码能够保证是前缀编码?
因为没有一片树叶是另一片树叶的祖先,所以每个叶结点的编码就不可能是其它叶结点编码的前缀
为什么哈夫曼编码能够保证字符编码总长最短?
因为哈夫曼树的带权路径长度最短,故字符编码的总长最短
性质1:哈夫曼编码是前缀码
性质2:哈夫曼编码是最优前缀码
void CreateHuffmanCode(HuffmanTree HT,HuffmanCode &HC,int n){ //从叶子到根逆向求每个字符的哈夫曼编码,存储在编码表HC中 HC=new char *[n+1]; //分配n个字符编码的头指针矢量 cd=new char[n]; //分配临时存放编码的动态数组空间 cd[n-1]='\0'; //编码结束符 for(i=1;i<=n;++i){ //逐个字符求哈夫曼编码 start=n-1; c=i; f=HT[i].parent; while(f!=0){ //从叶子结点开始向上回溯,直到根结点 --start; //回溯一次start向前指一个位置 if(HT[f].lchild==c) cd[start]='0'; //结点c是f的左孩子,则生成代码0 else cd[start]='1'; //结点c是f的右孩子,则生成代码1 c=f; f=HT[f].parent; //继续向上回溯 } //求出第i个字符的编码 HC[i]=new char[n-start]; //为第i个字符串编码分配空间 strcpy(HC[i],&cd[start]); //将求得的编码从临时空间cd复制到HC的当前行中 } delete cd; //释放临时空间 }//CreateHuffanCode
图:G=(V,E) Graph=(Vertex,Edge)
V:顶点(数据元素)的有穷非空集合
E:边的有穷集合
无向图:每条边都是无方向的
有向图:每条边都是有方向的
完全图:任意两个点都有一条边相连
无向完全图:n个顶点,n(n-1)/2条边
有向完全图:n个顶点,n(n-1)条边
稀疏图:有很少边或弧的图(e<nlogn)
稠密图:有较多边或弧的图
网:边/弧带权的图
邻接:有边/弧相连的两个顶点之间的关系
存在(vi,vj),则称vi和vj互为邻接点
存在<vi,vj>,则称vi邻接到vj,vj邻接于vi
关联(依附):边/弧与顶点之间的关系
顶点的度:与该顶点相关联的边的数目,记为TD(v)
在有向图中,顶点的度等于该顶点的入度与出度之和
顶点v的入度是以v为终点的有向边的条数,记作ID(v)
顶点v的出度是以v为终点的有向边的条数,记作OD(v)
当有向图中仅1个顶点的入度为0,其余顶点的入度均为1,此时是一棵树,而且是一棵有向树
路径:接续的边构成的顶点序列
路径长度:路径上边或弧的数目/权值之和
回路(环):第一个顶点和最后一个顶点相同的路径
简单路径:除路径起点和终点可以相同外,其余顶点均不相同的路径
简单回路(简单环):除路径起点和终点相同外,其余顶点均不相同的路径
连通图(强连通图):在无(有)向图G=(V,{E})中,若对任何两个顶点v、u都存在从v到u的路径,则称G是连通图(强连通图)
权:图中边或弧所具有的相关数称为权,表明从一个顶点到另一个顶点的距离或耗费
网:带权的图
子图
设有两个图G=(V,{E})、G1=(V1,{E1}),若V1属于V,E1属于E,则称G1是G的子图
连通分量:无向图G的极大连通子图称为G的连通分量
强连通分量:有向图G的极大强连通子图
极大连通子图:该子图是G连通子图,将G的任何不在该子图中的顶点加入,子图不再连通
极小连通子图:该子图是G的连通子图,在该子图中删除任何一条边,子图不再连通
生成树:包含无向图G所有顶点的极小连通子图
生成森林:对非连通图,由各个连通分量的生成树的集合
操作 | 描述 |
---|---|
Create_Graph() | 创建图 |
GetVex(G,v) | 求顶点v的值 |
Create_Graph(&G,V,VR) | V是图的顶点集,VR是图中弧的集合,按V和VR的定义构造图G |
DFSTraverse(G) | 对图进行深度优先遍历 |
BFSTraverse | 对图进行广度优先遍历 |
图的逻辑结构:多对多
图没有顺序存储结构,可以借助二维数组来表示元素间的关系——数组表示法(邻接矩阵)
链式存储结构:多重链表(邻接表、邻接多重表、十字链表)
建立一个顶点表(记录各个顶点信息)和一个邻接矩阵(表示各个顶点之间关系)
设图A=(V,E)有n个顶点,则顶点表Vexs[n]
图的邻接矩阵是一个二维数组A.arcs[n] [n],定义为
A.arcs[i] [j]=1 如果<i,j>属于E 或者 (i,j)属于E
A.arcs[i] [j]=0 否则
定义为A.arcs[i] [j] =Wij <vi,vj>或(vi,vj) 属于VR
无穷 无边(弧)
用两个数组分别存储顶点表和邻接矩阵
#define MaxInt 32767 //表示极大值,即无穷
#define MVNum 100 //最大顶点数
typedef char VerTexType; //设顶点的数据类型为字符型
typedef int ArcType; //假设边的权值类型为整型
typedef struct{
VerTexType verxs[MVNum]; //顶点表
ArcType arcs[MVNum][MVNum]; //邻接矩阵
int vexnum,arcnum; //图的当前点数和边数
}AMGraph; //Adjacency Matrix Graph
Status CreateUDN(AMGraph &G){ cin>>G.vexnum>>G.arcnum; //输入总顶点数,总边数 for(i=0;i<G.vexnum;++i) cin>>G.vexs[i]; //依次输入点的信息 for(i=0;i<G.vexnum;++i) //初始化邻接矩阵 for(j=0;j<G.vexnum;++j) G.arcs[i][j]=MaxInt;//边的权值均置为极大值 for(k=0;k<G.arcnum;++k){ //构造邻接矩阵 cin>>v1>>v2>>w; //输入一条边所依附的顶点及边的权值 i=LocateVex(G,v1); j=LocateVex(G,v2); //确定v1和v2在G中的位置 G.arcs[i][j]=w; //边<v1,v2>的权值置为w G.arcs[j][i]=G.arcs[i][j]; //置<v1,v2>的对称边<v2,v1>的权值为w } //for return OK; }//CreateUDN int LocateVex(AMGraph G,VertexType u){ //图G中查找顶点u,存在则返回顶点表中的下标,否则返回-1 int i; for(i=0;i<G.vexnum;++i) if(u==G.vexs[i]) return i; return -1; }
直观、简单、好理解
方便检查任意一对顶点间是否存在边
方便找任一顶点的所有“邻接点”(有边直接相连的顶点)
方便计算任一顶点的“度”(从该点发出的边数为"出度",指向该点的边数为"入度")
无向图:对应行(或列)非0元素的个数
有向图:对应行非0元素的个数是"出度";对应列非0的个数是"入度"
顶点:按编号顺序将顶点数据存储在一维数组中;
关联同一顶点的边(以顶点为尾的弧):用线性链表存储
头结点、表结点
逆邻接表:找入度易,找出度难
typedef struct VNode{ VerTexType data; //顶点信息 ArcNode *firstarc; //指向第一条依附该顶点的边的指针 }VNode,AdjList[MVNum]; //AdjList表示邻接表类型 //AdjList v; 相当于 VNode v[MVNum]; //弧(边)的结点结构 #define MVNum 100 //最大顶点数 typedef struct ArcNode{ //边结点 int adjvex; //该边所指向的顶点的位置 struct ArcNode *nextarc; //指向下一条边的指针 OtherInfo info; //和边相关的信息 }ArcNode; typedef struct{ AdjList vertices; //vertices--vertex的复数 int vexnum,arcnum; //图的当前顶点数和弧数 }ALGraph; //邻接表操作举例说明 ALGraph G; //定义了邻接表表示的图G G.vexum=5; //图G包含5个顶点,5条边 G.arcnum=5; G.vertices[1].data='b'; //图G中第2个顶点是b p=G.vertices[1].firtarc; //指针p指向顶点b的第一条边结点 p->adjvex=4; //p指针所指边结点是到下标为4的结点的边
算法思想:
输入总顶点数和总边数
建立顶点表
依次输入点的信息存入顶点表中
使每个表头结点的指针域初始化为NULL
创建邻接表
依次输入每条边依附的两个顶点
确定两个顶点的序号i和j,建立边结点
将此边结点分别插入到vi和vj对应的两个边链表的头部
Status CreateUDG(ALGraph &G){ //采用邻接表表示法,创建无向图G cin>>G.vexnum>>G.arcnum; //输入总顶点数,总边数 for(i=0;i<G.vexnum;++i){ //输入各点,构造表头结点表 cin>>G.vertices[i].data //输入顶点值 G.vertices[i].firstarc=NULL //初始化表头结点的指针域 }//for for(k=0;k<G.arcnum;++k){ //输入各边,构造邻接表 cin>>v1>>v2; //输入一条边依附的两个顶点 i=LocateVex(G,v1); j=LocateVex(G,v2); p1=newArcNode; //生成一个新的边结点*p1 p1->adjvex=j; //邻接点序号为j p1->nextarc=G.vertices[i].firstarc; G.vertices[i].firstarc=p1; //将新结点*p2插入顶点vi的边表头部 p2=newArcNode; //生成另一个对称的新的边结点*p2 p2->adjvex=i; //邻接点序号为i p2->nextarc=G.vertices[j].firstarc; G.vertices[j].firstarc=p2; //将新结点*p2插入顶点vj的边表头部 }//for return OK; }//CreateUDG
方便找任一顶点的所有“邻接点”
节约稀疏图的空间
需要N个头指针+2E个结点(每个结点至少2个域)
对无向图方便计算任一顶点的“度”
对有向图只能计算“出度”;需要构造“逆邻接表”(存指向自己的边)来方便计算“入度”
不方便检查任意一对顶点间是否存在边
用于有向图
邻接表——有向图——缺点:求结点的度困难——十字链表
邻接表——无向图——缺点:每条边都要存储两遍——邻接多重表
遍历的定义:
从已给的连通图中某一顶点出发,沿着一些边访遍图中所有的顶点,且使每个顶点仅被访问一次,叫做图的遍历,它是图的基本运算。
遍历实质:
找每个顶点的邻接点的过程
图的特点:
图中可能存在回路,且图的任一顶点都可能与其他顶点相通,在访问完某个顶点之后可能会沿着某些边又回到了曾经访问过的顶点
怎样避免重复访问?
解决思路:设置辅助数组visited[n]用来标记每个被访问过的顶点
(Depth_First Search——DFS)
方法:
void DFS(AMGraph G,int v){ //图G为邻接矩阵类型
printf("%d",v);
visited[v]=true; //访问第v个顶点
for(w=0;w<G.vexnum;w++) //依次检查邻接矩阵v所在的行
if((G.arcs[v][w]!=0)&&(visited[w]))
DFS(G,w);
//w是v的邻接点,如果w未访问,则递归调用DFS
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JjhT6NkT-1677386139857)(C:\Users\LQN\AppData\Roaming\Typora\typora-user-images\image-20230223075815966.png)]
(Breadth_First Search——BFS)
方法:
从图的某一结点出发,首先依次访问该结点的所有邻接顶点Vi1,Vi2,…,Vin,再按这些顶点被访问的先后次序依次访问与它们相邻接的所有未被访问的顶点,重复此过程,直至所有顶点均被访问为止。
void BFS(Graph G,int v){ //按广度优先非递归遍历连通图G
count<<v;
visited[v]=true; //访问第v个顶点
InitQueue(Q); //辅助队列Q初始化,置空
EnQueue(Q,v); //v进队
while(!QueueEmpty(Q)){ //队列非空
DeQueue(Q,u); //队头元素出队并置为u
for(w=FirstAdjVex(G,u);w>=0;w=NextAdjVex(G,u,w))
if(!visited[w]){ //w为u的尚未访问的邻接顶点
cout<<w;visited[w]=true;
EnQueue(Q,w); //w进队
}//if
}//while
}//BFS
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aAmIpNBh-1677386139860)(C:\Users\LQN\AppData\Roaming\Typora\typora-user-images\image-20230223075830186.png)]
DFS与BFS算法效率比较:
生成树:所有顶点均由边连接在一起,但不存在回路的图
一个图可以有许多棵不同的生成树
所有生成树具有以下共同特点:
无向图的生成树:
最小生成树:给定一个无向网络,在该网的所有生成树中,使得各边权值之和最小的那棵生成树称为该网的最小生成树,也叫最小代价生成树
构造最小生成树(Minimum Spanning Tree)
MST性质:
在生成树的构造过程中,图中n个顶点分属两个集合:
接下来则应在所有联通U中顶点和V-U中顶点的边中选取权值最小的边。
算法思想:
克鲁斯卡尔(Kruskal)算法
算法思想:
最小生成树可能不唯一
算法名 | 普里姆算法 | 克鲁斯卡尔算法 |
---|---|---|
算法思想 | 选择点 | 选择边 |
时间复杂度 | O(n^2)(n为顶点数) | O(eloge)(e为边数) |
适应范围 | 稠密图 | 稀疏图 |
问题抽象:在有向网中A点(源点)到达B点(终点)的多条路径中,寻找一条各边权值之和最小的路径,即最短路径
最短路径与最小生成树不同,路径上不一定包含n个顶点,也不一定包含n-1条边
第一类问题:两点间最短路径——单源最短路径——用Dijkstra(迪杰斯特拉)算法
第二类问题:某源点到其他各点最短路径——所有顶点间的最短路径——用Floyd(弗洛伊德)算法
“有效的程序员不应该浪费很多时间用于程序调试,他们应该一开始就不要把故障引入。”
初始化:先找出从源点v0到各终点vk的直达路径(v0,vk),即通过一条弧到达的路径。
选择:从这些路径中找出一条长度最短的路径(v0,u)。
更新:然后对其余各条路径进行适当调整
若在图中存在弧(u,vk),且(v0,u)+(u,vk)<(v0,vk),则以路径(v0,u,vk)代替(v0,vk)
在调整后的各条路径中,再找长度最短的路径,依次类推
按路径长度递增次序产生最短路径
把V分成两组
S:已求出最短路径的顶点的集合
T=V-S:尚未确定最短路径的顶点集合
将T中顶点按最短路径递增的次序加入到S中
保证:
从源点v0到S中各顶点的最短路径长度都不大于从v0到T中任何顶点的最短路径长度
每个顶点对应一个距离值:
S中顶点:从v0到此顶点的最短距离长度
T中顶点:从v0到此顶点的只包括S中顶点作中间顶点的最短路径长度
所有顶点间的最短路径:
算法思想:
有向无环图:无环的有向图,简称DAG图(Directed Acycline Graph)
常用来描述一个工程或系统的进行过程。(通常把计划、施工、生产、程序流程等当成是一个工程)
一个工程可以分为若干个子工程,只要完成了这些子工程(活动),就可以导致整个工程的完成。
AOV网:(拓扑排序)
用一个有向图表示一个工程的各子工程及其相互制约的关系,其中以顶点表示活动,弧表示活动之间的优先制约关系,称这种有向图为顶点表示活动的网,简称AOV网(Activity On Vertex network)。
AOV网中不允许有回路。(因为如果有回路存在,则表明某项活动以自己为先决条件,显然这是荒谬的)
如何判别AOV网中是否存在回路?拓扑排序。
拓扑排序:
在AOV网没有回路的前提下,我们将全部活动排列成一个线性序列,使得若AOV网中有弧<i,j>存在,则在这个序列中,i一定排在j的前面,具有这种性质的线性序列称为拓扑有序序列,相应的拓扑有序排列的算法称为拓扑排序。
拓扑排序的方法:
一个AOV网的拓扑序列不是唯一的
检测AOV网中是否存在环方法:
对有向图构造其顶点的拓扑有序序列,若网中所有顶点都在它的拓扑有序序列中,则该AOV网必定不存在环。
AOE网:(关键路径)
用一个有向图表示一个工程的各子工程及其相互制约的关系,以弧表示活动,以顶点表示活动的开始或结束事件,称这种有向图为边表示活动的网,简称AOE网(Activity On Edge)。
把工程计划表示为边表示活动的网络,即AOV网,用顶点表示事件,弧表示活动,弧的权表示活动持续时间。
求解关键路径问题。
事件表示在它之前的活动已经完成,在它之后的活动可以开始
源点——表示整个工程的开始,入度为0的顶点
汇点——表示整个工程的结束,出度为0的顶点
关键路径——路径长度最长的路径
路径长度——路径各活动持续时间之和
求解关键路径:
应用范围:
数据元素类型定义:
typedef struct{
KeyType key; //关键字
... //其他域
}ElemType;
typedef struct{ //顺序表结构类型定义
ElemType *R; //表基址
int length; //表长
}SSTable; //Sequential Search Table
SSTable ST; //定义顺序表ST
int Search_Seq(SSTable ST,KeyType key){ //若成功返回其位置信息,否则返回0 for(i=ST.length;i>=1;--1) if(ST.R[i].key==key)return i; return 0; } //其他形式 int Search_Seq(SSTable ST,KeyType key){ for(i=ST.length;ST.R[i].key!=key;--i) if(i<=0)break; //for(i=ST.length;ST.R[i].key!=key&&i>0;--i); if(i>0)retrun i; else retrun 0; } //改进:把待查关键字key存入表头("哨兵""监视哨") int Search_Seq(SSTable ST,KeyType key){ ST.R[0].key=key; for(i=ST.length;ST.R[i].key!=key;--i); return i; }
比较次数
时间复杂度:O(n)
空间复杂度:一个辅助空间——O(1)
顺序查找的特点:
如何提高查找概率
查找表存储记录原则——按查找概率高低存储
有序表表示静态查找表——折半查找
折半查找——每次将待查记录所在区间缩小一半
折半查找算法:(非递归算法)
int Search_Bin(SSTable ST,KeyType key){
low=1;
high=ST.length; //置区间初值
while(low<=high){
mid=(low+higt)/2;
if(ST.R[mid].key==key) return mid; //找到待查元素
else if(key<ST.R[mid].key) //缩小查找区间
high=mid-1; //继续在前半区间进行查找
else low=mid+1; //继续在后半区间进行查找
}
return 0; //顺序表中不存在待查元素
}//Search_Bin
折半查找——递归算法
int Search_Bin(SSTable ST,keyType key,int low,int high){
if(low>high)return 0; //查找不到时返回0
mid=(low+high)/2;
if(key==ST.elem[mid].key) return mid;
else if(key<ST.elem[mid].key)
...... //递归,在前半区间进行查找
else
...... //递归,在后半区间进行查找
}
判定树
平均查找长度ASL约等于log2(n+1)-1(n>50)
折半查找的特点
顺序查找 | 折半查找 | 分块查找 | |
---|---|---|---|
ASL | 最大 | 最小 | 中间 |
表结构 | 有序表、无序表 | 有序表 | 分块有序 |
存储结构 | 顺序表、线性链表 | 顺序表 | 顺序表、线性链表 |
二叉排序树(Binary Sort Tree)又称为二叉搜索树、二叉查找树
二叉排序树或是空树,或是满足如下性质的二叉树:
二叉排序树的性质:中序遍历非空的二叉排序树,所得到的数据元素序列是一个按关键字排列的递增有序序列
二叉排序树查找递归算法:
typedef struct{
KeyType key; //关键字项
Info Type otherinfo; //其他数据域
}ElemType;
typedef struct BSTNode{
ElemType data; //数据域
struct BSTNode *lchild,*rchild; //左右孩子指针
}BSTNode,*BSTree;
BSTree T; //定义二叉排序树T
BSTree SearchBST(BSTree T,KeyType key){
if((!T) || key==T->data.key)return T;
else if(key<T->data.key)
return SearchBST(T->lchild,key); //在左子树中继续查找
else return SearchBST(T->rchild,key); //在右子树中继续查找
}//SearchBST
二叉排序树效率分析
提高形态不均衡的二叉排序树的查找效率:做"平衡化"处理,即尽量让二叉树的形状均衡——平衡二叉树
一个无序序列可通过构造二叉排序树而变成一个有序序列
构造树的过程就是对无序序列进行排序的过程
不同插入次序的序列生成不同形态的二叉树排序树
从二叉排序树中删除一个结点,不能把以该结点为根的子树都删去,只能删掉该结点,并且还应保证删除后所得的二叉树仍然满足二叉排序树的性质不变
二叉排序树的操作——删除
平衡二叉树(balanced binary tree),又称AVL树(Adelson-Velskii and Landis)
一棵平衡二叉树或者是空树,或者是具有下列性质的二叉排序树:
平衡因子(BF)——该结点左子树与右子树的高度差
平衡因子=结点左子树的高度-结点右子树的高度
根据平衡二叉树的定义,平衡二叉树上所有结点的平衡因子只能是-1、0、或1
失衡二叉排序树的调整
如果再一棵AVL树中插入一个新结点后造成失衡,则必须重新调整树的结构,使之恢复平衡。
基本思想:记录的存储位置与关键字之间存在对应关系
对应关系——hash函数
Loc(i)=H(keyi)
散列方法(杂凑法)
选取某个函数,依该函数按关键字计算元素的存储位置,并按此存放;
查找时,由同一个函数对给定值k计算地址,将k与地址单元中元素键码进行比,确定查找是否成功
散列函数(杂凑函数):散列方法中使用的转换函数
散列表(杂凑表)
优点:查找效率高
缺点:空间效率低
冲突:不同的关键码映射到同一个散列地址
key1!=key2,但是H(key1)=H(key2)
在散列查找方法中,冲突是不可能避免的,只能尽可能减少
同义词:具有相同函数值的多个关键字
考虑因素
方法
直接定址法
Hash(key)=a.key+b (a、b为常数)
优点:以关键码key的某个线性函数值为散列地址,不会产生冲突
缺点:要占用连续地址空间,空间效率低
除留余数法
Hash(key)=key mod p (p是一个整数)
使用平均查找长度ASL来衡量查找算法,ASL取决于:
排序:将一组杂乱无章的数据按一定规律顺次排列起来,即将无序序列排成一个有序序列(由小到大或由大到小)的运算
分类:
按数据存储介质:内部排序和外部排序
按比较器个数:串行排序和并行排序
按主要操作:比较排序和基数排序
按辅助空间:原地排序和非原地排序
按稳定性:稳定排序和非稳定排序
按自然性:自然排序和非自然排序
内部排序:数据量不大、数据在内存,无需内外存交换数据
外部排序:数据量较大、数据在外存(文件排序)
串行排序:单处理机(同一时刻比较一对元素)
并行排序:多处理机(同一时刻比较多对元素)
比较排序:用比较的方法(插入排序、交换排序、选择排序、归并排序)
基数排序:不比较元素的大小,仅仅根据元素本身的取值确定其有序位置
原地排序:辅助空间用量为O(1)的排序方法(所占的辅助存储空间与参加排序的数据量大小无关)
非原地排序:辅助空间用量超过O(1)的排序方法
稳定排序:能够使任何数值相等的元素,排序以后相对次序不变
非稳定性排序:不是稳定排序的方法
排序的稳定性只对结构类型数据排序有意义
自然排序:输入数据越有序,排序的速度越快的排序方法
非自然排序:不是自然排序的方法
按排序依据原则
1. 插入排序:直接插入排序、折半插入排序、希尔排序
2. 交换排序:冒泡排序、快速排序
3. 选择排序:简单选择排序、堆排序
4. 归并排序:2-路归并排序
5. 基数排序
按排序所需工作量
存储结构——记录序列以顺序表存储
#define MAXSIZE 20 //设记录不超过20个
typedef int KeyType; //设关键字为整型量(int)
Typedef struct{ //定义每个记录(数据元素)的结构
KeyType key; //关键字
InfoType otherinfo; //其它数据项
}RedType; //Record Type
Typedef struct{ //定义顺序表的结构
RedType r[MAXSIZE+1]; //存储顺序表的向量
int length; //顺序表的长度
}SqList;
基本思想:每一步将一个待排序的对象,按其关键码大小,插入到前面已经排好序的一组对象的适当位置上,知道对象全部插入为止
即边插入边排序,保证子序列中随时都是排好序的
在插入a[i]前,数组a的前半段(a[0]a[i-1])是有序段,后半段(a[i]a[n-1])是停留于输入次序的"无序段"
顺序法定位插入位置
1. 复制插入元素
x=a[i]
2. 记录后移,查找插入位置
for(j=i-1;j>=0&&x<a[j];j--)
a[j+1]=a[j];
3. 插入到正确位置
a[j+1]=x;
1. 复制为哨兵
L.r[0]=L.r[i];
2. 记录后移,查找插入位置
for(j=i-1;L.r[0].key<L.r[j].key;--j)
L.r[j+1]=L.r[j];
3. 插入到正确位置
L.r[j+1]=L.r[0]
void InsertSort(SqList &L){
int i,j;
for(i=2;i<-L.length;++i){
if(L.r[i].key<L.r[i-1].key){ //若"<",需将L.r[i]插入有序子表
L.r[0]=L.r[i]; //复制为哨兵
for(j=i-1;L.r[0].key<L.r[j].key;--j){
L.r[j+1]=L.r[j]; //记录后移
}
L.r[j+1]=L.r[0]; //插入到正确位置
}
}
}
时间复杂度结论:
1. 原始数据越接近有序,排序速度越快
2. 最坏情况下(输入数据是逆有序的),Tw(n)=O(n^2)
3. 平均情况下,耗时差不多是最坏情况的一半,Te(n)=O(n^2)
4. 要提高查找速度:减少元素的比较次数、减少元素的移动次数
二分法定位插入位置
void BInsertSort(SqList &L){
for(i=2;i<=L.length;++i){ //依次插入第2~第n个元素
L.r[0]=L.r[i]; //当前插入元素存到"哨兵"位置
low=1; //采用二分法查找插入位置
high=i-1;
while(low<=high){
mid=(low+high)/2;
if(L.r[0].key<L.r[mid].key)high=mid-1;
else low=mid+1;
}//循环结束,high+1则为插入位置
for(j=i-1;j>=high+1;--j)L.r[j+1]=L.r[j]; //移动元素
L.r[high+1]=L.r[0]; //插入到正确位置
}
}//BInsertSort
(Donald.L.Shell) 缩小增量多变插入排序
基本思想:先将整个待排记录序列分割成若干子序列,分别进行直接插入排序,带整个序列中的记录"基本有序"时,再对全体记录进行一次直接插入排序
特点:
void ShellSort(Sqlist &L,int dlta[],int t){
//按增量序列dlta[0..t-1]对顺序表L作希尔排序
for(k=0;k<t;++k)
ShellInsert(L,dlta[k]); //一趟增量为dlta[k]的插入排序
}//ShellSort
void ShellInsert(SqList &L,int dk){
//对顺序表L进行一趟增量为dk的Shell排序,dk为步长因子
for(i=dk+1;i<=L.length;++i)
if(r[i].key<r[i-dk].key){
r[0]=r[i];
for(j=i-dk;j>0&&(r[0].key<r[j].key);j=j-dk)
r[j+dk]=r[j];
r[j+dk]=r[0];
}
}
void bubble_sort(SqList &L){ //冒泡排序算法
int m,i,j;
RedType x; //交换时临时存储
for(m=1;m<=n-1;m++){ //总共需m趟
for(j=1;j<=n-m;j++)
if(L.r[j].key>L.r[j+1].key){//发生逆序
x=L.r[j];
L.r[j]=L.r[j+1];
L.r[j+1]=x; //交换
}//endif
}//for
}
//改进
void bubble_sort(SqList &L){//赶紧的冒泡排序算法
int m,i,j,flag=1; //flag作为是否有交换的标记
RedType x;
for(m=1;m<=n&&flag==1;m++){
flag=0;
for(j-1;j<=m;j++)
if(L.r[j].key>L.r[j+1].key){//发生逆序
flag=1; //发生交换,flag置位1
x=L.r[j]; //交换
L.r[j]=L.r[j+1];
L.r[j+1]=x;
}//endfi
}//for
}
void main(){ QSort(L,1,L.length); } void Qsort(SqList &L,int low,int high){//对顺序表L快速排序 if(low<high){ //长度大于1 pivotloc=Partition(L,low,high); //将L.r[low...high]一分为二,pivotloc为枢轴元素排好序的位置 QSort(L,low,pivotloc-1); //对低子表递归排序 QSort(L,pivotloc+1,high); //对高子表递归排序 }//endif }//QSort int Partition(SqList &L,int low,int high){ L.r[0]=L.r[low]; pivotkey=L.r[low].key; while(low<high){ while(low<high&&L.r[high].key>=pivotkey) --high; L.r[row]=L.r[high]; while(low<high&&L.r[low].key<=pivotkey) ++low; L.r[high]=L.r[low]; } L.r[low]=L.r[0]; return low; }
void SelectSort(SqList &K){
for(i=1;i<L.length;++i){
k=i;
for(j=i+1;j<=L.length;j++)
if(L.r[j].key<L.r[k].key)k=j; //记录最小值位置
if(k!=i)L.r[i]=L.r[k]; //交换
}
}
void HeapAdjust(elem R[],int s,int m){
rc=R[s];
for(j=2*s;j<=m;j*=2){ //沿key较大的孩子结点向下筛选
if(j<m&&R[j]<R[j+1])++j;//j为key较大的记录的下标
if(rc>=R[j])break;
R[s]=R[j]; //rc应插入在位置s上
s=j;
}//for
R[s]=rc; //插入
}//HeapAdjust
void HeapSort(elem R[]){ //对R[1]到R[n]进行堆排序
int i;
for(i=n/2;i>=1;i--)
HeapAdjust(R,i,n); //建初始堆
for(i=n;i>1;i--){
Swap(R[1],R[i]); //根与最后一个元素交换
HeapAdjust(R,1,i-1); //对R[1]到R[i-1]重新建堆
}
}//HeapSort
void merge(node r[], int low, int mid, int high) { node tmp[MAXSIZE]; // tmp是汇总2个有序区间的临时区域。 int i = low; // 第一个有序区的索引 int j = mid + 1; // 第二个有序区的索引 int k = 0; // 临时区域的索引 while (i <= mid && j <= high) { if (r[i].key <= r[j].key) { tmp[k++].key = r[i++].key; } else { tmp[k++].key = r[j++].key; } } while (i <= mid) { tmp[k++].key = r[i++].key; } while (j <= high) { tmp[k++].key = r[j++].key; // 将两个有序区间合并 } // 排序后的元素,全部都整合到数组a中 for (i = 0; i < k; i++) { r[low + i].key = tmp[i].key; } } void Msort(node r[MAXSIZE],int low,int high){ if (low >= high) { return ; } int mid = (low + high) / 2; Msort(r, low, mid); // 递归排序r[low..mid] Msort(r, mid + 1, high); // 递归排序r[mid..high] // r[low..mid]和r[mid..high]是两个有序空间 merge(r, low, mid,high); // 将它们排序成一个有序空间r[low..high] }
类别 | 排序方法 | 时间复杂度 | 空间复杂度 | 稳定性 | ||
---|---|---|---|---|---|---|
最好情况 | 最坏情况 | 平均情况 | 辅助存储 | |||
插入排序 | 直接插入排序 | O(n) | O(n^2) | O(n^2) | O(1) | 稳定 |
希尔排序 | O(n) | O(n^2) | O(n^1.3) | O(1) | 不稳定 | |
交换排序 | 冒泡排序 | O(n) | O(n^2) | O(n^2) | O(1) | 稳定 |
快速排序 | O(nlogn) | O(n^2) | O(nlogn) | O(nlogn) | 不稳定 | |
选择排序 | 直接选择排序 | O(n^2) | O(n^2) | O(n^2) | O(1) | 不稳定 |
堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n^2) | 不稳定 | |
归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 | |
基数排序 | k:待排元素的维数m:基数的个数 | O(n+m) | O(k*(n+m)) | O(k*(n+m)) | O(n+m) | 稳定 |
未完待续。。。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。